lodash#throttle TypeScript Examples

The following examples show how to use lodash#throttle. 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: inspector.ts    From next-basics with GNU General Public License v3.0 7 votes vote down vote up
hoverOnBrick = throttle(
  (brick: HTMLElement) => {
    const iidList = getPossibleBrickIidList(brick);
    if (iidList.length > 0) {
      window.parent.postMessage(
        {
          sender: "previewer",
          type: "hover-on-brick",
          iidList,
        } as PreviewMessagePreviewerHoverOnBrick,
        previewProxyOrigin
      );
    }
  },
  100,
  { leading: false }
)
Example #2
Source File: PanelResizer.tsx    From grafana-chinese with Apache License 2.0 6 votes vote down vote up
constructor(props: Props) {
    super(props);

    this.state = {
      editorHeight: this.initialHeight,
    };

    this.throttledChangeHeight = throttle(this.changeHeight, 20, { trailing: true });
  }
Example #3
Source File: useDebounceState.ts    From querybook with Apache License 2.0 6 votes vote down vote up
export function useDebounceState<T>(
    value: T,
    setValue: (v: T) => any,
    delay: number,
    options?: IUseDebounceStateOptions
): [T, (v: T) => any] {
    // State and setters for debounced value
    const [cachedValue, setCachedValue] = useState(value);
    const lastValueUpdatedRef = useRef(value);
    const setValueDebounced = useMemo(() => {
        const delayMethod =
            options?.method === 'throttle' ? throttle : debounce;

        return delayMethod((newValue: T) => {
            lastValueUpdatedRef.current = newValue;
            setValue(newValue);
        }, delay);
    }, [delay, options?.method, setValue]);

    useEffect(() => {
        if (value !== lastValueUpdatedRef.current) {
            lastValueUpdatedRef.current = value;
            setCachedValue(value);
        }
    }, [value]);

    useEffect(() => {
        if (cachedValue !== value) {
            setValueDebounced(cachedValue);
        }
    }, [cachedValue]);

    return [cachedValue, setCachedValue];
}
Example #4
Source File: index.tsx    From erda-ui with GNU Affero General Public License v3.0 6 votes vote down vote up
BackToTop = ({ containerId }: { containerId?: string }) => {
  const [visible, setVisible] = React.useState(false);
  const isMoving = React.useRef(false);
  const mainElement = React.useMemo(
    () => document.querySelector(containerId ? `#${containerId}` : '#main'),
    [containerId],
  );

  const handleScroll = React.useCallback(
    throttle(() => {
      const isV = mainElement ? mainElement.scrollTop * 1.25 > mainElement!.clientHeight : false;
      setVisible(isV);
    }, 300),
    [mainElement],
  );

  useEffectOnce(() => {
    mainElement && mainElement.addEventListener('scroll', handleScroll);
    return () => {
      mainElement && mainElement.removeEventListener('scroll', handleScroll);
    };
  });

  const onBackToTop = () => {
    if (isMoving.current) {
      return;
    }
    isMoving.current = true;
    mainElement!.scrollTo({ top: 0, behavior: 'smooth' });
    isMoving.current = false;
  };

  return visible ? (
    <Tooltip title={i18n.t('Back to Top')}>
      <ErdaIcon size="20" className="scroll-top-btn" type="huidaodingbu" onClick={onBackToTop} />
    </Tooltip>
  ) : null;
}
Example #5
Source File: index.ts    From am-editor with MIT License 6 votes vote down vote up
wheelYScroll = throttle(
		(event: any) => {
			event.preventDefault();
			const dir =
				(isMacos
					? event.wheelDeltaX
					: event.wheelDelta / 120 || -event.detail) > 0
					? 'up'
					: 'down';
			const containerElement = this.container.get<HTMLElement>();
			if (!containerElement) return;
			const containerHeight = this.container.height();
			const step = Math.max(
				containerHeight /
					(isMacos ? 20 - Math.abs(event.wheelDelta) : 8),
				20,
			);
			let top =
				containerElement.scrollTop + (dir === 'up' ? -step : step);
			top =
				dir === 'up'
					? Math.max(0, top)
					: Math.min(top, this.sHeight - this.oHeight);
			containerElement.scrollTop = top;
		},
		isMacos ? 100 : 0,
		{ trailing: true },
	);
Example #6
Source File: Portal.tsx    From excalidraw with MIT License 6 votes vote down vote up
queueFileUpload = throttle(async () => {
    try {
      await this.collab.fileManager.saveFiles({
        elements: this.collab.excalidrawAPI.getSceneElementsIncludingDeleted(),
        files: this.collab.excalidrawAPI.getFiles(),
      });
    } catch (error: any) {
      if (error.name !== "AbortError") {
        this.collab.excalidrawAPI.updateScene({
          appState: {
            errorMessage: error.message,
          },
        });
      }
    }

    this.collab.excalidrawAPI.updateScene({
      elements: this.collab.excalidrawAPI
        .getSceneElementsIncludingDeleted()
        .map((element) => {
          if (this.collab.fileManager.shouldUpdateImageElementStatus(element)) {
            // this will signal collaborators to pull image data from server
            // (using mutation instead of newElementWith otherwise it'd break
            // in-progress dragging)
            return newElementWith(element, { status: "saved" });
          }
          return element;
        }),
    });
  }, FILE_UPLOAD_TIMEOUT);
Example #7
Source File: queue-sse.service.ts    From office-hours with GNU General Public License v3.0 6 votes vote down vote up
private throttleUpdate(updateFunction: (queueId: number) => Promise<void>) {
    return throttle(
      async (queueId: number) => {
        try {
          await updateFunction(queueId);
        } catch (e) {}
      },
      1000,
      {
        leading: false,
        trailing: true,
      },
    );
  }
