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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
/** 设置状态 */
    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 vote down vote up
/** 更新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 vote down vote up
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 vote down vote up
/**
 * 复制组件
 * @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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
/**
   * 更新插槽状态的方法
   * @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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
/**
 * 往画板或者容器组件添加组件
 * @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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
// 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 vote down vote up
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 vote down vote up
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 vote down vote up
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>
  );
}