mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-10-29 05:29:52 +01:00
feat: use any account selection
This commit is contained in:
1 parent
71c9d1461f
commit
3293afba76
4 files changed
+163
-98
No files matched your search
@@ -0,0 +1,150 @@
|
|||||||
|
import {
|
||||||
|
searchPlayersAcrossRegions,
|
||||||
|
type AccountListWithServer,
|
||||||
|
} from '@blitzkit/core';
|
||||||
|
import strings from '@blitzkit/core/lang/en-US.json';
|
||||||
|
import { Link2Icon, PersonIcon } from '@radix-ui/react-icons';
|
||||||
|
import {
|
||||||
|
Badge,
|
||||||
|
Button,
|
||||||
|
Dialog,
|
||||||
|
Flex,
|
||||||
|
Heading,
|
||||||
|
Spinner,
|
||||||
|
Text,
|
||||||
|
TextField,
|
||||||
|
} from '@radix-ui/themes';
|
||||||
|
import { debounce } from 'lodash-es';
|
||||||
|
import { useCallback, useRef, useState } from 'react';
|
||||||
|
import { stringify } from 'urlon';
|
||||||
|
import type { configurations } from '../../constants/embeds';
|
||||||
|
import { App } from '../../stores/app';
|
||||||
|
import { EmbedState, type EmbedStateStore } from '../../stores/embedState';
|
||||||
|
import { CopyButton } from '../CopyButton';
|
||||||
|
|
||||||
|
interface CopyURLProps {
|
||||||
|
embed: keyof typeof configurations;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function CopyURL({ embed }: CopyURLProps) {
|
||||||
|
const appStore = App.useStore();
|
||||||
|
const embedStateStore = EmbedState.useStore();
|
||||||
|
const input = useRef<HTMLInputElement>(null);
|
||||||
|
const [searching, setSearching] = useState(false);
|
||||||
|
const [results, setResults] = useState<AccountListWithServer>([]);
|
||||||
|
const performSearch = useCallback(
|
||||||
|
debounce(async () => {
|
||||||
|
if (!input.current) return;
|
||||||
|
|
||||||
|
const sanitized = input.current.value.trim();
|
||||||
|
|
||||||
|
if (sanitized.length === 0) {
|
||||||
|
setSearching(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const results = await searchPlayersAcrossRegions(sanitized, 12);
|
||||||
|
|
||||||
|
setSearching(false);
|
||||||
|
setResults(results);
|
||||||
|
}, 500),
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
const handleChange = useCallback(() => {
|
||||||
|
if (!input.current) return;
|
||||||
|
|
||||||
|
const sanitized = input.current.value.trim();
|
||||||
|
|
||||||
|
if (sanitized.length === 0) {
|
||||||
|
setSearching(false);
|
||||||
|
setResults([]);
|
||||||
|
} else {
|
||||||
|
setSearching(true);
|
||||||
|
performSearch();
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog.Root>
|
||||||
|
<Dialog.Trigger>
|
||||||
|
<Button>
|
||||||
|
<Link2Icon />
|
||||||
|
Copy URL
|
||||||
|
</Button>
|
||||||
|
</Dialog.Trigger>
|
||||||
|
|
||||||
|
<Dialog.Content width="fit-content">
|
||||||
|
<Flex direction="column">
|
||||||
|
<Heading size="5">Copy URL</Heading>
|
||||||
|
<Text>Search an account to track</Text>
|
||||||
|
</Flex>
|
||||||
|
|
||||||
|
<TextField.Root
|
||||||
|
mt="4"
|
||||||
|
style={{ width: '20rem' }}
|
||||||
|
placeholder="Search players..."
|
||||||
|
ref={input}
|
||||||
|
onChange={handleChange}
|
||||||
|
>
|
||||||
|
<TextField.Slot>
|
||||||
|
<PersonIcon />
|
||||||
|
</TextField.Slot>
|
||||||
|
</TextField.Root>
|
||||||
|
|
||||||
|
<Flex justify="center" mt="4">
|
||||||
|
{searching && (
|
||||||
|
<Text color="gray">
|
||||||
|
<Flex align="center" gap="2">
|
||||||
|
<Spinner /> Searching
|
||||||
|
</Flex>
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
|
{!searching && results.length === 0 && (
|
||||||
|
<Text color="gray">No results</Text>
|
||||||
|
)}
|
||||||
|
</Flex>
|
||||||
|
|
||||||
|
{!searching && results.length > 0 && (
|
||||||
|
<Flex justify="center">
|
||||||
|
<Flex
|
||||||
|
direction="column"
|
||||||
|
gap="2"
|
||||||
|
mt="4"
|
||||||
|
width="fit-content"
|
||||||
|
minWidth="75%"
|
||||||
|
>
|
||||||
|
{results.map((result) => (
|
||||||
|
<CopyButton
|
||||||
|
variant="ghost"
|
||||||
|
copy={() => {
|
||||||
|
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: `${result.account_id}`,
|
||||||
|
state: stringify(shallowState),
|
||||||
|
});
|
||||||
|
|
||||||
|
return `${location.origin}/tools/embed/${embed}/host?${searchParams.toString()}`;
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Flex justify="between" width="100%" gap="2">
|
||||||
|
{result.nickname}
|
||||||
|
<Badge>{strings.common.regions.short[result.region]}</Badge>
|
||||||
|
</Flex>
|
||||||
|
</CopyButton>
|
||||||
|
))}
|
||||||
|
</Flex>
|
||||||
|
</Flex>
|
||||||
|
)}
|
||||||
|
</Dialog.Content>
|
||||||
|
</Dialog.Root>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -3,7 +3,6 @@ import {
|
|||||||
CodeIcon,
|
CodeIcon,
|
||||||
HeightIcon,
|
HeightIcon,
|
||||||
ImageIcon,
|
ImageIcon,
|
||||||
Link2Icon,
|
|
||||||
ResetIcon,
|
ResetIcon,
|
||||||
TimerIcon,
|
TimerIcon,
|
||||||
WidthIcon,
|
WidthIcon,
|
||||||
@@ -11,10 +10,10 @@ import {
|
|||||||
import { Box, Button, Flex, Heading, ScrollArea, Text } from '@radix-ui/themes';
|
import { Box, Button, Flex, Heading, ScrollArea, Text } from '@radix-ui/themes';
|
||||||
import { capitalize, startCase } from 'lodash-es';
|
import { capitalize, startCase } from 'lodash-es';
|
||||||
import { useEffect, useRef, useState, type ReactNode } from 'react';
|
import { useEffect, useRef, useState, type ReactNode } from 'react';
|
||||||
import { stringify } from 'urlon';
|
|
||||||
import { CopyButton } from '../../../../components/CopyButton';
|
import { CopyButton } from '../../../../components/CopyButton';
|
||||||
import { Boolean } from '../../../../components/Embeds/Boolean';
|
import { Boolean } from '../../../../components/Embeds/Boolean';
|
||||||
import { Color } from '../../../../components/Embeds/Color';
|
import { Color } from '../../../../components/Embeds/Color';
|
||||||
|
import { CopyURL } from '../../../../components/Embeds/CopyURL';
|
||||||
import { Enum } from '../../../../components/Embeds/Enum';
|
import { Enum } from '../../../../components/Embeds/Enum';
|
||||||
import { Import } from '../../../../components/Embeds/Import';
|
import { Import } from '../../../../components/Embeds/Import';
|
||||||
import { PreviewWrapper } from '../../../../components/Embeds/PreviewWrapper';
|
import { PreviewWrapper } from '../../../../components/Embeds/PreviewWrapper';
|
||||||
@@ -25,18 +24,13 @@ import { SizeWithout0 } from '../../../../components/Embeds/SizeWithout0';
|
|||||||
import { Slider } from '../../../../components/Embeds/Slider';
|
import { Slider } from '../../../../components/Embeds/Slider';
|
||||||
import { TextController } from '../../../../components/Embeds/TextController';
|
import { TextController } from '../../../../components/Embeds/TextController';
|
||||||
import { PageWrapper } from '../../../../components/PageWrapper';
|
import { PageWrapper } from '../../../../components/PageWrapper';
|
||||||
import { WargamingLoginButton } from '../../../../components/WargamingLoginButton';
|
|
||||||
import {
|
import {
|
||||||
configurations,
|
configurations,
|
||||||
extractEmbedConfigDefaults,
|
extractEmbedConfigDefaults,
|
||||||
} from '../../../../constants/embeds';
|
} from '../../../../constants/embeds';
|
||||||
import { NAVBAR_HEIGHT } from '../../../../constants/navbar';
|
import { NAVBAR_HEIGHT } from '../../../../constants/navbar';
|
||||||
import { App } from '../../../../stores/app';
|
import { App } from '../../../../stores/app';
|
||||||
import {
|
import { EmbedState, type EmbedConfig } from '../../../../stores/embedState';
|
||||||
EmbedState,
|
|
||||||
type EmbedConfig,
|
|
||||||
type EmbedStateStore,
|
|
||||||
} from '../../../../stores/embedState';
|
|
||||||
import { EmbedItemType } from '../../../../stores/embedState/constants';
|
import { EmbedItemType } from '../../../../stores/embedState/constants';
|
||||||
|
|
||||||
export interface EmbedPreviewControllerProps {
|
export interface EmbedPreviewControllerProps {
|
||||||
@@ -60,10 +54,6 @@ export function Page({ embed }: PageProps) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function Content({ embed }: PageProps) {
|
function Content({ embed }: PageProps) {
|
||||||
const wargaming = App.useDeferred(
|
|
||||||
(state) => state.logins.wargaming,
|
|
||||||
undefined,
|
|
||||||
);
|
|
||||||
const embedStateStore = EmbedState.useStore();
|
const embedStateStore = EmbedState.useStore();
|
||||||
const appStore = App.useStore();
|
const appStore = App.useStore();
|
||||||
const config = configurations[embed] as EmbedConfig;
|
const config = configurations[embed] as EmbedConfig;
|
||||||
@@ -100,39 +90,7 @@ function Content({ embed }: PageProps) {
|
|||||||
</Text>
|
</Text>
|
||||||
|
|
||||||
<Flex mb="6" gap="2" wrap="wrap">
|
<Flex mb="6" gap="2" wrap="wrap">
|
||||||
<CopyButton
|
<CopyURL embed={embed} />
|
||||||
disabled={!wargaming}
|
|
||||||
copy={() => {
|
|
||||||
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()}`;
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Link2Icon />
|
|
||||||
{wargaming ? 'Copy URL' : 'Log in to general URL'}
|
|
||||||
</CopyButton>
|
|
||||||
{!wargaming && (
|
|
||||||
<WargamingLoginButton>
|
|
||||||
Log in with Wargaming
|
|
||||||
</WargamingLoginButton>
|
|
||||||
)}
|
|
||||||
<CopyButton
|
<CopyButton
|
||||||
variant="outline"
|
variant="outline"
|
||||||
copy={() =>
|
copy={() =>
|
||||||
|
|||||||
@@ -1,51 +0,0 @@
|
|||||||
import { ExclamationTriangleIcon } from '@radix-ui/react-icons';
|
|
||||||
import { Callout, Flex, Link } from '@radix-ui/themes';
|
|
||||||
import { PageWrapper } from '../../../components/PageWrapper';
|
|
||||||
import { WargamingLoginButton } from '../../../components/WargamingLoginButton';
|
|
||||||
import { configurations } from '../../../constants/embeds';
|
|
||||||
import { App, type WargamingLogin } from '../../../stores/app';
|
|
||||||
|
|
||||||
export function Page() {
|
|
||||||
return (
|
|
||||||
<App.Provider>
|
|
||||||
<Content />
|
|
||||||
</App.Provider>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function Content() {
|
|
||||||
const wargaming = App.useDeferred((state) => state.logins.wargaming, {
|
|
||||||
expires: 0,
|
|
||||||
id: 0,
|
|
||||||
token: '',
|
|
||||||
} satisfies WargamingLogin);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<PageWrapper color="red">
|
|
||||||
{!wargaming && (
|
|
||||||
<>
|
|
||||||
<Flex justify="center">
|
|
||||||
<Callout.Root size="1">
|
|
||||||
<Callout.Icon>
|
|
||||||
<ExclamationTriangleIcon />
|
|
||||||
</Callout.Icon>
|
|
||||||
|
|
||||||
<Callout.Text>
|
|
||||||
You must be logged in with your Wargaming account to export
|
|
||||||
embeds.
|
|
||||||
</Callout.Text>
|
|
||||||
</Callout.Root>
|
|
||||||
</Flex>
|
|
||||||
|
|
||||||
<Flex justify="center">
|
|
||||||
<WargamingLoginButton>Log in with Wargaming</WargamingLoginButton>
|
|
||||||
</Flex>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{Object.entries(configurations).map(([embed, config]) => (
|
|
||||||
<Link href={`/tools/embed/${embed}`}>{embed}</Link>
|
|
||||||
))}
|
|
||||||
</PageWrapper>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,9 +1,17 @@
|
|||||||
---
|
---
|
||||||
|
import { Link } from '@radix-ui/themes';
|
||||||
|
import { PageWrapper } from '../../../components/PageWrapper';
|
||||||
|
import { configurations } from '../../../constants/embeds';
|
||||||
import { embedTool } from '../../../constants/tools';
|
import { embedTool } from '../../../constants/tools';
|
||||||
import ToolLayout from '../../../layouts/ToolLayout.astro';
|
import ToolLayout from '../../../layouts/ToolLayout.astro';
|
||||||
import { Page } from './_index';
|
|
||||||
---
|
---
|
||||||
|
|
||||||
<ToolLayout tool={embedTool}>
|
<ToolLayout tool={embedTool}>
|
||||||
<Page client:load />
|
<PageWrapper color="red">
|
||||||
|
{
|
||||||
|
Object.entries(configurations).map(([embed, config]) => (
|
||||||
|
<Link href={`/tools/embed/${embed}`}>{embed}</Link>
|
||||||
|
))
|
||||||
|
}
|
||||||
|
</PageWrapper>
|
||||||
</ToolLayout>
|
</ToolLayout>
|
||||||
Reference in new issue
Block a user