Example #8
Source File: index.tsx    From gant-design with MIT License 6 votes vote down vote up
ContextContent: React.FC<ContextContentProps> = ({
  id,
  onSizeChange,
  throttleTime,
  children,
}) => {
  const {
    state: { modals },
  } = useContext(ModalContext);
  const { width, height } = modals[id];

  useEffect(() => {
    sizeChange(width, height);
  }, [width, height]);

  const sizeChange = useCallback(
    throttle((width, height) => {
      onSizeChange && onSizeChange(width, height);
    }, throttleTime),
    [],
  );

  return <>{children}</>;
}
Example #9
Source File: useOverflowWrapper.ts    From hub with Apache License 2.0 6 votes vote down vote up
useOverflowWrapper = (
  wrapperRef: MutableRefObject<HTMLDivElement | null>,
  maxHeight: number,
  itemsLength: number
) => {
  const getHeight = (): number => {
    if (wrapperRef && wrapperRef.current) {
      return wrapperRef.current.offsetHeight;
    } else {
      return 0;
    }
  };

  const checkDimensions = () => {
    const height = getHeight();
    return height > maxHeight;
  };

  const [overflowContainer, setOverflowContainer] = useState<boolean>(() => checkDimensions());

  const handleOverflow = () => {
    setOverflowContainer(checkDimensions());
  };

  useEffect(() => {
    window.addEventListener('resize', throttle(handleOverflow, 200));
    return () => window.removeEventListener('resize', handleOverflow);
  }, []); /* eslint-disable-line react-hooks/exhaustive-deps */

  useLayoutEffect(() => {
    handleOverflow();
  }, []); /* eslint-disable-line react-hooks/exhaustive-deps */

  useEffect(() => {
    handleOverflow();
  }, [itemsLength]); /* eslint-disable-line react-hooks/exhaustive-deps */

  return overflowContainer;
}
Example #10
Source File: useBreakpointDetect.ts    From hub with Apache License 2.0 6 votes vote down vote up
useBreakpointDetect = () => {
  const [brkPnt, setBrkPnt] = useState(() => getDeviceConfig(window.innerWidth));

  useEffect(() => {
    const calcInnerWidth = throttle(() => {
      setBrkPnt(getDeviceConfig(window.innerWidth));
    }, 200);
    window.addEventListener('resize', calcInnerWidth);

    return () => window.removeEventListener('resize', calcInnerWidth);
  }, []);

  return brkPnt;
}
Example #11
Source File: brush-selection.ts    From S2 with MIT License 6 votes vote down vote up
private handleScroll = throttle((x, y) => {
    if (
      this.brushSelectionStage === InteractionBrushSelectionStage.UN_DRAGGED
    ) {
      return;
    }

    const {
      x: { value: newX, needScroll: needScrollForX },
      y: { value: newY, needScroll: needScrollForY },
    } = this.formatBrushPointForScroll({ x, y });

    const config = this.autoScrollConfig;
    if (needScrollForY) {
      config.y.value = y;
      config.y.scroll = true;
    }
    if (needScrollForX) {
      config.x.value = x;
      config.x.scroll = true;
    }

    this.setMoveDistanceFromCanvas({ x, y }, needScrollForX, needScrollForY);

    this.renderPrepareSelected({
      x: newX,
      y: newY,
    });

    if (needScrollForY || needScrollForX) {
      this.clearAutoScroll();
      this.autoScroll();
      this.autoScrollIntervalId = setInterval(this.autoScroll, 16);
    }
  }, 30);
Example #12
Source File: fetch.ts    From vue-reuse with MIT License 6 votes vote down vote up
constructor(
    service: Service<R, P>,
    config: FetchConfig<R, P>,
    subscribe: Subscribe<R, P>,
    initResult?: { loading?: boolean; data?: R; params: P }
  ) {
    this.service = service
    this.config = config
    this.subscribe = subscribe
    if (initResult) {
      this.result = {
        ...this.result,
        ...initResult
      }
    }

    this.debounceRun = this.config?.debounceTime
      ? debounce(this._run, this.config.debounceTime)
      : undefined

    this.throttleRun = this.config?.throttleTime
      ? throttle(this._run, this.config.throttleTime)
      : undefined
  }
