mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-10-30 22:19:50 +01:00
feat: new dynamic system
This commit is contained in:
1 parent
e8b8b19332
commit
94623c43eb
17 files changed
+895
-832
No files matched your search
@@ -0,0 +1,86 @@
|
|||||||
|
import { grayA, grayDark, grayDarkA } from '@radix-ui/colors';
|
||||||
|
import { use } from 'react';
|
||||||
|
import {
|
||||||
|
BreakdownEmbedCard,
|
||||||
|
BreakdownEmbedWrapper,
|
||||||
|
} from '../../../../components/TanksEmbed';
|
||||||
|
import { tankDefinitionsArray } from '../../../../core/blitzkit/tankDefinitions';
|
||||||
|
import { EmbedConfig } from '../../../../stores/embedState';
|
||||||
|
import { EmbedItemType } from '../../../../stores/embedState/constants';
|
||||||
|
import { useEmbedStateCurry } from '../../../../stores/embedState/utilities';
|
||||||
|
|
||||||
|
export const breakdownConfig = {
|
||||||
|
showTotal: { type: EmbedItemType.Boolean, default: true, pad: true },
|
||||||
|
|
||||||
|
listWidth: {
|
||||||
|
type: EmbedItemType.Slider,
|
||||||
|
default: 320,
|
||||||
|
min: 128,
|
||||||
|
max: 640,
|
||||||
|
},
|
||||||
|
listGap: { type: EmbedItemType.Size, default: '2' },
|
||||||
|
listMaxTanks: {
|
||||||
|
type: EmbedItemType.Slider,
|
||||||
|
default: 4,
|
||||||
|
min: 0,
|
||||||
|
max: 8,
|
||||||
|
pad: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
cardRadius: { type: EmbedItemType.Radius, default: '2' },
|
||||||
|
cardHeaderBackgroundColor: {
|
||||||
|
type: EmbedItemType.Color,
|
||||||
|
default: grayDarkA.grayA3,
|
||||||
|
},
|
||||||
|
cardHeaderPadding: { type: EmbedItemType.Size, default: '1' },
|
||||||
|
cardTitle: {
|
||||||
|
type: EmbedItemType.RichText,
|
||||||
|
default: { color: grayDark.gray12, weight: 'bold', size: '3' },
|
||||||
|
},
|
||||||
|
cardTitleTypeColor: { type: EmbedItemType.Boolean, default: true },
|
||||||
|
cardTitleClassIcon: { type: EmbedItemType.Boolean, default: true },
|
||||||
|
cardBodyBackgroundColor: {
|
||||||
|
type: EmbedItemType.Color,
|
||||||
|
default: grayA.grayA11,
|
||||||
|
},
|
||||||
|
cardBodyPadding: {
|
||||||
|
type: EmbedItemType.Size,
|
||||||
|
default: '2',
|
||||||
|
pad: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
columnGap: { type: EmbedItemType.Size, default: '0' },
|
||||||
|
columnValue: {
|
||||||
|
type: EmbedItemType.RichText,
|
||||||
|
default: { color: grayDark.gray12, size: '3', weight: 'regular' },
|
||||||
|
},
|
||||||
|
columnLabel: {
|
||||||
|
type: EmbedItemType.RichText,
|
||||||
|
default: { color: 'gray', size: '2', weight: 'regular' },
|
||||||
|
pad: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
column1Unit: { type: EmbedItemType.String, default: '' },
|
||||||
|
column2Unit: { type: EmbedItemType.String, default: '%' },
|
||||||
|
column3Unit: { type: EmbedItemType.String, default: '' },
|
||||||
|
column4Unit: { type: EmbedItemType.String, default: '' },
|
||||||
|
column1Label: { type: EmbedItemType.String, default: 'Battles' },
|
||||||
|
column2Label: { type: EmbedItemType.String, default: 'Winrate' },
|
||||||
|
column3Label: { type: EmbedItemType.String, default: 'WN8' },
|
||||||
|
column4Label: { type: EmbedItemType.String, default: 'Damage' },
|
||||||
|
} satisfies EmbedConfig;
|
||||||
|
|
||||||
|
export function BreakdownPreview() {
|
||||||
|
const tanks = use(tankDefinitionsArray);
|
||||||
|
const { useState } = useEmbedStateCurry<typeof breakdownConfig>();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<BreakdownEmbedWrapper>
|
||||||
|
{useState('showTotal') && <BreakdownEmbedCard tank={null} />}
|
||||||
|
|
||||||
|
{tanks.slice(0, useState('listMaxTanks')).map((tank) => (
|
||||||
|
<BreakdownEmbedCard key={tank.id} tank={tank} />
|
||||||
|
))}
|
||||||
|
</BreakdownEmbedWrapper>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
import { ReactNode } from 'react';
|
||||||
|
import { EmbedConfig } from '../../../../stores/embedState';
|
||||||
|
import { BreakdownPreview, breakdownConfig } from './breakdown';
|
||||||
|
|
||||||
|
export const configurations = {
|
||||||
|
breakdown: breakdownConfig,
|
||||||
|
} satisfies Record<string, EmbedConfig>;
|
||||||
|
|
||||||
|
export const previews = {
|
||||||
|
breakdown: BreakdownPreview,
|
||||||
|
} satisfies Record<string, () => ReactNode>;
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
import { Switch } from '@radix-ui/themes';
|
||||||
|
import * as EmbedState from '../../../../../../stores/embedState';
|
||||||
|
import { EmbedPreviewControllerProps } from '../page';
|
||||||
|
|
||||||
|
export function Boolean({ configKey }: EmbedPreviewControllerProps) {
|
||||||
|
const mutateEmbedState = EmbedState.useMutation();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Switch
|
||||||
|
checked={EmbedState.use((state) => state[configKey] as boolean)}
|
||||||
|
onCheckedChange={() => {
|
||||||
|
mutateEmbedState((draft) => {
|
||||||
|
draft[configKey] = !draft[configKey];
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
import { Slider } from '@radix-ui/themes';
|
||||||
|
import { useRef } from 'react';
|
||||||
|
import * as EmbedState from '../../../../../../stores/embedState';
|
||||||
|
import { EmbedPreviewControllerProps } from '../page';
|
||||||
|
|
||||||
|
export function Color({ configKey }: EmbedPreviewControllerProps) {
|
||||||
|
const mutateEmbedState = EmbedState.useMutation();
|
||||||
|
const embedStore = EmbedState.useStore();
|
||||||
|
const input = useRef<HTMLInputElement>(null);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<input
|
||||||
|
ref={input}
|
||||||
|
type="color"
|
||||||
|
value={EmbedState.use((state) =>
|
||||||
|
(state[configKey] as string).slice(0, 7),
|
||||||
|
)}
|
||||||
|
onChange={(event) => {
|
||||||
|
mutateEmbedState((draft) => {
|
||||||
|
let alpha = (draft[configKey] as string).slice(7, 9);
|
||||||
|
if (alpha.length === 0) alpha = 'ff';
|
||||||
|
draft[configKey] = `${event.target.value}${alpha}`;
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Slider
|
||||||
|
min={0}
|
||||||
|
max={255}
|
||||||
|
value={[
|
||||||
|
EmbedState.use((state) => {
|
||||||
|
let alpha = (state[configKey] as string).slice(7, 9);
|
||||||
|
if (alpha.length === 0) return 255;
|
||||||
|
return parseInt(alpha, 16);
|
||||||
|
}),
|
||||||
|
]}
|
||||||
|
onValueChange={([value]) => {
|
||||||
|
mutateEmbedState((draft) => {
|
||||||
|
if (!input.current) return;
|
||||||
|
|
||||||
|
const color = input.current.value;
|
||||||
|
draft[configKey] = `${color}${value.toString(16)}`;
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
import { useMemo } from 'react';
|
||||||
|
import * as EmbedState from '../../../../../stores/embedState';
|
||||||
|
import { configurations } from '../../configurations';
|
||||||
|
import { extractEmbedConfigDefaults } from '../../utilities';
|
||||||
|
|
||||||
|
export default function Layout({
|
||||||
|
params,
|
||||||
|
children,
|
||||||
|
}: {
|
||||||
|
params: { embed: keyof typeof configurations };
|
||||||
|
children: React.ReactNode;
|
||||||
|
}) {
|
||||||
|
const config = configurations[params.embed];
|
||||||
|
const initialState = useMemo(
|
||||||
|
() => extractEmbedConfigDefaults(config),
|
||||||
|
[params.embed],
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<EmbedState.Provider data={initialState}>{children}</EmbedState.Provider>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,524 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { ImageIcon } from '@radix-ui/react-icons';
|
||||||
|
import { Box, Button, Flex, Heading, ScrollArea, Text } from '@radix-ui/themes';
|
||||||
|
import { capitalize, startCase } from 'lodash';
|
||||||
|
import { ReactNode, useEffect, useRef, useState } from 'react';
|
||||||
|
import { NAVBAR_HEIGHT } from '../../../../../components/Navbar';
|
||||||
|
import PageWrapper from '../../../../../components/PageWrapper';
|
||||||
|
import { imgur } from '../../../../../core/blitzkit/imgur';
|
||||||
|
import { EmbedConfig } from '../../../../../stores/embedState';
|
||||||
|
import { EmbedItemType } from '../../../../../stores/embedState/constants';
|
||||||
|
import { configurations, previews } from '../../configurations';
|
||||||
|
import { Boolean } from './components/Boolean';
|
||||||
|
import { Color } from './components/Color';
|
||||||
|
|
||||||
|
export interface EmbedPreviewControllerProps {
|
||||||
|
configKey: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function Page({
|
||||||
|
params,
|
||||||
|
}: {
|
||||||
|
params: { embed: keyof typeof configurations };
|
||||||
|
}) {
|
||||||
|
const config = configurations[params.embed] as EmbedConfig;
|
||||||
|
const Preview = previews[params.embed];
|
||||||
|
const [backgroundImage, setBackgroundImage] = useState(imgur('SO13zur'));
|
||||||
|
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.keys(config).map((configKey) => {
|
||||||
|
const item = config[configKey];
|
||||||
|
let control: ReactNode;
|
||||||
|
let oneLiner = false;
|
||||||
|
|
||||||
|
switch (item.type) {
|
||||||
|
case EmbedItemType.Boolean: {
|
||||||
|
control = <Boolean configKey={configKey} />;
|
||||||
|
oneLiner = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case EmbedItemType.Color: {
|
||||||
|
control = <Color configKey={configKey} />;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Flex
|
||||||
|
direction={oneLiner ? undefined : 'column'}
|
||||||
|
gap={oneLiner ? '2' : '1'}
|
||||||
|
justify={oneLiner ? 'between' : undefined}
|
||||||
|
mb="2"
|
||||||
|
pb={item.pad ? '6' : undefined}
|
||||||
|
>
|
||||||
|
<Text>{capitalize(startCase(configKey as string))}</Text>
|
||||||
|
<Flex gap="2" align="center">
|
||||||
|
{control}
|
||||||
|
</Flex>
|
||||||
|
</Flex>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
|
||||||
|
{/* {Object.entries(config).map(([configKeyRaw, configItem]) => {
|
||||||
|
const configKey = configKeyRaw as keyof (
|
||||||
|
| Immutable<ExtractEmbedConfigTypes<Config>>
|
||||||
|
| ExtractEmbedConfigTypes<Config>
|
||||||
|
);
|
||||||
|
const stateItem = state[configKey];
|
||||||
|
let control: ReactNode;
|
||||||
|
|
||||||
|
if (configItem.type === EmbedItemType.Slider) {
|
||||||
|
control = (
|
||||||
|
<Slider
|
||||||
|
state={
|
||||||
|
stateItem as EmbedConfigItemStateType<EmbedItemType.Slider>
|
||||||
|
}
|
||||||
|
config={configItem as EmbedItemType<EmbedItemType.Slider>}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// switch (configItem.type) {
|
||||||
|
// case EmbedConfigType.Slider: {
|
||||||
|
// control = (
|
||||||
|
|
||||||
|
// );
|
||||||
|
// break;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// case EmbedConfigType.Boolean: {
|
||||||
|
// control = (
|
||||||
|
// <Switch
|
||||||
|
// checked={state[configKey] as boolean}
|
||||||
|
// onCheckedChange={(checked) =>
|
||||||
|
// mutateState((draft) => {
|
||||||
|
// (draft[configKey] as boolean) = checked;
|
||||||
|
// })
|
||||||
|
// }
|
||||||
|
// />
|
||||||
|
// );
|
||||||
|
// break;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// case EmbedConfigType.RichText: {
|
||||||
|
// control = (
|
||||||
|
// <>
|
||||||
|
// <Select.Root
|
||||||
|
// value={`${(state[configKey] as EmbedConfigItemType<EmbedConfigType.RichText>['default']).color}`}
|
||||||
|
// onValueChange={(value) => {
|
||||||
|
// mutateState((draft) => {
|
||||||
|
// (
|
||||||
|
// draft[
|
||||||
|
// configKey
|
||||||
|
// ] as EmbedConfigItemType<EmbedConfigType.RichText>['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[
|
||||||
|
// configKey
|
||||||
|
// ] as EmbedConfigItemType<EmbedConfigType.RichText>['default']
|
||||||
|
// ).size
|
||||||
|
// }
|
||||||
|
// onValueChange={(value) => {
|
||||||
|
// mutateState((draft) => {
|
||||||
|
// (
|
||||||
|
// draft[
|
||||||
|
// configKey
|
||||||
|
// ] as EmbedConfigItemType<EmbedConfigType.RichText>['default']
|
||||||
|
// ).size = value as RadixSizeWithout0;
|
||||||
|
// });
|
||||||
|
// }}
|
||||||
|
// >
|
||||||
|
// <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[
|
||||||
|
// configKey
|
||||||
|
// ] as EmbedConfigItemType<EmbedConfigType.RichText>['default']
|
||||||
|
// ).weight
|
||||||
|
// }
|
||||||
|
// onValueChange={(value) => {
|
||||||
|
// mutateState((draft) => {
|
||||||
|
// (
|
||||||
|
// draft[
|
||||||
|
// configKey
|
||||||
|
// ] as EmbedConfigItemType<EmbedConfigType.RichText>['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[configKey] as number}
|
||||||
|
// onChange={(event) => {
|
||||||
|
// mutateState((draft) => {
|
||||||
|
// (draft[configKey] as number) = (
|
||||||
|
// event.target as HTMLInputElement
|
||||||
|
// ).valueAsNumber;
|
||||||
|
// });
|
||||||
|
// }}
|
||||||
|
// >
|
||||||
|
// {configItem.unit && (
|
||||||
|
// <TextField.Slot side="right">px</TextField.Slot>
|
||||||
|
// )}
|
||||||
|
// </TextField.Root>
|
||||||
|
// );
|
||||||
|
// break;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// case EmbedConfigType.String: {
|
||||||
|
// control = (
|
||||||
|
// <TextField.Root
|
||||||
|
// placeholder="Empty"
|
||||||
|
// style={{ width: 64 }}
|
||||||
|
// value={state[configKey] as string}
|
||||||
|
// onChange={(event) => {
|
||||||
|
// mutateState((draft) => {
|
||||||
|
// (draft[configKey] as string) = (
|
||||||
|
// event.target as HTMLInputElement
|
||||||
|
// ).value;
|
||||||
|
// });
|
||||||
|
// }}
|
||||||
|
// />
|
||||||
|
// );
|
||||||
|
// break;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// case EmbedConfigType.SizeWithout0:
|
||||||
|
// case EmbedConfigType.Size: {
|
||||||
|
// control = (
|
||||||
|
// <Slider
|
||||||
|
// value={[Number(state[configKey])]}
|
||||||
|
// min={
|
||||||
|
// configItem.type === EmbedConfigType.SizeWithout0 ? 1 : 0
|
||||||
|
// }
|
||||||
|
// max={9}
|
||||||
|
// onValueChange={([value]) =>
|
||||||
|
// mutateState((draft) => {
|
||||||
|
// (draft[configKey] as RadixSize) =
|
||||||
|
// `${value}` as RadixSize;
|
||||||
|
// })
|
||||||
|
// }
|
||||||
|
// />
|
||||||
|
// );
|
||||||
|
// break;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// case EmbedConfigType.Radius: {
|
||||||
|
// control = (
|
||||||
|
// <Slider
|
||||||
|
// value={[
|
||||||
|
// (state[configKey] as RadixRadius) === 'full'
|
||||||
|
// ? 5
|
||||||
|
// : Number(state[configKey]),
|
||||||
|
// ]}
|
||||||
|
// min={0}
|
||||||
|
// max={5}
|
||||||
|
// onValueChange={([value]) =>
|
||||||
|
// mutateState((draft) => {
|
||||||
|
// (draft[configKey] as RadixRadius) = (
|
||||||
|
// value === 5 ? 'full' : `${value}`
|
||||||
|
// ) as RadixRadius;
|
||||||
|
// })
|
||||||
|
// }
|
||||||
|
// />
|
||||||
|
// );
|
||||||
|
// break;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// case EmbedConfigType.Color: {
|
||||||
|
// control = (
|
||||||
|
// <>
|
||||||
|
// <Select.Root
|
||||||
|
// value={(state[configKey] as RadixColor).base}
|
||||||
|
// onValueChange={(value) => {
|
||||||
|
// mutateState((draft) => {
|
||||||
|
// (draft[configKey] 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[configKey] as RadixColor).variant}
|
||||||
|
// onValueChange={(value) => {
|
||||||
|
// mutateState((draft) => {
|
||||||
|
// (draft[configKey] 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[configKey] 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[configKey] as RadixColor)
|
||||||
|
// .base,
|
||||||
|
// variant: `a${variant}`,
|
||||||
|
// }),
|
||||||
|
// }}
|
||||||
|
// >
|
||||||
|
// <Box
|
||||||
|
// style={{
|
||||||
|
// width: '50%',
|
||||||
|
// height: '100%',
|
||||||
|
// backgroundColor: toColorVar({
|
||||||
|
// base: (state[configKey] as RadixColor)
|
||||||
|
// .base,
|
||||||
|
// variant,
|
||||||
|
// }),
|
||||||
|
// }}
|
||||||
|
// />
|
||||||
|
// </Box>{' '}
|
||||||
|
// {variant} translucent
|
||||||
|
// </Select.Item>
|
||||||
|
// </>
|
||||||
|
// );
|
||||||
|
// })}
|
||||||
|
// </Select.Content>
|
||||||
|
// </Select.Root>
|
||||||
|
// </>
|
||||||
|
// );
|
||||||
|
// break;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Flex
|
||||||
|
direction="column"
|
||||||
|
gap="1"
|
||||||
|
mb="1"
|
||||||
|
pb={configItem.pad ? '6' : undefined}
|
||||||
|
>
|
||||||
|
<Text>{capitalize(startCase(configKey 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 />
|
||||||
|
</Flex>
|
||||||
|
</Flex>
|
||||||
|
</Box>
|
||||||
|
</Flex>
|
||||||
|
</PageWrapper>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,89 +0,0 @@
|
|||||||
'use client';
|
|
||||||
|
|
||||||
import { use } from 'react';
|
|
||||||
import { EmbedCustomize } from '../../../../../components/EmbedCustomize';
|
|
||||||
import {
|
|
||||||
BreakdownEmbedCard,
|
|
||||||
BreakdownEmbedWrapper,
|
|
||||||
} from '../../../../../components/TanksEmbed';
|
|
||||||
import { tanksDefinitionsArray } from '../../../../../core/blitzkit/tankDefinitions';
|
|
||||||
import {
|
|
||||||
EmbedConfig,
|
|
||||||
EmbedConfigType,
|
|
||||||
ExtractEmbedConfigType,
|
|
||||||
} from '../../types';
|
|
||||||
|
|
||||||
const breakdownEmbedConfig = {
|
|
||||||
showTotal: { type: EmbedConfigType.Boolean, default: true },
|
|
||||||
|
|
||||||
listWidth: { type: EmbedConfigType.Slider, default: 320, min: 128, max: 640 },
|
|
||||||
listGap: { type: EmbedConfigType.Size, default: '2' },
|
|
||||||
listMaxTanks: {
|
|
||||||
type: EmbedConfigType.Slider,
|
|
||||||
default: 4,
|
|
||||||
min: 0,
|
|
||||||
max: 8,
|
|
||||||
pad: true,
|
|
||||||
},
|
|
||||||
|
|
||||||
cardRadius: { type: EmbedConfigType.Radius, default: '2' },
|
|
||||||
cardHeaderBackgroundColor: {
|
|
||||||
type: EmbedConfigType.Color,
|
|
||||||
default: { base: 'gray', variant: '3' },
|
|
||||||
},
|
|
||||||
cardHeaderPadding: { type: EmbedConfigType.Size, default: '1' },
|
|
||||||
cardTitle: {
|
|
||||||
type: EmbedConfigType.FullTextControl,
|
|
||||||
default: { color: undefined, weight: 'bold', size: '3' },
|
|
||||||
},
|
|
||||||
cardTitleTypeColor: { type: EmbedConfigType.Boolean, default: true },
|
|
||||||
cardTitleClassIcon: { type: EmbedConfigType.Boolean, default: true },
|
|
||||||
cardBodyBackgroundColor: {
|
|
||||||
type: EmbedConfigType.Color,
|
|
||||||
default: { base: 'gray', variant: '2' },
|
|
||||||
},
|
|
||||||
cardBodyPadding: { type: EmbedConfigType.Size, default: '2', pad: true },
|
|
||||||
|
|
||||||
columnGap: { type: EmbedConfigType.Size, default: '0' },
|
|
||||||
columnValue: {
|
|
||||||
type: EmbedConfigType.FullTextControl,
|
|
||||||
default: { color: undefined, size: '3', weight: 'regular' },
|
|
||||||
},
|
|
||||||
columnLabel: {
|
|
||||||
type: EmbedConfigType.FullTextControl,
|
|
||||||
default: { color: 'gray', size: '2', weight: 'regular' },
|
|
||||||
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;
|
|
||||||
|
|
||||||
export type BreakdownEmbedState = ExtractEmbedConfigType<
|
|
||||||
typeof breakdownEmbedConfig
|
|
||||||
>;
|
|
||||||
|
|
||||||
export default function Page() {
|
|
||||||
const tanks = use(tanksDefinitionsArray);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<EmbedCustomize
|
|
||||||
config={breakdownEmbedConfig}
|
|
||||||
preview={(state) => (
|
|
||||||
<BreakdownEmbedWrapper state={state}>
|
|
||||||
{state.showTotal && <BreakdownEmbedCard state={state} tank={null} />}
|
|
||||||
|
|
||||||
{tanks.slice(0, state.listMaxTanks).map((tank) => (
|
|
||||||
<BreakdownEmbedCard key={tank.id} tank={tank} state={state} />
|
|
||||||
))}
|
|
||||||
</BreakdownEmbedWrapper>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,106 +0,0 @@
|
|||||||
import * as radixColors from '@radix-ui/colors';
|
|
||||||
|
|
||||||
export enum EmbedConfigType {
|
|
||||||
Number,
|
|
||||||
Radius,
|
|
||||||
Size,
|
|
||||||
Color,
|
|
||||||
SizeNo0,
|
|
||||||
TextColor,
|
|
||||||
String,
|
|
||||||
FullTextControl,
|
|
||||||
Boolean,
|
|
||||||
Slider,
|
|
||||||
}
|
|
||||||
|
|
||||||
export const radixGrays = [
|
|
||||||
'gray',
|
|
||||||
'mauve',
|
|
||||||
'slate',
|
|
||||||
'sage',
|
|
||||||
'olive',
|
|
||||||
'sand',
|
|
||||||
] as const;
|
|
||||||
|
|
||||||
export const radixTextWeights = ['light', 'regular', 'medium', 'bold'] as const;
|
|
||||||
|
|
||||||
export type EmbedConfig = Record<string, EmbedConfigItem>;
|
|
||||||
|
|
||||||
export type EmbedConfigItem = (
|
|
||||||
| {
|
|
||||||
type: EmbedConfigType.Slider;
|
|
||||||
default: number;
|
|
||||||
min: number;
|
|
||||||
max: number;
|
|
||||||
}
|
|
||||||
| {
|
|
||||||
type: EmbedConfigType.Boolean;
|
|
||||||
default: boolean;
|
|
||||||
}
|
|
||||||
| {
|
|
||||||
type: EmbedConfigType.Number;
|
|
||||||
default: number;
|
|
||||||
unit?: string;
|
|
||||||
}
|
|
||||||
| {
|
|
||||||
type: EmbedConfigType.String;
|
|
||||||
default: string;
|
|
||||||
}
|
|
||||||
| {
|
|
||||||
type: EmbedConfigType.Radius;
|
|
||||||
default: RadixRadius;
|
|
||||||
}
|
|
||||||
| {
|
|
||||||
type: EmbedConfigType.Size;
|
|
||||||
default: RadixSize;
|
|
||||||
}
|
|
||||||
| {
|
|
||||||
type: EmbedConfigType.SizeNo0;
|
|
||||||
default: RadixSizeNo0;
|
|
||||||
}
|
|
||||||
| {
|
|
||||||
type: EmbedConfigType.Color;
|
|
||||||
default: RadixColor;
|
|
||||||
}
|
|
||||||
| {
|
|
||||||
type: EmbedConfigType.TextColor;
|
|
||||||
default: RadixTextColor;
|
|
||||||
}
|
|
||||||
| {
|
|
||||||
type: EmbedConfigType.FullTextControl;
|
|
||||||
default: {
|
|
||||||
color: RadixTextColor;
|
|
||||||
size: RadixSizeNo0;
|
|
||||||
weight: RadixTextWeight;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
) & {
|
|
||||||
pad?: boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type RadixRadius = `${1 | 2 | 3 | 4}` | 'full';
|
|
||||||
export type RadixSize = `${0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9}`;
|
|
||||||
export type RadixTextWeight = (typeof radixTextWeights)[number];
|
|
||||||
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> = {
|
|
||||||
[Key in keyof Config]: ({
|
|
||||||
type: Config[Key]['type'];
|
|
||||||
} & EmbedConfigItem)['default'];
|
|
||||||
};
|
|
||||||
|
|
||||||
export type EmbedConfigItemType<Type extends EmbedConfigType> =
|
|
||||||
(EmbedConfigItem & { type: Type })
|
|
||||||
@@ -1,16 +1,16 @@
|
|||||||
import {
|
import {
|
||||||
EmbedConfig,
|
EmbedConfig,
|
||||||
ExtractEmbedConfigType,
|
ExtractEmbedConfigTypes,
|
||||||
RadixColor,
|
RadixColorCompound,
|
||||||
RadixRadius,
|
RadixRadius,
|
||||||
} from './types';
|
} from '../../../stores/embedState';
|
||||||
|
|
||||||
export function extractEmbedConfigDefaults<Config extends EmbedConfig>(
|
export function extractEmbedConfigDefaults<Config extends EmbedConfig>(
|
||||||
config: Config,
|
config: Config,
|
||||||
) {
|
) {
|
||||||
return Object.fromEntries(
|
return Object.fromEntries(
|
||||||
Object.entries(config).map(([key, value]) => [key, value.default]),
|
Object.entries(config).map(([key, value]) => [key, value.default]),
|
||||||
) as ExtractEmbedConfigType<Config>;
|
) as ExtractEmbedConfigTypes<Config>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function toWidthVar(state: number) {
|
export function toWidthVar(state: number) {
|
||||||
@@ -21,6 +21,6 @@ export function toRadiusVar(state: RadixRadius) {
|
|||||||
return `var(--radius-${state})`;
|
return `var(--radius-${state})`;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function toColorVar(state: RadixColor) {
|
export function toColorVar(state: RadixColorCompound) {
|
||||||
return `var(--${state.base}-${state.variant})`;
|
return `var(--${state.base}-${state.variant})`;
|
||||||
}
|
}
|
||||||
@@ -17,8 +17,8 @@ import { resolveReload } from '../../../../../core/blitzkit/resolveReload';
|
|||||||
import {
|
import {
|
||||||
TankDefinition,
|
TankDefinition,
|
||||||
tankDefinitions,
|
tankDefinitions,
|
||||||
|
tankDefinitionsArray,
|
||||||
tankNames,
|
tankNames,
|
||||||
tanksDefinitionsArray,
|
|
||||||
} from '../../../../../core/blitzkit/tankDefinitions';
|
} from '../../../../../core/blitzkit/tankDefinitions';
|
||||||
import { unionBoundingBox } from '../../../../../core/blitzkit/unionBoundingBox';
|
import { unionBoundingBox } from '../../../../../core/blitzkit/unionBoundingBox';
|
||||||
import { useAdExempt } from '../../../../../hooks/useAdExempt';
|
import { useAdExempt } from '../../../../../hooks/useAdExempt';
|
||||||
@@ -51,7 +51,7 @@ export const TankSearch = memo<TankSearchProps>(
|
|||||||
const awaitedGameDefinitions = use(gameDefinitions);
|
const awaitedGameDefinitions = use(gameDefinitions);
|
||||||
const awaitedModelDefinitions = use(modelDefinitions);
|
const awaitedModelDefinitions = use(modelDefinitions);
|
||||||
const awaitedTankDefinitions = use(tankDefinitions);
|
const awaitedTankDefinitions = use(tankDefinitions);
|
||||||
const awaitedTanksDefinitionsArray = use(tanksDefinitionsArray);
|
const awaitedTanksDefinitionsArray = use(tankDefinitionsArray);
|
||||||
const awaitedTankNames = use(tankNames);
|
const awaitedTankNames = use(tankNames);
|
||||||
const filters = TankFilters.use();
|
const filters = TankFilters.use();
|
||||||
const sort = TankopediaSort.use();
|
const sort = TankopediaSort.use();
|
||||||
|
|||||||
@@ -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,6 +1,6 @@
|
|||||||
import { Flex, FlexProps, Theme } from '@radix-ui/themes';
|
import { Flex, FlexProps, Theme } from '@radix-ui/themes';
|
||||||
import { ComponentProps } from 'react';
|
import { ComponentProps } from 'react';
|
||||||
import { RadixSize } from '../app/tools/embed/types';
|
import { RadixSize } from '../stores/embedState/constants';
|
||||||
|
|
||||||
type PageWrapperProps = FlexProps & {
|
type PageWrapperProps = FlexProps & {
|
||||||
color?: ComponentProps<typeof Theme>['accentColor'];
|
color?: ComponentProps<typeof Theme>['accentColor'];
|
||||||
|
|||||||
@@ -1,13 +1,10 @@
|
|||||||
import { Flex, Text } from '@radix-ui/themes';
|
import { Flex, Text } from '@radix-ui/themes';
|
||||||
import { times } from 'lodash';
|
import { times } from 'lodash';
|
||||||
import { ReactNode } from 'react';
|
import { ReactNode } from 'react';
|
||||||
import { BreakdownEmbedState } from '../app/tools/embed/customize/breakdown/page';
|
import { breakdownConfig } from '../app/tools/embed/configurations/breakdown';
|
||||||
import {
|
import { toRadiusVar } from '../app/tools/embed/utilities';
|
||||||
toColorVar,
|
|
||||||
toRadiusVar,
|
|
||||||
toWidthVar,
|
|
||||||
} from '../app/tools/embed/utilities';
|
|
||||||
import { TankDefinition } from '../core/blitzkit/tankDefinitions';
|
import { TankDefinition } from '../core/blitzkit/tankDefinitions';
|
||||||
|
import { useEmbedStateCurry } from '../stores/embedState/utilities';
|
||||||
import { ClassHeavy } from './ClassIcon/components/ClassHeavy';
|
import { ClassHeavy } from './ClassIcon/components/ClassHeavy';
|
||||||
import { ClassLight } from './ClassIcon/components/ClassLight';
|
import { ClassLight } from './ClassIcon/components/ClassLight';
|
||||||
import { ClassMedium } from './ClassIcon/components/ClassMedium';
|
import { ClassMedium } from './ClassIcon/components/ClassMedium';
|
||||||
@@ -15,18 +12,18 @@ import { ClassTankDestroyer } from './ClassIcon/components/ClassTankDestroyer';
|
|||||||
|
|
||||||
interface BreakdownEmbedWrapperProps {
|
interface BreakdownEmbedWrapperProps {
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
state: BreakdownEmbedState;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function BreakdownEmbedWrapper({
|
export function BreakdownEmbedWrapper({
|
||||||
children,
|
children,
|
||||||
state,
|
|
||||||
}: BreakdownEmbedWrapperProps) {
|
}: BreakdownEmbedWrapperProps) {
|
||||||
|
const { useState } = useEmbedStateCurry<typeof breakdownConfig>();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Flex
|
<Flex
|
||||||
direction="column"
|
direction="column"
|
||||||
gap={state.listGap}
|
gap={useState('listGap')}
|
||||||
width={toWidthVar(state.listWidth)}
|
width={`${useState('listWidth')}px`}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</Flex>
|
</Flex>
|
||||||
@@ -35,30 +32,33 @@ export function BreakdownEmbedWrapper({
|
|||||||
|
|
||||||
interface BreakdownEmbedCardProps {
|
interface BreakdownEmbedCardProps {
|
||||||
tank: TankDefinition | null;
|
tank: TankDefinition | null;
|
||||||
state: BreakdownEmbedState;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function BreakdownEmbedCard({ tank, state }: BreakdownEmbedCardProps) {
|
export function BreakdownEmbedCard({ tank }: BreakdownEmbedCardProps) {
|
||||||
const { color: cardTitleColor, ...restCardTitleProps } = state.cardTitle;
|
const { useState, useRichText } =
|
||||||
|
useEmbedStateCurry<typeof breakdownConfig>();
|
||||||
|
const { color: cardTitleColor, ...restCardTitleProps } =
|
||||||
|
useState('cardTitle');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Flex
|
<Flex
|
||||||
direction="column"
|
direction="column"
|
||||||
style={{
|
style={{
|
||||||
borderRadius: toRadiusVar(state.cardRadius),
|
borderRadius: toRadiusVar(useState('cardRadius')),
|
||||||
overflow: 'hidden',
|
overflow: 'hidden',
|
||||||
backgroundColor: toColorVar(state.cardBodyBackgroundColor),
|
backgroundColor: useState('cardBodyBackgroundColor'),
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Flex
|
<Flex
|
||||||
justify="center"
|
justify="center"
|
||||||
style={{
|
style={{
|
||||||
backgroundColor: toColorVar(state.cardHeaderBackgroundColor),
|
backgroundColor: useState('cardHeaderBackgroundColor'),
|
||||||
}}
|
}}
|
||||||
p={state.cardHeaderPadding}
|
p={useState('cardHeaderPadding')}
|
||||||
>
|
>
|
||||||
<Text
|
<Text
|
||||||
color={
|
style={{
|
||||||
state.cardTitleTypeColor
|
color: useState('cardTitleTypeColor')
|
||||||
? tank === null
|
? tank === null
|
||||||
? cardTitleColor
|
? cardTitleColor
|
||||||
: tank.treeType === 'collector'
|
: tank.treeType === 'collector'
|
||||||
@@ -66,12 +66,12 @@ export function BreakdownEmbedCard({ tank, state }: BreakdownEmbedCardProps) {
|
|||||||
: tank.treeType === 'premium'
|
: tank.treeType === 'premium'
|
||||||
? 'amber'
|
? 'amber'
|
||||||
: cardTitleColor
|
: cardTitleColor
|
||||||
: cardTitleColor
|
: cardTitleColor,
|
||||||
}
|
}}
|
||||||
{...restCardTitleProps}
|
{...restCardTitleProps}
|
||||||
>
|
>
|
||||||
<Flex align="center" gap="1">
|
<Flex align="center" gap="1">
|
||||||
{tank !== null && state.cardTitleClassIcon && (
|
{useState('cardTitleClassIcon') && tank !== null && (
|
||||||
<>
|
<>
|
||||||
{tank.class === 'lightTank' && (
|
{tank.class === 'lightTank' && (
|
||||||
<ClassLight width="1em" height="1em" />
|
<ClassLight width="1em" height="1em" />
|
||||||
@@ -92,7 +92,7 @@ export function BreakdownEmbedCard({ tank, state }: BreakdownEmbedCardProps) {
|
|||||||
</Text>
|
</Text>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|
||||||
<Flex p={state.cardBodyPadding}>
|
<Flex p={useState('cardBodyPadding')}>
|
||||||
{times(4, (index) => {
|
{times(4, (index) => {
|
||||||
const column = (index + 1) as 1 | 2 | 3 | 4;
|
const column = (index + 1) as 1 | 2 | 3 | 4;
|
||||||
|
|
||||||
@@ -100,14 +100,14 @@ export function BreakdownEmbedCard({ tank, state }: BreakdownEmbedCardProps) {
|
|||||||
<Flex
|
<Flex
|
||||||
direction="column"
|
direction="column"
|
||||||
align="center"
|
align="center"
|
||||||
gap={state.columnGap}
|
gap={useState('columnGap')}
|
||||||
flexGrow="1"
|
flexGrow="1"
|
||||||
>
|
>
|
||||||
<Text {...state.columnValue}>
|
<Text {...useRichText('columnValue')}>
|
||||||
70{state[`column${column}Unit`]}
|
70{useState(`column${column}Unit`)}
|
||||||
</Text>
|
</Text>
|
||||||
<Text {...state.columnLabel}>
|
<Text {...useRichText('columnLabel')}>
|
||||||
{state[`column${column}Label`]}
|
{useState(`column${column}Label`)}
|
||||||
</Text>
|
</Text>
|
||||||
</Flex>
|
</Flex>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -172,13 +172,13 @@ export const tankDefinitions = fetchCdonLz4<TankDefinitions>(
|
|||||||
asset('definitions/tanks.cdon.lz4'),
|
asset('definitions/tanks.cdon.lz4'),
|
||||||
);
|
);
|
||||||
|
|
||||||
export const tanksDefinitionsArray = tankDefinitions.then((tanks) =>
|
export const tankDefinitionsArray = tankDefinitions.then((tanks) =>
|
||||||
Object.values(tanks).map((tank) => tank),
|
Object.values(tanks).map((tank) => tank),
|
||||||
);
|
);
|
||||||
export const tankNames = tanksDefinitionsArray.then((tanks) =>
|
export const tankNames = tankDefinitionsArray.then((tanks) =>
|
||||||
Promise.all(
|
Promise.all(
|
||||||
tanks.map(async (tank, index) => {
|
tanks.map(async (tank, index) => {
|
||||||
const { id } = (await tanksDefinitionsArray)[index];
|
const { id } = (await tankDefinitionsArray)[index];
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id,
|
id,
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
import { EmbedConfigItem } from '.';
|
||||||
|
|
||||||
|
export enum EmbedItemType {
|
||||||
|
Number,
|
||||||
|
Radius,
|
||||||
|
Size,
|
||||||
|
SizeWithout0,
|
||||||
|
Color,
|
||||||
|
String,
|
||||||
|
RichText,
|
||||||
|
Boolean,
|
||||||
|
Slider,
|
||||||
|
}
|
||||||
|
|
||||||
|
export const radixGrays = [
|
||||||
|
'gray',
|
||||||
|
'mauve',
|
||||||
|
'slate',
|
||||||
|
'sage',
|
||||||
|
'olive',
|
||||||
|
'sand',
|
||||||
|
] as const;
|
||||||
|
|
||||||
|
export const radixTextWeights = ['light', 'regular', 'medium', 'bold'] as const;
|
||||||
|
|
||||||
|
export type EmbedConfigItemType<Type extends EmbedItemType> =
|
||||||
|
EmbedConfigItem & { type: Type };
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import * as radixColors from '@radix-ui/colors';
|
||||||
|
import { create } from 'zustand';
|
||||||
|
import { createNextSafeStore } from '../../core/zustand/createNextSafeStore';
|
||||||
|
import { EmbedItemType, radixGrays, radixTextWeights } from './constants';
|
||||||
|
|
||||||
|
export type RadixSize = `${0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9}`;
|
||||||
|
export type RadixSizeWithout0 = Exclude<RadixSize, '0'>;
|
||||||
|
export type RadixRadius = `${1 | 2 | 3 | 4}` | 'full';
|
||||||
|
export type RadixTextWeight = (typeof radixTextWeights)[number];
|
||||||
|
export type RadixColorCompound = {
|
||||||
|
base: RadixColor;
|
||||||
|
variant: RadixColorVariant;
|
||||||
|
};
|
||||||
|
export type RadixTextColor =
|
||||||
|
| Exclude<RadixColor, 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 RadixColor = Exclude<
|
||||||
|
keyof typeof radixColors,
|
||||||
|
`${string}${'A' | 'P3' | 'Dark'}`
|
||||||
|
>;
|
||||||
|
export type RadixColorGrays = (typeof radixGrays)[number];
|
||||||
|
|
||||||
|
export type ExtractEmbedConfigTypes<Config extends EmbedConfig> = {
|
||||||
|
[Key in keyof Config]: ({
|
||||||
|
type: Config[Key]['type'];
|
||||||
|
} & EmbedConfigItem)['default'];
|
||||||
|
};
|
||||||
|
|
||||||
|
export type EmbedConfig = Record<string, EmbedConfigItem>;
|
||||||
|
|
||||||
|
export type EmbedConfigItem = (
|
||||||
|
| {
|
||||||
|
type: EmbedItemType.Slider;
|
||||||
|
default: number;
|
||||||
|
min: number;
|
||||||
|
max: number;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
type: EmbedItemType.Boolean;
|
||||||
|
default: boolean;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
type: EmbedItemType.Number;
|
||||||
|
default: number;
|
||||||
|
unit?: string;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
type: EmbedItemType.String;
|
||||||
|
default: string;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
type: EmbedItemType.Radius;
|
||||||
|
default: RadixRadius;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
type: EmbedItemType.Size;
|
||||||
|
default: RadixSize;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
type: EmbedItemType.SizeWithout0;
|
||||||
|
default: RadixSizeWithout0;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
type: EmbedItemType.Color;
|
||||||
|
default: string;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
type: EmbedItemType.RichText;
|
||||||
|
default: {
|
||||||
|
color: string;
|
||||||
|
size: RadixSizeWithout0;
|
||||||
|
weight: RadixTextWeight;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
) & {
|
||||||
|
pad?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type EmbedState = Record<string, EmbedConfigItem['default']>;
|
||||||
|
|
||||||
|
export const { Provider, use, useMutation, useStore } = createNextSafeStore(
|
||||||
|
(init: EmbedState) => create<EmbedState>()(() => init),
|
||||||
|
);
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
import { TextProps } from '@radix-ui/themes';
|
||||||
|
import { EmbedConfig } from '.';
|
||||||
|
import * as EmbedState from './';
|
||||||
|
import { EmbedConfigItemType, EmbedItemType } from './constants';
|
||||||
|
|
||||||
|
export function useEmbedStateCurry<Config extends EmbedConfig>() {
|
||||||
|
function useState<Key extends keyof Config>(key: Key) {
|
||||||
|
return EmbedState.use((state) => {
|
||||||
|
return (state as EmbedState.ExtractEmbedConfigTypes<Config>)[key];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function useRichText<
|
||||||
|
Key extends {
|
||||||
|
[K in keyof Config]: Config[K] extends { type: EmbedItemType.RichText }
|
||||||
|
? K
|
||||||
|
: never;
|
||||||
|
}[keyof Config],
|
||||||
|
>(key: Key) {
|
||||||
|
const { color, ...state } = useState(
|
||||||
|
key,
|
||||||
|
) as EmbedConfigItemType<EmbedItemType.RichText>['default'];
|
||||||
|
|
||||||
|
return {
|
||||||
|
style: { color },
|
||||||
|
...state,
|
||||||
|
} satisfies TextProps;
|
||||||
|
}
|
||||||
|
|
||||||
|
return { useState, useRichText };
|
||||||
|
}
|
||||||
Reference in new issue
Block a user