mirror of
https://github.com/duckietm/Nitro-V3.git
synced 2026-06-19 23:16:21 +00:00
feat: UI color theming system with live preview, presets and server sync
- RGBA color picker with live preview (debounce 50ms) - 30 preset colors + 12 theme presets (Ocean, Forest, Sunset, Royal, etc.) - Header image selection from configurable image library - Export/Import theme as JSON via clipboard - CSS variable theming across all UI elements: NitroCard headers/tabs, context menus, buttons (primary/dark/gray), InfoStand, toolbar, room tools, purse, progress bars, sliders - All elements use var(--name, fallback) for zero visual change when default - Smooth 0.3s CSS transitions on theme change - Server-side persistence via WebSocket (packets 10047/10048) - Integrated Color/Image tabs into BackgroundsView panel - All strings use LocalizeText() for i18n support - Settings persisted in localStorage + server sync with 1s debounce - Added react-colorful dependency
This commit is contained in:
@@ -2,9 +2,11 @@ import { GetSessionDataManager, HabboClubLevelEnum} from '@nitrots/nitro-rendere
|
||||
import { Dispatch, FC, SetStateAction, useCallback, useMemo, useState } from 'react';
|
||||
import { Base, Grid, Flex, NitroCardView, NitroCardHeaderView, NitroCardTabsView, NitroCardTabsItemView, NitroCardContentView, Text, LayoutCurrencyIcon } from '../../common';
|
||||
import { useRoom } from '../../hooks';
|
||||
import { GetClubMemberLevel, GetConfigurationValue } from '../../api';
|
||||
import { GetClubMemberLevel, GetConfigurationValue, LocalizeText } from '../../api';
|
||||
import { InterfaceColorTabView } from '../interface-settings/InterfaceColorTabView';
|
||||
import { InterfaceImageTabView } from '../interface-settings/InterfaceImageTabView';
|
||||
|
||||
interface ItemData {
|
||||
interface ItemData {
|
||||
id: number;
|
||||
isHcOnly: boolean;
|
||||
minRank: number;
|
||||
@@ -22,7 +24,7 @@ interface BackgroundsViewProps {
|
||||
setSelectedOverlay: Dispatch<SetStateAction<number>>;
|
||||
}
|
||||
|
||||
const TABS = ['backgrounds', 'stands', 'overlays'] as const;
|
||||
const TABS = ['backgrounds', 'stands', 'overlays', 'color', 'image'] as const;
|
||||
type TabType = typeof TABS[number];
|
||||
|
||||
export const BackgroundsView: FC<BackgroundsViewProps> = ({
|
||||
@@ -36,7 +38,7 @@ export const BackgroundsView: FC<BackgroundsViewProps> = ({
|
||||
}) => {
|
||||
const [activeTab, setActiveTab] = useState<TabType>('backgrounds');
|
||||
const { roomSession } = useRoom();
|
||||
|
||||
|
||||
const userData = useMemo(() => ({
|
||||
isHcMember: GetClubMemberLevel() >= HabboClubLevelEnum.CLUB,
|
||||
securityLevel: GetSessionDataManager().canChangeName,
|
||||
@@ -45,7 +47,7 @@ export const BackgroundsView: FC<BackgroundsViewProps> = ({
|
||||
|
||||
const processData = useCallback((configData: any[], dataType: string): ItemData[] => {
|
||||
if (!configData?.length) return [];
|
||||
|
||||
|
||||
return configData
|
||||
.filter(item => {
|
||||
const meetsRank = userData.securityLevel >= item.minRank;
|
||||
@@ -65,10 +67,10 @@ export const BackgroundsView: FC<BackgroundsViewProps> = ({
|
||||
if (!roomSession) return;
|
||||
|
||||
const setters = { backgrounds: setSelectedBackground, stands: setSelectedStand, overlays: setSelectedOverlay };
|
||||
|
||||
|
||||
const currentValues = { backgrounds: selectedBackground, stands: selectedStand, overlays: selectedOverlay };
|
||||
|
||||
setters[activeTab](id);
|
||||
setters[activeTab as 'backgrounds' | 'stands' | 'overlays'](id);
|
||||
const newValues = { ...currentValues, [activeTab]: id };
|
||||
roomSession.sendBackgroundMessage( newValues.backgrounds, newValues.stands, newValues.overlays );
|
||||
}, [activeTab, roomSession, selectedBackground, selectedStand, selectedOverlay, setSelectedBackground, setSelectedStand, setSelectedOverlay]);
|
||||
@@ -86,9 +88,11 @@ export const BackgroundsView: FC<BackgroundsViewProps> = ({
|
||||
</Flex>
|
||||
), [handleSelection]);
|
||||
|
||||
const isProfileTab = activeTab === 'backgrounds' || activeTab === 'stands' || activeTab === 'overlays';
|
||||
|
||||
return (
|
||||
<NitroCardView uniqueKey="backgrounds" className="absolute min-w-[535px] max-w-[535px] min-h-[389px] max-h-[389px]">
|
||||
<NitroCardHeaderView headerText="Profile Background" onCloseClick={() => setIsVisible(false)} />
|
||||
<NitroCardHeaderView headerText={ LocalizeText('interface.settings.title') } onCloseClick={() => setIsVisible(false)} />
|
||||
<NitroCardTabsView>
|
||||
{TABS.map(tab => (
|
||||
<NitroCardTabsItemView
|
||||
@@ -96,16 +100,22 @@ export const BackgroundsView: FC<BackgroundsViewProps> = ({
|
||||
isActive={activeTab === tab}
|
||||
onClick={() => setActiveTab(tab)}
|
||||
>
|
||||
{tab.charAt(0).toUpperCase() + tab.slice(1)}
|
||||
{ LocalizeText(`interface.settings.tab.${ tab }`) }
|
||||
</NitroCardTabsItemView>
|
||||
))}
|
||||
</NitroCardTabsView>
|
||||
<NitroCardContentView gap={1}>
|
||||
<Text bold center>Select an Option</Text>
|
||||
<Grid gap={1} columnCount={7} overflow="auto">
|
||||
{allData[activeTab].map(item => renderItem(item, activeTab.slice(0, -1)))}
|
||||
</Grid>
|
||||
{ isProfileTab && (
|
||||
<>
|
||||
<Text bold center>{ LocalizeText('interface.settings.select.option') }</Text>
|
||||
<Grid gap={1} columnCount={7} overflow="auto">
|
||||
{allData[activeTab as 'backgrounds' | 'stands' | 'overlays'].map(item => renderItem(item, activeTab.slice(0, -1)))}
|
||||
</Grid>
|
||||
</>
|
||||
) }
|
||||
{ activeTab === 'color' && <InterfaceColorTabView /> }
|
||||
{ activeTab === 'image' && <InterfaceImageTabView /> }
|
||||
</NitroCardContentView>
|
||||
</NitroCardView>
|
||||
);
|
||||
};
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user