Example #13
Source File: index.tsx    From gant-design with MIT License 5 votes vote down vote up
ModalComponent = (modelProps: ModalProps) => {
  const globalConfig = getGlobalConfig();
  const props = { ...globalConfig, ...modelProps };
  const {
    id = uuid,
    throttle = 0,
    children,
    maxZIndex = 999,
    isModalDialog = true,
    onSizeChange,
    type = 'resize',
    ...restProps
  } = props;
  const { itemState = {}, width: restWidth } = restProps;
  const { width: itemWidth, height: itemHeight } = itemState;

  //兼容type为autoHeight的情况中的指定高度
  //宽度
  let modelWidth: number | string;
  if (typeof itemWidth === 'number') {
    modelWidth = itemWidth;
  }
  if (typeof itemWidth === 'string' && itemWidth.indexOf('%')) {
    modelWidth = (window.innerWidth * parseInt(itemWidth)) / 100;
  }
  if (restWidth) {
    modelWidth = restWidth;
  }

  //高度
  let modelHeight: number;
  if (typeof itemHeight === 'number') {
    modelHeight = itemHeight;
  }
  if (typeof itemHeight === 'string' && itemHeight.indexOf('%')) {
    modelHeight = (window.innerHeight * parseInt(itemHeight)) / 100;
  }

  const contentHeight = useMemo(() => {
    if (type === 'autoHeight') {
      return 'auto';
    }
    if (itemHeight && modelHeight) {
      return modelHeight - 0;
    }
  }, [type, itemHeight, modelHeight]);

  return (
    <>
      {type === 'resize' ? (
        <ResizableProvider maxZIndex={maxZIndex} {...pick(restProps, providerPropKeys)}>
          <ResizableModal
            id={id}
            isModalDialog={isModalDialog}
            {...omit(restProps, providerPropKeys)}
          >
            <ContextContent
              id={id}
              children={children}
              throttleTime={throttle}
              onSizeChange={onSizeChange}
            />
          </ResizableModal>
        </ResizableProvider>
      ) : (
        <Modal width={modelWidth} {...restProps}>
          <div
            style={{
              height: contentHeight,
            }}
          >
            {children}
          </div>
        </Modal>
      )}
    </>
  );
}
Example #14
Source File: dbmss.local.ts    From relate with GNU General Public License v3.0 5 votes vote down vote up
private throttledDiscoverDbmss = throttle(this.discoverDbmss, DISCOVER_DBMS_THROTTLE_MS);
Example #15
Source File: index.ts    From fe-v5 with Apache License 2.0 5 votes vote down vote up
initEvent() {
    let mousedownStatus = false;
    let mousedownPos: EventPosition = {} as EventPosition;
    let mouseleavePos: EventPosition;
    let isMouserover = false;
    // eslint-disable-next-line no-underscore-dangle
    const _this = this;
    const handleMouseover = debounce((eventPosition: EventPosition) => {
      if (isMouserover) {
        _this.tooltip.draw(eventPosition, _this.xScales, _this.yScales);
        if (mousedownStatus) {
          _this.zoom.drawMarker(mousedownPos, eventPosition);
        }
      }
    }, 10);
    const handleMouseLeave = throttle(() => {
      _this.tooltip.clear();
    }, 10);
    d3.select(this.eventCanvas)
      .on('mousemove', function mousemove(this: HTMLCanvasElement) {
        isMouserover = true;
        handleMouseover(d3.event);
      })
      .on('mouseleave', function mouseleave(this: HTMLCanvasElement) {
        mouseleavePos = d3.event;
        isMouserover = false;
        handleMouseLeave();
      })
      .on('mousedown', function mousedown(this: HTMLCanvasElement) {
        mousedownStatus = true;
        mousedownPos = d3.event;
      })
      .on('mouseup', function mouseup(this: HTMLCanvasElement) {
        d3.event.stopPropagation();
        const eventPosition = d3.event as EventPosition;
        if (mousedownPos && mousedownPos.offsetX !== eventPosition.offsetX) {
          _this.zoom.clearMarker();
          _this.zoom.onZoom(mousedownPos, eventPosition, (transform: Transform) => {
            _this.handleZoom(transform);
          });
        } else {
          _this.options.onClick(d3.event);
        }
        mousedownStatus = false;
        mousedownPos = {} as EventPosition;
      })
      .on('touchmove', function touchmove(this: HTMLCanvasElement) {
        // canvas的touch事件不能获得offsetX和offsetY,需要计算
        const eventPosition = getTouchPosition(_this.eventCanvas, d3.event) as EventPosition;
        _this.tooltip.draw(eventPosition, _this.xScales, _this.yScales);
        if (mousedownStatus) {
          _this.zoom.drawMarker(mousedownPos, eventPosition);
        }
      })
      .on('touchend', function mouseleave(this: HTMLCanvasElement) {
        mouseleavePos = getTouchPosition(_this.eventCanvas, d3.event) as EventPosition;
        isMouserover = false;
        handleMouseLeave();
      });

    // TODO: removeListener
    window.addEventListener('mouseup', () => {
      if (!isEmpty(mousedownPos)) {
        const eventPosition = mouseleavePos;
        mousedownStatus = false;
        _this.zoom.clearMarker();
        _this.zoom.onZoom(mousedownPos, eventPosition, (transform: Transform) => {
          _this.handleZoom(transform);
        });
        mousedownPos = {} as EventPosition;
      }
    });

    addListener(this.options.chart.renderTo, this.handleResize);
  }
Example #16
Source File: deploy-cluster-log.tsx    From erda-ui with GNU Affero General Public License v3.0 5 votes vote down vote up
throttleScroll = throttle(() => this.onScroll(), 100);
Example #17
Source File: go-to.tsx    From erda-ui with GNU Affero General Public License v3.0 5 votes vote down vote up
changeBrowserHistory = throttle(
  (action, _path) => {
    const history = getConfig('history');
    action === 'replace' ? history.replace(_path) : history.push(_path);
  },
  1000,
  { trailing: false },
)
Example #18
Source File: Searchbar.tsx    From nextjs-shopify with MIT License 5 votes vote down vote up
SearchModalContent = (props: {
  initialSearch?: string
  onSearch: (term: string) => any
}) => {
  const [search, setSearch] = useState(
    props.initialSearch && String(props.initialSearch)
  )
  const [products, setProducts] = useState([] as any[])
  const [loading, setLoading] = useState(false)
  const getProducts = async (searchTerm: string) => {
    setLoading(true)
    const results = await searchProducts(
      shopifyConfig,
      String(searchTerm),
    )
    setSearch(searchTerm)
    setProducts(results)
    setLoading(false)
    if (searchTerm) {
      props.onSearch(searchTerm)
    }
  }

  useEffect(() => {
    if (search) {
      getProducts(search)
    }
  }, [])

  const throttleSearch = useCallback(throttle(getProducts), [])

  return (
    <Themed.div
      sx={{
        display: 'flex',
        flexDirection: 'column',
        justifyContent: 'center',
        p: [1, 2],
        width: '100%',
      }}
    >
      <Input
        type="search"
        sx={{ marginBottom: 15 }}
        defaultValue={props.initialSearch}
        placeholder="Search for products..."
        onChange={(event) => throttleSearch(event.target.value)}
      />
      {loading ? (
        <LoadingDots />
      ) : products.length ? (
        <>
          <Label>
            Search Results for "<strong>{search}</strong>"
          </Label>
          <ProductGrid
            cardProps={{
              imgHeight: 540,
              imgWidth: 540,
              imgPriority: false,
            }}
            products={products}
            offset={0}
            limit={products.length}
          ></ProductGrid>
        </>
      ) : (
        <span>
          {search ? (
            <>
              There are no products that match "<strong>{search}</strong>"
            </>
          ) : (
            <> </>
          )}
        </span>
      )}
    </Themed.div>
  )
}
Example #19
Source File: log-roller.tsx    From erda-ui with GNU Affero General Public License v3.0 5 votes vote down vote up
throttleScroll = throttle(() => this.onScroll(), 100);
Example #20
Source File: index.tsx    From erda-ui with GNU Affero General Public License v3.0 5 votes vote down vote up
onScroll = throttle(() => {
    if (!this.props.isLoading) {
      const { scrollHeight, scrollTop, clientHeight } = this.targetDom;
      if (scrollHeight - scrollTop - clientHeight < this.threshold && this.props.hasMore) {
        this.load();
      }
    }
  }, 100);
