From 9daab37ba1bbd006c60b62f156be47b0e6edc4e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tr=C3=A8sAbhi?= <43380238+tresabhi@users.noreply.github.com> Date: Sat, 26 Oct 2024 14:33:04 -0500 Subject: [PATCH] feat: recover embed code --- .../website/src/components/Embeds/Boolean.tsx | 19 ++ .../website/src/components/Embeds/Color.tsx | 19 ++ .../components/Embeds/ColorControllerRaw.tsx | 38 +++ .../website/src/components/Embeds/Enum.tsx | 36 +++ .../website/src/components/Embeds/Import.tsx | 80 +++++ .../Embeds/PreviewWrapper/index.css.ts | 26 ++ .../Embeds/PreviewWrapper/index.tsx | 36 +++ .../website/src/components/Embeds/Radius.tsx | 26 ++ .../src/components/Embeds/RichText.tsx | 32 ++ .../website/src/components/Embeds/Size.tsx | 23 ++ .../src/components/Embeds/SizeWithout0.tsx | 25 ++ .../website/src/components/Embeds/Slider.tsx | 32 ++ .../src/components/Embeds/TextController.tsx | 19 ++ .../src/pages/tools/embed/[embed]/_index.tsx | 303 ++++++++++++++++++ .../src/pages/tools/embed/[embed]/index.astro | 16 + .../website/src/pages/tools/embed/index.astro | 16 + 16 files changed, 746 insertions(+) create mode 100644 packages/website/src/components/Embeds/Boolean.tsx create mode 100644 packages/website/src/components/Embeds/Color.tsx create mode 100644 packages/website/src/components/Embeds/ColorControllerRaw.tsx create mode 100644 packages/website/src/components/Embeds/Enum.tsx create mode 100644 packages/website/src/components/Embeds/Import.tsx create mode 100644 packages/website/src/components/Embeds/PreviewWrapper/index.css.ts create mode 100644 packages/website/src/components/Embeds/PreviewWrapper/index.tsx create mode 100644 packages/website/src/components/Embeds/Radius.tsx create mode 100644 packages/website/src/components/Embeds/RichText.tsx create mode 100644 packages/website/src/components/Embeds/Size.tsx create mode 100644 packages/website/src/components/Embeds/SizeWithout0.tsx create mode 100644 packages/website/src/components/Embeds/Slider.tsx create mode 100644 packages/website/src/components/Embeds/TextController.tsx create mode 100644 packages/website/src/pages/tools/embed/[embed]/_index.tsx create mode 100644 packages/website/src/pages/tools/embed/[embed]/index.astro create mode 100644 packages/website/src/pages/tools/embed/index.astro diff --git a/packages/website/src/components/Embeds/Boolean.tsx b/packages/website/src/components/Embeds/Boolean.tsx new file mode 100644 index 000000000..0ae6c06bf --- /dev/null +++ b/packages/website/src/components/Embeds/Boolean.tsx @@ -0,0 +1,19 @@ +import { Switch } from '@radix-ui/themes'; +import type { EmbedPreviewControllerProps } from '../../pages/tools/embed/[embed]/_index'; +import { EmbedState } from '../../stores/embedState'; + +export function Boolean({ configKey }: EmbedPreviewControllerProps) { + const mutateEmbedState = EmbedState.useMutation(); + + return ( + state[configKey] as boolean)} + onCheckedChange={() => { + mutateEmbedState((draft) => { + draft[configKey] = !draft[configKey]; + }); + }} + /> + ); +} diff --git a/packages/website/src/components/Embeds/Color.tsx b/packages/website/src/components/Embeds/Color.tsx new file mode 100644 index 000000000..cad88b7e3 --- /dev/null +++ b/packages/website/src/components/Embeds/Color.tsx @@ -0,0 +1,19 @@ +import type { EmbedPreviewControllerProps } from '../../pages/tools/embed/[embed]/_index'; +import { EmbedState } from '../../stores/embedState'; +import { ColorControllerRaw } from './ColorControllerRaw'; + +export function Color({ configKey }: EmbedPreviewControllerProps) { + const mutateEmbedState = EmbedState.useMutation(); + const color = EmbedState.use((state) => state[configKey] as string); + + return ( + { + mutateEmbedState((draft) => { + draft[configKey] = value; + }); + }} + /> + ); +} diff --git a/packages/website/src/components/Embeds/ColorControllerRaw.tsx b/packages/website/src/components/Embeds/ColorControllerRaw.tsx new file mode 100644 index 000000000..c547720a6 --- /dev/null +++ b/packages/website/src/components/Embeds/ColorControllerRaw.tsx @@ -0,0 +1,38 @@ +import { Slider } from '@radix-ui/themes'; + +interface ColorControllerRawProps { + value: string; + onValueChange: (value: string) => void; +} + +export function ColorControllerRaw({ + value, + onValueChange, +}: ColorControllerRawProps) { + const tintString = value.slice(0, 7); + const alphaString = value.slice(7, 9); + const alpha = alphaString.length === 0 ? 255 : parseInt(alphaString, 16); + + return ( + <> + { + let alpha = value.slice(7, 9); + if (alpha.length === 0) alpha = 'ff'; + onValueChange(`${event.target.value}${alpha}`); + }} + /> + { + onValueChange(`${tintString}${value.toString(16)}`); + }} + /> + + ); +} diff --git a/packages/website/src/components/Embeds/Enum.tsx b/packages/website/src/components/Embeds/Enum.tsx new file mode 100644 index 000000000..b83bcbee6 --- /dev/null +++ b/packages/website/src/components/Embeds/Enum.tsx @@ -0,0 +1,36 @@ +import { Select } from '@radix-ui/themes'; +import type { EmbedPreviewControllerProps } from '../../pages/tools/embed/[embed]/_index'; +import { EmbedState } from '../../stores/embedState'; +import type { + EmbedConfigItemType, + EmbedItemType, +} from '../../stores/embedState/constants'; + +export function Enum({ + configKey, + config, +}: EmbedPreviewControllerProps & { + config: EmbedConfigItemType; +}) { + const mutateEmbedState = EmbedState.useMutation(); + + return ( + state[configKey] as string)} + onValueChange={(value) => { + mutateEmbedState((draft) => { + draft[configKey] = value; + }); + }} + > + + + {config.options.map((option) => ( + + {option.label} + + ))} + + + ); +} diff --git a/packages/website/src/components/Embeds/Import.tsx b/packages/website/src/components/Embeds/Import.tsx new file mode 100644 index 000000000..819470b13 --- /dev/null +++ b/packages/website/src/components/Embeds/Import.tsx @@ -0,0 +1,80 @@ +import { + AlertDialog, + Button, + Flex, + Heading, + Text, + TextField, + Tooltip, +} from '@radix-ui/themes'; +import { useRef, useState } from 'react'; +import { EmbedState } from '../../stores/embedState'; + +export function Import() { + const importInput = useRef(null); + const [showInvalidURLPrompt, setShowInvalidURLPrompt] = useState(false); + const [showConfirmation, setConfirmation] = useState(false); + const mutateEmbedState = EmbedState.useMutation(); + + return ( + <> + Import + + Paste your current URL if you have one already to import your settings + + + + + Invalid URL + + Try pasting the URL that BlitzKit previously generated, you silly + goose! + + + + + + + + + + + + + + + + + + ); +} diff --git a/packages/website/src/components/Embeds/PreviewWrapper/index.css.ts b/packages/website/src/components/Embeds/PreviewWrapper/index.css.ts new file mode 100644 index 000000000..8f6c18989 --- /dev/null +++ b/packages/website/src/components/Embeds/PreviewWrapper/index.css.ts @@ -0,0 +1,26 @@ +import { keyframes, style } from '@vanilla-extract/css'; + +const fadeOutBorder = keyframes({ + from: { + outline: '1rem solid #ff0000ff', + }, + to: { + outline: '1rem solid #ff000000', + }, +}); + +export const wrapper = style({ + position: 'absolute', + top: '50%', + left: '50%', + transform: 'translate(-50%, -50%)', + overflow: 'hidden', +}); + +export const animated = style({ + animation: `${fadeOutBorder} 0.5s linear`, + + ':hover': { + outline: '1rem solid #ff000080', + }, +}); diff --git a/packages/website/src/components/Embeds/PreviewWrapper/index.tsx b/packages/website/src/components/Embeds/PreviewWrapper/index.tsx new file mode 100644 index 000000000..dc6ae2c91 --- /dev/null +++ b/packages/website/src/components/Embeds/PreviewWrapper/index.tsx @@ -0,0 +1,36 @@ +import { Box } from '@radix-ui/themes'; +import { useEffect, useRef } from 'react'; +import { previews } from '../../../constants/embeds'; +import { useEmbedStateCurry } from '../../../stores/embedState/utilities'; +import * as styles from './index.css'; + +interface PreviewWrapperProps { + name: keyof typeof previews; +} + +export function PreviewWrapper({ name }: PreviewWrapperProps) { + const { useState } = useEmbedStateCurry(); + const width = useState('width'); + const height = useState('height'); + const Preview = previews[name]; + const wrapper = useRef(null); + + useEffect(() => { + if (!wrapper.current) return; + + wrapper.current.classList.remove(styles.animated); + void wrapper.current.offsetWidth; + wrapper.current.classList.add(styles.animated); + }, [width, height]); + + return ( + + + + ); +} diff --git a/packages/website/src/components/Embeds/Radius.tsx b/packages/website/src/components/Embeds/Radius.tsx new file mode 100644 index 000000000..cd4f2c05c --- /dev/null +++ b/packages/website/src/components/Embeds/Radius.tsx @@ -0,0 +1,26 @@ +import { Slider } from '@radix-ui/themes'; +import type { EmbedPreviewControllerProps } from '../../pages/tools/embed/[embed]/_index'; +import { EmbedState, type RadixRadius } from '../../stores/embedState'; + +export function Radius({ configKey }: EmbedPreviewControllerProps) { + const mutateEmbedState = EmbedState.useMutation(); + + return ( + { + const radius = state[configKey] as RadixRadius; + return radius === 'full' ? 5 : parseInt(radius); + }), + ]} + onValueChange={([value]) => { + mutateEmbedState((draft) => { + draft[configKey] = value === 5 ? 'full' : `${value}`; + }); + }} + /> + ); +} diff --git a/packages/website/src/components/Embeds/RichText.tsx b/packages/website/src/components/Embeds/RichText.tsx new file mode 100644 index 000000000..ad7bfd8db --- /dev/null +++ b/packages/website/src/components/Embeds/RichText.tsx @@ -0,0 +1,32 @@ +import type { EmbedPreviewControllerProps } from '../../pages/tools/embed/[embed]/_index'; +import { EmbedState } from '../../stores/embedState'; +import type { + EmbedConfigItemType, + EmbedItemType, +} from '../../stores/embedState/constants'; +import { ColorControllerRaw } from './ColorControllerRaw'; + +export function RichText({ configKey }: EmbedPreviewControllerProps) { + const mutateEmbedState = EmbedState.useMutation(); + const state = EmbedState.use( + (state) => + state[ + configKey + ] as EmbedConfigItemType['default'], + ); + + return ( + { + mutateEmbedState((draft) => { + ( + draft[ + configKey + ] as EmbedConfigItemType['default'] + ).color = value; + }); + }} + /> + ); +} diff --git a/packages/website/src/components/Embeds/Size.tsx b/packages/website/src/components/Embeds/Size.tsx new file mode 100644 index 000000000..248cec4ea --- /dev/null +++ b/packages/website/src/components/Embeds/Size.tsx @@ -0,0 +1,23 @@ +import { Slider } from '@radix-ui/themes'; +import type { EmbedPreviewControllerProps } from '../../pages/tools/embed/[embed]/_index'; +import { EmbedState, type RadixSize } from '../../stores/embedState'; + +export function Size({ configKey }: EmbedPreviewControllerProps) { + const mutateEmbedState = EmbedState.useMutation(); + + return ( + parseInt(state[configKey] as RadixSize)), + ]} + onValueChange={([value]) => { + mutateEmbedState((draft) => { + draft[configKey] = `${value}`; + }); + }} + /> + ); +} diff --git a/packages/website/src/components/Embeds/SizeWithout0.tsx b/packages/website/src/components/Embeds/SizeWithout0.tsx new file mode 100644 index 000000000..ea0ed192f --- /dev/null +++ b/packages/website/src/components/Embeds/SizeWithout0.tsx @@ -0,0 +1,25 @@ +import { Slider } from '@radix-ui/themes'; +import type { EmbedPreviewControllerProps } from '../../pages/tools/embed/[embed]/_index'; +import { EmbedState, type RadixSizeWithout0 } from '../../stores/embedState'; + +export function SizeWithout0({ configKey }: EmbedPreviewControllerProps) { + const mutateEmbedState = EmbedState.useMutation(); + + return ( + + parseInt(state[configKey] as RadixSizeWithout0), + ), + ]} + onValueChange={([value]) => { + mutateEmbedState((draft) => { + draft[configKey] = `${value}`; + }); + }} + /> + ); +} diff --git a/packages/website/src/components/Embeds/Slider.tsx b/packages/website/src/components/Embeds/Slider.tsx new file mode 100644 index 000000000..6fe525684 --- /dev/null +++ b/packages/website/src/components/Embeds/Slider.tsx @@ -0,0 +1,32 @@ +import { Slider as RadixSlider } from '@radix-ui/themes'; +import type { EmbedPreviewControllerProps } from '../../pages/tools/embed/[embed]/_index'; +import { EmbedState, type RadixSize } from '../../stores/embedState'; +import type { + EmbedConfigItemType, + EmbedItemType, +} from '../../stores/embedState/constants'; + +export function Slider({ + configKey, + config, +}: EmbedPreviewControllerProps & { + config: EmbedConfigItemType; +}) { + const mutateEmbedState = EmbedState.useMutation(); + + return ( + parseInt(state[configKey] as RadixSize)), + ]} + onValueChange={([value]) => { + mutateEmbedState((draft) => { + draft[configKey] = value; + }); + }} + /> + ); +} diff --git a/packages/website/src/components/Embeds/TextController.tsx b/packages/website/src/components/Embeds/TextController.tsx new file mode 100644 index 000000000..5e0c95cf0 --- /dev/null +++ b/packages/website/src/components/Embeds/TextController.tsx @@ -0,0 +1,19 @@ +import { TextField } from '@radix-ui/themes'; +import type { EmbedPreviewControllerProps } from '../../pages/tools/embed/[embed]/_index'; +import { EmbedState } from '../../stores/embedState'; + +export function TextController({ configKey }: EmbedPreviewControllerProps) { + const mutateEmbedState = EmbedState.useMutation(); + + return ( + state[configKey] as string)} + onChange={(event) => { + mutateEmbedState((draft) => { + draft[configKey] = event.target.value; + }); + }} + /> + ); +} diff --git a/packages/website/src/pages/tools/embed/[embed]/_index.tsx b/packages/website/src/pages/tools/embed/[embed]/_index.tsx new file mode 100644 index 000000000..d7219b815 --- /dev/null +++ b/packages/website/src/pages/tools/embed/[embed]/_index.tsx @@ -0,0 +1,303 @@ +import { imgur } from '@blitzkit/core'; +import { + CodeIcon, + HeightIcon, + ImageIcon, + Link2Icon, + ResetIcon, + TimerIcon, + WidthIcon, +} from '@radix-ui/react-icons'; +import { Box, Button, Flex, Heading, ScrollArea, Text } from '@radix-ui/themes'; +import { capitalize, startCase } from 'lodash-es'; +import { useEffect, useRef, useState, type ReactNode } from 'react'; +import { stringify } from 'urlon'; +import { CopyButton } from '../../../../components/CopyButton'; +import { Boolean } from '../../../../components/Embeds/Boolean'; +import { Color } from '../../../../components/Embeds/Color'; +import { Enum } from '../../../../components/Embeds/Enum'; +import { Import } from '../../../../components/Embeds/Import'; +import { PreviewWrapper } from '../../../../components/Embeds/PreviewWrapper'; +import { Radius } from '../../../../components/Embeds/Radius'; +import { RichText } from '../../../../components/Embeds/RichText'; +import { Size } from '../../../../components/Embeds/Size'; +import { SizeWithout0 } from '../../../../components/Embeds/SizeWithout0'; +import { Slider } from '../../../../components/Embeds/Slider'; +import { TextController } from '../../../../components/Embeds/TextController'; +import { PageWrapper } from '../../../../components/PageWrapper'; +import { + configurations, + extractEmbedConfigDefaults, +} from '../../../../constants/embeds'; +import { NAVBAR_HEIGHT } from '../../../../constants/navbar'; +import { App } from '../../../../stores/app'; +import { + EmbedState, + type EmbedConfig, + type EmbedStateStore, +} from '../../../../stores/embedState'; +import { EmbedItemType } from '../../../../stores/embedState/constants'; + +export interface EmbedPreviewControllerProps { + configKey: string; +} + +interface PageProps { + embed: keyof typeof configurations; +} + +export function Page({ embed }: PageProps) { + const config = configurations[embed]; + + return ( + + + + + + ); +} + +function Content({ embed }: PageProps) { + const embedStateStore = EmbedState.useStore(); + const appStore = App.useStore(); + const config = configurations[embed] as EmbedConfig; + const [backgroundImage, setBackgroundImage] = useState(imgur('SO13zur')); + 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 ( + + + + + + + Export + + Copy all properties for you streaming software + + + + { + const { wargaming } = appStore.getState().logins; + + if (!wargaming) return; + + const state = embedStateStore.getState(); + const initial = embedStateStore.getInitialState(); + const shallowState: EmbedStateStore = {}; + + Object.entries(state).forEach(([key, value]) => { + if (value !== initial[key]) { + shallowState[key] = value; + } + }); + + const searchParams = new URLSearchParams({ + id: `${wargaming.id}`, + state: stringify(shallowState), + }); + + return `${location.origin}/tools/embed/${embed}/host?${searchParams.toString()}`; + }} + > + + Copy URL + + + 'body { margin: 0; background-color: transparent; overflow: hidden; }' + } + > + + Copy CSS + + `${embedStateStore.getState().width}`} + > + + Copy width + + `${embedStateStore.getState().height}`} + > + + Copy height + + '`'}> + + Copy framerate + + + + + Customize + + + + Bells and whistles of your embed + + + {Object.keys(config).map((configKey) => { + const item = config[configKey]; + let control: ReactNode; + let oneLiner = false; + + switch (item.type) { + case EmbedItemType.Boolean: { + control = ; + oneLiner = true; + break; + } + + case EmbedItemType.Color: { + control = ; + break; + } + + case EmbedItemType.Radius: { + control = ; + oneLiner = true; + break; + } + + case EmbedItemType.Size: { + control = ; + oneLiner = true; + break; + } + + case EmbedItemType.SizeWithout0: { + control = ; + oneLiner = true; + break; + } + + case EmbedItemType.Slider: { + control = ; + oneLiner = true; + break; + } + + case EmbedItemType.String: { + control = ; + break; + } + + case EmbedItemType.RichText: { + control = ; + break; + } + + case EmbedItemType.Enum: { + control = ; + oneLiner = true; + break; + } + } + + return ( + + {capitalize(startCase(configKey as string))} + + {control} + + + ); + })} + + + + + + + Example preview + + + + + + + + + + + + ); +} diff --git a/packages/website/src/pages/tools/embed/[embed]/index.astro b/packages/website/src/pages/tools/embed/[embed]/index.astro new file mode 100644 index 000000000..1565fae32 --- /dev/null +++ b/packages/website/src/pages/tools/embed/[embed]/index.astro @@ -0,0 +1,16 @@ +--- +import type { GetStaticPaths } from 'astro'; +import { configurations } from '../../../../constants/embeds'; +import BaseLayout from '../../../../layouts/BaseLayout.astro'; +import { Page } from './_index'; + +export const getStaticPaths = (() => { + return Object.keys(configurations).map((embed) => ({ + params: { embed: embed as keyof typeof configurations }, + })); +}) satisfies GetStaticPaths; +--- + + + + diff --git a/packages/website/src/pages/tools/embed/index.astro b/packages/website/src/pages/tools/embed/index.astro new file mode 100644 index 000000000..d1c9cb0e0 --- /dev/null +++ b/packages/website/src/pages/tools/embed/index.astro @@ -0,0 +1,16 @@ +--- +import { PageWrapper } from '../../../components/PageWrapper'; +import { configurations } from '../../../constants/embeds'; +import { embedTool } from '../../../constants/tools'; +import ToolLayout from '../../../layouts/ToolLayout.astro'; +--- + + + + { + Object.entries(configurations).map(([embed, config]) => ( + {embed} + )) + } + +