add CopyableText component for raw data tab
This commit is contained in:
parent
dea02ef359
commit
2771e202cb
2 changed files with 131 additions and 9 deletions
81
src/lib/components/ui/CopyableText.svelte
Normal file
81
src/lib/components/ui/CopyableText.svelte
Normal 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>
|
||||
|
|
@ -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>
|
||||
|
|
|
|||
Loading…
Reference in a new issue