Example #21
Source File: form-builder.tsx    From erda-ui with GNU Affero General Public License v3.0 5 votes vote down vote up
PureFormBuilder = <T extends Obj>({
  form,
  children,
  layout = 'vertical',
  isMultiColumn,
  columnNum,
  readonly,
  ...rest
}: IPureProps<T>) => {
  const [realColumnNum, setRealColumnNum] = React.useState<number | undefined>();

  form.validateFieldsAndScroll = (successCallBack, errorCallBack) => {
    form
      .validateFields()
      .then(successCallBack)
      .catch((err) => {
        errorCallBack?.(err);
        form.scrollToField(err.errorFields[0]?.name);
      });
  };

  form.fieldsInfo = {};

  const setFieldsInfo = (key: string, value: IFieldType[]) => {
    form?.fieldsInfo?.[key] && (form.fieldsInfo[key] = value);
  };

  const handleResize = throttle(({ width }: { width: number }) => {
    let columns = 1;
    if (width <= 400) {
      columns = 1;
    } else if (width < 600) {
      columns = 2;
    } else if (width < 1024) {
      columns = 3;
    } else if (width < 1440) {
      columns = 4;
    } else if (width < 1920) {
      columns = 6;
    } else {
      columns = 8;
    }
    setRealColumnNum(columns);
  }, 500);

  return (
    <ResizeObserver onResize={handleResize}>
      <div className="w-full erda-form-builder">
        <Form {...rest} form={form} layout={layout}>
          <FormContext.Provider
            value={{
              realColumnNum,
              parentIsMultiColumn: isMultiColumn,
              parentColumnNum: columnNum,
              parentReadonly: readonly,
              setFieldsInfo,
            }}
          >
            {children}
          </FormContext.Provider>
        </Form>
      </div>
    </ResizeObserver>
  );
}
Example #22
Source File: ResizableTextArea.tsx    From querybook with Apache License 2.0 5 votes vote down vote up
ResizableTextArea: React.FC<IResizableTextareaProps> = ({
    value = '',
    className = '',
    transparent = false,
    disabled = false,
    autoResize = true,
    rows = 1,
    onChange,

    ...textareaProps
}) => {
    const textareaRef = useRef<HTMLTextAreaElement>();
    const autoHeight = useCallback(
        throttle(() => {
            if (textareaRef.current && autoResize) {
                const textarea = textareaRef.current;
                textarea.style.height = 'auto';
                textarea.style.height = `${textarea.scrollHeight}px`;
            }
        }, 500),
        [autoResize]
    );

    useEffect(() => {
        autoHeight();
    }, [value, autoResize]);

    useResizeObserver(textareaRef.current, autoHeight);

    const handleChange = useCallback(
        (evt: React.ChangeEvent<HTMLTextAreaElement>) => {
            onChange(evt.target.value);
        },
        [onChange]
    );

    return (
        <StyledTextarea
            className={clsx({
                ResizableTextArea: true,
                [className]: Boolean(className),
            })}
            rows={rows}
            ref={textareaRef}
            value={value}
            onChange={handleChange}
            onInput={autoHeight}
            disabled={disabled}
            transparent={transparent}
            {...textareaProps}
        />
    );
}
Example #23
Source File: index.ts    From am-editor with MIT License 5 votes vote down vote up
wheelXScroll = throttle(
		(event: any) => {
			event.preventDefault();
			const dir =
				(isMacos
					? event.wheelDeltaX
					: event.wheelDelta / 120 || -event.detail) > 0
					? 'up'
					: 'down';
			const containerElement = this.container.get<HTMLElement>();
			if (!containerElement) return;
			const width = this.container.width();
			const containerWidth = this.#scroll?.getOffsetWidth
				? this.#scroll.getOffsetWidth(width)
				: width;
			const step = Math.max(
				containerWidth /
					(isMacos ? 20 - Math.abs(event.wheelDelta) : 8),
				20,
			);

			let left =
				(this.#scroll?.getScrollLeft
					? this.#scroll.getScrollLeft(containerElement.scrollLeft)
					: containerElement.scrollLeft) +
				(dir === 'up' ? -step : step);

			left =
				dir === 'up'
					? Math.max(0, left)
					: Math.min(left, this.sWidth - this.oWidth);
			if (this.#scroll) {
				const { onScrollX } = this.#scroll;
				if (onScrollX) {
					const result = onScrollX(left);
					if (result > 0) containerElement.scrollLeft = result;
					else containerElement.scrollLeft = 0;
				}
				this.scroll({ left });
			} else {
				containerElement.scrollLeft = left;
			}
		},
		isMacos ? 50 : 0,
		{ trailing: true },
	);
