Merge branch 'develop' into feature/refactoring-media-viewer

# Conflicts:
#	packages/shared/package.json
This commit is contained in:
Alexey Safronov 2024-02-05 17:21:59 +04:00
commit 72be9e6da7
18 changed files with 2762 additions and 125 deletions

View File

@ -4,19 +4,23 @@ import { TableVersions } from "SRC_DIR/helpers/constants";
const TABLE_COLUMNS = `filesTableColumns_ver-${TableVersions.Files}`;
const TABLE_ROOMS_COLUMNS = `roomsTableColumns_ver-${TableVersions.Rooms}`;
const TABLE_TRASH_COLUMNS = `trashTableColumns_ver-${TableVersions.Trash}`;
const TABLE_SDK_COLUMNS = `filesSDKTableColumns_ver-${TableVersions.Files}`;
const COLUMNS_SIZE = `filesColumnsSize_ver-${TableVersions.Files}`;
const COLUMNS_ROOMS_SIZE = `roomsColumnsSize_ver-${TableVersions.Rooms}`;
const COLUMNS_TRASH_SIZE = `trashColumnsSize_ver-${TableVersions.Trash}`;
const COLUMNS_SDK_SIZE = `filesSDKColumnsSize_ver-${TableVersions.Files}`;
const COLUMNS_SIZE_INFO_PANEL = `filesColumnsSizeInfoPanel_ver-${TableVersions.Files}`;
const COLUMNS_ROOMS_SIZE_INFO_PANEL = `roomsColumnsSizeInfoPanel_ver-${TableVersions.Rooms}`;
const COLUMNS_TRASH_SIZE_INFO_PANEL = `trashColumnsSizeInfoPanel_ver-${TableVersions.Trash}`;
const COLUMNS_SDK_SIZE_INFO_PANEL = `filesSDKColumnsSizeInfoPanel_ver-${TableVersions.Files}`;
class TableStore {
authStore;
treeFoldersStore;
userStore;
settingsStore;
roomColumnNameIsEnabled = true; // always true
roomColumnTypeIsEnabled = false;
@ -40,12 +44,13 @@ class TableStore {
sizeTrashColumnIsEnabled = false;
typeTrashColumnIsEnabled = false;
constructor(authStore, treeFoldersStore, userStore) {
constructor(authStore, treeFoldersStore, userStore, settingsStore) {
makeAutoObservable(this);
this.authStore = authStore;
this.treeFoldersStore = treeFoldersStore;
this.userStore = userStore;
this.settingsStore = settingsStore;
}
setRoomColumnType = (enable) => {
@ -234,6 +239,9 @@ class TableStore {
this.treeFoldersStore;
const isRooms = isRoomsFolder || isArchiveFolder;
const userId = this.userStore.user?.id;
const isFrame = this.settingsStore.isFrame;
if (isFrame) return `${TABLE_SDK_COLUMNS}=${userId}`;
return isRooms
? `${TABLE_ROOMS_COLUMNS}=${userId}`
@ -247,6 +255,9 @@ class TableStore {
this.treeFoldersStore;
const isRooms = isRoomsFolder || isArchiveFolder;
const userId = this.userStore.user?.id;
const isFrame = this.settingsStore.isFrame;
if (isFrame) return `${COLUMNS_SDK_SIZE}=${userId}`;
return isRooms
? `${COLUMNS_ROOMS_SIZE}=${userId}`
@ -260,6 +271,9 @@ class TableStore {
this.treeFoldersStore;
const isRooms = isRoomsFolder || isArchiveFolder;
const userId = this.userStore.user?.id;
const isFrame = this.settingsStore.isFrame;
if (isFrame) return `${COLUMNS_SDK_SIZE_INFO_PANEL}=${userId}`;
return isRooms
? `${COLUMNS_ROOMS_SIZE_INFO_PANEL}=${userId}`

View File

@ -93,7 +93,7 @@ class UploadDataStore {
removeFiles = (fileIds) => {
fileIds.forEach((id) => {
this.files = this.files?.filter(
(file) => !(file.action === "converted" && file.fileInfo.id === id)
(file) => !(file.action === "converted" && file.fileInfo?.id === id)
);
});
};
@ -464,7 +464,7 @@ class UploadDataStore {
while (progress < 100) {
const res = await this.getConversationProgress(fileId);
progress = res && res[0] && res[0].progress;
fileInfo = res && res[0].result;
fileInfo = res && res[0] && res[0].result;
runInAction(() => {
const file = this.files.find((file) => file.fileId === fileId);
@ -905,19 +905,22 @@ class UploadDataStore {
}
};
checkChunkUpload = (
t,
res,
fileSize,
index,
indexOfFile,
path,
length,
resolve,
reject,
isAsyncUpload = false,
isFinalize = false
) => {
checkChunkUpload = (chunkUploadObj) => {
const {
t,
res, // file response data
fileSize, // file size
index, // chunk index
indexOfFile, // file index in the list
path, // file path
chunksLength, // length of file chunks
resolve, // resolve cb
reject, // reject cb
isAsyncUpload = false, // async upload checker
isFinalize = false, // is finalize chunk
allChunkUploaded, // needed for progress, files is uploaded, awaiting finalized chunk
} = chunkUploadObj;
if (!res.data.data && res.data.message) {
return reject(res.data.message);
}
@ -933,7 +936,7 @@ class UploadDataStore {
newPercent = this.getNewPercent(uploadedSize, indexOfFile);
} else {
if (!uploaded) {
if (!uploaded && !allChunkUploaded) {
uploadedSize =
fileSize <= this.filesSettingsStore.chunkUploadSize
? fileSize
@ -948,11 +951,7 @@ class UploadDataStore {
newPercent = this.getFilesPercent(uploadedSize);
}
if (isAsyncUpload && !uploaded && newPercent >= 100) {
newPercent = 99;
}
const percentCurrentFile = ((index + 1) / length) * 100;
const percentCurrentFile = ((index + 1) / chunksLength) * 100;
const fileIndex = this.uploadedFilesHistory.findIndex(
(f) => f.uniqueId === this.files[indexOfFile].uniqueId
@ -1055,8 +1054,11 @@ class UploadDataStore {
chunkObjIndex
].onUpload();
this.asyncUploadObj[operationId].chunksArray[chunkObjIndex].isFinished =
true;
if (this.asyncUploadObj[operationId]) {
this.asyncUploadObj[operationId].chunksArray[
chunkObjIndex
].isFinished = true;
}
if (!res.data.data && res.data.message) {
delete this.asyncUploadObj[operationId];
@ -1069,22 +1071,39 @@ class UploadDataStore {
).length - 1
: 0;
this.checkChunkUpload(
let allIsUploaded;
if (this.asyncUploadObj[operationId]) {
const finished = this.asyncUploadObj[operationId].chunksArray.filter(
(x) => x.isFinished
);
allIsUploaded =
this.asyncUploadObj[operationId].chunksArray.length -
finished.length -
1; // 1 last
}
this.checkChunkUpload({
t,
res,
fileSize,
activeLength,
index: activeLength,
indexOfFile,
path,
length,
chunksLength: length,
resolve,
reject,
true
);
isAsyncUpload: true,
isFinalize: false,
allChunkUploaded: allIsUploaded === 0,
});
const finalizeChunk = this.asyncUploadObj[
operationId
].chunksArray.findIndex((x) => !x.isFinished && !x.isFinalize);
let finalizeChunk = -1;
if (this.asyncUploadObj[operationId]) {
finalizeChunk = this.asyncUploadObj[
operationId
].chunksArray.findIndex((x) => !x.isFinished && !x.isFinalize);
}
if (finalizeChunk === -1) {
const finalizeChunkIndex = this.asyncUploadObj[
@ -1100,19 +1119,19 @@ class UploadDataStore {
finalizeChunkIndex
].onUpload();
this.checkChunkUpload(
this.checkChunkUpload({
t,
finalizeRes,
res: finalizeRes,
fileSize,
finalizeIndex,
index: finalizeIndex,
indexOfFile,
path,
length,
chunksLength: length,
resolve,
reject,
true, // isAsyncUpload
true //isFinalize
);
isAsyncUpload: true,
isFinalize: true,
});
}
}
} catch (error) {
@ -1191,17 +1210,17 @@ class UploadDataStore {
const resolve = (res) => Promise.resolve(res);
const reject = (err) => Promise.reject(err);
this.checkChunkUpload(
this.checkChunkUpload({
t,
res,
fileSize,
index,
indexOfFile,
path,
length,
chunksLength: length,
resolve,
reject
);
reject,
});
//console.log(`Uploaded chunk ${index}/${length}`, res);
}

View File

@ -220,7 +220,12 @@ const profileActionsStore = new ProfileActionsStore(
peopleStore.profileActionsStore = profileActionsStore;
const tableStore = new TableStore(authStore, treeFoldersStore, userStore);
const tableStore = new TableStore(
authStore,
treeFoldersStore,
userStore,
settingsStore
);
infoPanelStore.filesSettingsStore = filesSettingsStore;
infoPanelStore.filesStore = filesStore;

View File

@ -58,5 +58,10 @@ module.exports = {
},
],
},
ignorePatterns: ["./tsconfig.json", "coverage/**", "storybook-static/**"],
ignorePatterns: [
"./tsconfig.json",
"coverage/**",
"storybook-static/**",
"./utils/custom-scrollbar/**",
],
};

View File

@ -1,11 +1,12 @@
import Scrollbar from "react-scrollbars-custom";
import styled from "styled-components";
import { isIOS, isIOS13, isIPad13 } from "react-device-detect";
import { Scrollbar } from "./custom-scrollbar";
import { Base } from "../../themes";
import { mobile, desktop, tablet } from "../../utils";
const StyledScrollbar = styled(Scrollbar)`
const StyledScrollbar = styled(Scrollbar)<{ $fixedSize?: boolean }>`
.scroller::-webkit-scrollbar {
${(isIOS || isIOS13 || isIPad13) && `display: none;`}
}

View File

@ -0,0 +1,220 @@
import { isFun, isNum, isUndef } from "./util";
type EventHandler = (...args: unknown[]) => void;
type OnceHandler = OnceHandlerState & { (...args: unknown[]): void };
type EventHandlersList = (OnceHandler | EventHandler)[];
type EmitterEventHandlers = { [key: string]: EventHandlersList };
type OnceHandlerState = {
fired: boolean;
handler: EventHandler;
wrappedHandler?: OnceHandler;
emitter: Emittr;
event: string;
};
export default class Emittr {
private _handlers: EmitterEventHandlers;
private _maxHandlers: number;
constructor(maxHandlers = 10) {
this.setMaxHandlers(maxHandlers);
this._handlers = Object.create(null);
}
private static _callEventHandlers(
emitter: Emittr,
handlers: EventHandlersList,
args: unknown[],
) {
if (!handlers.length) {
return;
}
if (handlers.length === 1) {
Reflect.apply(handlers[0], emitter, args);
return;
}
handlers = [...handlers];
let idx;
for (idx = 0; idx < handlers.length; idx++) {
Reflect.apply(handlers[idx], emitter, args);
}
}
private static _addHandler = (
emitter: Emittr,
name: string,
handler: EventHandler,
prepend = false,
): Emittr => {
if (!isFun(handler)) {
throw new TypeError(
`Expected event handler to be a function, got ${typeof handler}`,
);
}
emitter._handlers[name] = emitter._handlers[name] || [];
emitter.emit("addHandler", name, handler);
if (prepend) {
emitter._handlers[name].unshift(handler);
} else {
emitter._handlers[name].push(handler);
}
return emitter;
};
private static _onceWrapper = function _onceWrapper(...args: unknown[]) {
if (!this.fired) {
this.fired = true;
this.emitter.off(this.event, this.wrappedHandler);
Reflect.apply(this.handler, this.emitter, args);
}
};
private static _removeHandler = (
emitter: Emittr,
name: string,
handler: EventHandler,
): Emittr => {
if (!isFun(handler)) {
throw new TypeError(
`Expected event handler to be a function, got ${typeof handler}`,
);
}
if (isUndef(emitter._handlers[name]) || !emitter._handlers[name].length) {
return emitter;
}
let idx = -1;
if (emitter._handlers[name].length === 1) {
if (
emitter._handlers[name][0] === handler ||
(emitter._handlers[name][0] as OnceHandler).handler === handler
) {
idx = 0;
handler =
(emitter._handlers[name][0] as OnceHandler).handler ||
emitter._handlers[name][0];
}
} else {
for (idx = emitter._handlers[name].length - 1; idx >= 0; idx--) {
if (
emitter._handlers[name][idx] === handler ||
(emitter._handlers[name][idx] as OnceHandler).handler === handler
) {
handler =
(emitter._handlers[name][idx] as OnceHandler).handler ||
emitter._handlers[name][idx];
break;
}
}
}
if (idx === -1) {
return emitter;
}
if (idx === 0) {
emitter._handlers[name].shift();
} else {
emitter._handlers[name].splice(idx, 1);
}
emitter.emit("removeHandler", name, handler);
return emitter;
};
setMaxHandlers(count: number): this {
if (!isNum(count) || count <= 0) {
throw new TypeError(
`Expected maxHandlers to be a positive number, got '${count}' of type ${typeof count}`,
);
}
this._maxHandlers = count;
return this;
}
getMaxHandlers(): number {
return this._maxHandlers;
}
public emit(name: string, ...args: unknown[]): boolean {
if (
typeof this._handlers[name] !== "object" ||
!Array.isArray(this._handlers[name])
) {
return false;
}
Emittr._callEventHandlers(this, this._handlers[name], args);
return true;
}
public on(name: string, handler: EventHandler): this {
Emittr._addHandler(this, name, handler);
return this;
}
public prependOn(name: string, handler: EventHandler): this {
Emittr._addHandler(this, name, handler, true);
return this;
}
public once(name: string, handler: EventHandler): this {
if (!isFun(handler)) {
throw new TypeError(
`Expected event handler to be a function, got ${typeof handler}`,
);
}
Emittr._addHandler(this, name, this._wrapOnceHandler(name, handler));
return this;
}
public prependOnce(name: string, handler: EventHandler): this {
if (!isFun(handler)) {
throw new TypeError(
`Expected event handler to be a function, got ${typeof handler}`,
);
}
Emittr._addHandler(this, name, this._wrapOnceHandler(name, handler), true);
return this;
}
public off(name: string, handler: EventHandler): this {
Emittr._removeHandler(this, name, handler);
return this;
}
public removeAllHandlers(): this {
const handlers = this._handlers;
this._handlers = Object.create(null);
const removeHandlers = handlers.removeHandler;
delete handlers.removeHandler;
let idx;
let eventName;
// eslint-disable-next-line guard-for-in,no-restricted-syntax
for (eventName in handlers) {
for (idx = handlers[eventName].length - 1; idx >= 0; idx--) {
Emittr._callEventHandlers(this, removeHandlers, [
eventName,
(handlers[eventName][idx] as OnceHandler).handler ||
handlers[eventName][idx],
]);
}
}
return this;
}
private _wrapOnceHandler(name: string, handler: EventHandler): OnceHandler {
const onceState: OnceHandlerState = {
fired: false,
handler,
wrappedHandler: undefined,
emitter: this,
event: name,
};
const wrappedHandler: OnceHandler = Emittr._onceWrapper.bind(onceState);
onceState.wrappedHandler = wrappedHandler;
wrappedHandler.handler = handler;
wrappedHandler.event = name;
return wrappedHandler;
}
}

View File

@ -0,0 +1,102 @@
interface UpdatableItem {
_unmounted?: boolean;
update: () => unknown;
}
export class RAFLoop {
/**
* @description List of targets to update
*/
private readonly targets: UpdatableItem[] = [];
/**
* @description ID of requested animation frame. Valuable only if loop is active and has items to iterate.
*/
private animationFrameID = 0;
/**
* @description Loop's state.
*/
private _isActive = false;
/**
* @description Loop's state.
*/
public get isActive(): boolean {
return this._isActive;
}
/**
* @description Start the loop if it wasn't yet.
*/
public start = (): this => {
if (!this._isActive && this.targets.length) {
this._isActive = true;
if (this.animationFrameID) cancelAnimationFrame(this.animationFrameID);
this.animationFrameID = requestAnimationFrame(this.rafCallback);
}
return this;
};
/**
* @description Stop the loop if is was active.
*/
public stop = (): this => {
if (this._isActive) {
this._isActive = false;
if (this.animationFrameID) cancelAnimationFrame(this.animationFrameID);
this.animationFrameID = 0;
}
return this;
};
/**
* @description Add target to the iteration list if it's not there.
*/
public addTarget = (target: UpdatableItem, silent = false): this => {
if (!this.targets.includes(target)) {
this.targets.push(target);
if (this.targets.length === 1 && !silent) this.start();
}
return this;
};
/**
* @description Remove target from iteration list if it was there.
*/
public removeTarget = (target: UpdatableItem): this => {
const idx = this.targets.indexOf(target);
if (idx !== -1) {
this.targets.splice(idx, 1);
if (this.targets.length === 0) this.stop();
}
return this;
};
/**
* @description Callback that called each animation frame.
*/
private rafCallback = (): number => {
if (!this._isActive) {
return 0;
}
for (let i = 0; i < this.targets.length; i++) {
if (!this.targets[i]._unmounted) this.targets[i].update();
}
this.animationFrameID = requestAnimationFrame(this.rafCallback);
return this.animationFrameID;
};
}
export default new RAFLoop();

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,234 @@
/* eslint-disable @typescript-eslint/no-useless-constructor */
import { cnb } from "cnbuilder";
import React from "react";
import { DraggableCore, DraggableData, DraggableEvent } from "react-draggable";
import { AxisDirection, ElementPropsWithElementRefAndRenderer } from "./types";
import { isBrowser, isFun, isUndef, renderDivWithRenderer } from "./util";
export type DragCallbackData = Pick<
DraggableData,
Exclude<keyof DraggableData, "node">
>;
export type ScrollbarThumbProps = ElementPropsWithElementRefAndRenderer & {
axis: AxisDirection;
onDrag?: (data: DragCallbackData) => void;
onDragStart?: (data: DragCallbackData) => void;
onDragEnd?: (data: DragCallbackData) => void;
ref?: (ref: ScrollbarThumb | null) => void;
};
class ScrollbarThumb extends React.Component<ScrollbarThumbProps, unknown> {
private static selectStartReplacer = () => false;
public element: HTMLDivElement | null = null;
public initialOffsetX = 0;
public initialOffsetY = 0;
private prevUserSelect: string;
private prevOnSelectStart: ((ev: Event) => boolean) | null;
private elementRefHack = React.createRef<HTMLElement>();
public lastDragData: DragCallbackData = {
x: 0,
y: 0,
deltaX: 0,
deltaY: 0,
lastX: 0,
lastY: 0,
};
public componentDidMount(): void {
if (!this.element) {
this.setState(() => {
throw new Error(
"<ScrollbarThumb> Element was not created. Possibly you haven't provided HTMLDivElement to renderer's `elementRef` function.",
);
});
}
}
public componentWillUnmount(): void {
this.handleOnDragStop();
this.elementRef(null);
}
public handleOnDragStart = (ev: DraggableEvent, data: DraggableData) => {
if (!this.element) {
this.handleOnDragStop(ev, data);
return;
}
if (isBrowser) {
this.prevUserSelect = document.body.style.userSelect;
document.body.style.userSelect = "none";
this.prevOnSelectStart = document.onselectstart;
document.addEventListener(
"selectstart",
ScrollbarThumb.selectStartReplacer,
);
}
if (this.props.onDragStart) {
this.props.onDragStart(
(this.lastDragData = {
x: data.x - this.initialOffsetX,
y: data.y - this.initialOffsetY,
lastX: data.lastX - this.initialOffsetX,
lastY: data.lastY - this.initialOffsetY,
deltaX: data.deltaX,
deltaY: data.deltaY,
}),
);
}
this.element.classList.add("dragging");
};
public handleOnDrag = (ev: DraggableEvent, data: DraggableData) => {
if (!this.element) {
this.handleOnDragStop(ev, data);
return;
}
if (this.props.onDrag) {
this.props.onDrag(
(this.lastDragData = {
x: data.x - this.initialOffsetX,
y: data.y - this.initialOffsetY,
lastX: data.lastX - this.initialOffsetX,
lastY: data.lastY - this.initialOffsetY,
deltaX: data.deltaX,
deltaY: data.deltaY,
}),
);
}
};
public handleOnDragStop = (ev?: DraggableEvent, data?: DraggableData) => {
const resultData = data
? {
x: data.x - this.initialOffsetX,
y: data.y - this.initialOffsetY,
lastX: data.lastX - this.initialOffsetX,
lastY: data.lastY - this.initialOffsetY,
deltaX: data.deltaX,
deltaY: data.deltaY,
}
: this.lastDragData;
if (this.props.onDragEnd) this.props.onDragEnd(resultData);
if (this.element) this.element.classList.remove("dragging");
if (isBrowser) {
document.body.style.userSelect = this.prevUserSelect;
if (this.prevOnSelectStart) {
document.addEventListener("selectstart", this.prevOnSelectStart);
}
this.prevOnSelectStart = null;
}
this.initialOffsetX = 0;
this.initialOffsetY = 0;
this.lastDragData = {
x: 0,
y: 0,
deltaX: 0,
deltaY: 0,
lastX: 0,
lastY: 0,
};
};
public handleOnMouseDown = (ev: MouseEvent) => {
if (!this.element) {
return;
}
ev.preventDefault();
ev.stopPropagation();
if (!isUndef(ev.offsetX)) {
/* istanbul ignore next */
this.initialOffsetX = ev.offsetX;
/* istanbul ignore next */
this.initialOffsetY = ev.offsetY;
} else {
const rect: DOMRect = this.element.getBoundingClientRect();
this.initialOffsetX =
(ev.clientX || (ev as unknown as TouchEvent).touches[0].clientX) -
rect.left;
this.initialOffsetY =
(ev.clientY || (ev as unknown as TouchEvent).touches[0].clientY) -
rect.top;
}
};
private elementRef = (ref: HTMLDivElement | null): void => {
if (!ref && this.element) return;
if (isFun(this.props.elementRef)) this.props.elementRef(ref);
this.element = ref;
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
this.elementRefHack.current = ref;
};
public render(): React.ReactElement<unknown> | null {
const {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
elementRef,
axis,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
onDrag,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
onDragEnd,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
onDragStart,
...props
} = this.props as ScrollbarThumbProps;
props.className = cnb(
"ScrollbarsCustom-Thumb",
axis === AxisDirection.X
? "ScrollbarsCustom-ThumbX"
: "ScrollbarsCustom-ThumbY",
props.className,
);
if (props.renderer) {
(props as ScrollbarThumbProps).axis = axis;
}
return (
<DraggableCore
allowAnyClick={false}
enableUserSelectHack={false}
onMouseDown={this.handleOnMouseDown}
onDrag={this.handleOnDrag}
onStart={this.handleOnDragStart}
onStop={this.handleOnDragStop}
nodeRef={this.elementRefHack}
>
{renderDivWithRenderer(props, this.elementRef)}
</DraggableCore>
);
}
}
export default ScrollbarThumb;

View File

@ -0,0 +1,109 @@
import { cnb } from "cnbuilder";
import React from "react";
import { AxisDirection, ElementPropsWithElementRefAndRenderer } from "./types";
import { isFun, isUndef, renderDivWithRenderer } from "./util";
export interface ScrollbarTrackClickParameters {
axis: AxisDirection;
offset: number;
}
export type ScrollbarTrackProps = ElementPropsWithElementRefAndRenderer & {
axis: AxisDirection;
onClick?: (ev: MouseEvent, values: ScrollbarTrackClickParameters) => void;
ref?: (ref: ScrollbarTrack | null) => void;
};
class ScrollbarTrack extends React.Component<ScrollbarTrackProps, unknown> {
public element: HTMLDivElement | null = null;
public componentDidMount(): void {
if (!this.element) {
this.setState(() => {
throw new Error(
"Element was not created. Possibly you haven't provided HTMLDivElement to renderer's `elementRef` function.",
);
});
return;
}
this.element?.addEventListener("click", this.handleClick);
}
public componentWillUnmount(): void {
if (this.element) {
this.element.removeEventListener("click", this.handleClick);
this.element = null;
this.elementRef(null);
}
}
private elementRef = (ref: HTMLDivElement | null): void => {
if (!ref && this.element) return;
if (isFun(this.props.elementRef)) this.props.elementRef(ref);
this.element = ref;
};
private handleClick = (ev: MouseEvent) => {
if (!ev || !this.element || ev.button !== 0) {
return;
}
if (isFun(this.props.onClick) && ev.target === this.element) {
if (!isUndef(ev.offsetX)) {
this.props.onClick(ev, {
axis: this.props.axis,
offset: this.props.axis === AxisDirection.X ? ev.offsetX : ev.offsetY,
});
} else {
// support for old browsers
/* istanbul ignore next */
const rect: ClientRect = this.element.getBoundingClientRect();
/* istanbul ignore next */
this.props.onClick(ev, {
axis: this.props.axis,
offset:
this.props.axis === AxisDirection.X
? (ev.clientX ||
(ev as unknown as TouchEvent).touches[0].clientX) - rect.left
: (ev.clientY ||
(ev as unknown as TouchEvent).touches[0].clientY) - rect.top,
});
}
}
return true;
};
public render(): React.ReactElement<unknown> | null {
const {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
elementRef,
axis,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
onClick,
...props
} = this.props as ScrollbarTrackProps;
props.className = cnb(
"ScrollbarsCustom-Track",
axis === AxisDirection.X
? "ScrollbarsCustom-TrackX"
: "ScrollbarsCustom-TrackY",
props.className,
);
if (props.renderer) {
(props as ScrollbarTrackProps).axis = axis;
}
return renderDivWithRenderer(props, this.elementRef);
}
}
export default ScrollbarTrack;

View File

@ -0,0 +1,7 @@
import Scrollbar, { ScrollbarProps, ScrollbarState } from "./Scrollbar";
export { ScrollbarContext } from "./Scrollbar";
export { Scrollbar };
export type { ScrollbarProps, ScrollbarState };

View File

@ -0,0 +1,60 @@
import * as React from "react";
export const style = {
holder: {
position: "relative",
width: "100%",
height: "100%",
} as React.CSSProperties,
wrapper: {
position: "absolute",
top: 0,
left: 0,
bottom: 0,
right: 0,
} as React.CSSProperties,
content: {
boxSizing: "border-box",
} as React.CSSProperties,
track: {
common: {
position: "absolute",
overflow: "hidden",
borderRadius: 4,
background: "rgba(0,0,0,.1)",
userSelect: "none",
} as React.CSSProperties,
x: {
height: 10,
width: "calc(100% - 20px)",
bottom: 0,
left: 10,
} as React.CSSProperties,
y: {
width: 10,
height: "calc(100% - 20px)",
top: 10,
} as React.CSSProperties,
},
thumb: {
common: {
cursor: "pointer",
borderRadius: 4,
background: "rgba(0,0,0,.4)",
} as React.CSSProperties,
x: {
height: "100%",
width: 0,
} as React.CSSProperties,
y: {
width: "100%",
height: 0,
} as React.CSSProperties,
},
};
export default style;

View File

@ -0,0 +1,106 @@
import * as React from "react";
export enum AxisDirection {
X = "x",
Y = "y",
}
export enum TrackClickBehavior {
JUMP = "jump",
STEP = "step",
}
export type ElementRef<T = HTMLDivElement> = (element: T | null) => void;
export type ElementPropsWithElementRef<T = HTMLDivElement> =
React.HTMLProps<T> & {
elementRef?: ElementRef<T>;
};
export type ElementRenderer<T = HTMLDivElement> = React.FC<
React.PropsWithChildren<ElementPropsWithElementRef<T>>
>;
export type ElementPropsWithElementRefAndRenderer<T = HTMLDivElement> =
React.HTMLProps<T> & {
elementRef?: ElementRef<T>;
renderer?: ElementRenderer<T>;
};
/**
* @description Contains all scroll-related values
*/
export type ScrollState = {
/**
* @description Scroller's native clientHeight parameter
*/
clientHeight: number;
/**
* @description Scroller's native clientWidth parameter
*/
clientWidth: number;
/**
* @description Content's scroll height
*/
contentScrollHeight: number;
/**
* @description Content's scroll width
*/
contentScrollWidth: number;
/**
* @description Scroller's native scrollHeight parameter
*/
scrollHeight: number;
/**
* @description Scroller's native scrollWidth parameter
*/
scrollWidth: number;
/**
* @description Scroller's native scrollTop parameter
*/
scrollTop: number;
/**
* @description Scroller's native scrollLeft parameter
*/
scrollLeft: number;
/**
* @description Indicates whether vertical scroll blocked via properties
*/
scrollYBlocked: boolean;
/**
* @description Indicates whether horizontal scroll blocked via properties
*/
scrollXBlocked: boolean;
/**
* @description Indicates whether the content overflows vertically and scrolling not blocked
*/
scrollYPossible: boolean;
/**
* @description Indicates whether the content overflows horizontally and scrolling not blocked
*/
scrollXPossible: boolean;
/**
* @description Indicates whether vertical track is visible
*/
trackYVisible: boolean;
/**
* @description Indicates whether horizontal track is visible
*/
trackXVisible: boolean;
/**
* @description Indicates whether display direction is right-to-left
*/
isRTL?: boolean;
/**
* @description Pages zoom level - it affects scrollbars
*/
zoomLevel: number;
};

View File

@ -0,0 +1,299 @@
import * as React from "react";
import { ElementPropsWithElementRefAndRenderer, ElementRef } from "./types";
let doc: Document | null = typeof document === "object" ? document : null;
export const isBrowser =
typeof window !== "undefined" &&
typeof navigator !== "undefined" &&
typeof document !== "undefined";
export const isUndef = (v: unknown): v is Exclude<typeof v, undefined> => {
return typeof v === "undefined";
};
export const isFun = (v: unknown): v is CallableFunction => {
return typeof v === "function";
};
export const isNum = (v: unknown): v is number => {
return typeof v === "number";
};
/**
* @description Will return renderer result if presented, div element otherwise.
* If renderer is presented it'll receive `elementRef` function which should be used as HTMLElement's ref.
*
* @param props {ElementPropsWithElementRefAndRenderer}
* @param elementRef {ElementRef}
*/
export const renderDivWithRenderer = (
props: ElementPropsWithElementRefAndRenderer,
elementRef: ElementRef,
): React.ReactElement | null => {
if (isFun(props.renderer)) {
props.elementRef = elementRef;
const { renderer } = props;
delete props.renderer;
return renderer(props);
}
delete props.elementRef;
return <div {...props} key={props.key} ref={elementRef} />;
};
const getInnerSize = (
el: HTMLElement,
dimension: string,
padding1: string,
padding2: string,
): number => {
const styles = getComputedStyle(el);
if (styles.boxSizing === "border-box") {
return Math.max(
0,
(Number.parseFloat(styles[dimension] as string) || 0) -
(Number.parseFloat(styles[padding1] as string) || 0) -
(Number.parseFloat(styles[padding2] as string) || 0),
);
}
return Number.parseFloat(styles[dimension] as string) || 0;
};
/**
* @description Return element's height without padding
*/
export const getInnerHeight = (el: HTMLElement): number => {
return getInnerSize(el, "height", "paddingTop", "paddingBottom");
};
/**
* @description Return element's width without padding
*/
export const getInnerWidth = (el: HTMLElement): number => {
return getInnerSize(el, "width", "paddingLeft", "paddingRight");
};
/**
* @description Return unique UUID v4
*/
export const uuid = () => {
// eslint-disable-next-line @typescript-eslint/no-shadow
let uuid = "";
for (let i = 0; i < 32; i++) {
switch (i) {
case 8:
case 20: {
uuid += `-${Math.trunc(Math.random() * 16).toString(16)}`;
break;
}
case 12: {
uuid += "-4";
break;
}
case 16: {
uuid += `-${((Math.random() * 16) | (0 & 3) | 8).toString(16)}`;
break;
}
default: {
uuid += Math.trunc(Math.random() * 16).toString(16);
}
}
}
return uuid;
};
/**
* @description Calculate thumb size for given viewport and track parameters
*
* @param {number} contentSize - Scrollable content size
* @param {number} viewportSize - Viewport size
* @param {number} trackSize - Track size thumb can move
* @param {number} minimalSize - Minimal thumb's size
* @param {number} maximalSize - Maximal thumb's size
*/
export const calcThumbSize = (
contentSize: number,
viewportSize: number,
trackSize: number,
minimalSize?: number,
maximalSize?: number,
): number => {
if (viewportSize >= contentSize) {
return 0;
}
let thumbSize = (viewportSize / contentSize) * trackSize;
if (isNum(maximalSize)) {
thumbSize = Math.min(maximalSize, thumbSize);
}
if (isNum(minimalSize)) {
thumbSize = Math.max(minimalSize, thumbSize);
}
return thumbSize;
};
/**
* @description Calculate thumb offset for given viewport, track and thumb parameters
*
* @param {number} contentSize - Scrollable content size
* @param {number} viewportSize - Viewport size
* @param {number} trackSize - Track size thumb can move
* @param {number} thumbSize - Thumb size
* @param {number} scroll - Scroll value to represent
*/
export const calcThumbOffset = (
contentSize: number,
viewportSize: number,
trackSize: number,
thumbSize: number,
scroll: number,
): number => {
if (!scroll || !thumbSize || viewportSize >= contentSize) {
return 0;
}
return ((trackSize - thumbSize) * scroll) / (contentSize - viewportSize);
};
/**
* @description Calculate scroll for given viewport, track and thumb parameters
*
* @param {number} contentSize - Scrollable content size
* @param {number} viewportSize - Viewport size
* @param {number} trackSize - Track size thumb can move
* @param {number} thumbSize - Thumb size
* @param {number} thumbOffset - Thumb's offset representing the scroll
*/
export const calcScrollForThumbOffset = (
contentSize: number,
viewportSize: number,
trackSize: number,
thumbSize: number,
thumbOffset: number,
): number => {
if (!thumbOffset || !thumbSize || viewportSize >= contentSize) {
return 0;
}
return (thumbOffset * (contentSize - viewportSize)) / (trackSize - thumbSize);
};
/**
* @description Set the document node to calculate the scrollbar width.<br/>
* <i>null</i> will force getter to return 0 (it'll imitate SSR).
*/
export const _dbgSetDocument = (v: Document | null): Document | null => {
if (v === null || v instanceof HTMLDocument) {
doc = v;
return doc;
}
throw new TypeError(
`override value expected to be an instance of HTMLDocument or null, got ${typeof v}`,
);
};
/**
* @description Return current document node
*/
export const _dbgGetDocument = (): Document | null => doc;
interface GetScrollbarWidthFN {
_cache?: number;
(force?: boolean): number | undefined;
}
/**
* @description Returns scrollbar width specific for current environment. Can return undefined if DOM is not ready yet.
*/
export const getScrollbarWidth: GetScrollbarWidthFN = (
force = false,
): number | undefined => {
if (!doc) {
getScrollbarWidth._cache = 0;
return getScrollbarWidth._cache;
}
if (!force && !isUndef(getScrollbarWidth._cache)) {
return getScrollbarWidth._cache as number;
}
const el = doc.createElement("div");
el.setAttribute(
"style",
"position:absolute;width:100px;height:100px;top:-999px;left:-999px;overflow:scroll;",
);
doc.body.append(el);
/* istanbul ignore next */
if (el.clientWidth === 0) {
// Do not even cache this value because there is no calculations. Issue https://github.com/xobotyi/react-scrollbars-custom/issues/123
el.remove();
return;
}
getScrollbarWidth._cache = 100 - el.clientWidth;
el.remove();
return getScrollbarWidth._cache;
};
interface ShouldReverseRtlScroll {
_cache?: boolean;
(force?: boolean): boolean;
}
/**
* @description Detect need of horizontal scroll reverse while RTL.
*/
export const shouldReverseRtlScroll: ShouldReverseRtlScroll = (
force = false,
): boolean => {
if (!force && !isUndef(shouldReverseRtlScroll._cache)) {
return shouldReverseRtlScroll._cache as boolean;
}
if (!doc) {
shouldReverseRtlScroll._cache = false;
return shouldReverseRtlScroll._cache;
}
const el = doc.createElement("div");
const child = doc.createElement("div");
el.append(child);
el.setAttribute(
"style",
"position:absolute;width:100px;height:100px;top:-999px;left:-999px;overflow:scroll;direction:rtl",
);
child.setAttribute("style", "width:1000px;height:1000px");
doc.body.append(el);
el.scrollLeft = -50;
shouldReverseRtlScroll._cache = el.scrollLeft === -50;
el.remove();
return shouldReverseRtlScroll._cache;
};

View File

@ -100,10 +100,6 @@ module.exports = {
singleton: true,
requiredVersion: deps["prop-types"],
},
"react-custom-scrollbars": {
singleton: true,
requiredVersion: compDeps["react-custom-scrollbars"],
},
"react-device-detect": {
singleton: true,
requiredVersion: compDeps["react-device-detect"],

View File

@ -24,6 +24,7 @@
"@use-gesture/react": "^10.2.24",
"attr-accept": "^2.2.2",
"axios": "^0.22.0",
"cnbuilder": "^3.1.0",
"cross-fetch": "3.1.5",
"csvjson-json_beautifier": "^5.0.4",
"email-addresses": "^3.1.0",
@ -47,9 +48,9 @@
"react-autosize-textarea": "^7.1.0",
"react-content-loader": "^5.1.4",
"react-countdown": "2.3.5",
"react-custom-scrollbars": "^4.2.1",
"react-device-detect": "^1.17.0",
"react-dom": "^18.2.0",
"react-draggable": "^4.4.6",
"react-dropzone": "^11.7.1",
"react-hammerjs": "^1.0.1",
"react-i18next": "^13.2.1",
@ -75,7 +76,8 @@
"socket.io-client": "^4.6.1",
"styled-components": "^5.3.9",
"utif": "^3.1.0",
"workbox-window": "^6.5.4"
"workbox-window": "^6.5.4",
"zoom-level": "^2.5.0"
},
"devDependencies": {
"@babel/cli": "^7.21.0",
@ -171,3 +173,4 @@
"react-dom": "^18.2.0"
}
}

View File

@ -348,10 +348,8 @@ export function convertLanguage(key: string) {
case "fr-FR":
return "fr";
default:
return "en-GB";
return key;
}
return key;
}
export function convertToCulture(key: string) {
@ -377,9 +375,8 @@ export function convertToCulture(key: string) {
case "zh":
return "zh-CN";
default:
return "en-US";
return key;
}
return key;
}
export function convertToLanguage(key: string) {

View File

@ -3304,6 +3304,7 @@ __metadata:
babel-plugin-transform-react-remove-prop-types: "npm:^0.4.24"
babel-plugin-transform-rename-import: "npm:^2.3.0"
babel-preset-react-app: "npm:^9.1.2"
cnbuilder: "npm:^3.1.0"
copy-webpack-plugin: "npm:6.4.1"
cross-env: "npm:^6.0.3"
cross-fetch: "npm:3.1.5"
@ -3347,10 +3348,10 @@ __metadata:
react-autosize-textarea: "npm:^7.1.0"
react-content-loader: "npm:^5.1.4"
react-countdown: "npm:2.3.5"
react-custom-scrollbars: "npm:^4.2.1"
react-device-detect: "npm:^1.17.0"
react-docgen-typescript-plugin: "npm:^1.0.5"
react-dom: "npm:^18.2.0"
react-draggable: "npm:^4.4.6"
react-dropzone: "npm:^11.7.1"
react-hammerjs: "npm:^1.0.1"
react-i18next: "npm:^13.2.1"
@ -3388,6 +3389,7 @@ __metadata:
ts-node: "npm:^10.9.1"
utif: "npm:^3.1.0"
workbox-window: "npm:^6.5.4"
zoom-level: "npm:^2.5.0"
peerDependencies:
react: ^18.2.0
react-dom: ^18.2.0
@ -9855,13 +9857,6 @@ __metadata:
languageName: node
linkType: hard
"add-px-to-style@npm:1.0.0":
version: 1.0.0
resolution: "add-px-to-style@npm:1.0.0"
checksum: 2e8c3c2adce84253c4240b83e69574b1799c2bc4fb97998985b6f04520a0c2be3e1ea7fd498c29f38286ca9fbd24f7974faa6ad3a91ef4a2b7abd0404957ded2
languageName: node
linkType: hard
"address@npm:^1.0.1":
version: 1.2.2
resolution: "address@npm:1.2.2"
@ -13676,17 +13671,6 @@ __metadata:
languageName: node
linkType: hard
"dom-css@npm:^2.0.0":
version: 2.1.0
resolution: "dom-css@npm:2.1.0"
dependencies:
add-px-to-style: "npm:1.0.0"
prefix-style: "npm:2.0.1"
to-camel-case: "npm:1.0.0"
checksum: 6180c872f17a4c026a690e399dd55e41a90f4e98c5af33ca0f16934309b67e762c929d07e38accc4e05ac4d22e8527af0252bf478a2d780b9e593a415b18905a
languageName: node
linkType: hard
"dom-helpers@npm:^5.0.1, dom-helpers@npm:^5.1.3":
version: 5.2.1
resolution: "dom-helpers@npm:5.2.1"
@ -23209,13 +23193,6 @@ __metadata:
languageName: node
linkType: hard
"prefix-style@npm:2.0.1":
version: 2.0.1
resolution: "prefix-style@npm:2.0.1"
checksum: 26503ee6629e8a5d6b1de75e4a6529bdcd17767d35afb4763c4985034d15a625def95831c94cb063bf4bfa7f6be430c9b41f7af8e0625017365a52510a6e25fc
languageName: node
linkType: hard
"prelude-ls@npm:^1.2.1":
version: 1.2.1
resolution: "prelude-ls@npm:1.2.1"
@ -23669,7 +23646,7 @@ __metadata:
languageName: node
linkType: hard
"raf@npm:^3.1.0, raf@npm:^3.4.1":
"raf@npm:^3.4.1":
version: 3.4.1
resolution: "raf@npm:3.4.1"
dependencies:
@ -23957,20 +23934,6 @@ __metadata:
languageName: node
linkType: hard
"react-custom-scrollbars@npm:^4.2.1":
version: 4.2.1
resolution: "react-custom-scrollbars@npm:4.2.1"
dependencies:
dom-css: "npm:^2.0.0"
prop-types: "npm:^15.5.10"
raf: "npm:^3.1.0"
peerDependencies:
react: ^0.14.0 || ^15.0.0 || ^16.0.0
react-dom: ^0.14.0 || ^15.0.0 || ^16.0.0
checksum: 58c62e04f6a23558f9f4c3df1e96f30efa344d0ce63eb7894f58ca497f8c383e25b86d8055496c7e7910d61eb77496dbe4b451097c98f0347a31aaa7880879f6
languageName: node
linkType: hard
"react-device-detect@npm:^1.17.0":
version: 1.17.0
resolution: "react-device-detect@npm:1.17.0"
@ -24054,7 +24017,7 @@ __metadata:
languageName: node
linkType: hard
"react-draggable@npm:^4.4.5":
"react-draggable@npm:^4.4.5, react-draggable@npm:^4.4.6":
version: 4.4.6
resolution: "react-draggable@npm:4.4.6"
dependencies:
@ -27468,15 +27431,6 @@ __metadata:
languageName: node
linkType: hard
"to-camel-case@npm:1.0.0":
version: 1.0.0
resolution: "to-camel-case@npm:1.0.0"
dependencies:
to-space-case: "npm:^1.0.0"
checksum: 2f74cfcffa58e8ddede7e01a03eda2cc3f0ab50efdad1d0f1092d55b4e499be43846d1f9087c458fa9efde4958e407738197d65858272c56c915b649b9ca1e62
languageName: node
linkType: hard
"to-fast-properties@npm:^2.0.0":
version: 2.0.0
resolution: "to-fast-properties@npm:2.0.0"
@ -27484,13 +27438,6 @@ __metadata:
languageName: node
linkType: hard
"to-no-case@npm:^1.0.0":
version: 1.0.2
resolution: "to-no-case@npm:1.0.2"
checksum: 80671e9cb9695ceea2fff23f4678caa6d7a579a2077ecb8f9a0745d90a6435b505bd299688041328f702836289b6ac6a32abba76c5c3922639c0d0224df3dffa
languageName: node
linkType: hard
"to-object-path@npm:^0.3.0":
version: 0.3.0
resolution: "to-object-path@npm:0.3.0"
@ -27531,15 +27478,6 @@ __metadata:
languageName: node
linkType: hard
"to-space-case@npm:^1.0.0":
version: 1.0.0
resolution: "to-space-case@npm:1.0.0"
dependencies:
to-no-case: "npm:^1.0.0"
checksum: 157cebe3e98e7cb465fe1978cf26450cc8ea8e637a01039854fac7ed60ad074e5e18b32333cc5f30df81b81ca374d63df768cd4c1fa0fe672605f965376227f4
languageName: node
linkType: hard
"tocbot@npm:^4.20.1":
version: 4.25.0
resolution: "tocbot@npm:4.25.0"