Compare commits

...

3 Commits

Author SHA1 Message Date
Bill a6a15b0771 Begin new card 2024-04-11 22:57:44 -04:00
Bill 5469c9df36 Update config things 2024-04-11 22:57:26 -04:00
Laynester eb605e8c29 add css color generator 2024-04-11 22:24:27 -04:00
12 changed files with 277 additions and 21 deletions

View File

@ -34,6 +34,9 @@
"css": "css"
},
"tailwindCSS.experimental.classRegex": [
["classes \\=([^;]*);", "'([^']*)'"],
["classes \\=([^;]*);", "\"([^\"]*)\""],
["classes \\=([^;]*);", "\\`([^\\`]*)\\`"]
],
"editor.quickSuggestions": {
"strings": true

View File

@ -0,0 +1,49 @@
const lightenHexColor = (hex, percent) =>
{
// Remove the hash symbol if present
hex = hex.replace(/^#/, '');
// Convert hex to RGB
let r = parseInt(hex.substring(0, 2), 16);
let g = parseInt(hex.substring(2, 4), 16);
let b = parseInt(hex.substring(4, 6), 16);
// Adjust RGB values
r = Math.round(Math.min(255, r + 255 * percent));
g = Math.round(Math.min(255, g + 255 * percent));
b = Math.round(Math.min(255, b + 255 * percent));
// Convert RGB back to hex
const result = ((r << 16) | (g << 8) | b).toString(16);
// Make sure result has 6 digits
return '#' + result.padStart(6, '0');
}
const generateShades = (colors) =>
{
for (let color in colors)
{
let hex = colors[color]
let extended = {}
const shades = [ 50, 100, 200, 300, 400, 500, 600, 700, 900, 950 ];
for (let i = 0; i < shades.length; i++)
{
let shade = shades[i];
extended[shade] = lightenHexColor(hex, shades[(shades.length - 1 - i) ] / 1000);
}
colors[color] = {
DEFAULT: hex,
...extended
}
}
return colors;
}
module.exports = {
generateShades,
lightenHexColor
}

View File

@ -44,6 +44,7 @@
"sass": "^1.72.0",
"tailwindcss": "^3.4.3",
"typescript": "^5.4.2",
"vite": "^5.1.6"
"vite": "^5.1.6",
"vite-tsconfig-paths": "^4.3.2"
}
}

View File

@ -1,7 +1,6 @@
import { MouseEventType, TouchEventType } from '@nitrots/nitro-renderer';
import { CSSProperties, FC, Key, MouseEvent as ReactMouseEvent, ReactNode, TouchEvent as ReactTouchEvent, useCallback, useEffect, useRef, useState } from 'react';
import { createPortal } from 'react-dom';
import { Base } from '..';
import { GetLocalStorage, SetLocalStorage, WindowSaveOptions } from '../../api';
import { DraggableWindowPosition } from './DraggableWindowPosition';
@ -263,8 +262,8 @@ export const DraggableWindow: FC<DraggableWindowProps> = props =>
return (
createPortal(
<Base className="draggable-window" innerRef={ elementRef } position="absolute" style={ dragStyle } onMouseDownCapture={ onMouseDown } onTouchStartCapture={ onTouchStart }>
<div ref={ elementRef } className="absolute draggable-window" style={ dragStyle } onMouseDownCapture={ onMouseDown } onTouchStartCapture={ onTouchStart }>
{ children }
</Base>, document.getElementById('draggable-windows-container'))
</div>, document.getElementById('draggable-windows-container'))
);
}

View File