Example #24
Source File: NumberInput.tsx    From yugong with MIT License 5 votes vote down vote up
QuadrangularSelect: React.FC<Props> = ({
    unit,
    label,
    defaultValue,
    onChange,
    ...other
}) => {
    const ref = useRef(null);
    const moduleId = useSelector(
        (state: RootState) => state.activationItem.moduleId
    );

    useEffect(() => {
        if (ref.current) {
            (ref.current as any).blur();
        }
    }, [moduleId]);

    const [onDebounce, setOnDebounce] = useState(false);
    const onFocus = useCallback(() => {
        // 开启防抖禁止defaultValue回填
        setOnDebounce(true);
    }, []);
    const onBlur = useCallback(() => {
        // 关闭防抖允许defaultValue回填
        setValue(defaultValue);
        setOnDebounce(false);
    }, [defaultValue]);

    // 接管默认值
    const [value, setValue] = useState(defaultValue);

    useEffect(() => {
      if (!onDebounce) {
        setValue(defaultValue);
      } 
    }, [defaultValue, onDebounce]);

    const refChange = useSafeCallback(onChange);

    /**
     * 高频编辑防抖处理
     */
    const onChangeDebounce = useMemo(
        () =>
            throttle((e: number) => {
                refChange(e);
            }, 500),
        [refChange]
    );

    const onChangeValue = useCallback(
      (e) => {
        setValue(e);
        onChangeDebounce(e);
      },
      [onChangeDebounce],
    )
    
    return (
        <Row className={s.row} gutter={4}>
            <Col className={s.label} span={7}>
                {label || ''}
            </Col>
            <Col span={17}>
                <InputNumber
                    {...other}
                    onChange={onChangeValue}
                    onBlur={onBlur}
                    onFocus={onFocus}
                    value={value}
                    ref={ref}
                    addonAfter={<span className={s.suf}>{unit}</span>}
                />
            </Col>
        </Row>
    );
}
Example #25
Source File: zoom.tsx    From backstage with Apache License 2.0 5 votes vote down vote up
export function ZoomProvider({ children }: PropsWithChildren<{}>) {
  const [registeredSelectors, setRegisteredSelectors] = useState<
    Array<Dispatch<ZoomState>>
  >([]);
  const [selectState, setSelectState] = useState<ZoomState>({});
  const [zoomState, setZoomState] = useState<ZoomState>({});

  const registerSelection = useCallback(
    (selector: Dispatch<ZoomState>) => {
      setRegisteredSelectors(old => [...old, selector]);

      return () => {
        setRegisteredSelectors(old => old.filter(sel => sel === selector));
      };
    },
    [setRegisteredSelectors],
  );

  const callSelectors = useCallback(
    (state: ZoomState) => {
      registeredSelectors.forEach(selector => {
        selector(state);
      });
    },
    [registeredSelectors],
  );

  const throttledCallSelectors = useMemo(
    () => throttle(callSelectors, 200),
    [callSelectors],
  );

  useEffect(() => {
    throttledCallSelectors({
      left: selectState.left,
      right: selectState.right,
    });
  }, [selectState.left, selectState.right, throttledCallSelectors]);

  const resetZoom = useCallback(() => {
    setSelectState({});
    setZoomState({});
  }, [setSelectState, setZoomState]);

  const value = useMemo(
    (): ZoomContext => ({
      registerSelection,
      setSelectState,

      zoomState,
      setZoomState,

      resetZoom,
    }),
    [registerSelection, setSelectState, zoomState, setZoomState, resetZoom],
  );

  return <context.Provider value={value} children={children} />;
}
Example #26
Source File: row-column-resize.ts    From S2 with MIT License 5 votes vote down vote up
private bindMouseMove() {
    this.spreadsheet.on(S2Event.LAYOUT_RESIZE_MOUSE_MOVE, (event) => {
      throttle(this.resizeMouseMove, 33)(event);
    });
  }
Example #27
Source File: SearchAndReplaceBar.tsx    From querybook with Apache License 2.0 4 votes vote down vote up
SearchAndReplaceBar = React.forwardRef<
    ISearchAndReplaceBarHandles,
    ISearchAndReplaceBarProps
