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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
throttleScroll = throttle(() => this.onScroll(), 100);
Example #20
Source File: index.tsx From erda-ui with GNU Affero General Public License v3.0 | 5 votes |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
/**
* 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
}
});
}