add CopyableText component for raw data tab

This commit is contained in:
Justin Edmund 2025-12-02 01:25:14 -08:00
parent dea02ef359
commit 2771e202cb
2 changed files with 131 additions and 9 deletions

View file

@ -0,0 +1,81 @@
<svelte:options runes={true} />
<script lang="ts">
interface Props {
value: string | number
}
let { value }: Props = $props()
let copied = $state(false)
async function copyToClipboard() {
if (!value) return
try {
await navigator.clipboard.writeText(String(value))
copied = true
setTimeout(() => {
copied = false
}, 1500)
} catch (err) {
console.error('Failed to copy:', err)
}
}
</script>
<button class="copyable-text" class:copied onclick={copyToClipboard} title="Click to copy">
<span class="text">{value}</span>
{#if copied}
<span class="copied-indicator">Copied!</span>
{/if}
</button>
<style lang="scss">
@use '$src/themes/colors' as colors;
@use '$src/themes/spacing' as spacing;
@use '$src/themes/typography' as typography;
.copyable-text {
display: inline-flex;
align-items: center;
gap: spacing.$unit;
background: none;
border: none;
padding: 0;
margin: 0;
font: inherit;
color: colors.$grey-30;
cursor: pointer;
border-radius: 4px;
transition: color 0.15s ease;
&:hover {
color: colors.$grey-10;
}
&.copied .text {
color: colors.$grey-50;
}
}
.text {
text-decoration: underline;
text-decoration-style: dotted;
text-underline-offset: 2px;
}
.copied-indicator {
font-size: typography.$font-small;
color: colors.$grey-50;
animation: fadeIn 0.15s ease;
}
@keyframes fadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
</style>

View file

@ -3,34 +3,73 @@
<script lang="ts">
import SegmentedControl from '$lib/components/ui/segmented-control/SegmentedControl.svelte'
import Segment from '$lib/components/ui/segmented-control/Segment.svelte'
import Button from '$lib/components/ui/Button.svelte'
import type { EntityRawData } from '$lib/api/adapters/entity.adapter'
interface Props {
wikiRaw?: string | null
gameRawEn?: Record<string, unknown> | null
gameRawJp?: Record<string, unknown> | null
isLoading?: boolean
canEdit?: boolean
onFetchWiki?: () => Promise<EntityRawData>
}
let { wikiRaw, gameRawEn, gameRawJp, isLoading = false }: Props = $props()
let { wikiRaw, gameRawEn, gameRawJp, isLoading = false, canEdit = false, onFetchWiki }: Props =
$props()
let selectedLang = $state('en')
let isFetching = $state(false)
let fetchError = $state<string | null>(null)
const currentGameRaw = $derived(selectedLang === 'en' ? gameRawEn : gameRawJp)
const formattedGameRaw = $derived(
currentGameRaw ? JSON.stringify(currentGameRaw, null, 2) : null
)
async function handleFetchWiki() {
if (!onFetchWiki) return
isFetching = true
fetchError = null
try {
await onFetchWiki()
} catch (err: unknown) {
fetchError = err instanceof Error ? err.message : 'Failed to fetch wiki data'
} finally {
isFetching = false
}
}
</script>
<div class="raw-data-tab">
{#if isLoading}
<p class="loading">Loading raw data...</p>
{:else}
{#if wikiRaw}
<section class="raw-section">
<section class="raw-section">
<div class="section-header">
<h3>Wiki Raw</h3>
{#if canEdit && onFetchWiki}
<Button
variant="secondary"
size="small"
onclick={handleFetchWiki}
disabled={isFetching}
>
{isFetching ? 'Fetching...' : 'Fetch Wiki'}
</Button>
{/if}
</div>
{#if fetchError}
<p class="error">{fetchError}</p>
{/if}
{#if wikiRaw}
<pre class="raw-content">{wikiRaw}</pre>
</section>
{/if}
{:else}
<p class="no-data">No wiki data available</p>
{/if}
</section>
{#if gameRawEn || gameRawJp}
<section class="raw-section">
@ -48,10 +87,6 @@
{/if}
</section>
{/if}
{#if !wikiRaw && !gameRawEn && !gameRawJp}
<p class="no-data">No raw data available</p>
{/if}
{/if}
</div>
@ -110,4 +145,10 @@
color: colors.$grey-50;
font-style: italic;
}
.error {
color: colors.$error;
font-size: typography.$font-small;
margin: 0 0 spacing.$unit 0;
}
</style>