feat: new dynamic system

This commit is contained in:
TrèsAbhi committed 2024-08-08 14:18:22 -05:00
1 parent e8b8b19332
commit 94623c43eb
17 files changed
+895 -832

No files matched your search

-598
View File
@@ -1,598 +0,0 @@
import * as radixColors from '@radix-ui/colors';
import { ImageIcon } from '@radix-ui/react-icons';
import {
Box,
Button,
Flex,
Heading,
ScrollArea,
Select,
Slider,
Switch,
Text,
TextField,
} from '@radix-ui/themes';
import { Immutable, produce } from 'immer';
import { capitalize, startCase, times } from 'lodash';
import { ReactNode, useCallback, useEffect, useRef, useState } from 'react';
import {
EmbedConfig,
EmbedConfigItemType,
EmbedConfigType,
ExtractEmbedConfigType,
RadixColor,
RadixColorBase,
RadixColorVariant,
RadixColorVariantRaw,
radixGrays,
RadixRadius,
RadixSize,
RadixSizeNo0,
RadixTextColor,
RadixTextWeight,
radixTextWeights,
} from '../app/tools/embed/types';
import {
extractEmbedConfigDefaults,
toColorVar,
toRadiusVar,
} from '../app/tools/embed/utilities';
import { imgur } from '../core/blitzkit/imgur';
import { NAVBAR_HEIGHT } from './Navbar';
import PageWrapper from './PageWrapper';
interface EmbedConfigInputsProps<Config extends EmbedConfig> {
config: Config;
preview: (state: ExtractEmbedConfigType<Config>) => ReactNode;
}
export function EmbedCustomize<Config extends EmbedConfig>({
config,
preview,
}: EmbedConfigInputsProps<Config>) {
const [state, setState] = useState(extractEmbedConfigDefaults(config));
const [backgroundImage, setBackgroundImage] = useState(imgur('SO13zur'));
const mutateState = useCallback(
(recipe: (draft: Immutable<ExtractEmbedConfigType<Config>>) => void) => {
setState(produce(recipe));
},
[],
);
const fileInput = useRef<HTMLInputElement>();
useEffect(() => {
fileInput.current = document.createElement('input');
fileInput.current.type = 'file';
fileInput.current.style.display = 'none';
fileInput.current.accept = 'image/*';
fileInput.current.onchange = (event) => {
const file = (event.target as HTMLInputElement).files?.[0];
if (!file) return;
setBackgroundImage(URL.createObjectURL(file));
};
}, []);
return (
<PageWrapper padding="0" size={1028 + 640}>
<Flex>
<ScrollArea
scrollbars="vertical"
style={{ height: '100%', maxWidth: 320 }}
>
<Flex direction="column" gap="2" p="4">
<Heading mb="4">Customize</Heading>
{Object.entries(config).map(([keyUntyped, setting]) => {
const key = keyUntyped as keyof (
| Immutable<ExtractEmbedConfigType<Config>>
| ExtractEmbedConfigType<Config>
);
let control: ReactNode;
switch (setting.type) {
case EmbedConfigType.Slider: {
control = (
<Slider
value={[state[key] as number]}
min={
(
config[
key
] as EmbedConfigItemType<EmbedConfigType.Slider>
).min
}
max={
(
config[
key
] as EmbedConfigItemType<EmbedConfigType.Slider>
).max
}
onValueChange={([value]) =>
mutateState((draft) => {
(draft[key] as number) = value;
})
}
/>
);
break;
}
case EmbedConfigType.Boolean: {
control = (
<Switch
checked={state[key] as boolean}
onCheckedChange={(checked) =>
mutateState((draft) => {
(draft[key] as boolean) = checked;
})
}
/>
);
break;
}
case EmbedConfigType.FullTextControl: {
control = (
<>
<Select.Root
value={`${(state[key] as EmbedConfigItemType<EmbedConfigType.FullTextControl>['default']).color}`}
onValueChange={(value) => {
mutateState((draft) => {
(
draft[
key
] as EmbedConfigItemType<EmbedConfigType.FullTextControl>['default']
).color = 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>
<Select.Root
value={
(
state[
key
] as EmbedConfigItemType<EmbedConfigType.FullTextControl>['default']
).size
}
onValueChange={(value) => {
mutateState((draft) => {
(
draft[
key
] as EmbedConfigItemType<EmbedConfigType.FullTextControl>['default']
).size = value as RadixSizeNo0;
});
}}
>
<Select.Trigger />
<Select.Content>
{times(9, (index) => (
<Select.Item key={index} value={`${index + 1}`}>
Size {index + 1}
</Select.Item>
))}
</Select.Content>
</Select.Root>
<Select.Root
value={
(
state[
key
] as EmbedConfigItemType<EmbedConfigType.FullTextControl>['default']
).weight
}
onValueChange={(value) => {
mutateState((draft) => {
(
draft[
key
] as EmbedConfigItemType<EmbedConfigType.FullTextControl>['default']
).weight = value as RadixTextWeight;
});
}}
>
<Select.Trigger />
<Select.Content>
{radixTextWeights.map((weight) => (
<Select.Item key={weight} value={weight}>
{startCase(weight)}
</Select.Item>
))}
</Select.Content>
</Select.Root>
</>
);
break;
}
case EmbedConfigType.Number: {
control = (
<TextField.Root
style={{ width: 64 }}
type="number"
value={state[key] as number}
onChange={(event) => {
mutateState((draft) => {
(draft[key] as number) = (
event.target as HTMLInputElement
).valueAsNumber;
});
}}
>
{setting.unit && (
<TextField.Slot side="right">px</TextField.Slot>
)}
</TextField.Root>
);
break;
}
case EmbedConfigType.String: {
control = (
<TextField.Root
placeholder="Empty"
style={{ width: 64 }}
value={state[key] as string}
onChange={(event) => {
mutateState((draft) => {
(draft[key] as string) = (
event.target as HTMLInputElement
).value;
});
}}
/>
);
break;
}
case EmbedConfigType.SizeNo0:
case EmbedConfigType.Size: {
control = (
<Slider
value={[Number(state[key])]}
min={setting.type === EmbedConfigType.SizeNo0 ? 1 : 0}
max={9}
onValueChange={([value]) =>
mutateState((draft) => {
(draft[key] as RadixSize) = `${value}` as RadixSize;
})
}
/>
);
break;
}
case EmbedConfigType.Radius: {
control = (
<Slider
value={[
(state[key] as RadixRadius) === 'full'
? 5
: Number(state[key]),
]}
min={0}
max={5}
onValueChange={([value]) =>
mutateState((draft) => {
(draft[key] as RadixRadius) = (
value === 5 ? 'full' : `${value}`
) as RadixRadius;
})
}
/>
);
break;
}
case EmbedConfigType.Color: {
control = (
<>
<Select.Root
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
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 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 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
value={`${state[key]}`}
onValueChange={(value) => {
mutateState((draft) => {
(draft[key] as RadixTextColor) =
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>{capitalize(startCase(key as string))}</Text>
{control && <Flex gap="1">{control}</Flex>}
{!control && <Text color="red">Immutable</Text>}
</Flex>
);
})}
</Flex>
</ScrollArea>
<Box
p="4"
flexGrow="1"
position="sticky"
top={`${NAVBAR_HEIGHT}px`}
height={`calc(100vh - ${NAVBAR_HEIGHT}px)`}
>
<Flex
direction="column"
height="100%"
style={{
borderRadius: 'var(--radius-3)',
overflow: 'hidden',
boxShadow: 'var(--shadow-3)',
}}
>
<Flex
justify="between"
align="center"
p="2"
pl="3"
style={{
backgroundColor: 'var(--color-panel-solid)',
}}
>
<Text color="gray">Example preview</Text>
<Button
variant="outline"
color="gray"
onClick={() => fileInput.current?.click()}
>
<ImageIcon /> Upload test background
</Button>
</Flex>
<Flex
flexGrow="1"
style={{
background: `url(${backgroundImage}) center / cover no-repeat`,
}}
align="center"
justify="center"
>
{preview(state)}
</Flex>
</Flex>
</Box>
</Flex>
</PageWrapper>
);
}
+1 -1
View File
@@ -1,6 +1,6 @@
import { Flex, FlexProps, Theme } from '@radix-ui/themes';
import { ComponentProps } from 'react';
import { RadixSize } from '../app/tools/embed/types';
import { RadixSize } from '../stores/embedState/constants';
type PageWrapperProps = FlexProps & {
color?: ComponentProps<typeof Theme>['accentColor'];
+28 -28
View File
@@ -1,13 +1,10 @@
import { Flex, Text } from '@radix-ui/themes';
import { times } from 'lodash';
import { ReactNode } from 'react';
import { BreakdownEmbedState } from '../app/tools/embed/customize/breakdown/page';
import {
toColorVar,
toRadiusVar,
toWidthVar,
} from '../app/tools/embed/utilities';
import { breakdownConfig } from '../app/tools/embed/configurations/breakdown';
import { toRadiusVar } from '../app/tools/embed/utilities';
import { TankDefinition } from '../core/blitzkit/tankDefinitions';
import { useEmbedStateCurry } from '../stores/embedState/utilities';
import { ClassHeavy } from './ClassIcon/components/ClassHeavy';
import { ClassLight } from './ClassIcon/components/ClassLight';
import { ClassMedium } from './ClassIcon/components/ClassMedium';
@@ -15,18 +12,18 @@ import { ClassTankDestroyer } from './ClassIcon/components/ClassTankDestroyer';
interface BreakdownEmbedWrapperProps {
children: ReactNode;
state: BreakdownEmbedState;
}
export function BreakdownEmbedWrapper({
children,
state,
}: BreakdownEmbedWrapperProps) {
const { useState } = useEmbedStateCurry<typeof breakdownConfig>();
return (
<Flex
direction="column"
gap={state.listGap}
width={toWidthVar(state.listWidth)}
gap={useState('listGap')}
width={`${useState('listWidth')}px`}
>
{children}
</Flex>
@@ -35,30 +32,33 @@ export function BreakdownEmbedWrapper({
interface BreakdownEmbedCardProps {
tank: TankDefinition | null;
state: BreakdownEmbedState;
}
export function BreakdownEmbedCard({ tank, state }: BreakdownEmbedCardProps) {
const { color: cardTitleColor, ...restCardTitleProps } = state.cardTitle;
export function BreakdownEmbedCard({ tank }: BreakdownEmbedCardProps) {
const { useState, useRichText } =
useEmbedStateCurry<typeof breakdownConfig>();
const { color: cardTitleColor, ...restCardTitleProps } =
useState('cardTitle');
return (
<Flex
direction="column"
style={{
borderRadius: toRadiusVar(state.cardRadius),
borderRadius: toRadiusVar(useState('cardRadius')),
overflow: 'hidden',
backgroundColor: toColorVar(state.cardBodyBackgroundColor),
backgroundColor: useState('cardBodyBackgroundColor'),
}}
>
<Flex
justify="center"
style={{
backgroundColor: toColorVar(state.cardHeaderBackgroundColor),
backgroundColor: useState('cardHeaderBackgroundColor'),
}}
p={state.cardHeaderPadding}
p={useState('cardHeaderPadding')}
>
<Text
color={
state.cardTitleTypeColor
style={{
color: useState('cardTitleTypeColor')
? tank === null
? cardTitleColor
: tank.treeType === 'collector'
@@ -66,12 +66,12 @@ export function BreakdownEmbedCard({ tank, state }: BreakdownEmbedCardProps) {
: tank.treeType === 'premium'
? 'amber'
: cardTitleColor
: cardTitleColor
}
: cardTitleColor,
}}
{...restCardTitleProps}
>
<Flex align="center" gap="1">
{tank !== null && state.cardTitleClassIcon && (
{useState('cardTitleClassIcon') && tank !== null && (
<>
{tank.class === 'lightTank' && (
<ClassLight width="1em" height="1em" />
@@ -92,7 +92,7 @@ export function BreakdownEmbedCard({ tank, state }: BreakdownEmbedCardProps) {
</Text>
</Flex>
<Flex p={state.cardBodyPadding}>
<Flex p={useState('cardBodyPadding')}>
{times(4, (index) => {
const column = (index + 1) as 1 | 2 | 3 | 4;
@@ -100,14 +100,14 @@ export function BreakdownEmbedCard({ tank, state }: BreakdownEmbedCardProps) {
<Flex
direction="column"
align="center"
gap={state.columnGap}
gap={useState('columnGap')}
flexGrow="1"
>
<Text {...state.columnValue}>
70{state[`column${column}Unit`]}
<Text {...useRichText('columnValue')}>
70{useState(`column${column}Unit`)}
</Text>
<Text {...state.columnLabel}>
{state[`column${column}Label`]}
<Text {...useRichText('columnLabel')}>
{useState(`column${column}Label`)}
</Text>
</Flex>
);