>(
    (
        {
            onHide,
            onSearchStringChange,
            onReplaceStringChange,
            moveResultIndex,
            onReplace,
            onSearchOptionsChange,
        },
        ref
    ) => {
        const lastActiveElementRef = useRef<HTMLElement | Element>(
            document.activeElement
        );
        const selfRef = useRef<HTMLDivElement>(null);
        const [showReplace, setShowReplace] = useState(false);
        const {
            searchState: {
                searchString,
                searchResults,
                replaceString,
                currentSearchResultIndex,
                searchOptions,
            },
        } = useContext(SearchAndReplaceContext);
        const searchInputRef = useRef<HTMLInputElement>(null);
        const replaceInputRef = useRef<HTMLInputElement>(null);
        const focusSearchInput = useCallback(() => {
            const activeElement = document.activeElement;
            if (
                activeElement !== replaceInputRef.current &&
                activeElement !== searchInputRef.current
            ) {
                // To prevent the case when typing in search and then tab to go to replace
                // but then searching would then refocus to search input
                searchInputRef.current?.focus();
                lastActiveElementRef.current = activeElement;
            }
        }, []);
        const handleHide = useCallback(() => {
            onHide();
            if (
                lastActiveElementRef.current &&
                lastActiveElementRef.current !== document.body
            ) {
                (lastActiveElementRef.current as HTMLElement).focus();
            }
        }, [onHide]);
        useEvent(
            'focusin',
            (e: FocusEvent) => {
                const activeElement = e.target as HTMLElement;
                if (
                    activeElement !== replaceInputRef.current &&
                    activeElement !== searchInputRef.current
                ) {
                    lastActiveElementRef.current = activeElement;
                }
            },
            {
                element: document,
            }
        );

        // Throttling because if you press enter to focus it
        // might edit the cells underneath.
        const onEnterPressThrottled = useMemo(
            () =>
                throttle(() => {
                    moveResultIndex(1).then(() => {
                        focusSearchInput();
                    });
                }, 50),
            [moveResultIndex]
        );

        const onKeyDown = useCallback(
            (evt: React.KeyboardEvent) => {
                let handled = true;
                if (matchKeyPress(evt, 'Enter') && !evt.repeat) {
                    onEnterPressThrottled();
                } else if (matchKeyMap(evt, KeyMap.dataDoc.openSearch)) {
                    focusSearchInput();
                } else if (matchKeyMap(evt, KeyMap.dataDoc.closeSearch)) {
                    handleHide();
                } else {
                    handled = false;
                }

                if (handled) {
                    evt.stopPropagation();
                    evt.preventDefault();
                }
            },
            [moveResultIndex, handleHide]
        );

        useImperativeHandle(ref, () => ({
            focus: () => {
                focusSearchInput();
            },
        }));

        const noPrevRes = React.useMemo(
            () =>
                searchResults.length === 0 ||
                currentSearchResultIndex <= searchResults.length,
            [searchResults.length, currentSearchResultIndex]
        );
        const noNextRes = React.useMemo(
            () =>
                searchResults.length === 0 ||
                currentSearchResultIndex >= searchResults.length,
            [searchResults.length, currentSearchResultIndex]
        );

        const searchRow = (
            <div className="flex-row ">
                <div className="datadoc-search-input">
                    <DebouncedInput
                        value={searchString}
                        onChange={onSearchStringChange}
                        inputProps={{
                            autoFocus: true,
                            onKeyDown,
                            ref: searchInputRef,
                        }}
                        className="flex-center mr8"
                    />
                </div>
                <div className="data-doc-search-buttons">
                    <TextToggleButton
                        text="Aa"
                        value={searchOptions.matchCase}
                        onChange={(matchCase) =>
                            onSearchOptionsChange({
                                ...searchOptions,
                                matchCase,
                            })
                        }
                        tooltip="Match Case"
                    />
                    <TextToggleButton
                        text=".*"
                        value={searchOptions.useRegex}
                        onChange={(useRegex) =>
                            onSearchOptionsChange({
                                ...searchOptions,
                                useRegex,
                            })
                        }
                        tooltip="Use Regex"
                        className="ml16"
                    />
                </div>
                <span className="position-info mh12">
                    {searchResults.length
                        ? `${
                              searchResults.length > currentSearchResultIndex
                                  ? currentSearchResultIndex + 1
                                  : '?'
                          } of ${searchResults.length}`
                        : 'No results'}
                </span>
                <IconButton
                    icon="ArrowUp"
                    noPadding
                    onClick={() => moveResultIndex(-1)}
                    tooltip={noPrevRes ? null : 'Previous Result'}
                    tooltipPos="down"
                    size={16}
                    disabled={noPrevRes}
                />
                <IconButton
                    icon="ArrowDown"
                    noPadding
                    onClick={() => moveResultIndex(1)}
                    tooltip={noNextRes ? null : 'Next Result'}
                    tooltipPos="down"
                    size={16}
                    className="ml4"
                    disabled={noNextRes}
                />
                <IconButton
                    className="ml16"
                    noPadding
                    icon="X"
                    onClick={handleHide}
                    tooltip="Exit"
                    tooltipPos="right"
                    size={16}
                />
            </div>
        );

        const replaceRow = showReplace && (
            <div className="flex-row mt4">
                <div className="datadoc-search-input">
                    <DebouncedInput
                        value={replaceString}
                        onChange={onReplaceStringChange}
                        inputProps={{
                            ref: replaceInputRef,
                            onKeyDown,
                            placeholder: 'Replace',
                        }}
                        className="flex-center mr8"
                    />
                </div>
                <TextButton
                    icon="Repeat"
                    aria-label="Replace"
                    data-balloon-pos="down"
                    size="small"
                    onClick={() => onReplace()}
                />
                <Button
                    icon="Repeat"
                    title="All"
                    aria-label="Replace all"
                    data-balloon-pos="down"
                    size="small"
                    theme="text"
                    onClick={() => onReplace(true)}
                />
            </div>
        );

        return (
            <div className="SearchAndReplaceBar flex-row p8" ref={selfRef}>
                <IconButton
                    noPadding
                    icon={showReplace ? 'ChevronDown' : 'ChevronRight'}
                    onClick={() => setShowReplace(!showReplace)}
                    className="expand-icon m4"
                />
                <div>
                    {searchRow}
                    {replaceRow}
                </div>
            </div>
        );
    }
)
Example #28
Source File: FormList.tsx    From condo with MIT License 4 votes vote down vote up
FormWithAction: React.FC<IFormWithAction> = (props) => {
    const intl = useIntl()
    const ClientSideErrorMsg = intl.formatMessage({ id: 'ClientSideError' })

    const {
        action,
        mutation,
        mutationExtraVariables,
        mutationExtraData,
        formValuesToMutationDataPreprocessor,
        formValuesToMutationDataPreprocessorContext,
        children,
        onMutationCompleted,
        ErrorToFormFieldMsgMapping,
        OnErrorMsg,
        OnCompletedMsg,
        initialValues,
        handleSubmit,
        resetOnComplete,
        onChange,
        colon = true,
        layout = 'vertical',
        validateTrigger,
        style,
        onFieldsChange,
        ...formProps
    } = props

    const [form] = Form.useForm()
    const [isLoading, setIsLoading] = useState(false)

    let create = null

    if (!action && mutation) {
        [create] = useMutation(mutation) // eslint-disable-line react-hooks/rules-of-hooks
    }

    const _handleSubmit = useCallback((values) => {
        if (handleSubmit) {
            return handleSubmit(values)
        }
        if (values.hasOwnProperty(NON_FIELD_ERROR_NAME)) delete values[NON_FIELD_ERROR_NAME]
        let data
        try {
            data = (formValuesToMutationDataPreprocessor) ? formValuesToMutationDataPreprocessor(values, formValuesToMutationDataPreprocessorContext, form) : values
        } catch (err) {
            if (err instanceof ValidationError) {
                let errors = []
                if (ErrorToFormFieldMsgMapping) {
                    const errorString = `${err}`
                    Object.keys(ErrorToFormFieldMsgMapping).forEach((msg) => {
                        if (errorString.includes(msg)) {
                            errors.push(ErrorToFormFieldMsgMapping[msg])
                        }
                    })
                }
                if (errors.length === 0) {
                    errors = [{ name: err.field || NON_FIELD_ERROR_NAME, errors: [String(err.message)] }]
                }
                form.setFields(errors)
                return
            } else {
                form.setFields([{ name: NON_FIELD_ERROR_NAME, errors: [ClientSideErrorMsg] }])
                throw err  // unknown error, rethrow it (**)
            }
        }
        form.setFields([{ name: NON_FIELD_ERROR_NAME, errors: [] }])
        setIsLoading(true)

        const actionOrMutationProps = !action
            ? { mutation: create, variables: { data: { ...data, ...mutationExtraData }, ...mutationExtraVariables } }
            : { action: () => action({ ...data }) }

        return runMutation({
            ...actionOrMutationProps,
            onCompleted: (...args) => {
                if (onMutationCompleted) {
                    onMutationCompleted(...args)
                }
                if (resetOnComplete) {
                    form.resetFields()
                }
            },
            onFinally: () => {
                setIsLoading(false)
            },
            intl,
            form,
            ErrorToFormFieldMsgMapping,
            OnErrorMsg,
            OnCompletedMsg,
        })
    // eslint-disable-next-line react-hooks/exhaustive-deps
    }, [action])

    function handleSave () {
        // TODO(zuch) Possible bug: If user press save button and form not touched he will stay on edit screen with no response from system
        // if (form.isFieldsTouched()) {
        form.submit()
        //}
    }

    const errors = {}

    const throttledValidateFields = throttle((field) => {
        const item = form.getFieldsError().find(item => item.name[0] === field)

        errors[field] = errors[field] || Boolean(item && item.errors.length)
        errors[field] && form.validateFields([field])
    }, 400)

    async function handleChange (changedValues, allValues) {
        const field = Object.keys(changedValues)[0]
        throttledValidateFields(field)

        if (onChange) onChange(changedValues, allValues)
    }

    return (
        <Form
            form={form}
            layout={layout}
            onFinish={_handleSubmit}
            initialValues={initialValues}
            validateTrigger={validateTrigger}
            onValuesChange={handleChange}
            colon={colon}
            scrollToFirstError
            style={style}
            {...formProps}
        >
            <Form.Item className='ant-non-field-error' name={NON_FIELD_ERROR_NAME}><Input /></Form.Item>
            {children({ handleSave, isLoading, handleSubmit: _handleSubmit, form })}
        </Form>
    )
}
Example #29
Source File: setupViewEventHandlers.ts    From TidGi-Desktop with Mozilla Public License 2.0 4 votes vote down vote up
/**
 * Bind workspace related event handler to view.webContent
 */
