feat: float

This commit is contained in:
TrèsAbhi committed 2024-08-05 14:25:12 -05:00
1 parent 6984794be1
commit 6a18abf17f
6 files changed
+462 -47

No files matched your search

+7 -9
View File
@@ -28,7 +28,9 @@ const robotoFlex = Roboto_Flex({
export default function RootLayout({ children }: RootLayoutProps) { export default function RootLayout({ children }: RootLayoutProps) {
const pathname = usePathname(); const pathname = usePathname();
const isEmbed = pathname.startsWith('/tools/embed/host'); const hideNav =
pathname.startsWith('/tools/embed/host') ||
pathname.startsWith('/tools/embed/customize');
const isRoot = pathname === '/'; const isRoot = pathname === '/';
return ( return (
@@ -53,10 +55,9 @@ export default function RootLayout({ children }: RootLayoutProps) {
<body <body
style={{ style={{
margin: 0, margin: 0,
backgroundColor: isEmbed ? 'transparent' : undefined, backgroundColor: hideNav ? 'transparent' : undefined,
}} }}
> >
{!isEmbed && (
<Theme <Theme
appearance="dark" appearance="dark"
panelBackground="solid" panelBackground="solid"
@@ -69,21 +70,18 @@ export default function RootLayout({ children }: RootLayoutProps) {
direction="column" direction="column"
style={{ style={{
minHeight: '100vh', minHeight: '100vh',
paddingTop: isEmbed ? undefined : NAVBAR_HEIGHT, paddingTop: hideNav ? undefined : NAVBAR_HEIGHT,
}} }}
> >
{!isEmbed && <Navbar />} {!hideNav && <Navbar />}
<Checks /> <Checks />
<Suspense fallback={<PageLoader />}>{children}</Suspense> <Suspense fallback={<PageLoader />}>{children}</Suspense>
{!isEmbed && <Footer />} {!hideNav && <Footer />}
</Flex> </Flex>
</Theme> </Theme>
)}
{isEmbed && children}
</body> </body>
</html> </html>
</App.Provider> </App.Provider>
+41 -8
View File
@@ -1,13 +1,13 @@
'use client'; 'use client';
import { Flex } from '@radix-ui/themes'; import { Box } from '@radix-ui/themes';
import { Dispatch, SetStateAction, use, useState } from 'react'; import { Dispatch, SetStateAction, use, useState } from 'react';
import { EmbedConfigInputs } from '../../../../../components/EmbedConfigInputs'; import { EmbedConfigInputs } from '../../../../../components/EmbedConfigInputs';
import PageWrapper from '../../../../../components/PageWrapper';
import { import {
TanksEmbedCard, TanksEmbedCard,
TanksEmbedWrapper, TanksEmbedWrapper,
} from '../../../../../components/TanksEmbed'; } from '../../../../../components/TanksEmbed';
import { imgur } from '../../../../../core/blitzkit/imgur';
import { tanksDefinitionsArray } from '../../../../../core/blitzkit/tankDefinitions'; import { tanksDefinitionsArray } from '../../../../../core/blitzkit/tankDefinitions';
import { import {
EmbedConfig, EmbedConfig,
@@ -19,10 +19,38 @@ import { extractEmbedConfigDefaults } from '../../utilities';
const tankEmbedConfig = { const tankEmbedConfig = {
listWidth: { type: EmbedConfigType.Number, default: 320, unit: 'px' }, listWidth: { type: EmbedConfigType.Number, default: 320, unit: 'px' },
listGap: { type: EmbedConfigType.Size, default: '2' }, listGap: { type: EmbedConfigType.Size, default: '2' },
listMaxTanks: { type: EmbedConfigType.Number, default: 8 }, listMaxTanks: { type: EmbedConfigType.Number, default: 8, pad: true },
cardRadius: { type: EmbedConfigType.Radius, default: '2' }, cardRadius: { type: EmbedConfigType.Radius, default: '2' },
cardHeaderPadding: { type: EmbedConfigType.Size, default: '2' }, cardHeaderBackgroundColor: {
type: EmbedConfigType.Color,
default: { base: 'gray', variant: '3' },
},
cardHeaderPadding: { type: EmbedConfigType.Size, default: '1' },
cardBodyBackgroundColor: {
type: EmbedConfigType.Color,
default: { base: 'gray', variant: '2' },
},
cardBodyPadding: { type: EmbedConfigType.Size, default: '2', pad: true },
columnGap: { type: EmbedConfigType.Size, default: '0' },
columnValueSize: { type: EmbedConfigType.SizeNo0, default: '3' },
columnValueColor: { type: EmbedConfigType.TextColor, default: undefined },
columnLabelSize: { type: EmbedConfigType.SizeNo0, default: '2' },
columnLabelColor: {
type: EmbedConfigType.TextColor,
default: 'gray',
pad: true,
},
column1Unit: { type: EmbedConfigType.String, default: '' },
column2Unit: { type: EmbedConfigType.String, default: '%' },
column3Unit: { type: EmbedConfigType.String, default: '' },
column4Unit: { type: EmbedConfigType.String, default: '' },
column1Label: { type: EmbedConfigType.String, default: 'Battles' },
column2Label: { type: EmbedConfigType.String, default: 'Winrate' },
column3Label: { type: EmbedConfigType.String, default: 'WN8' },
column4Label: { type: EmbedConfigType.String, default: 'Damage' },
} satisfies EmbedConfig; } satisfies EmbedConfig;
export type TanksEmbedState = ExtractEmbedConfigType<typeof tankEmbedConfig>; export type TanksEmbedState = ExtractEmbedConfigType<typeof tankEmbedConfig>;
@@ -34,8 +62,14 @@ export default function Page() {
); );
return ( return (
<PageWrapper> <Box
<Flex align="start" justify="between" gap="4"> width="100vw"
height="100vh"
style={{
background: `url(${imgur('pAidayf')})`,
backgroundSize: 'cover',
}}
>
<TanksEmbedWrapper state={state}> <TanksEmbedWrapper state={state}>
{tanks.slice(0, state.listMaxTanks).map((tank) => ( {tanks.slice(0, state.listMaxTanks).map((tank) => (
<TanksEmbedCard key={tank.id} tank={tank} state={state} /> <TanksEmbedCard key={tank.id} tank={tank} state={state} />
@@ -51,7 +85,6 @@ export default function Page() {
> >
} }
/> />
</Flex> </Box>
</PageWrapper>
); );
} }
+50 -2
View File
@@ -1,17 +1,36 @@
import * as radixColors from '@radix-ui/colors';
export enum EmbedConfigType { export enum EmbedConfigType {
Number, Number,
Radius, Radius,
Size, Size,
Color,
SizeNo0,
TextColor,
String,
} }
export const radixGrays = [
'gray',
'mauve',
'slate',
'sage',
'olive',
'sand',
] as const;
export type EmbedConfig = Record<string, EmbedConfigItem>; export type EmbedConfig = Record<string, EmbedConfigItem>;
export type EmbedConfigItem = export type EmbedConfigItem = (
| { | {
type: EmbedConfigType.Number; type: EmbedConfigType.Number;
default: number; default: number;
unit?: string; unit?: string;
} }
| {
type: EmbedConfigType.String;
default: string;
}
| { | {
type: EmbedConfigType.Radius; type: EmbedConfigType.Radius;
default: RadixRadius; default: RadixRadius;
@@ -19,10 +38,39 @@ export type EmbedConfigItem =
| { | {
type: EmbedConfigType.Size; type: EmbedConfigType.Size;
default: RadixSize; default: RadixSize;
}; }
| {
type: EmbedConfigType.SizeNo0;
default: RadixSizeNo0;
}
| {
type: EmbedConfigType.Color;
default: RadixColor;
}
| {
type: EmbedConfigType.TextColor;
default: RadixTextColor;
}
) & {
pad?: boolean;
};
export type RadixRadius = `${1 | 2 | 3 | 4}` | 'full'; export type RadixRadius = `${1 | 2 | 3 | 4}` | 'full';
export type RadixSize = `${0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9}`; export type RadixSize = `${0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9}`;
export type RadixSizeNo0 = Exclude<RadixSize, '0'>;
export type RadixColor = { base: RadixColorBase; variant: RadixColorVariant };
export type RadixColorBase = Exclude<
keyof typeof radixColors,
`${string}${'A' | 'P3' | 'Dark'}`
>;
type RadixColorGrays = (typeof radixGrays)[number];
export type RadixTextColor =
| Exclude<RadixColorBase, RadixColorGrays>
| 'gray'
| undefined;
export type RadixColorVariantRaw =
`${1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12}`;
export type RadixColorVariant = `${'' | 'a'}${RadixColorVariantRaw}`;
export type ExtractEmbedConfigType<Config extends EmbedConfig> = { export type ExtractEmbedConfigType<Config extends EmbedConfig> = {
[Key in keyof Config]: ({ [Key in keyof Config]: ({
+18 -1
View File
@@ -1,4 +1,9 @@
import { EmbedConfig, ExtractEmbedConfigType } from './types'; import {
EmbedConfig,
ExtractEmbedConfigType,
RadixColor,
RadixRadius,
} from './types';
export function extractEmbedConfigDefaults<Config extends EmbedConfig>( export function extractEmbedConfigDefaults<Config extends EmbedConfig>(
config: Config, config: Config,
@@ -7,3 +12,15 @@ export function extractEmbedConfigDefaults<Config extends EmbedConfig>(
Object.entries(config).map(([key, value]) => [key, value.default]), Object.entries(config).map(([key, value]) => [key, value.default]),
) as ExtractEmbedConfigType<Config>; ) as ExtractEmbedConfigType<Config>;
} }
export function toWidthVar(state: number) {
return `${state}px`;
}
export function toRadiusVar(state: RadixRadius) {
return `var(--radius-${state})`;
}
export function toColorVar(state: RadixColor) {
return `var(--${state.base}-${state.variant})`;
}
+309 -21
View File
@@ -1,14 +1,37 @@
import { Flex, Select, Text, TextField } from '@radix-ui/themes'; import * as radixColors from '@radix-ui/colors';
import { DragHandleDots2Icon } from '@radix-ui/react-icons';
import {
Box,
Card,
Flex,
ScrollArea,
Select,
Text,
TextField,
} from '@radix-ui/themes';
import { produce } from 'immer'; import { produce } from 'immer';
import { capitalize, startCase, times } from 'lodash'; import { capitalize, startCase, times } from 'lodash';
import { Dispatch, SetStateAction, useCallback } from 'react'; import {
Dispatch,
ReactNode,
SetStateAction,
useCallback,
useRef,
} from 'react';
import { import {
EmbedConfig, EmbedConfig,
EmbedConfigType, EmbedConfigType,
ExtractEmbedConfigType, ExtractEmbedConfigType,
RadixColor,
RadixColorBase,
RadixColorVariant,
RadixColorVariantRaw,
radixGrays,
RadixRadius, RadixRadius,
RadixSize, RadixSize,
RadixTextColor,
} from '../app/tools/embed/types'; } from '../app/tools/embed/types';
import { toColorVar, toRadiusVar } from '../app/tools/embed/utilities';
interface EmbedConfigInputsProps { interface EmbedConfigInputsProps {
config: EmbedConfig; config: EmbedConfig;
@@ -27,21 +50,60 @@ export function EmbedConfigInputs({
}, },
[], [],
); );
const wrapper = useRef<HTMLDivElement>(null);
const handlePointerMove = useCallback((event: PointerEvent) => {
event.preventDefault();
if (!wrapper.current) return;
wrapper.current.style.top = `${parseFloat(wrapper.current.style.top) + event.movementY}px`;
wrapper.current.style.right = `${parseFloat(wrapper.current.style.right) - event.movementX}px`;
}, []);
const handlePointerUp = useCallback((event: PointerEvent) => {
window.removeEventListener('pointermove', handlePointerMove);
window.removeEventListener('pointerup', handlePointerUp);
}, []);
return ( return (
<Flex direction="column" gap="2" align="end"> <Card
style={{ position: 'fixed', top: 0, right: 0, minWidth: 320 }}
ref={wrapper}
>
<Flex
align="center"
justify="center"
p="2"
mb="2"
style={{
backgroundColor: 'var(--color-panel-translucent)',
borderRadius: 'var(--radius-2)',
cursor: 'grab',
}}
onPointerDown={(event) => {
window.addEventListener('pointermove', handlePointerMove);
window.addEventListener('pointerup', handlePointerUp);
}}
>
<DragHandleDots2Icon style={{ transform: 'rotate(90deg)' }} />
</Flex>
<ScrollArea
scrollbars="vertical"
style={{ height: '100%', maxHeight: 640 }}
>
<Flex direction="column" gap="2">
{Object.entries(config).map(([key, setting]) => { {Object.entries(config).map(([key, setting]) => {
const name = capitalize(startCase(key)); let control: ReactNode;
switch (setting.type) { switch (setting.type) {
case EmbedConfigType.Number: { case EmbedConfigType.Number: {
return ( control = (
<Flex align="center" gap="2">
<Text>{name}</Text>
<TextField.Root <TextField.Root
style={{ width: 96 }} size="1"
style={{ width: 64 }}
type="number" type="number"
value={state[key]} value={state[key] as number}
onChange={(event) => { onChange={(event) => {
mutateState((draft) => { mutateState((draft) => {
draft[key] = ( draft[key] = (
@@ -54,15 +116,32 @@ export function EmbedConfigInputs({
<TextField.Slot side="right">px</TextField.Slot> <TextField.Slot side="right">px</TextField.Slot>
)} )}
</TextField.Root> </TextField.Root>
</Flex>
); );
break;
} }
case EmbedConfigType.String: {
control = (
<TextField.Root
placeholder="Empty"
size="1"
style={{ width: 64 }}
value={state[key] as string}
onChange={(event) => {
mutateState((draft) => {
draft[key] = (event.target as HTMLInputElement).value;
});
}}
/>
);
break;
}
case EmbedConfigType.SizeNo0:
case EmbedConfigType.Size: { case EmbedConfigType.Size: {
return ( control = (
<Flex align="center" gap="2">
<Text>{name}</Text>
<Select.Root <Select.Root
size="1"
value={state[key] as RadixSize} value={state[key] as RadixSize}
onValueChange={(value) => { onValueChange={(value) => {
mutateState((draft) => { mutateState((draft) => {
@@ -72,22 +151,24 @@ export function EmbedConfigInputs({
> >
<Select.Trigger /> <Select.Trigger />
<Select.Content> <Select.Content>
{times(10, (index) => ( {times(10, (index) =>
setting.type === EmbedConfigType.SizeNo0 &&
index === 0 ? null : (
<Select.Item key={index} value={`${index}`}> <Select.Item key={index} value={`${index}`}>
{index} {index}
</Select.Item> </Select.Item>
))} ),
)}
</Select.Content> </Select.Content>
</Select.Root> </Select.Root>
</Flex>
); );
break;
} }
case EmbedConfigType.Radius: { case EmbedConfigType.Radius: {
return ( control = (
<Flex align="center" gap="2">
<Text>Card radius</Text>
<Select.Root <Select.Root
size="1"
value={state[key] as RadixRadius} value={state[key] as RadixRadius}
onValueChange={(value) => { onValueChange={(value) => {
mutateState((draft) => { mutateState((draft) => {
@@ -105,11 +186,218 @@ export function EmbedConfigInputs({
<Select.Item value="full">Full</Select.Item> <Select.Item value="full">Full</Select.Item>
</Select.Content> </Select.Content>
</Select.Root> </Select.Root>
);
break;
}
case EmbedConfigType.Color: {
control = (
<>
<Select.Root
size="1"
value={(state[key] as RadixColor).base}
onValueChange={(value) => {
mutateState((draft) => {
(draft[key] as RadixColor).base =
value as RadixColorBase;
});
}}
>
<Select.Trigger />
<Select.Content>
{Object.entries(radixColors)
.filter(
([key]) =>
!key.endsWith('A') &&
!key.endsWith('P3') &&
!key.endsWith('Dark'),
)
.map(([key]) => (
<Select.Item key={key} value={key}>
<Box
style={{
display: 'inline-block',
width: '1em',
height: '1em',
verticalAlign: 'text-top',
borderRadius: toRadiusVar('full'),
backgroundColor: toColorVar({
base: key as RadixColorBase,
variant: '9',
}),
}}
/>{' '}
{capitalize(key)}
</Select.Item>
))}
</Select.Content>
</Select.Root>
<Select.Root
size="1"
value={(state[key] as RadixColor).variant}
onValueChange={(value) => {
mutateState((draft) => {
(draft[key] as RadixColor).variant =
value as RadixColorVariant;
});
}}
>
<Select.Trigger />
<Select.Content>
{times(12, (index) => {
const variant =
`${index + 1}` as RadixColorVariantRaw;
return (
<>
<Select.Item key={key} value={variant}>
<Box
style={{
display: 'inline-block',
width: '1em',
height: '1em',
verticalAlign: 'text-top',
borderRadius: toRadiusVar('full'),
backgroundColor: toColorVar({
base: (state[key] as RadixColor).base,
variant,
}),
}}
/>{' '}
{variant}
</Select.Item>
<Select.Item key={key} value={`a${variant}`}>
<Box
style={{
display: 'inline-block',
overflow: 'hidden',
width: '1em',
height: '1em',
verticalAlign: 'text-top',
borderRadius: toRadiusVar('full'),
backgroundColor: toColorVar({
base: (state[key] as RadixColor).base,
variant: `a${variant}`,
}),
}}
>
<Box
style={{
width: '50%',
height: '100%',
backgroundColor: toColorVar({
base: (state[key] as RadixColor).base,
variant,
}),
}}
/>
</Box>{' '}
{variant} translucent
</Select.Item>
</>
);
})}
</Select.Content>
</Select.Root>
</>
);
break;
}
case EmbedConfigType.TextColor: {
control = (
<Select.Root
size="1"
value={`${state[key]}`}
onValueChange={(value) => {
mutateState((draft) => {
draft[key] = value as RadixTextColor;
});
}}
>
<Select.Trigger />
<Select.Content>
<Select.Item value="undefined">
<Box
style={{
display: 'inline-block',
width: '1em',
height: '1em',
verticalAlign: 'text-top',
borderRadius: toRadiusVar('full'),
backgroundColor: 'white',
}}
/>{' '}
White
</Select.Item>
<Select.Item value="gray">
<Box
style={{
display: 'inline-block',
width: '1em',
height: '1em',
verticalAlign: 'text-top',
borderRadius: toRadiusVar('full'),
backgroundColor: 'gray',
}}
/>{' '}
Gray
</Select.Item>
{Object.entries(radixColors)
.filter(
([key]) =>
!key.endsWith('A') &&
!key.endsWith('P3') &&
!key.endsWith('Dark') &&
!radixGrays.includes(key),
)
.map(([key]) => (
<Select.Item key={key} value={key}>
<Box
style={{
display: 'inline-block',
width: '1em',
height: '1em',
verticalAlign: 'text-top',
borderRadius: toRadiusVar('full'),
backgroundColor: toColorVar({
base: key as RadixColorBase,
variant: '9',
}),
}}
/>{' '}
{capitalize(key)}
</Select.Item>
))}
</Select.Content>
</Select.Root>
);
break;
}
}
return (
<Flex
direction="column"
gap="1"
mb="1"
pb={setting.pad ? '6' : undefined}
>
<Text color="gray" size="2">
{capitalize(startCase(key))}
</Text>
{control && <Flex gap="1">{control}</Flex>}
{!control && (
<Text color="red" size="1">
Immutable
</Text>
)}
</Flex> </Flex>
); );
}
}
})} })}
</Flex> </Flex>
</ScrollArea>
</Card>
); );
} }
+37 -6
View File
@@ -1,6 +1,12 @@
import { Flex } from '@radix-ui/themes'; import { Flex, Text } from '@radix-ui/themes';
import { times } from 'lodash';
import { ReactNode } from 'react'; import { ReactNode } from 'react';
import { TanksEmbedState } from '../app/tools/embed/customize/tanks/page'; import { TanksEmbedState } from '../app/tools/embed/customize/tanks/page';
import {
toColorVar,
toRadiusVar,
toWidthVar,
} from '../app/tools/embed/utilities';
import { TankDefinition } from '../core/blitzkit/tankDefinitions'; import { TankDefinition } from '../core/blitzkit/tankDefinitions';
interface TanksEmbedWrapperProps { interface TanksEmbedWrapperProps {
@@ -10,7 +16,11 @@ interface TanksEmbedWrapperProps {
export function TanksEmbedWrapper({ children, state }: TanksEmbedWrapperProps) { export function TanksEmbedWrapper({ children, state }: TanksEmbedWrapperProps) {
return ( return (
<Flex direction="column" gap={state.listGap} width={`${state.listWidth}px`}> <Flex
direction="column"
gap={state.listGap}
width={toWidthVar(state.listWidth)}
>
{children} {children}
</Flex> </Flex>
); );
@@ -26,21 +36,42 @@ export function TanksEmbedCard({ tank, state }: TanksEmbedCardProps) {
<Flex <Flex
direction="column" direction="column"
style={{ style={{
borderRadius: `var(--radius-${state.cardRadius})`, borderRadius: toRadiusVar(state.cardRadius),
overflow: 'hidden', overflow: 'hidden',
backgroundColor: 'var(--color-panel-solid)', backgroundColor: toColorVar(state.cardBodyBackgroundColor),
}} }}
> >
<Flex <Flex
justify="center" justify="center"
style={{ style={{
backgroundColor: 'var(--color-panel-translucent)', backgroundColor: toColorVar(state.cardHeaderBackgroundColor),
}} }}
p={state.cardHeaderPadding} p={state.cardHeaderPadding}
> >
{tank.name} {tank.name}
</Flex> </Flex>
<Flex>asd</Flex>
<Flex p={state.cardBodyPadding}>
{times(4, (index) => {
const column = (index + 1) as 1 | 2 | 3 | 4;
return (
<Flex
direction="column"
align="center"
gap={state.columnGap}
flexGrow="1"
>
<Text size={state.columnValueSize} color={state.columnValueColor}>
70{state[`column${column}Unit`]}
</Text>
<Text size={state.columnLabelSize} color={state.columnLabelColor}>
{state[`column${column}Label`]}
</Text>
</Flex>
);
})}
</Flex>
</Flex> </Flex>
); );
} }