@ -1,8 +1,9 @@
import { NitroCard, NitroCardTabs } from '@layout/NitroCard';
import { AddLinkEventTracker, ConvertGlobalRoomIdMessageComposer, HabboWebTools, ILinkEventTracker, LegacyExternalInterface, NavigatorInitComposer, NavigatorSearchComposer, RemoveLinkEventTracker, RoomSessionEvent } from '@nitrots/nitro-renderer';
import { FC, useCallback, useEffect, useRef, useState } from 'react';
import { FaPlus } from 'react-icons/fa';
import { LocalizeText, SendMessageComposer, TryVisitRoom } from '../../api';
import { Column, NitroCardContentView, NitroCardHeaderView, NitroCardTabsItemView, NitroCardTabsView, NitroCardView } from '../../common';
import { Column, NitroCardTabsItemView } from '../../common';
import { useNavigator, useNitroEvent } from '../../hooks';
import { NavigatorDoorStateView } from './views/NavigatorDoorStateView';
import { NavigatorRoomCreatorView } from './views/NavigatorRoomCreatorView';
@ -196,24 +197,28 @@ export const NavigatorView: FC<{}> = props =>
return (
<>
{ isVisible &&
<NitroCardView className="nitro-navigator" uniqueKey="navigator">
<NitroCardHeaderView headerText={ LocalizeText(isCreatorOpen ? 'navigator.createroom.title' : 'navigator.title') } onCloseClick={ event => setIsVisible(false) } />
<NitroCardTabsView>
<NitroCard.Main
className="w-navigator h-navigator"
uniqueKey="navigator">
<NitroCard.Header
headerText={ LocalizeText(isCreatorOpen ? 'navigator.createroom.title' : 'navigator.title') }
onCloseClick={ event => setIsVisible(false) } />
<NitroCardTabs.Main>
{ topLevelContexts && (topLevelContexts.length > 0) && topLevelContexts.map((context, index) =>
{
return (
<NitroCardTabsItemView key={ index } isActive={ ((topLevelContext === context) && !isCreatorOpen) } onClick={ event => sendSearch('', context.code) }>
<NitroCardTabs.Item key={ index } isActive={ ((topLevelContext === context) && !isCreatorOpen) } onClick={ event => sendSearch('', context.code) }>
{ LocalizeText(('navigator.toplevelview.' + context.code)) }
</NitroCardTabsItemView>
</NitroCardTabs.Item>
);
}) }
<NitroCardTabsItemView isActive={ isCreatorOpen } onClick={ event => setCreatorOpen(true) }>
<FaPlus className="fa-icon" />
</NitroCardTabsItemView>
</NitroCardTabsView>
<NitroCardContentView position="relative">
</NitroCardTabs.Main>
<NitroCard.Content>
{ isLoading &&
<div className="position-absolute size-full top-0 start-0 z-index-1 bg-muted opacity-0-5" /> }
<div className="top-0 position-absolute size-full start-0 z-index-1 bg-muted opacity-0-5" /> }
{ !isCreatorOpen &&
<>
<NavigatorSearchView sendSearch={ sendSearch } />
@ -222,8 +227,8 @@ export const NavigatorView: FC<{}> = props =>
</Column>
</> }
{ isCreatorOpen && <NavigatorRoomCreatorView /> }
</NitroCardContentView>
</NitroCardView> }
</NitroCard.Content>
</NitroCard.Main> }
<NavigatorDoorStateView />
{ isRoomInfoOpen && <NavigatorRoomInfoView onCloseClick={ () => setRoomInfoOpen(false) } /> }
{ isRoomLinkOpen && <NavigatorRoomLinkView onCloseClick={ () => setRoomLinkOpen(false) } /> }

133
src/layout/NitroCard.tsx Normal file
View File

@ -0,0 +1,133 @@
import { DetailedHTMLProps, forwardRef, HTMLAttributes, MouseEvent, PropsWithChildren } from 'react';
import { FaTimes } from 'react-icons/fa';
import { classNames, DraggableWindow, DraggableWindowPosition, DraggableWindowProps } from '../common';
import { NitroItemCountBadge } from './NitroItemCountBadge';
const classes = {
base: 'flex flex-col rounded shadow',
themes: {
'primary': 'border'
}
}
const NitroCardMain = forwardRef<HTMLDivElement, PropsWithChildren<{
theme?: 'primary';
} & DraggableWindowProps> & DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>>((props, ref) =>
{
const { theme = 'primary', uniqueKey = null, handleSelector = '.drag-handler', windowPosition = DraggableWindowPosition.CENTER, disableDrag = false, className = null, ...rest } = props;
return (
<DraggableWindow disableDrag={ disableDrag } handleSelector={ handleSelector } uniqueKey={ uniqueKey } windowPosition={ windowPosition }>
<div
ref={ ref }
className={ classNames(
classes.base,
classes.themes[theme],
className
) }
{ ...rest } />
</DraggableWindow>
);
});
NitroCardMain.displayName = 'NitroCardMain';
const NitroCardHeader = forwardRef<HTMLDivElement, {
headerText: string;
onCloseClick?: (event: MouseEvent) => void;
} & DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>>((props, ref) =>
{
const { headerText = '', onCloseClick = null, className = null, ...rest } = props;
const onMouseDown = (event: MouseEvent<HTMLDivElement>) =>
{
event.stopPropagation();
event.nativeEvent.stopImmediatePropagation();
}
return (
<div ref={ ref } className={ classNames('relative flex items-center justify-center flex-column drag-handler', className) }>
<div className="flex items-center justify-center w-full">
<span>{ headerText }</span>
<div className="absolute flex items-center justify-center right-2" onClick={ onCloseClick } onMouseDownCapture={ onMouseDown }>
<FaTimes className="fa-icon w-[12px] h-[12px]" />
</div>
</div>
</div>
)
});
NitroCardHeader.displayName = 'NitroCardHeader';
const NitroCardContent = forwardRef<HTMLDivElement, DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>>((props, ref) =>
{
const { className = null, ...rest } = props;
return (
<div
ref={ ref }
className={ classNames(
'overflow-auto',
className
) }
{ ...rest } />
);
});
NitroCardContent.displayName = 'NitroCardContent';
const NitroCardTabsMain = forwardRef<HTMLDivElement, {
} & DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>>((props, ref) =>
{
const { className = null, ...rest } = props;
return (
<div
ref={ ref }
className={ classNames(
'justify-center gap-1 flex',
className)
}
{ ...rest } />
)
});
NitroCardTabsMain.displayName = 'NitroCardTabsMain';
const NitroCardTabsItem = forwardRef<HTMLDivElement, {
isActive?: boolean;
count?: number;
} & DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>>((props, ref) =>
{
const { isActive = false, count = 0, className = null, children = null, ...rest } = props;
return (
<div
ref={ ref }
className={ classNames(
'overflow-hidden relative cursor-pointer rounded-t-lg flex',
isActive && 'active',
className)
}
{ ...rest }>
<div className="flex items-center justify-center shrink-0">
{ children }
</div>
{ (count > 0) &&
<NitroItemCountBadge count={ count } /> }
</div>
)
});
NitroCardTabsItem.displayName = 'NitroCardTabsItem';
export const NitroCard = {
Main: NitroCardMain,
Header: NitroCardHeader,
Content: NitroCardContent
};
export const NitroCardTabs = {
Main: NitroCardTabsMain,
Item: NitroCardTabsItem
};

View File

@ -0,0 +1,33 @@
import { DetailedHTMLProps, forwardRef, HTMLAttributes, PropsWithChildren } from 'react';
import { classNames } from '../common';
const classes = {
base: 'top-2 right-2 py-0.5 px-[3px] z-[1] rounded border',
themes: {
'primary': 'border-black bg-red-700'
}
}
export const NitroItemCountBadge = forwardRef<HTMLDivElement, PropsWithChildren<{
theme?: 'primary';
count: number;
}> & DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>>((props, ref) =>
{
const { theme = 'primary', count = 0, className = null, children = null, ...rest } = props;
return (
<div
ref={ ref }
className={ classNames(
classes.base,
classes.themes[theme],
className
) }
{ ...rest }>
{ count }
{ children }
</div>
);
});
NitroItemCountBadge.displayName = 'NitroItemCountBadge';

