From a34acc38ac0be89a5b7a0b995ed658ed5a18d04f Mon Sep 17 00:00:00 2001 From: JensForstmann <19289807+JensForstmann@users.noreply.github.com> Date: Wed, 15 Nov 2023 03:10:05 +0100 Subject: [PATCH] Fix copy to clipboard --- frontend/src/components/GameServerCard.tsx | 73 ++-------------------- frontend/src/components/MatchCard.tsx | 25 ++------ frontend/src/utils/copyToClipboard.ts | 26 ++++++++ 3 files changed, 34 insertions(+), 90 deletions(-) create mode 100644 frontend/src/utils/copyToClipboard.ts diff --git a/frontend/src/components/GameServerCard.tsx b/frontend/src/components/GameServerCard.tsx index 255903e..957fdaf 100644 --- a/frontend/src/components/GameServerCard.tsx +++ b/frontend/src/components/GameServerCard.tsx @@ -1,12 +1,9 @@ -import { Component, createSignal } from 'solid-js'; -import { IMatchResponse, IMatchUpdateDto } from '../../../common'; +import { Component } from 'solid-js'; +import { IMatchResponse } from '../../../common'; import { SvgCopyAll, SvgOpenInNew } from '../assets/Icons'; -import { createFetcher } from '../utils/fetcher'; import { t } from '../utils/locale'; import { Card } from './Card'; -import { CardMenu } from './CardMenu'; -import { Modal } from './Modal'; -import { TextInput } from './Inputs'; +import { copyToClipboard } from '../utils/copyToClipboard'; export const GameServerCard: Component<{ match: IMatchResponse; @@ -16,11 +13,9 @@ export const GameServerCard: Component<{ const command = () => (props.match.serverPassword ? `password "${props.match.serverPassword}"; ` : '') + `connect ${ipPort()}`; - let modalRef: HTMLDialogElement | undefined; return ( - modalRef?.showModal()]]} />

{t('Game Server')}

@@ -28,70 +23,10 @@ export const GameServerCard: Component<{
{command()} -

- - modalRef?.close()} /> -
); }; - -const GameServerChangeForm: Component<{ - match: IMatchResponse; - onClose: () => void; -}> = (props) => { - const [ip, setIp] = createSignal(props.match.gameServer.ip); - const [port, setPort] = createSignal(props.match.gameServer.port); - const [rconPassword, setRconPassword] = createSignal(props.match.gameServer.rconPassword); - - const fetcher = createFetcher(props.match.tmtSecret); - const changeGameServer = async () => { - await fetcher('PATCH', `/api/matches/${props.match.id}`, { - gameServer: { - ip: ip(), - port: port(), - rconPassword: rconPassword(), - }, - } as IMatchUpdateDto); - props.onClose(); - }; - - return ( - <> -
- setIp(e.currentTarget.value)} - class="bg-base-300" - /> - setPort(parseInt(e.currentTarget.value))} - class="bg-base-300" - /> - setRconPassword(e.currentTarget.value)} - class="bg-base-300" - /> -
-
- - - - ); -}; diff --git a/frontend/src/components/MatchCard.tsx b/frontend/src/components/MatchCard.tsx index 37d8a0a..728f715 100644 --- a/frontend/src/components/MatchCard.tsx +++ b/frontend/src/components/MatchCard.tsx @@ -1,20 +1,15 @@ import { useNavigate, useSearchParams } from '@solidjs/router'; import { Component, Show } from 'solid-js'; -import { - getMapDraws, - getMapScore, - IMatchResponse, - IMatchUpdateDto, - TMatchState, -} from '../../../common'; +import { IMatchResponse, IMatchUpdateDto, getMapDraws, getMapScore } from '../../../common'; import { SvgCopyAll } from '../assets/Icons'; import { createFetcher } from '../utils/fetcher'; import { t } from '../utils/locale'; import { mustConfirm } from '../utils/mustConfirm'; import { Card } from './Card'; import { CardMenu } from './CardMenu'; -import { Modal } from './Modal'; import { TextInput } from './Inputs'; +import { Modal } from './Modal'; +import { copyToClipboard } from '../utils/copyToClipboard'; export const MatchCard: Component<{ match: IMatchResponse; @@ -29,14 +24,6 @@ export const MatchCard: Component<{ const init = () => patchMatch({ _init: true }); const setup = () => patchMatch({ _setup: true }); const revive = () => fetcher('PATCH', `/api/matches/${props.match.id}/revive`); - const changeState = () => { - const response = prompt(t('enter state'), 'MATCH_MAP'); - if (response) { - patchMatch({ - state: response as TMatchState, - }); - } - }; const l = window.location; const shareLink = l.protocol + '//' + l.host + l.pathname + '?secret=' + props.match.tmtSecret; let modalRef: HTMLDialogElement | undefined; @@ -52,7 +39,6 @@ export const MatchCard: Component<{ [t('restart election'), mustConfirm(restartElection)], [t('init'), init], [t('setup'), setup], - [t('change state'), changeState], [t('share match with token'), () => modalRef?.showModal()], [ t('edit match'), @@ -94,10 +80,7 @@ export const MatchCard: Component<{ class="bg-base-300 w-full" containerClass="w-full" /> -
diff --git a/frontend/src/utils/copyToClipboard.ts b/frontend/src/utils/copyToClipboard.ts new file mode 100644 index 0000000..624bd4b --- /dev/null +++ b/frontend/src/utils/copyToClipboard.ts @@ -0,0 +1,26 @@ +// from https://stackoverflow.com/a/65996386 +export const copyToClipboard = async (textToCopy: string) => { + // Navigator clipboard api needs a secure context (https) + if (navigator.clipboard && window.isSecureContext) { + await navigator.clipboard.writeText(textToCopy); + } else { + // Use the 'out of viewport hidden text area' trick + const textArea = document.createElement('textarea'); + textArea.value = textToCopy; + + // Move textarea out of the viewport so it's not visible + textArea.style.position = 'absolute'; + textArea.style.left = '-999999px'; + + document.body.prepend(textArea); + textArea.select(); + + try { + document.execCommand('copy'); + } catch (error) { + console.error(error); + } finally { + textArea.remove(); + } + } +};