From c0259ac40915db90fbcbb589e3b047af63519c4f Mon Sep 17 00:00:00 2001 From: FoxxMD Date: Mon, 26 Jan 2026 20:05:17 +0000 Subject: [PATCH] feat: Add debug copy action to ui --- src/backend/sources/SpotifySource.ts | 10 +++- src/client/components/copyToClipboardHook.tsx | 49 +++++++++++++++++++ src/client/recent/RecentPage.tsx | 34 +++++++++++-- src/client/scrobbled/ScrobbledPage.tsx | 22 ++++++++- 4 files changed, 109 insertions(+), 6 deletions(-) create mode 100644 src/client/components/copyToClipboardHook.tsx diff --git a/src/backend/sources/SpotifySource.ts b/src/backend/sources/SpotifySource.ts index 9562004a..d92ea998 100644 --- a/src/backend/sources/SpotifySource.ts +++ b/src/backend/sources/SpotifySource.ts @@ -114,7 +114,12 @@ export default class SpotifySource extends MemoryPositionalSource { track_number, } = track; + // we don't use available markets for anything and it can be 100+ strings + // so delete for debugging sake delete obj.track.available_markets; + if(obj.track.album !== undefined) { + delete obj.track.album?.available_markets; + } scrobbleTsSOC = SCROBBLE_TS_SOC_END; played_at = dayjs(pa); @@ -155,6 +160,9 @@ export default class SpotifySource extends MemoryPositionalSource { } = item as TrackObjectFull; delete (obj.item as TrackObjectFull).available_markets; + if((obj.item as TrackObjectFull).album !== undefined) { + delete (obj.item as TrackObjectFull).album.available_markets; + } scrobbleTsSOC = SCROBBLE_TS_SOC_START; played_at = dayjs(timestamp); @@ -397,7 +405,7 @@ export default class SpotifySource extends MemoryPositionalSource { getPlayHistory = async (options: RecentlyPlayedOptions = {}) => { const {limit = 20} = options; const func = (api: SpotifyWebApi) => api.getMyRecentlyPlayedTracks({ - limit: 3 + limit }); const result = await this.callApi>(func); return result.body.items.map((x: PlayHistoryObject) => SpotifySource.formatPlayObj(x)).sort(sortByOldestPlayDate); diff --git a/src/client/components/copyToClipboardHook.tsx b/src/client/components/copyToClipboardHook.tsx new file mode 100644 index 00000000..0f9d371f --- /dev/null +++ b/src/client/components/copyToClipboardHook.tsx @@ -0,0 +1,49 @@ +import { useCallback, useState } from "react"; + +export function useCopyToClipboard(resetInterval = 2000) { + const [isCopied, setIsCopied] = useState(false); + + const copy = useCallback(async (text) => { + if (!text) return false; + + let success = false; + + if (navigator.clipboard && window.isSecureContext) { + try { + await navigator.clipboard.writeText(text); + success = true; + } catch (err) { + console.warn("Clipboard API failed:", err); + } + } + + // Fallback for insecure contexts + if (!success) { + const textarea = document.createElement("textarea"); + textarea.value = text; + textarea.style.position = "fixed"; + textarea.style.opacity = "0"; + document.body.appendChild(textarea); + textarea.focus(); + textarea.select(); + + try { + success = document.execCommand("copy"); + } catch (err) { + console.error("Fallback copy failed:", err); + success = false; + } finally { + document.body.removeChild(textarea); + } + } + + setIsCopied(success); + if (success && resetInterval > 0) { + setTimeout(() => setIsCopied(false), resetInterval); + } + + return success; + }, [resetInterval]); + + return { copy, isCopied }; +} \ No newline at end of file diff --git a/src/client/recent/RecentPage.tsx b/src/client/recent/RecentPage.tsx index c09b9d45..b6d917d3 100644 --- a/src/client/recent/RecentPage.tsx +++ b/src/client/recent/RecentPage.tsx @@ -1,11 +1,14 @@ -import React, { Fragment, useMemo } from 'react'; +import React, { Fragment, useMemo, useCallback, useState } from 'react'; import PlayDisplay from "../components/PlayDisplay"; import { recentIncludes } from "../../core/Atomic"; import { useSearchParams } from "react-router-dom"; import { useGetRecentQuery } from "./recentDucks"; import Tooltip from "../components/Tooltip"; -import { faQuestionCircle } from "@fortawesome/free-solid-svg-icons"; +import { faQuestionCircle, faClipboard, faFileClipboard, faBug } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { useCopyToClipboard } from '../components/copyToClipboardHook'; +import clsx from "clsx"; +import {patch} from 'jsondiffpatch'; const displayOpts = { include: recentIncludes, @@ -42,6 +45,17 @@ const recent = () => { upstream: searchParams.get('upstream') }); + const { copy, isCopied } = useCopyToClipboard(); + + const [copiedIndex, setIndex] = useState(null); + + const copyActionCB = useCallback((obj, index) => { + copy(JSON.stringify(obj, null, 2)); + setIndex(index); + },[copy, setIndex]); + + const baseClass = ['float-right']; + const isUpstream = searchParams.get('upstream') === '1'; const tipContents = useMemo(() => { @@ -67,7 +81,21 @@ const recent = () => {
{/*{isUpstream ? Below is data directly returned by the Source API. MS uses : null}*/} {isSuccess && !isLoading && data.length === 0 ? 'No recently played tracks!' : null} -
    {data.map(x =>
  • )}
+
    {data.map(x => { + const classes = [...baseClass].concat(copiedIndex !== x.index ? ['underline','cursor-pointer'] : []); + // let last = x.meta.lifecycle.original; + // x.meta.lifecycle.steps = x.meta.lifecycle.steps.map(y => { + // if(y.patch !== undefined) { + // // @ts-ignore + // y.full = patch(last, y.patch); + // // @ts-ignore + // last = y.full; + // } + // return y; + // }); + return
  • + })}
diff --git a/src/client/scrobbled/ScrobbledPage.tsx b/src/client/scrobbled/ScrobbledPage.tsx index 56b301ed..e36e9019 100644 --- a/src/client/scrobbled/ScrobbledPage.tsx +++ b/src/client/scrobbled/ScrobbledPage.tsx @@ -1,8 +1,10 @@ -import React from 'react'; +import React, {useCallback, useState} from 'react'; import PlayDisplay from "../components/PlayDisplay"; import {recentIncludes} from "../../core/Atomic"; import {useSearchParams} from "react-router-dom"; import {useGetRecentQuery} from "./scrobbledDucks"; +import { useCopyToClipboard } from '../components/copyToClipboardHook'; +import clsx from "clsx"; const displayOpts = { include: recentIncludes, @@ -18,6 +20,18 @@ const scrobbled = () => { isSuccess } = useGetRecentQuery({name: searchParams.get('name'), type: searchParams.get('type')}); + const { copy, isCopied } = useCopyToClipboard(); + + const [copiedIndex, setIndex] = useState(null); + + const copyActionCB = useCallback((obj, index) => { + copy(JSON.stringify(obj, null, 2)); + setIndex(index); + },[copy, setIndex]); + + const baseClass = ['float-right']; + + return (
@@ -27,7 +41,11 @@ const scrobbled = () => {
{isSuccess && !isLoading && data.length === 0 ? 'No recently scrobbled tracks!' : null} -
    {data.map(x =>
  • )}
+
    {data.map(x => { + const classes = [...baseClass].concat(copiedIndex !== x.index ? ['underline','cursor-pointer'] : []); + + return
  • ; + })}
-- 2.51.2