export default function setupViewEventHandlers(
  view: BrowserView,
  browserWindow: BrowserWindow,
  { workspace, sharedWebPreferences, loadInitialUrlWithCatch }: IViewContext,
): void {
  // metadata and state about current BrowserView
  const viewMeta: IViewMeta = {
    forceNewWindow: false,
  };

  const workspaceService = container.get<IWorkspaceService>(serviceIdentifier.Workspace);
  const workspaceViewService = container.get<IWorkspaceViewService>(serviceIdentifier.WorkspaceView);
  const windowService = container.get<IWindowService>(serviceIdentifier.Window);
  const preferenceService = container.get<IPreferenceService>(serviceIdentifier.Preference);

  view.webContents.on('did-start-loading', async () => {
    const workspaceObject = await workspaceService.get(workspace.id);
    // this event might be triggered
    // even after the workspace obj and BrowserView
    // are destroyed. See https://github.com/atomery/webcatalog/issues/836
    if (workspaceObject === undefined) {
      return;
    }
    if (workspaceObject.active && (await workspaceService.workspaceDidFailLoad(workspace.id)) && browserWindow !== undefined && !browserWindow.isDestroyed()) {
      // fix https://github.com/webcatalog/singlebox-legacy/issues/228
      const contentSize = browserWindow.getContentSize();
      view.setBounds(await getViewBounds(contentSize as [number, number]));
    }
    await workspaceService.updateMetaData(workspace.id, {
      // eslint-disable-next-line unicorn/no-null
      didFailLoadErrorMessage: null,
      isLoading: true,
    });
  });
  view.webContents.on('did-navigate-in-page', async () => {
    await workspaceViewService.updateLastUrl(workspace.id, view);
  });

  const throttledDidFinishedLoad = throttle(async () => {
    // if have error, don't realignActiveWorkspace, which will hide the error message
    if (await workspaceService.workspaceDidFailLoad(workspace.id)) {
      return;
    }
    logger.debug(`throttledDidFinishedLoad() workspace.id: ${workspace.id}, now workspaceViewService.realignActiveWorkspace() then set isLoading to false`);
    // focus on initial load
    // https://github.com/atomery/webcatalog/issues/398
    if (workspace.active && !browserWindow.isDestroyed() && browserWindow.isFocused() && !view.webContents.isFocused()) {
      view.webContents.focus();
    }
    // fix https://github.com/atomery/webcatalog/issues/870
    await workspaceViewService.realignActiveWorkspace();
    // update isLoading to false when load succeed
    await workspaceService.updateMetaData(workspace.id, {
      isLoading: false,
    });
  }, 2000);
  view.webContents.on('did-finish-load', () => {
    logger.debug('did-finish-load called');
    void throttledDidFinishedLoad();
  });
  view.webContents.on('did-stop-loading', () => {
    logger.debug('did-stop-loading called');
    void throttledDidFinishedLoad();
  });
  view.webContents.on('dom-ready', () => {
    logger.debug('dom-ready called');
    void throttledDidFinishedLoad();
  });

  // https://electronjs.org/docs/api/web-contents#event-did-fail-load
  // https://github.com/webcatalog/neutron/blob/3d9e65c255792672c8bc6da025513a5404d98730/main-src/libs/views.js#L397
  view.webContents.on('did-fail-load', async (_event, errorCode, errorDesc, _validateUrl, isMainFrame) => {
    const [workspaceObject, workspaceDidFailLoad] = await Promise.all([
      workspaceService.get(workspace.id),
      workspaceService.workspaceDidFailLoad(workspace.id),
    ]);
    // this event might be triggered
    // even after the workspace obj and BrowserView
    // are destroyed. See https://github.com/atomery/webcatalog/issues/836
    if (workspaceObject === undefined) {
      return;
    }
    if (workspaceDidFailLoad) {
      return;
    }
    if (isMainFrame && errorCode < 0 && errorCode !== -3) {
      // Fix nodejs wiki start slow on system startup, which cause `-102 ERR_CONNECTION_REFUSED` even if wiki said it is booted, we have to retry several times
      if (errorCode === -102 && view.webContents.getURL().length > 0 && workspaceObject.homeUrl.startsWith('http')) {
        setTimeout(async () => {
          await loadInitialUrlWithCatch();
        }, 1000);
        return;
      }
      await workspaceService.updateMetaData(workspace.id, {
        isLoading: false,
        didFailLoadErrorMessage: `${errorCode} ${errorDesc}`,
      });
      if (workspaceObject.active && browserWindow !== undefined && !browserWindow.isDestroyed()) {
        // fix https://github.com/atomery/singlebox/issues/228
        const contentSize = browserWindow.getContentSize();
        view.setBounds(await getViewBounds(contentSize as [number, number], false, 0, 0)); // hide browserView to show error message
      }
    }
    // edge case to handle failed auth, use setTimeout to prevent infinite loop
    if (errorCode === -300 && view.webContents.getURL().length === 0 && workspaceObject.homeUrl.startsWith('http')) {
      setTimeout(async () => {
        await loadInitialUrlWithCatch();
      }, 1000);
    }
  });
  view.webContents.on('did-navigate', async (_event, url) => {
    const workspaceObject = await workspaceService.get(workspace.id);
    // this event might be triggered
    // even after the workspace obj and BrowserView
    // are destroyed. See https://github.com/atomery/webcatalog/issues/836
    if (workspaceObject === undefined) {
      return;
    }
    if (workspaceObject.active) {
      await windowService.sendToAllWindows(WindowChannel.updateCanGoBack, view.webContents.canGoBack());
      await windowService.sendToAllWindows(WindowChannel.updateCanGoForward, view.webContents.canGoForward());
    }
  });
  view.webContents.on('did-navigate-in-page', async (_event, url) => {
    const workspaceObject = await workspaceService.get(workspace.id);
    // this event might be triggered
    // even after the workspace obj and BrowserView
    // are destroyed. See https://github.com/atomery/webcatalog/issues/836
    if (workspaceObject === undefined) {
      return;
    }
    if (workspaceObject.active) {
      await windowService.sendToAllWindows(WindowChannel.updateCanGoBack, view.webContents.canGoBack());
      await windowService.sendToAllWindows(WindowChannel.updateCanGoForward, view.webContents.canGoForward());
    }
  });
  view.webContents.on('page-title-updated', async (_event, title) => {
    const workspaceObject = await workspaceService.get(workspace.id);
    // this event might be triggered
    // even after the workspace obj and BrowserView
    // are destroyed. See https://github.com/atomery/webcatalog/issues/836
    if (workspaceObject === undefined) {
      return;
    }
    if (workspaceObject.active) {
      browserWindow.setTitle(title);
    }
  });

  view.webContents.setWindowOpenHandler((details: Electron.HandlerDetails) =>
    handleNewWindow(
      details.url,
      {
        workspace,
        sharedWebPreferences,
        view,
        meta: viewMeta,
      },
      details.disposition,
      view.webContents,
    ),
  );
  // Handle downloads
  // https://electronjs.org/docs/api/download-item
  view.webContents.session.on('will-download', async (_event, item) => {
    const { askForDownloadPath, downloadPath } = await preferenceService.getPreferences();
    // Set the save path, making Electron not to prompt a save dialog.
    if (!askForDownloadPath) {
      const finalFilePath = path.join(downloadPath, item.getFilename());
      if (!fsExtra.existsSync(finalFilePath)) {
        // eslint-disable-next-line no-param-reassign
        item.savePath = finalFilePath;
      }
    } else {
      // set preferred path for save dialog
      const options = {
        ...item.getSaveDialogOptions(),
        defaultPath: path.join(downloadPath, item.getFilename()),
      };
      item.setSaveDialogOptions(options);
    }
  });
  // Unread count badge
  void preferenceService.get('unreadCountBadge').then((unreadCountBadge) => {
    if (unreadCountBadge) {
      view.webContents.on('page-title-updated', async (_event, title) => {
        const itemCountRegex = /[([{](\d*?)[)\]}]/;
        const match = itemCountRegex.exec(title);
        const incString = match !== null ? match[1] : '';
        // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
        const inc = Number.parseInt(incString, 10) || 0;
        await workspaceService.updateMetaData(workspace.id, {
          badgeCount: inc,
        });
        let count = 0;
        const workspaceMetaData = await workspaceService.getAllMetaData();
        Object.values(workspaceMetaData).forEach((metaData) => {
          if (typeof metaData?.badgeCount === 'number') {
            count += metaData.badgeCount;
          }
        });
        app.badgeCount = count;
        if (process.platform === 'win32') {
          if (count > 0) {
            const icon = nativeImage.createFromPath(path.resolve(buildResourcePath, 'overlay-icon.png'));
            browserWindow.setOverlayIcon(icon, `You have ${count} new messages.`);
          } else {
            // eslint-disable-next-line unicorn/no-null
            browserWindow.setOverlayIcon(null, '');
          }
        }
      });
    }
  });
  // Find In Page
  view.webContents.on('found-in-page', async (_event, result) => {
    await windowService.sendToAllWindows(ViewChannel.updateFindInPageMatches, result.activeMatchOrdinal, result.matches);
  });
  // Link preview
  view.webContents.on('update-target-url', (_event, url) => {
    try {
      view.webContents.send('update-target-url', url);
    } catch (error) {
      logger.warn(error); // eslint-disable-line no-console
    }
  });
}