🆙 Init V3

This commit is contained in:
DuckieTM
2026-01-31 09:10:52 +01:00
commit 7feb10ab15
1733 changed files with 53405 additions and 0 deletions
@@ -0,0 +1,26 @@
import { FC, useMemo } from 'react';
import { FaCaretDown, FaCaretUp } from 'react-icons/fa';
import { Flex, FlexProps } from '../../../../common';
interface CaretViewProps extends FlexProps
{
collapsed?: boolean;
}
export const ContextMenuCaretView: FC<CaretViewProps> = props =>
{
const { justifyContent = 'center', alignItems = 'center', classNames = [], collapsed = true, ...rest } = props;
const getClassNames = useMemo(() =>
{
const newClassNames: string[] = [ 'menu-footer' ];
if(classNames.length) newClassNames.push(...classNames);
return newClassNames;
}, [ classNames ]);
return <Flex alignItems={ alignItems } classNames={ getClassNames } justifyContent={ justifyContent } { ...rest }>
{ !collapsed && <FaCaretDown className="fa-icon align-self-center" /> }
{ collapsed && <FaCaretUp className="fa-icon align-self-center" /> }
</Flex>;
};
@@ -0,0 +1,18 @@
import { FC, useMemo } from 'react';
import { Flex, FlexProps } from '../../../../common';
export const ContextMenuHeaderView: FC<FlexProps> = props =>
{
const { justifyContent = 'center', alignItems = 'center', classNames = [], ...rest } = props;
const getClassNames = useMemo(() =>
{
const newClassNames: string[] = [ 'bg-[#3d5f6e] text-[#fff] min-w-[117px] h-[25px] max-h-[25px] text-[16px] mb-[2px]', 'p-1' ];
if(classNames.length) newClassNames.push(...classNames);
return newClassNames;
}, [ classNames ]);
return <Flex alignItems={ alignItems } classNames={ getClassNames } justifyContent={ justifyContent } { ...rest } />;
};
@@ -0,0 +1,32 @@
import { FC, MouseEvent, useMemo } from 'react';
import { Flex, FlexProps } from '../../../../common';
interface ContextMenuListItemViewProps extends FlexProps
{
disabled?: boolean;
}
export const ContextMenuListItemView: FC<ContextMenuListItemViewProps> = props =>
{
const { disabled = false, fullWidth = true, justifyContent = 'center', alignItems = 'center', classNames = [], onClick = null, ...rest } = props;
const handleClick = (event: MouseEvent<HTMLDivElement>) =>
{
if(disabled) return;
if(onClick) onClick(event);
};
const getClassNames = useMemo(() =>
{
const newClassNames: string[] = [ 'relative mb-[2px] p-[3px] overflow-hidden', 'h-[24px] max-h-[24px] p-[3px] bg-[repeating-linear-gradient(#131e25,_#131e25_50%,_#0d171d_50%,_#0d171d_100%)] cursor-pointer' ];
if(disabled) newClassNames.push('disabled');
if(classNames.length) newClassNames.push(...classNames);
return newClassNames;
}, [ disabled, classNames ]);
return <Flex alignItems={ alignItems } classNames={ getClassNames } fullWidth={ fullWidth } justifyContent={ justifyContent } onClick={ handleClick } { ...rest } />;
};
@@ -0,0 +1,18 @@
import { FC, useMemo } from 'react';
import { Column, ColumnProps } from '../../../../common';
export const ContextMenuListView: FC<ColumnProps> = props =>
{
const { classNames = [], ...rest } = props;
const getClassNames = useMemo(() =>
{
const newClassNames: string[] = [ 'menu-list' ];
if(classNames.length) newClassNames.push(...classNames);
return newClassNames;
}, [ classNames ]);
return <Column classNames={ getClassNames } { ...rest } />;
};
@@ -0,0 +1,144 @@
import { GetStage, GetTicker, NitroRectangle, NitroTicker, RoomObjectType } from '@nitrots/nitro-renderer';
import { CSSProperties, FC, useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { FixedSizeStack, GetRoomObjectBounds, GetRoomObjectScreenLocation, GetRoomSession } from '../../../../api';
import { BaseProps } from '../../../../common';
import { ContextMenuCaretView } from './ContextMenuCaretView';
interface ContextMenuViewProps extends BaseProps<HTMLDivElement> {
objectId: number;
category: number;
userType?: number;
fades?: boolean;
onClose: () => void;
collapsable?: boolean;
}
const LOCATION_STACK_SIZE = 25;
const BUBBLE_DROP_SPEED = 3;
const FADE_DELAY = 5000;
const FADE_LENGTH = 75;
const SPACE_AROUND_EDGES = 10;
export const ContextMenuView: FC<ContextMenuViewProps> = ({
objectId = -1,
category = -1,
userType = -1,
fades = false,
onClose,
classNames = [],
style = {},
children = null,
collapsable = false,
...rest
}) => {
const [pos, setPos] = useState<{ x: number; y: number }>({ x: null, y: null });
const [opacity, setOpacity] = useState(1);
const [isFading, setIsFading] = useState(false);
const [isCollapsed, setIsCollapsed] = useState(false);
const elementRef = useRef<HTMLDivElement>(null);
const stackRef = useRef<FixedSizeStack>(new FixedSizeStack(LOCATION_STACK_SIZE));
const maxStackRef = useRef(-1000000);
const updatePosition = useCallback(
(bounds: NitroRectangle, location: { x: number; y: number }) => {
if (!bounds || !location || !elementRef.current) return;
let offset = -elementRef.current.offsetHeight;
if (userType > -1 && [RoomObjectType.USER, RoomObjectType.BOT, RoomObjectType.RENTABLE_BOT].includes(userType)) {
offset += bounds.height > 50 ? 15 : 0;
} else {
offset -= 14;
}
stackRef.current.addValue(location.y - bounds.top);
let maxStack = stackRef.current.getMax();
if (maxStack < maxStackRef.current - BUBBLE_DROP_SPEED) {
maxStack = maxStackRef.current - BUBBLE_DROP_SPEED;
}
maxStackRef.current = maxStack;
const deltaY = location.y - maxStack;
let x = Math.round(location.x - elementRef.current.offsetWidth / 2);
let y = Math.round(deltaY + offset);
const stage = GetStage();
const maxLeft = stage.width - elementRef.current.offsetWidth - SPACE_AROUND_EDGES;
const maxTop = stage.height - elementRef.current.offsetHeight - SPACE_AROUND_EDGES;
x = Math.max(SPACE_AROUND_EDGES, Math.min(x, maxLeft));
y = Math.max(SPACE_AROUND_EDGES, Math.min(y, maxTop));
setPos({ x, y });
},
[userType]
);
const getClassNames = useMemo(() => {
const classes = [
'!p-[2px]',
'bg-[#1c323f]',
'border-[2px]',
'border-[solid]',
'border-[rgba(255,255,255,.5)]',
'rounded-[.25rem]',
'text-[.7875rem]',
'text-white',
'z-40',
'pointer-events-auto',
'absolute',
pos.x !== null ? 'visible' : 'invisible',
];
if (isCollapsed) classes.push('menu-hidden');
return [...classes, ...classNames];
}, [pos.x, isCollapsed, classNames]);
const getStyle = useMemo(
() => ({
left: pos.x ?? 0,
top: pos.y ?? 0,
transition: isFading ? 'opacity 75ms linear' : undefined,
opacity,
...style,
}),
[pos, opacity, isFading, style]
);
useEffect(() => {
if (!elementRef.current) return;
const update = () => {
if (!elementRef.current) return;
const bounds = GetRoomObjectBounds(GetRoomSession().roomId, objectId, category);
const location = GetRoomObjectScreenLocation(GetRoomSession().roomId, objectId, category);
updatePosition(bounds, location);
};
const ticker = GetTicker();
ticker.add(update);
return () => ticker.remove(update);
}, [objectId, category, updatePosition]);
useEffect(() => {
if (!fades) return;
const timeout = setTimeout(() => {
setIsFading(true);
setTimeout(onClose, FADE_LENGTH);
}, FADE_DELAY);
return () => clearTimeout(timeout);
}, [fades, onClose]);
useEffect(() => {
if (!isFading) return;
setOpacity(0);
}, [isFading]);
return (
<div ref={elementRef} className={getClassNames.join(' ')} style={getStyle} {...rest}>
{!(collapsable && isCollapsed) && children}
{collapsable && <ContextMenuCaretView collapsed={isCollapsed} onClick={() => setIsCollapsed((prev) => !prev)} />}
</div>
);
};