mirror of
https://github.com/FoxxMD/multi-scrobbler.git
synced 2026-07-09 17:28:28 +00:00
feat(ui): Implement log copy
This commit is contained in:
parent
b2b2018764
commit
7151d96b59
4 changed files with 85 additions and 18 deletions
|
|
@ -1,6 +1,7 @@
|
|||
import { ComponentProps, useMemo } from 'react';
|
||||
import { Clipboard, IconButton } from "@chakra-ui/react"
|
||||
import { ComponentProps, useMemo, useCallback, useEffect, useState } from 'react';
|
||||
import { Clipboard, IconButton, useClipboard } from "@chakra-ui/react"
|
||||
import {safeStringify} from '../../core/StringUtils';
|
||||
import { CheckIcon, CopyIcon } from './icons/ChakraIcons';
|
||||
|
||||
|
||||
export const ChakraClip = (props: Omit<ComponentProps<typeof Clipboard.Root>, 'children' | 'value'> & {value: any}) => {
|
||||
|
|
@ -23,4 +24,48 @@ export const ChakraClip = (props: Omit<ComponentProps<typeof Clipboard.Root>, 'c
|
|||
</Clipboard.Trigger>
|
||||
</Clipboard.Root>
|
||||
)
|
||||
}
|
||||
|
||||
const createCopyVal = (value: any) => {
|
||||
if(typeof value === 'string') {
|
||||
return value;
|
||||
}
|
||||
return safeStringify(value);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Copies value from onCopy only when clicked
|
||||
*/
|
||||
export const ChakraClipDynamic = (props: Omit<ComponentProps<typeof Clipboard.Root>, 'children' | 'value'> & {onCopy: () => any}) => {
|
||||
const {
|
||||
onCopy,
|
||||
...rest
|
||||
} = props;
|
||||
|
||||
// idk what's going on here exactly but just using clip.setValue does not set clipboard, it requires two clicks to get actual value
|
||||
// so instead using some useState abuse to re-render component and wait to copy until everything matches
|
||||
// probably related to this https://github.com/chakra-ui/chakra-ui/issues/6759
|
||||
|
||||
const [clipVal, setClipVal] = useState<string | undefined>();
|
||||
const clip = useClipboard({timeout: 1000});
|
||||
|
||||
useEffect(() => {
|
||||
if(clipVal !== undefined && clip.value === clipVal) {
|
||||
//console.log('run copy');
|
||||
clip.copy();
|
||||
setClipVal(undefined);
|
||||
}
|
||||
},[clip.value, clipVal, setClipVal]);
|
||||
const invokeCopy = useCallback(() => {
|
||||
const val = createCopyVal(onCopy());
|
||||
//console.log(val);
|
||||
setClipVal(val);
|
||||
clip.setValue(val);
|
||||
},[onCopy, clip.setValue]);
|
||||
return (
|
||||
<IconButton variant="surface" size="xs" onClick={invokeCopy}>
|
||||
{!clip.copied ? <CopyIcon/> : <CheckIcon/>}
|
||||
</IconButton>
|
||||
)
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
import React, {PropsWithChildren, useState, useEffect} from 'react';
|
||||
import React, {PropsWithChildren, useState, useEffect, useRef, useCallback} from 'react';
|
||||
import * as AnsiImport from "ansi-to-react";
|
||||
import { Text, Box, SegmentGroup, Separator, HStack, Stack, Span } from '@chakra-ui/react';
|
||||
import {FixedSizeList} from "fixed-size-list";
|
||||
|
|
@ -6,8 +6,9 @@ import {useSSE} from "@flamefrontend/sse-runtime-react";
|
|||
import { useQueryClient, QueryFunctionContext, useQuery, useMutation } from '@tanstack/react-query'
|
||||
import { LogOutputConfig } from '../../core/Atomic';
|
||||
import ky from 'ky';
|
||||
import { baseUrl } from '../utils';
|
||||
import { LogLevel } from '@foxxmd/logging';
|
||||
import { tanQueries } from '../queries';
|
||||
import { ChakraClip, ChakraClipDynamic } from './ChakraClipboard';
|
||||
|
||||
// @ts-expect-error Ansi export is built incorrectly
|
||||
const Ansi = AnsiImport.default.default as typeof AnsiImport.default;
|
||||
|
|
@ -35,8 +36,8 @@ const createFixedList = (size, initialList: MinLogInfo[] = []): FixedSizeList<Mi
|
|||
|
||||
let list = createFixedList(50);
|
||||
|
||||
export const Logs = (props: {logs: Readonly<LogLineProps[]>}) => {
|
||||
return <Box fontFamily="source-code-pro, Menlo, Monaco, Consolas,'Courier New',monospace;">
|
||||
export const Logs = (props: {logs: Readonly<LogLineProps[]>, ref?: React.Ref<HTMLDivElement>}) => {
|
||||
return <Box ref={props.ref} fontFamily="source-code-pro, Menlo, Monaco, Consolas,'Courier New',monospace;">
|
||||
{props.logs.map(x => <LogLine message={x.message}/>)}
|
||||
</Box>
|
||||
}
|
||||
|
|
@ -55,9 +56,9 @@ export const LogsFetchable = (props: {settings?: LogOutputConfig, streamable?: b
|
|||
const [logLimit, setLogLimit] = useState(limit);
|
||||
const [logList, setLogList] = useState<MinLogInfo[]>([]);
|
||||
|
||||
|
||||
const { isPending, isError, data, error } = useQuery({
|
||||
queryKey: ['logs', { level: logLevel, limit: logLimit }],
|
||||
queryFn: queryFn,
|
||||
...tanQueries.logs.list(logLevel, logLimit),
|
||||
staleTime: Infinity
|
||||
});
|
||||
|
||||
|
|
@ -123,14 +124,20 @@ export const LogsFetchable = (props: {settings?: LogOutputConfig, streamable?: b
|
|||
}
|
||||
}, [data, limit,setLogList]);
|
||||
|
||||
const logRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const getLogCopyText = useCallback(() =>{
|
||||
const content = logRef.current.innerText;
|
||||
return content.replaceAll(/\n\[/g, '[');
|
||||
},[logRef]);
|
||||
|
||||
return (<Stack>
|
||||
<HStack gap="5"><Span>Level: {levelGroup}</Span><Separator orientation="vertical" height="4"/><Span>Limit: {limitGroup}</Span></HStack>
|
||||
<Logs logs={logList}/>
|
||||
<HStack gap="5">
|
||||
<Span>Level: {levelGroup}</Span>
|
||||
<Separator orientation="vertical" height="4"/><Span marginEnd="auto">Limit: {limitGroup}</Span>
|
||||
<ChakraClipDynamic onCopy={getLogCopyText}/>
|
||||
</HStack>
|
||||
<Logs ref={logRef} logs={logList}/>
|
||||
</Stack>);
|
||||
|
||||
}
|
||||
|
||||
type LogsQueryKey = ['logs', {level: string, limit: number}];
|
||||
const queryFn = async (context: QueryFunctionContext<LogsQueryKey>) => {
|
||||
return await ky.get(`logs`, { baseUrl: baseUrl }).json() as {data: {line: string, time: number, levelLabel: string, level: number}[]};
|
||||
}
|
||||
|
|
@ -20,7 +20,8 @@ import {
|
|||
LuEye,
|
||||
LuEyeClosed,
|
||||
LuCalendar,
|
||||
LuRefreshCw
|
||||
LuRefreshCw,
|
||||
LuCopy
|
||||
} from "react-icons/lu"
|
||||
import { VscDebugRestart } from 'react-icons/vsc';
|
||||
import { MdOutlineFiberNew } from "react-icons/md";
|
||||
|
|
@ -159,4 +160,7 @@ export const RefreshButton = makeIconButton(RefreshIcon);
|
|||
|
||||
export const InsertedIcon = makeChakraIcon(MdOutlineFiberNew);
|
||||
|
||||
export const UpdatedIcon = makeChakraIcon(LuCircleArrowUp);
|
||||
export const UpdatedIcon = makeChakraIcon(LuCircleArrowUp);
|
||||
|
||||
export const CopyIcon = makeChakraIcon(LuCopy);
|
||||
export const CopyIconButton = makeIconButton(LuCopy);
|
||||
|
|
@ -59,9 +59,20 @@ const players = createQueryKeys('players', {
|
|||
queryKey: ['components', componentId, 'play', platformId],
|
||||
queryFn: (ctx) => ky.get(`components/${componentId}/players/${platformId}`, { baseUrl }).json<SourcePlayerJson>()
|
||||
})
|
||||
});
|
||||
|
||||
const logs = createQueryKeys('logs', {
|
||||
list: (level: string, limit: number) => ({
|
||||
queryKey: ['logs', {level, limit}],
|
||||
queryFn: (ctx) => {
|
||||
return ky.get(`logs`, {
|
||||
baseUrl: baseUrl
|
||||
}).json<{data: {line: string, time: number, levelLabel: string, level: number}[]}>();
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
export const tanQueries = mergeQueryKeys(components, activities, players);
|
||||
export const tanQueries = mergeQueryKeys(components, activities, players, logs);
|
||||
|
||||
export const useQueryState = (queryKey: Readonly<unknown[]>) => {
|
||||
const queryClient = useQueryClient()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue