From 38f3ccfbd85b0660b6d0f3d3aae1cf10a25e7320 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Tr=C3=A8sAbhi?= <43380238+tresabhi@users.noreply.github.com>
Date: Tue, 6 Aug 2024 13:33:39 -0500
Subject: [PATCH] feat: more customizations
---
src/app/tools/embed/customize/tanks/page.tsx | 54 +-
src/app/tools/embed/types.ts | 20 +
src/components/EmbedConfigInputs.tsx | 840 ++++++++++++-------
src/components/TanksEmbed.tsx | 46 +-
4 files changed, 627 insertions(+), 333 deletions(-)
diff --git a/src/app/tools/embed/customize/tanks/page.tsx b/src/app/tools/embed/customize/tanks/page.tsx
index fd3b91b5e..77159723d 100644
--- a/src/app/tools/embed/customize/tanks/page.tsx
+++ b/src/app/tools/embed/customize/tanks/page.tsx
@@ -1,13 +1,11 @@
'use client';
-import { Box } from '@radix-ui/themes';
import { Dispatch, SetStateAction, use, useState } from 'react';
import { EmbedConfigInputs } from '../../../../../components/EmbedConfigInputs';
import {
TanksEmbedCard,
TanksEmbedWrapper,
} from '../../../../../components/TanksEmbed';
-import { imgur } from '../../../../../core/blitzkit/imgur';
import { tanksDefinitionsArray } from '../../../../../core/blitzkit/tankDefinitions';
import {
EmbedConfig,
@@ -17,9 +15,11 @@ import {
import { extractEmbedConfigDefaults } from '../../utilities';
const tankEmbedConfig = {
+ showTotal: { type: EmbedConfigType.Boolean, default: true },
+
listWidth: { type: EmbedConfigType.Number, default: 320, unit: 'px' },
listGap: { type: EmbedConfigType.Size, default: '2' },
- listMaxTanks: { type: EmbedConfigType.Number, default: 8, pad: true },
+ listMaxTanks: { type: EmbedConfigType.Number, default: 4, pad: true },
cardRadius: { type: EmbedConfigType.Radius, default: '2' },
cardHeaderBackgroundColor: {
@@ -27,6 +27,12 @@ const tankEmbedConfig = {
default: { base: 'gray', variant: '3' },
},
cardHeaderPadding: { type: EmbedConfigType.Size, default: '1' },
+ cardTitle: {
+ type: EmbedConfigType.FullTextControl,
+ default: { color: undefined, weight: 'bold', size: '3' },
+ },
+ cardTitleAutoColor: { type: EmbedConfigType.Boolean, default: true },
+ cardTitleAutoIcon: { type: EmbedConfigType.Boolean, default: true },
cardBodyBackgroundColor: {
type: EmbedConfigType.Color,
default: { base: 'gray', variant: '2' },
@@ -34,12 +40,13 @@ const tankEmbedConfig = {
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',
+ columnValue: {
+ type: EmbedConfigType.FullTextControl,
+ default: { color: undefined, size: '3', weight: 'regular' },
+ },
+ columnLabel: {
+ type: EmbedConfigType.FullTextControl,
+ default: { color: 'gray', size: '2', weight: 'regular' },
pad: true,
},
@@ -62,29 +69,22 @@ export default function Page() {
);
return (
- >
+ >
+ }
>
+ {state.showTotal && }
+
{tanks.slice(0, state.listMaxTanks).map((tank) => (
))}
-
- >
- >
- }
- />
-
+
);
}
diff --git a/src/app/tools/embed/types.ts b/src/app/tools/embed/types.ts
index e75d3aad5..f8a351dce 100644
--- a/src/app/tools/embed/types.ts
+++ b/src/app/tools/embed/types.ts
@@ -8,6 +8,8 @@ export enum EmbedConfigType {
SizeNo0,
TextColor,
String,
+ FullTextControl,
+ Boolean,
}
export const radixGrays = [
@@ -19,9 +21,15 @@ export const radixGrays = [
'sand',
] as const;
+export const radixTextWeights = ['light', 'regular', 'medium', 'bold'] as const;
+
export type EmbedConfig = Record;
export type EmbedConfigItem = (
+ | {
+ type: EmbedConfigType.Boolean;
+ default: boolean;
+ }
| {
type: EmbedConfigType.Number;
default: number;
@@ -51,12 +59,24 @@ export type EmbedConfigItem = (
type: EmbedConfigType.TextColor;
default: RadixTextColor;
}
+ | {
+ type: EmbedConfigType.FullTextControl;
+ default: {
+ color: RadixTextColor;
+ size: RadixSizeNo0;
+ weight: RadixTextWeight;
+ };
+ }
) & {
pad?: boolean;
};
+export type EmbedConfigItemType =
+ (EmbedConfigItem & { type: Type })['default'];
+
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;
export type RadixColor = { base: RadixColorBase; variant: RadixColorVariant };
export type RadixColorBase = Exclude<
diff --git a/src/components/EmbedConfigInputs.tsx b/src/components/EmbedConfigInputs.tsx
index f5a3ab5f0..e853c0111 100644
--- a/src/components/EmbedConfigInputs.tsx
+++ b/src/components/EmbedConfigInputs.tsx
@@ -1,25 +1,37 @@
import * as radixColors from '@radix-ui/colors';
-import { DragHandleDots2Icon } from '@radix-ui/react-icons';
+import {
+ DragHandleDots2Icon,
+ EnterFullScreenIcon,
+ ExitFullScreenIcon,
+ ImageIcon,
+} from '@radix-ui/react-icons';
import {
Box,
+ Button,
Card,
Flex,
+ IconButton,
+ Inset,
ScrollArea,
Select,
+ Switch,
Text,
TextField,
} from '@radix-ui/themes';
import { produce } from 'immer';
-import { capitalize, startCase, times } from 'lodash';
+import { capitalize, clamp, startCase, times } from 'lodash';
import {
Dispatch,
ReactNode,
SetStateAction,
useCallback,
+ useEffect,
useRef,
+ useState,
} from 'react';
import {
EmbedConfig,
+ EmbedConfigItemType,
EmbedConfigType,
ExtractEmbedConfigType,
RadixColor,
@@ -29,7 +41,10 @@ import {
radixGrays,
RadixRadius,
RadixSize,
+ RadixSizeNo0,
RadixTextColor,
+ RadixTextWeight,
+ radixTextWeights,
} from '../app/tools/embed/types';
import { toColorVar, toRadiusVar } from '../app/tools/embed/utilities';
@@ -37,13 +52,19 @@ interface EmbedConfigInputsProps {
config: EmbedConfig;
state: ExtractEmbedConfigType;
setState: Dispatch>>;
+ children: ReactNode;
}
export function EmbedConfigInputs({
config,
state,
setState,
+ children,
}: EmbedConfigInputsProps) {
+ const [expanded, setExpanded] = useState(true);
+ const [backgroundImage, setBackgroundImage] = useState(
+ 'https://upload.wikimedia.org/wikipedia/commons/f/f6/Swiss_National_Park_131.JPG',
+ );
const mutateState = useCallback(
(recipe: (draft: ExtractEmbedConfigType) => void) => {
setState(produce(recipe));
@@ -56,164 +77,133 @@ export function EmbedConfigInputs({
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`;
+ wrapper.current.style.top = `${clamp(parseFloat(wrapper.current.style.top) + event.movementY, 32, window.innerHeight - 32 - 32)}px`;
+ wrapper.current.style.right = `${clamp(parseFloat(wrapper.current.style.right) - event.movementX, 32, window.innerWidth - 32 - 320)}px`;
}, []);
-
- const handlePointerUp = useCallback((event: PointerEvent) => {
+ const handlePointerUp = useCallback(() => {
window.removeEventListener('pointermove', handlePointerMove);
window.removeEventListener('pointerup', handlePointerUp);
}, []);
+ const fileInput = useRef();
+
+ 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 (
-
- {
- window.addEventListener('pointermove', handlePointerMove);
- window.addEventListener('pointerup', handlePointerUp);
- }}
+
-
-
+
+ {
+ event.preventDefault();
+ window.addEventListener('pointermove', handlePointerMove);
+ window.addEventListener('pointerup', handlePointerUp);
+ }}
+ >
+
+
+
-
-
- {Object.entries(config).map(([key, setting]) => {
- let control: ReactNode;
+
+ setExpanded((expanded) => !expanded)}
+ >
+ {expanded ? : }
+
+
+
+
- switch (setting.type) {
- case EmbedConfigType.Number: {
- control = (
- {
- mutateState((draft) => {
- draft[key] = (
- event.target as HTMLInputElement
- ).valueAsNumber;
- });
- }}
- >
- {setting.unit && (
- px
- )}
-
- );
- break;
- }
+ {expanded && (
+
+
+
- case EmbedConfigType.String: {
- control = (
- {
- mutateState((draft) => {
- draft[key] = (event.target as HTMLInputElement).value;
- });
- }}
- />
- );
- break;
- }
+ {Object.entries(config).map(([key, setting]) => {
+ let control: ReactNode;
- case EmbedConfigType.SizeNo0:
- case EmbedConfigType.Size: {
- control = (
- {
- mutateState((draft) => {
- draft[key] = value as RadixSize;
- });
- }}
- >
-
-
- {times(10, (index) =>
- setting.type === EmbedConfigType.SizeNo0 &&
- index === 0 ? null : (
-
- {index}
-
- ),
- )}
-
-
- );
- break;
- }
+ switch (setting.type) {
+ case EmbedConfigType.Boolean: {
+ control = (
+
+ mutateState((draft) => {
+ draft[key] = checked;
+ })
+ }
+ />
+ );
+ break;
+ }
- case EmbedConfigType.Radius: {
- control = (
- {
- mutateState((draft) => {
- draft[key] = value as RadixRadius;
- });
- }}
- >
-
-
- {times(5, (index) => (
-
- {index}
-
- ))}
- Full
-
-
- );
- break;
- }
-
- case EmbedConfigType.Color: {
- control = (
- <>
- {
- mutateState((draft) => {
- (draft[key] as RadixColor).base =
- value as RadixColorBase;
- });
- }}
- >
-
-
- {Object.entries(radixColors)
- .filter(
- ([key]) =>
- !key.endsWith('A') &&
- !key.endsWith('P3') &&
- !key.endsWith('Dark'),
- )
- .map(([key]) => (
-
+ case EmbedConfigType.FullTextControl: {
+ control = (
+ <>
+ ).color}`}
+ onValueChange={(value) => {
+ mutateState((draft) => {
+ (
+ draft[
+ key
+ ] as EmbedConfigItemType
+ ).color = value as RadixTextColor;
+ });
+ }}
+ >
+
+
+
{' '}
- {capitalize(key)}
+ White
- ))}
-
-
-
- {
- mutateState((draft) => {
- (draft[key] as RadixColor).variant =
- value as RadixColorVariant;
- });
- }}
- >
-
-
- {times(12, (index) => {
- const variant =
- `${index + 1}` as RadixColorVariantRaw;
-
- return (
- <>
-
- {' '}
- {variant}
-
-
-
+
+ {' '}
+ Gray
+
+ {Object.entries(radixColors)
+ .filter(
+ ([key]) =>
+ !key.endsWith('A') &&
+ !key.endsWith('P3') &&
+ !key.endsWith('Dark') &&
+ !radixGrays.includes(key),
+ )
+ .map(([key]) => (
+
- {' '}
- {variant} translucent
+ />{' '}
+ {capitalize(key)}
+
+ ))}
+
+
+
+ ).size
+ }
+ onValueChange={(value) => {
+ mutateState((draft) => {
+ (
+ draft[
+ key
+ ] as EmbedConfigItemType
+ ).size = value as RadixSizeNo0;
+ });
+ }}
+ >
+
+
+ {times(9, (index) => (
+
+ Size {index + 1}
- >
- );
- })}
-
-
- >
- );
- break;
- }
+ ))}
+
+
+
+ ).weight
+ }
+ onValueChange={(value) => {
+ mutateState((draft) => {
+ (
+ draft[
+ key
+ ] as EmbedConfigItemType
+ ).weight = value as RadixTextWeight;
+ });
+ }}
+ >
+
+
+ {radixTextWeights.map((weight) => (
+
+ {startCase(weight)}
+
+ ))}
+
+
+ >
+ );
+ break;
+ }
- case EmbedConfigType.TextColor: {
- control = (
- {
- mutateState((draft) => {
- draft[key] = value as RadixTextColor;
- });
- }}
- >
-
-
-
- {
+ mutateState((draft) => {
+ draft[key] = (
+ event.target as HTMLInputElement
+ ).valueAsNumber;
+ });
+ }}
+ >
+ {setting.unit && (
+ px
+ )}
+
+ );
+ break;
+ }
+
+ case EmbedConfigType.String: {
+ control = (
+ {
+ mutateState((draft) => {
+ draft[key] = (
+ event.target as HTMLInputElement
+ ).value;
+ });
+ }}
+ />
+ );
+ break;
+ }
+
+ case EmbedConfigType.SizeNo0:
+ case EmbedConfigType.Size: {
+ control = (
+ {
+ mutateState((draft) => {
+ draft[key] = value as RadixSize;
+ });
+ }}
+ >
+
+
+ {times(10, (index) =>
+ setting.type === EmbedConfigType.SizeNo0 &&
+ index === 0 ? null : (
+
+ {index}
+
+ ),
+ )}
+
+
+ );
+ break;
+ }
+
+ case EmbedConfigType.Radius: {
+ control = (
+ {
+ mutateState((draft) => {
+ draft[key] = value as RadixRadius;
+ });
+ }}
+ >
+
+
+ {times(5, (index) => (
+
+ {index}
+
+ ))}
+ Full
+
+
+ );
+ break;
+ }
+
+ case EmbedConfigType.Color: {
+ control = (
+ <>
+ {
+ mutateState((draft) => {
+ (draft[key] as RadixColor).base =
+ value as RadixColorBase;
+ });
}}
- />{' '}
- White
-
-
-
+
+
+ {Object.entries(radixColors)
+ .filter(
+ ([key]) =>
+ !key.endsWith('A') &&
+ !key.endsWith('P3') &&
+ !key.endsWith('Dark'),
+ )
+ .map(([key]) => (
+
+ {' '}
+ {capitalize(key)}
+
+ ))}
+
+
+
+ {
+ mutateState((draft) => {
+ (draft[key] as RadixColor).variant =
+ value as RadixColorVariant;
+ });
}}
- />{' '}
- Gray
-
- {Object.entries(radixColors)
- .filter(
- ([key]) =>
- !key.endsWith('A') &&
- !key.endsWith('P3') &&
- !key.endsWith('Dark') &&
- !radixGrays.includes(key),
- )
- .map(([key]) => (
-
+ >
+
+
+ {times(12, (index) => {
+ const variant =
+ `${index + 1}` as RadixColorVariantRaw;
+
+ return (
+ <>
+
+ {' '}
+ {variant}
+
+
+
+
+ {' '}
+ {variant} translucent
+
+ >
+ );
+ })}
+
+
+ >
+ );
+ break;
+ }
+
+ case EmbedConfigType.TextColor: {
+ control = (
+ {
+ mutateState((draft) => {
+ draft[key] = value as RadixTextColor;
+ });
+ }}
+ >
+
+
+
{' '}
- {capitalize(key)}
+ White
- ))}
-
-
- );
- break;
- }
- }
+
+ {' '}
+ Gray
+
+ {Object.entries(radixColors)
+ .filter(
+ ([key]) =>
+ !key.endsWith('A') &&
+ !key.endsWith('P3') &&
+ !key.endsWith('Dark') &&
+ !radixGrays.includes(key),
+ )
+ .map(([key]) => (
+
+ {' '}
+ {capitalize(key)}
+
+ ))}
+
+
+ );
+ break;
+ }
+ }
- return (
-
-
- {capitalize(startCase(key))}
-
- {control && {control}}
- {!control && (
-
- Immutable
-
- )}
-
- );
- })}
-
-
-
+ return (
+
+
+ {capitalize(startCase(key))}
+
+ {control && {control}}
+ {!control && (
+
+ Immutable
+
+ )}
+
+ );
+ })}
+
+
+ )}
+
+ {!expanded && (
+
+
+ Expand to configure
+
+
+ )}
+
+
+ {children}
+
);
}
diff --git a/src/components/TanksEmbed.tsx b/src/components/TanksEmbed.tsx
index 9d85a440f..c6b8b12c4 100644
--- a/src/components/TanksEmbed.tsx
+++ b/src/components/TanksEmbed.tsx
@@ -8,6 +8,10 @@ import {
toWidthVar,
} from '../app/tools/embed/utilities';
import { TankDefinition } from '../core/blitzkit/tankDefinitions';
+import { ClassHeavy } from './ClassIcon/components/ClassHeavy';
+import { ClassLight } from './ClassIcon/components/ClassLight';
+import { ClassMedium } from './ClassIcon/components/ClassMedium';
+import { ClassTankDestroyer } from './ClassIcon/components/ClassTankDestroyer';
interface TanksEmbedWrapperProps {
children: ReactNode;
@@ -27,11 +31,12 @@ export function TanksEmbedWrapper({ children, state }: TanksEmbedWrapperProps) {
}
interface TanksEmbedCardProps {
- tank: TankDefinition;
+ tank: TankDefinition | null;
state: TanksEmbedState;
}
export function TanksEmbedCard({ tank, state }: TanksEmbedCardProps) {
+ const { color: cardTitleColor, ...restCardTitleProps } = state.cardTitle;
return (
- {tank.name}
+
+
+ {tank !== null && state.cardTitleAutoIcon && (
+ <>
+ {tank.class === 'lightTank' && (
+
+ )}
+ {tank.class === 'mediumTank' && (
+
+ )}
+ {tank.class === 'heavyTank' && (
+
+ )}
+ {tank.class === 'AT-SPG' && (
+
+ )}
+ >
+ )}{' '}
+ {tank === null ? 'Total' : tank.name}
+
+
@@ -62,10 +100,10 @@ export function TanksEmbedCard({ tank, state }: TanksEmbedCardProps) {
gap={state.columnGap}
flexGrow="1"
>
-
+
70{state[`column${column}Unit`]}
-
+
{state[`column${column}Label`]}