2
src/layout/index.ts Normal file
View File

@ -0,0 +1,2 @@
export * from './NitroCard';
export * from './NitroItemCountBadge';

View File

@ -1,22 +1,30 @@
/** @type {import('tailwindcss').Config} */
const { generateShades } = require('./css-utils/CSSColorUtils');
const colors = {
'toolbar': 'rgba(28, 28, 32, .95)'
'toolbar': '#555555',
};
const boxShadow = {
'inner1px': 'inset 0 0 0 1px rgba(255,255,255,.3)'
};
module.exports = {
theme: {
extend: {
fontFamily: {
sans: [ 'Ubuntu', 'sans-serif' ],
},
colors,
colors: generateShades(colors),
boxShadow,
width: {
'navigator': '420px'
},
height: {
'toolbar': '55px'
'toolbar': '55px',
'navigator': '440px'
},
zIndex: {
'toolbar': ''

View File

@ -20,7 +20,10 @@
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx"
"jsx": "react-jsx",
"paths": {
"@layout/*": ["layout/*"],
}
},
"include": [
"src",

View File

@ -2,9 +2,10 @@
import react from '@vitejs/plugin-react';
import { resolve } from 'path';
import { defineConfig } from 'vite';
import tsconfigPaths from 'vite-tsconfig-paths';
export default defineConfig({
plugins: [ react() ],
plugins: [ react(), tsconfigPaths() ],
resolve: {
alias: {
'@': resolve(__dirname, 'src'),

View File

@ -1261,7 +1261,7 @@ debug@^3.2.7:
dependencies:
ms "^2.1.1"
debug@^4.1.0, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4:
debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4:
version "4.3.4"
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865"
integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==
@ -1912,6 +1912,11 @@ globby@^11.1.0:
merge2 "^1.4.1"
slash "^3.0.0"
globrex@^0.1.2:
version "0.1.2"
resolved "https://registry.yarnpkg.com/globrex/-/globrex-0.1.2.tgz#dd5d9ec826232730cd6793a5e33a9302985e6098"
integrity sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==
gopd@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.0.1.tgz#29ff76de69dac7489b7c0918a5788e56477c332c"
@ -3242,6 +3247,11 @@ ts-interface-checker@^0.1.9:
resolved "https://registry.yarnpkg.com/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz#784fd3d679722bc103b1b4b8030bcddb5db2a699"
integrity sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==
tsconfck@^3.0.3:
version "3.0.3"
resolved "https://registry.yarnpkg.com/tsconfck/-/tsconfck-3.0.3.tgz#d9bda0e87d05b1c360e996c9050473c7e6f8084f"
integrity sha512-4t0noZX9t6GcPTfBAbIbbIU4pfpCwh0ueq3S4O/5qXI1VwK1outmxhe9dOiEWqMz3MW2LKgDTpqWV+37IWuVbA==
tsconfig-paths@^3.15.0:
version "3.15.0"
resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz#5299ec605e55b1abb23ec939ef15edaf483070d4"
@ -3373,6 +3383,15 @@ util-deprecate@^1.0.2:
resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==
vite-tsconfig-paths@^4.3.2:
version "4.3.2"
resolved "https://registry.yarnpkg.com/vite-tsconfig-paths/-/vite-tsconfig-paths-4.3.2.tgz#321f02e4b736a90ff62f9086467faf4e2da857a9"
integrity sha512-0Vd/a6po6Q+86rPlntHye7F31zA2URZMbH8M3saAZ/xR9QoGN/L21bxEGfXdWmFdNkqPpRdxFT7nmNe12e9/uA==
dependencies:
debug "^4.1.1"
globrex "^0.1.2"
tsconfck "^3.0.3"
vite@^5.1.6:
version "5.2.7"
resolved "https://registry.yarnpkg.com/vite/-/vite-5.2.7.tgz#e1b8a985eb54fcb9467d7f7f009d87485016df6e"