immer#produce TypeScript Examples
The following examples show how to use
immer#produce.
You can vote up the ones you like or vote down the ones you don't like,
and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: reducer.ts From wiregui with MIT License | 6 votes |
export default function (state = INITIAL_STATE, action: AnyAction) {
return produce(state, () => {
switch (action.type) {
default: {
break;
}
}
});
}
Example #2
Source File: transformer.ts From prisma-schema-transformer with MIT License | 6 votes |
function transformEnum(enumm: DMMF.DatamodelEnum) {
const { name } = enumm;
const fixModelName = produce(enumm, draftModel => {
if (name !== singularizeModelName(name)) {
draftModel.name = singularizeModelName(name);
draftModel.dbName = name;
}
});
const fixFieldsName = produce(fixModelName, draftModel => {
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
draftModel.values = draftModel.values.map(field => produce(field, draftField => {
const {name, dbName} = draftField;
// Transform field name
draftField.name = camelcase(pluralize.singular(name));
if (draftField.name !== name) {
draftField.dbName = dbName || name;
}
}));
});
return fixFieldsName;
}
Example #3
Source File: input_control.ts From vorb.chat with GNU Affero General Public License v3.0 | 6 votes |
setDeviceId(kind: TrackKind, deviceId: string | undefined, forceEnable = false) {
const config = produce(this.configuration, (draft) => {
const trackConfig = draft[kind];
trackConfig.deviceId = deviceId;
if(forceEnable) {
trackConfig.enabled = true;
}
});
this.setConfiguration(config);
}
Example #4
Source File: core-update-state.ts From tanfu.js with MIT License | 6 votes |
/** 设置状态 */
setState(states: SetStatesAction<VM>): void {
if (typeof states === 'function') {
return this.setState(states(this.store))
}
Object.keys(states).forEach(tId => {
const preState: Record<string, any> = get(this.store, tId, {});
// @ts-ignore
const currentState: Record<string, any> = states[tId] ?? {}
const nextState = produce(preState, draft => {
Object.keys(currentState).forEach(propName => {
if (typeof currentState[propName] !== 'function' && currentState[propName] !== draft[propName])
draft[propName] = currentState[propName]
})
})
if (preState === nextState) return;
this.needUpdateElements.add(tId)
set(this.store, tId, nextState);
})
}
Example #5
Source File: rx-model.ts From XFlow with MIT License | 6 votes |
/** 更新model */
public setValue: NsModel.ISetValue<T> = value => {
if (!this.subject$) {
return
}
if (typeof value === 'function') {
const currentValue = this.subject$.getValue()
const nextState = produce(currentValue, draft => {
value(draft)
})
if (NsModel.isValidValue<T>(nextState)) {
this.setValue(nextState)
}
return
}
this.subject$.next(value)
}
Example #6
Source File: potentialEdgeReducer.ts From diagram-maker with Apache License 2.0 | 6 votes |
export default function potentialEdgeReducer<NodeType, EdgeType>(
state: DiagramMakerPotentialEdge | null | undefined,
action: DiagramMakerAction<NodeType, EdgeType>,
): DiagramMakerPotentialEdge | null {
if (state === undefined) {
return null;
}
switch (action.type) {
case (EdgeActionsType.EDGE_DRAG_START):
return {
position: action.payload.position,
src: action.payload.id,
};
case (EdgeActionsType.EDGE_DRAG):
return produce(state, (draftState) => {
if (draftState) {
draftState.position = action.payload.position;
}
});
case (EdgeActionsType.EDGE_DRAG_END):
return null;
default:
return state;
}
}
Example #7
Source File: handleComponentSchema.ts From brick-design with MIT License | 6 votes |
/**
* 复制组件
* @param state
* @returns {{pageConfig: *}}
*/
export function copyComponent(state: StateType): StateType {
const { undo, redo, pageConfig, selectedInfo } = state;
/**
* 未选中组件不做任何操作
*/
if (!selectedInfo) {
warn('Please select the node you want to copy');
return state;
}
if (selectedInfo.selectedKey === ROOT) {
warn('Prohibit copying root node');
return state;
}
const { selectedKey, parentPropName, parentKey } = selectedInfo;
undo.push({ pageConfig });
redo.length = 0;
const newKey = getNewKey(pageConfig);
return {
...state,
pageConfig: produce(pageConfig, (oldState) => {
update(
oldState,
getLocation(parentKey!, parentPropName),
(childNodes) => [...childNodes, `${newKey}`],
);
copyConfig(oldState, selectedKey, newKey);
}),
undo,
redo,
};
}
Example #8
Source File: inspector.test-utils.tsx From utopia with MIT License | 6 votes |
export function getStoreHook(
mockDispatch: EditorDispatch,
): EditorStateContextData & UpdateFunctionHelpers {
const editor = createEditorStates([
EP.appendNewElementPath(ScenePathForTestUiJsFile, ['aaa', 'bbb']),
])
const defaultState: EditorStorePatched = {
editor: editor.editor,
derived: editor.derivedState,
strategyState: editor.strategyState,
history: {
previous: [],
next: [],
current: {} as any,
},
userState: defaultUserState,
workers: null as any,
persistence: null as any,
dispatch: mockDispatch,
alreadySaved: false,
builtInDependencies: createBuiltInDependenciesList(null),
}
const storeHook = create<
EditorStorePatched,
SetState<EditorStorePatched>,
GetState<EditorStorePatched>,
Mutate<StoreApi<EditorStorePatched>, [['zustand/subscribeWithSelector', never]]>
>(subscribeWithSelector((set) => defaultState))
const updateStoreWithImmer = (fn: (store: EditorStorePatched) => void) =>
storeHook.setState(produce(fn))
const updateStore = (fn: (store: EditorStorePatched) => EditorStorePatched) =>
storeHook.setState(fn)
return {
api: storeHook,
useStore: storeHook,
updateStoreWithImmer: updateStoreWithImmer,
updateStore: updateStore,
}
}
Example #9
Source File: regist-router.tsx From erda-ui with GNU Affero General Public License v3.0 | 6 votes |
resetRouter = (routers: Obj<SHELL.Route[]>) => {
return produce(routers, (draft) => {
const routerMarkObj: Obj = {};
const toMarkObj: Obj = {};
const getRouterMarks = (_r: SHELL.Route[], _path: string) => {
_r.forEach((rItem: SHELL.Route, idx: number) => {
const { mark, routes: _rs, toMark } = rItem;
if (mark && !routerMarkObj[mark]) {
routerMarkObj[mark] = rItem;
}
if (toMark) {
toMarkObj[toMark] = (toMarkObj[toMark] || []).concat({ router: rItem, key: `${_path}.[${idx}]` });
}
if (_rs) {
getRouterMarks(_rs, `${_path}.[${idx}].routes`);
}
});
};
map(draft, (rItem, key) => {
getRouterMarks(rItem, key);
});
map(toMarkObj, (_toObjArr, k) => {
map(_toObjArr, (_toObj) => {
const { key, router: _toRouter } = _toObj;
if (_toRouter && routerMarkObj[k]) {
_toRouter.toMark = undefined;
routerMarkObj[k].routes = (routerMarkObj[k].routes || []).concat(_toRouter);
set(draft, key, undefined);
}
});
});
});
}
Example #10
Source File: PropertyGrid.tsx From viewer-components-react with MIT License | 6 votes |
private _onCategoryExpansionToggled = (categoryName: string) => {
this.setState((state) => {
return produce(state, (draft) => {
const records = findCategory(draft.categories, categoryName, true);
// istanbul ignore else
if (records) {
const category = records.category;
category.expand = !category.expand;
}
});
});
};
Example #11
Source File: reducer.ts From querybook with Apache License 2.0 | 6 votes |
function loadedEnvironmentFilterModeReducer(
state = initialState.loadedEnvironmentFilterMode,
action: DataDocAction | EnvironmentAction
) {
return produce(state, (draft) => {
switch (action.type) {
case '@@dataDoc/RECEIVE_DATA_DOCS': {
const { environmentId, filterMode } = action.payload;
if (environmentId == null) {
return;
}
draft[environmentId] = draft[environmentId] || {};
if (filterMode == null) {
return;
}
draft[environmentId][filterMode] = true;
return;
}
case '@@environment/SET_ENVIRONMENT_BY_ID': {
const { id: currentEnvId } = action.payload;
for (const envId of Object.keys(draft).map(Number)) {
if (envId !== currentEnvId) {
delete draft[envId];
}
}
return;
}
}
});
}
Example #12
Source File: index.ts From core with MIT License | 6 votes |
function topReducer(state: any, action: any) {
switch (action.type) {
case 'PATCH': {
return produce(state, (draftState: any) => {
const patch = {
...action.payload.patch,
path: jsonPatchPathToImmerPath(action.payload.patch.path),
};
draftState[action.payload.subtree].state = applyPatches(
draftState[action.payload.subtree].state,
[patch]
);
draftState[action.payload.subtree].patches.push({
patch: action.payload.patch,
inversePatch: action.payload.inversePatch,
});
});
}
case 'CREATE_SUBTREE': {
return produce(state, (draftState: any) => {
draftState[action.payload.subtree] = {
state: action.payload.initialState,
patches: [],
};
});
}
default:
return state;
}
}
Example #13
Source File: slot.ts From textbus with GNU General Public License v3.0 | 6 votes |
/**
* 更新插槽状态的方法
* @param fn
*/
updateState(fn: (draft: Draft<T>) => void): T {
let changes!: Patch[]
let inverseChanges!: Patch[]
const oldState = this.state
const newState = produce(oldState, fn, (p, ip) => {
changes = p
inverseChanges = ip
})
this.state = newState
const applyAction: ApplyAction = {
type: 'apply',
patches: changes,
value: newState
}
this.changeMarker.markAsDirtied({
path: [],
apply: [applyAction],
unApply: [{
type: 'apply',
patches: inverseChanges,
value: oldState
}]
})
this.stateChangeEvent.next([applyAction])
return newState!
}
Example #14
Source File: reducer.ts From wiregui with MIT License | 5 votes |
wgConfig: Reducer<WgConfigState> = (state = INITIAL_STATE, action) => {
return produce(state, (draft) => {
switch (action.type) {
case WgConfigTypes.fetchFiles: {
const { files } = action.payload;
draft.files = files;
ipcRenderer.send("WgConfigStateChange", draft.files.map(file => ({
name: file.name,
path: file.path,
active: file.active,
})));
break;
}
case WgConfigTypes.addFile: {
const { file } = action.payload;
draft.files.push(file);
ipcRenderer.send("WgConfigStateChange", draft.files.map(file => ({
name: file.name,
path: file.path,
active: file.active,
})));
break;
}
case WgConfigTypes.deleteFile: {
const { filename } = action.payload;
draft.files = draft.files.filter((file) => file.name !== filename);
ipcRenderer.send("WgConfigStateChange", draft.files.map(file => ({
name: file.name,
path: file.path,
active: file.active,
})));
break;
}
case WgConfigTypes.updateStatus: {
const { activeTunnelName } = action.payload;
draft.activeTunnelName = activeTunnelName;
draft.files = draft.files.map((file) => {
file.active = file.name === activeTunnelName;
if (file.active) {
file.lastConnectAt = new Date().toISOString();
localStorage.setItem(file.name, file.lastConnectAt);
}
return file;
});
ipcRenderer.send("WgConfigStateChange", draft.files.map(file => ({
name: file.name,
path: file.path,
active: file.active,
})));
break;
}
default: {
break;
}
}
});
}
Example #15
Source File: transformer.ts From prisma-schema-transformer with MIT License | 5 votes |
function transformModel(model: Model) {
const {name, uniqueFields, idFields} = model;
const fixModelName = produce(model, draftModel => {
if (name !== singularizeModelName(name)) {
draftModel.name = singularizeModelName(name);
draftModel.dbName = name;
}
});
const fixFieldsName = produce(fixModelName, draftModel => {
const fields = draftModel.fields as unknown as Field[];
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
draftModel.fields = fields.map(field => produce(field, draftField => {
const {name, kind, type, relationFromFields, relationToFields, isList} = draftField;
// Transform field name
draftField.name = isList ? camelcase(pluralize.plural(name)) : camelcase(pluralize.singular(name));
if (draftField.name !== name) {
draftField.columnName = name;
}
// Posts posts[]
if (kind === 'object' && type !== singularizeModelName(type)) {
draftField.type = singularizeModelName(type);
}
// Enum
if (kind === 'enum' && type !== singularizeModelName(type)) {
draftField.type = singularizeModelName(type);
if (draftField.default)
draftField.default = camelcase(draftField.default)
}
// Object kind, with @relation attributes
if (kind === 'object' && relationFromFields && relationFromFields.length > 0 && relationToFields) {
draftField.relationFromFields = [camelcase(relationFromFields[0])];
draftField.relationToFields = [camelcase(relationToFields[0])];
}
if (name === 'updated_at') {
draftField.isUpdatedAt = true;
}
})) as DMMF.Field[]; // Force type conversion
});
const fixUniqueName = produce(fixFieldsName, draftModel => {
if (uniqueFields.length > 0) {
draftModel.uniqueFields = uniqueFields.map(eachUniqueField => eachUniqueField.map(each => camelcase(each)));
}
});
const fixIdFieldsName = produce(fixUniqueName, draftModel => {
if (idFields && idFields.length > 0) {
draftModel.idFields = idFields.map(eachIdField => camelcase(eachIdField));
}
});
return fixIdFieldsName;
}
Example #16
Source File: input_control.ts From vorb.chat with GNU Affero General Public License v3.0 | 5 votes |
setDeviceEnabled(kind: TrackKind, enabled: boolean) {
const config = produce(this.configuration, (draft) => {
draft[kind].enabled = enabled;
});
this.setConfiguration(config);
}
Example #17
Source File: index.ts From atlas with GNU General Public License v3.0 | 5 votes |
immer =
<T extends State>(config: StateCreator<T, (fn: (state: Draft<T>) => void) => void>): StateCreator<T> =>
(set, get, api) =>
config((fn) => set(produce<T>(fn)), get, api)
Example #18
Source File: edgeReducer.ts From diagram-maker with Apache License 2.0 | 5 votes |
export default function edgeReducer<NodeType, EdgeType>(
state: DiagramMakerEdges<EdgeType> | undefined,
action: DiagramMakerAction<NodeType, EdgeType>,
): DiagramMakerEdges<EdgeType> {
if (state === undefined) {
return {};
}
switch (action.type) {
case GlobalActionsType.CREATE_ITEMS:
return produce(state, (draftState) => {
action.payload.edges.forEach((edge) => {
draftState[edge.id] = edge as Draft<DiagramMakerEdge<EdgeType>>;
});
});
case EdgeActionsType.EDGE_DELETE:
return produce(state, (draftState) => {
delete draftState[action.payload.id];
});
case (EdgeActionsType.EDGE_CREATE):
return produce(state, (draftState) => {
const {
id, src, dest, consumerData: untypedConsumerData,
} = action.payload;
const consumerData = untypedConsumerData as Draft<EdgeType>;
const diagramMakerData = {};
draftState[id] = {
consumerData,
dest,
diagramMakerData,
id,
src,
};
});
case (EdgeActionsType.EDGE_SELECT):
return produce(state, (draftState) => {
const edgeIds = Object.keys(draftState);
edgeIds.forEach((edgeId) => {
if (edgeId !== action.payload.id) {
draftState[edgeId].diagramMakerData.selected = false;
} else {
draftState[edgeId].diagramMakerData.selected = true;
}
});
});
case (WorkspaceActionsType.WORKSPACE_DESELECT):
case (NodeActionsType.NODE_SELECT):
return produce(state, (draftState) => {
const edgeIds = Object.keys(draftState);
edgeIds.forEach((edgeId) => {
draftState[edgeId].diagramMakerData.selected = false;
});
});
case (GlobalActionsType.DELETE_ITEMS):
return produce(state, (draftState) => {
const { edgeIds } = action.payload;
edgeIds.forEach((edgeId: string) => {
delete draftState[edgeId];
});
});
default:
return state;
}
}
Example #19
Source File: handleComponentSchema.ts From brick-design with MIT License | 5 votes |
/**
* 往画板或者容器组件添加组件
* @param state
* @returns {{pageConfig: *}}
*/
export function addComponent(state: StateType): StateType {
const { undo, redo, pageConfig, dragSource, dropTarget,dragSort } = state;
/**
* 如果没有拖拽的组件不做添加动作, 如果没有
*/
if (!dragSource || (!dropTarget && pageConfig[ROOT]))
return { ...state, dragSource: null,dragSort:null };
const { vDOMCollection, dragKey, parentKey, parentPropName } = dragSource;
/**
* 如果没有root根节点,新添加的组件添加到root
*/
if (!pageConfig[ROOT]) {
undo.push({ pageConfig });
redo.length = 0;
return {
...state,
pageConfig: vDOMCollection!,
dragSource: null,
dropTarget: null,
dragSort:null,
undo,
redo,
};
}
// eslint-disable-next-line prefer-const
let { selectedKey, propName,childNodeKeys } = dropTarget;
/**
* 如果有root根节点,并且即没有选中的容器组件也没有drop的目标,那么就要回退到drag目标,
* 添加之前的页面配置
*/
if (!selectedKey) {
/**
* 如果有parentKey说明是拖拽的是新添加的组件,
* 返回原先的state状态
*/
if (!parentKey) {
return { ...state, ...undo.pop(), dragSource: null,dragSort:null };
} else {
return { ...state, dragSource: null,dragSort:null };
}
}
if (!dragSort||
parentKey===selectedKey&&isEqual(childNodeKeys,dragSort)||
handleRules(pageConfig, dragKey!, selectedKey, propName)
) {
return { ...state, dragSource: null, dropTarget: null ,dragSort:null};
}
parentKey && undo.push({ pageConfig });
redo.length = 0;
return {
...state,
pageConfig: produce(pageConfig, (oldConfigs) => {
//添加新组件到指定容器中
update(oldConfigs, getLocation(selectedKey!, propName), () => dragSort);
//如果有父key说明是跨组件的拖拽,原先的父容器需要删除该组件的引用
if (parentKey&&(parentKey!==selectedKey||parentPropName!==propName)) {
update(oldConfigs, getLocation(parentKey), (childNodes) =>
deleteChildNodesKey(childNodes, dragKey!, parentPropName),
);
}
}),
dragSource: null,
dropTarget: null,
dragSort:null,
undo,
redo,
};
}
Example #20
Source File: PipelinesContainer.tsx From baleen3 with Apache License 2.0 | 5 votes |
PipelinesContainer: React.FC = () => {
const {
data: pipelines,
error,
mutate,
} = useSWR<PipelineMetadata[], Error>(getPipelinesFetchKey, getPipelines, {
refreshInterval: 5000,
})
if (error !== undefined) {
return <FullError error={error} action={mutate} />
}
if (pipelines === undefined) {
return <Loading />
}
if (pipelines.length === 0) {
return <Home />
}
// Note, error not handled so can be handled in the calling component
const deletePipeline = async (pipeline: PipelineMetadata): Promise<void> => {
await mutate(async (pipelines) => {
await Api.deletePipeline(pipeline.name)
return produce(pipelines, (draft) => {
draft.splice(pipelines.indexOf(pipeline), 1)
})
})
}
const startPipeline = async (pipeline: PipelineMetadata): Promise<void> => {
await mutate(async (pipelines) => {
await Api.startPipeline(pipeline.name)
return produce(pipelines, (draft) => {
draft.splice(pipelines.indexOf(pipeline), 1)
})
})
}
const stopPipeline = async (pipeline: PipelineMetadata): Promise<void> => {
await mutate(async (pipelines) => {
await Api.stopPipeline(pipeline.name)
return produce(pipelines, (draft) => {
draft.splice(pipelines.indexOf(pipeline), 1)
})
})
}
return (
<Pipelines
pipelines={pipelines}
deletePipeline={deletePipeline}
startPipeline={startPipeline}
stopPipeline={stopPipeline}
/>
)
}
Example #21
Source File: combiner.tsx From erda-ui with GNU Affero General Public License v3.0 | 5 votes |
export function createCombiner<P, O>({
CombinerItem,
valueFixIn = defaultFix,
valueFixOut = defaultFix,
defaultItem,
}: IProps<P, O>) {
return (props: ICompProps<P, O>) => {
const { value, onChange, disabled, ...rest } = props;
const changeData = (val: any) => {
onChange(valueFixOut(cloneDeep(val)));
};
const updateItem = (d: any, index: number) => {
const newVal = map(valueFixIn(value), (val, idx) => {
if (isPlainObject(val)) {
if (index === idx) {
const curVal = produce(val, (draft: Obj) => {
const curKey = Object.keys(d)[0];
set(draft, curKey, d[curKey]);
});
return curVal;
}
return val;
} else {
return index === idx ? d : val;
}
});
changeData(newVal);
};
const addItem = () => {
const _defaultItem = typeof defaultItem === 'function' ? defaultItem(props) : defaultItem;
changeData([...valueFixIn(value), _defaultItem]);
};
const deleteItem = (index: number) => {
changeData(filter(value, (_, idx) => index !== idx));
};
return (
<div className="dice-form-nusi-combiner-component">
{map(valueFixIn(value), (item, index) => (
<CombinerItem
{...rest}
disabled={disabled}
className="combiner-item"
key={`${index}`}
data={item}
updateItem={(d: any) => {
updateItem(d, index);
}}
operation={
disabled ? (
<ErdaIcon type="reduce-one" className="combiner-operation not-allowed" />
) : (
<ErdaIcon type="reduce-one" className="combiner-operation" onClick={() => deleteItem(index)} />
)
}
/>
))}
{disabled ? (
<ErdaIcon type="add-one" className="combiner-operation not-allowed" />
) : (
<Tooltip title={i18n.t('common:click to add item')}>
<ErdaIcon type="add-one" className="combiner-operation" onClick={addItem} />
</Tooltip>
)}
</div>
);
};
}
Example #22
Source File: reducer.ts From querybook with Apache License 2.0 | 5 votes |
function dataDocCellByIdReducer(
state = initialState.dataDocCellById,
action: DataDocAction
) {
return produce(state, (draft) => {
switch (action.type) {
case '@@dataDoc/RECEIVE_DATA_DOC': {
const { dataDoc, dataDocCellById } = action.payload;
return {
...removeDocFromCells(state, dataDoc.id),
...dataDocCellById,
};
}
case '@@dataDoc/REMOVE_DATA_DOC': {
const { docId } = action.payload;
return removeDocFromCells(state, docId);
}
case '@@dataDoc/UPDATE_DATA_DOC_CELL':
case '@@dataDoc/INSERT_DATA_DOC_CELL': {
const { cell } = action.payload;
draft[cell.id] = cell;
return;
}
case '@@dataDoc/UPDATE_DATA_DOC_CELL_DATA': {
const { cellId } = action.payload;
if (action.payload.context != null) {
draft[cellId].context = action.payload.context;
}
if (action.payload.meta != null) {
draft[cellId].meta = action.payload.meta;
}
return;
}
default: {
return state;
}
}
});
}
Example #23
Source File: model.ts From reactant with MIT License | 5 votes |
model = <
S extends Record<string, any>,
A extends Record<string, (...args: any[]) => (state: S) => void>
>(
scheme: Scheme<S, A>
): Actions<A> & Service<S> & S => {
let module: Service<S>;
Object.keys(scheme.actions).forEach((key) => {
const fn = scheme.actions[key];
Object.assign(scheme.actions, {
[key]: (...args: any[]) => {
let state: Immutable<S> | S | undefined;
let patches: Patch[] | undefined;
let inversePatches: Patch[] | undefined;
if (module[enablePatchesKey]) {
[state, patches, inversePatches] = produceWithPatches(
module[stateKey],
(draftState: S) => {
fn(...args)(draftState);
}
);
} else {
state = produce(module[stateKey], (draftState: S) => {
fn(...args)(draftState);
});
}
const lastState = module[storeKey]?.getState();
module[storeKey]!.dispatch({
type: module[identifierKey],
method: key,
state: {
...lastState,
[module[identifierKey]!]: state,
},
_reactant: actionIdentifier,
...(module[enablePatchesKey]
? {
_patches: patches,
_inversePatches: inversePatches,
}
: {}),
});
},
});
});
module = {
[nameKey]: scheme.name,
[stateKey]: {
...scheme.state,
},
...scheme.actions,
};
return module as any;
}
Example #24
Source File: hierarchicalLayout.spec.ts From diagram-maker with Apache License 2.0 | 4 votes |
// Note:
// All these tests compare snapshots of huge objects.
// You can't really tell if those huge objects are correct or not.
//
// If you find yourself fixing these tests / updating snapshots,
// please just pull the input data into your application, lay it out
// using the API call and see how it looks.
//
// That's the most reliable way to verify this functionality,
// as its purpose is visually appealing graph arrangements
// (not bunch of strict numbers to compare).
describe('hierarchicalLayout', () => {
beforeEach(() => {
jest.clearAllMocks();
});
it('does not do anything to empty graph', () => {
const graph = fromAdjacencyList({});
const layoutConfig: HierarchicalLayoutConfig = {
layoutType: LayoutType.HIERARCHICAL,
fixedNodeIds: [],
distanceMin: 50,
};
expect(hierarchicalLayout(graph, layoutConfig)).toEqual(graph);
});
it('does not do anything to single node graph', () => {
let graph = fromAdjacencyList({ node: [] });
graph = produce(graph, (draft) => {
draft.nodes.node.diagramMakerData.position = { x: 123, y: 456 };
});
const layoutConfig: HierarchicalLayoutConfig = {
layoutType: LayoutType.HIERARCHICAL,
fixedNodeIds: ['node'],
distanceMin: 50,
};
expect(hierarchicalLayout(graph, layoutConfig)).toEqual(graph);
});
it('does not do anything to completely disconnected nodes', () => {
let graph = fromAdjacencyList({
'node-1': [],
'node-2': [],
});
graph = produce(graph, (draft) => {
draft.nodes['node-1'].diagramMakerData.position = { x: 123, y: 456 };
draft.nodes['node-2'].diagramMakerData.position = { x: 111, y: 222 };
});
const layoutConfig: HierarchicalLayoutConfig = {
layoutType: LayoutType.HIERARCHICAL,
fixedNodeIds: ['node-1'],
distanceMin: 70,
};
expect(hierarchicalLayout(graph, layoutConfig)).toEqual(graph);
});
it('correctly lays out a tree with one fixed node', () => {
let graph = fromAdjacencyList(
{
'node-start': ['node-a', 'node-b', 'node-c'],
'node-a': ['node-a-1', 'node-a-2', 'node-a-3', 'node-a-4'],
'node-a-1': [],
'node-a-2': [],
'node-a-3': [],
'node-a-4': [],
'node-b': ['node-b-1', 'node-b-2', 'node-b-3'],
'node-b-1': [],
'node-b-2': [],
'node-b-3': [],
'node-c': ['node-c-1', 'node-c-2'],
'node-c-1': ['node-c-1-1'],
'node-c-1-1': [],
'node-c-2': [],
},
{ width: 30, height: 30 },
);
graph = produce(graph, (draft) => {
draft.nodes['node-start'].diagramMakerData.position = { x: 400, y: 400 };
});
const layoutConfig: HierarchicalLayoutConfig = {
layoutType: LayoutType.HIERARCHICAL,
fixedNodeIds: ['node-start'],
distanceMin: 50,
distanceMax: 250,
distanceDeclineRate: 0.5,
};
expect(hierarchicalLayout(graph, layoutConfig)).toMatchSnapshot();
});
it('correctly lays out a tree with multiple fixed nodes', () => {
let graph = fromAdjacencyList(
{
'node-start': ['node-a', 'node-b', 'node-c'],
'node-a': ['node-a-1', 'node-a-2', 'node-a-3', 'node-a-4'],
'node-a-1': [],
'node-a-2': [],
'node-a-3': [],
'node-a-4': [],
'node-b': ['node-b-1', 'node-b-2', 'node-b-3'],
'node-b-1': [],
'node-b-2': [],
'node-b-3': [],
'node-c': ['node-c-1', 'node-c-2'],
'node-c-1': ['node-c-1-1'],
'node-c-1-1': [],
'node-c-2': [],
},
{ width: 20, height: 20 },
);
graph = produce(graph, (draft) => {
draft.nodes['node-a'].diagramMakerData.position = { x: 300, y: 400 };
draft.nodes['node-b'].diagramMakerData.position = { x: 500, y: 400 };
draft.nodes['node-c-1'].diagramMakerData.position = { x: 400, y: 100 };
});
const layoutConfig: HierarchicalLayoutConfig = {
layoutType: LayoutType.HIERARCHICAL,
fixedNodeIds: ['node-a', 'node-b', 'node-c-1'],
distanceMin: 30,
};
expect(hierarchicalLayout(graph, layoutConfig)).toMatchSnapshot();
});
it('finds optimal arrangement of free nodes between fixed nodes', () => {
let graph = fromAdjacencyList(
{
'fixed-node-1': ['node-center'],
'fixed-node-2': ['node-center'],
'fixed-node-3': ['node-center'],
'node-center': ['node-1', 'node-2', 'node-3', 'node-4', 'node-5', 'node-6'],
'node-1': [],
'node-2': [],
'node-3': [],
'node-4': [],
'node-5': [],
'node-6': [],
},
{ width: 40, height: 40 },
);
graph = produce(graph, (draft) => {
draft.nodes['fixed-node-1'].diagramMakerData.position = { x: 300, y: 400 };
draft.nodes['fixed-node-2'].diagramMakerData.position = { x: 500, y: 400 };
draft.nodes['fixed-node-3'].diagramMakerData.position = { x: 500, y: 300 };
});
const layoutConfig: HierarchicalLayoutConfig = {
layoutType: LayoutType.HIERARCHICAL,
fixedNodeIds: ['fixed-node-1', 'fixed-node-2', 'fixed-node-3'],
distanceMin: 40,
};
expect(hierarchicalLayout(graph, layoutConfig)).toMatchSnapshot();
});
it('uses `gravityAngle` even when `gravityStrength` is zero (odd number of nodes)', () => {
let graph = fromAdjacencyList(
{
'node-start': ['node-a', 'node-b', 'node-c'],
'node-a': [],
'node-b': [],
'node-c': [],
},
{ width: 30, height: 30 },
);
graph = produce(graph, (draft) => {
draft.nodes['node-start'].diagramMakerData.position = { x: 500, y: 500 };
});
const layoutConfig: HierarchicalLayoutConfig = {
layoutType: LayoutType.HIERARCHICAL,
fixedNodeIds: ['node-start'],
distanceMin: 75,
gravityAngle: Math.PI * 0.5,
gravityStrength: 0.0,
};
expect(hierarchicalLayout(graph, layoutConfig)).toMatchSnapshot();
});
it('uses `gravityAngle` even when `gravityStrength` is zero (even number of nodes)', () => {
let graph = fromAdjacencyList(
{
'node-start': ['node-a', 'node-b', 'node-c', 'node-d'],
'node-a': [],
'node-b': [],
'node-c': [],
'node-d': [],
},
{ width: 30, height: 30 },
);
graph = produce(graph, (draft) => {
draft.nodes['node-start'].diagramMakerData.position = { x: 500, y: 500 };
});
const layoutConfig: HierarchicalLayoutConfig = {
layoutType: LayoutType.HIERARCHICAL,
fixedNodeIds: ['node-start'],
distanceMin: 45,
gravityAngle: -Math.PI * 0.3,
gravityStrength: 0.0,
};
expect(hierarchicalLayout(graph, layoutConfig)).toMatchSnapshot();
});
it('pushes nodes towards `gravityAngle` (single node)', () => {
let graph = fromAdjacencyList(
{
'node-start': ['node-a'],
'node-a': [],
},
{ width: 30, height: 30 },
);
graph = produce(graph, (draft) => {
draft.nodes['node-start'].diagramMakerData.position = { x: 450, y: 450 };
});
const layoutConfig: HierarchicalLayoutConfig = {
layoutType: LayoutType.HIERARCHICAL,
fixedNodeIds: ['node-start'],
distanceMin: 60,
gravityAngle: Math.PI,
gravityStrength: 5.0,
};
expect(hierarchicalLayout(graph, layoutConfig)).toMatchSnapshot();
});
it('pushes nodes towards `gravityAngle` (odd number of nodes)', () => {
let graph = fromAdjacencyList(
{
'node-start': ['node-a', 'node-b', 'node-c', 'node-d', 'node-e'],
'node-a': [],
'node-b': [],
'node-c': [],
'node-d': [],
'node-e': [],
},
{ width: 30, height: 30 },
);
graph = produce(graph, (draft) => {
draft.nodes['node-start'].diagramMakerData.position = { x: 300, y: 450 };
});
const layoutConfig: HierarchicalLayoutConfig = {
layoutType: LayoutType.HIERARCHICAL,
fixedNodeIds: ['node-start'],
distanceMin: 30,
gravityAngle: Math.PI * 0.75,
gravityStrength: 1.75,
};
expect(hierarchicalLayout(graph, layoutConfig)).toMatchSnapshot();
});
it('pushes nodes towards `gravityAngle` (even number of nodes)', () => {
let graph = fromAdjacencyList(
{
'node-start': ['node-a', 'node-b', 'node-c', 'node-d'],
'node-a': [],
'node-b': [],
'node-c': [],
'node-d': [],
},
{ width: 30, height: 30 },
);
graph = produce(graph, (draft) => {
draft.nodes['node-start'].diagramMakerData.position = { x: 300, y: 450 };
});
const layoutConfig: HierarchicalLayoutConfig = {
layoutType: LayoutType.HIERARCHICAL,
fixedNodeIds: ['node-start'],
distanceMin: 50,
gravityAngle: Math.PI * 0.01,
gravityStrength: 0.8,
};
expect(hierarchicalLayout(graph, layoutConfig)).toMatchSnapshot();
});
it('applies gravity away from the mean of fixed angles', () => {
let graph = fromAdjacencyList(
{
'fixed-node-1': ['node-center'],
'fixed-node-2': ['node-center'],
'fixed-node-3': ['node-center'],
'node-center': ['node-1', 'node-2', 'node-3', 'node-4', 'node-5', 'node-6'],
'node-1': [],
'node-2': [],
'node-3': [],
'node-4': [],
'node-5': [],
'node-6': [],
},
{ width: 40, height: 40 },
);
graph = produce(graph, (draft) => {
draft.nodes['fixed-node-1'].diagramMakerData.position = { x: 300, y: 400 };
draft.nodes['fixed-node-2'].diagramMakerData.position = { x: 500, y: 400 };
draft.nodes['fixed-node-3'].diagramMakerData.position = { x: 500, y: 300 };
});
const layoutConfig: HierarchicalLayoutConfig = {
layoutType: LayoutType.HIERARCHICAL,
fixedNodeIds: ['fixed-node-1', 'fixed-node-2', 'fixed-node-3'],
distanceMin: 40,
gravityAngle: 0.0,
gravityStrength: 1.5,
};
expect(hierarchicalLayout(graph, layoutConfig)).toMatchSnapshot();
});
it('falls back to initial `gravityAngle` when mean is NaN', () => {
let graph = fromAdjacencyList(
{
'fixed-node-1': ['node-center'],
'fixed-node-2': ['node-center'],
'node-center': ['node-1'],
'node-1': [],
},
{ width: 50, height: 50 },
);
graph = produce(graph, (draft) => {
draft.nodes['fixed-node-1'].diagramMakerData.position = { x: 300, y: 400 };
draft.nodes['fixed-node-2'].diagramMakerData.position = { x: 500, y: 400 };
});
const layoutConfig: HierarchicalLayoutConfig = {
layoutType: LayoutType.HIERARCHICAL,
fixedNodeIds: ['fixed-node-1', 'fixed-node-2'],
distanceMin: 40,
gravityAngle: Math.PI * 0.5,
gravityStrength: 0.3,
};
expect(hierarchicalLayout(graph, layoutConfig)).toMatchSnapshot();
});
it('correctly propagates gravity throughout the graph', () => {
let graph = fromAdjacencyList(
{
'node-start': ['node-a', 'node-b', 'node-c'],
'node-a': ['node-a-1', 'node-a-2', 'node-a-3', 'node-a-4'],
'node-a-1': [],
'node-a-2': [],
'node-a-3': [],
'node-a-4': [],
'node-b': ['node-b-1', 'node-b-2', 'node-b-3'],
'node-b-1': [],
'node-b-2': [],
'node-b-3': [],
'node-c': ['node-c-1', 'node-c-2'],
'node-c-1': ['node-c-1-1'],
'node-c-1-1': [],
'node-c-2': [],
},
{ width: 30, height: 30 },
);
graph = produce(graph, (draft) => {
draft.nodes['node-start'].diagramMakerData.position = { x: 400, y: 400 };
});
const layoutConfig: HierarchicalLayoutConfig = {
layoutType: LayoutType.HIERARCHICAL,
fixedNodeIds: ['node-start'],
distanceMin: 50,
distanceMax: 250,
distanceDeclineRate: 0.5,
gravityAngle: Math.PI * 1.5,
gravityStrength: 1.0,
};
expect(hierarchicalLayout(graph, layoutConfig)).toMatchSnapshot();
});
it('handles nodes with varying sizes', () => {
let graph = fromAdjacencyList({
'node-start': ['node-1'],
'node-1': ['node-2'],
'node-2': [],
});
graph = produce(graph, (draft) => {
draft.nodes['node-start'].diagramMakerData.position = { x: 300, y: 300 };
draft.nodes['node-start'].diagramMakerData.size = { width: 150, height: 10 };
draft.nodes['node-1'].diagramMakerData.size = { width: 20, height: 80 };
draft.nodes['node-2'].diagramMakerData.size = { width: 50, height: 50 };
});
const layoutConfig: HierarchicalLayoutConfig = {
layoutType: LayoutType.HIERARCHICAL,
fixedNodeIds: ['node-start'],
distanceMin: 100,
distanceMax: 100,
gravityAngle: 0,
};
expect(hierarchicalLayout(graph, layoutConfig)).toMatchSnapshot();
});
});
Example #25
Source File: MessagesScreen.tsx From vsinder with Apache License 2.0 | 4 votes |
MessagesScreen: React.FC<MatchesStackNav<"messages">> = ({
route: { params },
navigation,
}) => {
const qKey = `/messages/${params.id}`;
const {
data,
isLoading,
isFetchingMore,
fetchMore,
} = useInfiniteQuery<IMessageResponse>(
qKey,
(key, cursor = "") =>
defaultQueryFn(`${key}/${cursor}`).then((x) => ({
hasMore: x.hasMore,
messages: x.messages.map((m: Message) =>
messageToGiftedMessage(m, { me: meData!.user!, them: params })
),
})),
{
staleTime: 0,
getFetchMore: ({ messages, hasMore }) =>
hasMore && messages.length
? messages[messages.length - 1].createdAt
: "",
}
);
const { data: meData } = useQuery<MeResponse>("/me", defaultQueryFn);
const cache = useQueryCache();
const [mutate] = useMutation(defaultMutationFn);
const {
inputForeground,
inputBackground,
buttonBackground,
buttonForeground,
buttonSecondaryBackground,
buttonSecondaryForeground,
buttonForegroundDarker,
buttonSecondaryForegroundDarker,
} = useTheme();
useOnWebSocket((e) => {
if (e.type === "new-message" && e.message.senderId === params.id) {
cache.setQueryData<IMessageResponse[]>(qKey, (d) => {
return produce(d!, (x) => {
x[0].messages = GiftedChat.append(x[0].messages, [
messageToGiftedMessage(e.message, {
me: meData!.user!,
them: params,
}),
]);
});
});
} else if (e.type === "unmatch") {
if (e.userId === params.id) {
navigation.goBack();
}
}
});
useEffect(() => {
getSocket().send(
JSON.stringify({ type: "message-open", userId: params.id })
);
const d = cache.getQueryData<MatchesResponse>("/matches/0");
if (d && d.matches.find((x) => x.userId === params.id && !x.read)) {
cache.setQueryData<MatchesResponse>("/matches/0", {
matches: d.matches.map((m) =>
m.userId === params.id ? { ...m, read: true } : m
),
});
}
return () => {
getSocket().send(JSON.stringify({ type: "message-open", userId: null }));
};
}, []);
if (isLoading) {
return <FullscreenLoading />;
}
if (!meData?.user) {
return null;
}
const messages = data ? data.map((x) => x.messages).flat() : [];
return (
<ScreenWrapper noPadding>
<GiftedChat
alignTop
loadEarlier={data?.[data?.length - 1]?.hasMore}
onPressAvatar={(u) =>
navigation.navigate("viewCard", { id: u._id.toString() })
}
isLoadingEarlier={!!isFetchingMore}
renderLoadEarlier={({ isLoadingEarlier }) =>
isLoadingEarlier ? (
<Loading />
) : (
<MyButton onPress={() => fetchMore()}>load more</MyButton>
)
}
listViewProps={{
showsVerticalScrollIndicator: false,
}}
timeTextStyle={{
left: { color: buttonSecondaryForegroundDarker },
right: { color: buttonForegroundDarker },
}}
renderBubble={(props) => {
return (
<Bubble
{...props}
textStyle={{
right: {
color: buttonForeground,
},
left: {
color: buttonSecondaryForeground,
},
}}
wrapperStyle={{
left: {
backgroundColor: buttonSecondaryBackground,
},
right: {
backgroundColor: buttonBackground,
},
}}
/>
);
}}
renderSend={(props) => (
<Send
{...props}
containerStyle={{
justifyContent: "center",
alignItems: "center",
alignSelf: "center",
marginRight: 15,
}}
>
<MaterialIcons name="send" size={24} color={buttonBackground} />
</Send>
)}
// @ts-ignore
containerStyle={{
backgroundColor: inputBackground,
}}
textInputStyle={{
color: inputForeground,
backgroundColor: inputBackground,
}}
// @ts-ignore
renderTicks={() => null}
messages={messages}
onSend={(messages) => {
messages.forEach((m) => {
mutate([
"/message",
{ recipientId: params.id, text: m.text, matchId: params.matchId },
"POST",
]).then(({ message: newMessage }) => {
cache.setQueryData<IMessageResponse[]>(qKey, (d) => {
return produce(d!, (x) => {
x[0].messages = GiftedChat.append(x[0].messages, [
messageToGiftedMessage(newMessage, {
me: meData.user!,
them: params,
}),
]);
});
});
const d = cache.getQueryData<MatchesResponse>("/matches/0");
if (d) {
cache.setQueryData<MatchesResponse>("/matches/0", {
matches: d.matches.map((m) =>
m.userId === params.id
? {
...m,
message: {
text: newMessage.text,
createdAt: newMessage.createdAt,
},
}
: m
),
});
}
});
});
}}
user={{
_id: meData.user.id,
}}
/>
</ScreenWrapper>
);
}
Example #26
Source File: SelectComponentDialog.tsx From baleen3 with Apache License 2.0 | 4 votes |
SelectComponentDialog: React.FC<SelectComponentDialogProps> = ({
open,
components,
onClose,
onSelect,
type,
...dialogProps
}: SelectComponentDialogProps) => {
const [selected, setSelected] = useState<ComponentInfo[]>([])
const [search, setSearch] = useState('')
const filtered = useMemo(
() => filterComponents(components, search),
[components, search]
)
useEffect(() => {
if (open) {
setSelected([])
setSearch('')
}
}, [open])
const handleSelect = useCallback(() => {
onClose()
onSelect(selected)
}, [selected, onSelect, onClose])
const toggleSelected = useCallback(
(component: ComponentInfo): void => {
setSelected(
produce((draft: ComponentInfo[]) => {
const index = draft.findIndex(
(toTest) => toTest.componentClass === component.componentClass
)
if (index === -1) {
draft.push(component)
} else {
draft.splice(index, 1)
}
})
)
},
[setSelected]
)
return (
<Dialog
open={open}
maxWidth="lg"
fullWidth={true}
scroll="paper"
onClose={onClose}
{...dialogProps}
>
<DialogTitle>Select {type}</DialogTitle>
<SearchInput onSearch={setSearch} p={3} />
<DialogContent>
<Box height="50vh">
<Flex flexWrap="wrap">
{filtered.length === 0 ? (
<Typography align="center">No {type}s</Typography>
) : (
filtered.map((component: Readonly<ComponentInfo>) => (
<ComponentsInfoCard
key={component.componentClass}
info={component}
selectable
selected={selected.includes(component)}
toggleSelected={toggleSelected}
/>
))
)}
</Flex>
</Box>
</DialogContent>
<Divider />
<DialogActions>
<Button variant="text" onClick={onClose}>
Cancel
</Button>
<Button
color="primary"
disabled={selected.length === 0}
onClick={handleSelect}
>
Select
</Button>
</DialogActions>
</Dialog>
)
}
Example #27
Source File: time-select.tsx From erda-ui with GNU Affero General Public License v3.0 | 4 votes |
TimeRange = ({ onChange, value, format }: ITimeRangeProps) => {
const [{ startOpen, endOpen, data }, updater, update] = useUpdate<ITimeRangeState>({
data: value ?? ({} as ITimeRange),
startOpen: false,
endOpen: false,
});
const dateRange = React.useRef<ITimeRange['customize']>(cloneDeep(data.customize || {}));
const triggerChange = (changedValue: ITimeRange) => {
onChange?.({ ...data, ...value, ...changedValue });
};
const handleSelectQuickTimeRange = (str: IRelativeTime) => {
const v = value ?? data;
const newData = produce(v, (draft) => {
draft.mode = 'quick';
draft.quick = str;
draft.customize = {
start: undefined,
end: undefined,
};
});
dateRange.current.end = undefined;
dateRange.current.start = undefined;
if (!('quick' in (value ?? {}))) {
updater.data(newData);
}
triggerChange(newData);
};
const handleChangeDate = React.useCallback(
(flag: string, date: Moment | null, _: string) => {
const v = value ?? data;
const newData = produce(v, (draft) => {
draft.mode = 'customize';
draft.quick = undefined;
if (!draft.customize) {
draft.customize = {};
}
draft.customize[flag] = date;
});
dateRange.current[flag] = date;
const { start, end } = dateRange.current;
const newState: Partial<ITimeRangeState> = {
startOpen: !start,
endOpen: !end,
};
if (!('customize' in (value ?? {}))) {
newState.data = newData;
}
update(newState);
if (start && end) {
triggerChange({
mode: 'customize',
customize: { start, end },
quick: undefined,
});
}
},
[data, value],
);
const disabledStart = (current: Moment) => {
return current && dateRange.current.end && current > dateRange.current.end;
};
const disabledEnd = (current: Moment) => {
return current && dateRange.current.start && current < dateRange.current.start;
};
const mode = value?.mode || data.mode;
const activeQuick = value?.quick || data.quick;
const start = mode === 'customize' ? value?.customize.start || data?.customize.start : undefined;
const end = mode === 'customize' ? value?.customize.end || data?.customize.end : undefined;
return (
<div className="flex h-full items-stretch">
<div className="w-56 h-full px-3">
<p className="pt-3 font-medium text-white-9">{i18n.t('Absolute time range')}</p>
<p className="mt-3 mb-1 text-white-6">{firstCharToUpper(i18n.t('common:start time'))}</p>
<DatePicker
format={format}
disabledDate={disabledStart}
disabledTime={disabledStart}
allowClear={false}
open={startOpen}
onOpenChange={updater.startOpen}
showTime
className="w-full"
value={start}
onChange={(...arg) => {
handleChangeDate('start', ...arg);
}}
/>
<p className="mt-3 mb-1 text-white-6">{i18n.t('common:End time')}</p>
<DatePicker
format={format}
disabledDate={disabledEnd}
disabledTime={disabledEnd}
allowClear={false}
open={endOpen}
onOpenChange={updater.endOpen}
showTime
className="w-full"
value={end}
onChange={(...arg) => {
handleChangeDate('end', ...arg);
}}
/>
</div>
<div className="w-44 h-full border-left flex flex-col">
<p className="px-3 pt-3 font-medium text-white-9">{i18n.t('Relative time range')}</p>
<ul className="time-quick-select overflow-y-auto flex-1 mt-3 scroll-bar-dark">
{map(relativeTimeRange, (label, range: IRelativeTime) => {
return (
<li
className={`time-quick-select-item font-normal h-9 px-3 flex items-center hover:bg-white-08 cursor-pointer ${
mode === 'quick' && activeQuick === range ? 'text-white bg-white-08' : 'text-white-9'
}`}
key={range}
onClick={() => {
handleSelectQuickTimeRange(range);
}}
>
{firstCharToUpper(label)}
</li>
);
})}
</ul>
</div>
</div>
);
}