You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

296 lines
18 KiB

7 months ago
<script lang="ts">
import './app.css';
import MetadataEditor from '../components/MetadataEditor.svelte';
import LingInput from '@/ling/svelte/LingInput.svelte';
import type { Metadata } from '../../lib';
import type { LingRecord } from '@/ling/lib';
7 months ago
7 months ago
// ── Demo metadata con todos los tipos ───────────────────────────
let metadata = $state<Metadata>({
7 months ago
order: ['nombre','año','disponible','descripcion','lanzamiento','ultima_mod','web','precio','foto','manual','specs'],
7 months ago
nombre: { type: 'string', value: 'Toyota Corolla' },
año: { type: 'number', value: 2024 },
disponible: { type: 'boolean', value: true },
7 months ago
descripcion: { type: 'ling', value: { es: 'Sedán compacto fiable', en: 'Reliable compact sedan' } as LingRecord},
7 months ago
lanzamiento: { type: 'date', value: '2024-01-15' },
ultima_mod: { type: 'datetime', value: '2024-03-09T10:00:00Z' },
web: { type: 'url', value: 'https://toyota.es' },
precio: { type: 'currency', value: { amount: 24990, currency: 'EUR' } },
foto: { type: 'image', value: { url: 'https://images.unsplash.com/photo-1621007947382-bb3c3994e3fb?w=400&h=300&fit=crop', mimeType: 'image/jpeg' } },
manual: { type: 'pdf', value: { url: 'https://example.com/manual.pdf', filename: 'manual.pdf' } },
specs: { type: 'group', value: {
7 months ago
motor: { type: 'string', value: '1.8 VVT-i' },
cv: { type: 'number', value: 122 },
traccion: { type: 'string', value: 'FWD' },
}, order: ['cv', 'motor', 'traccion'] },
7 months ago
});
// ── LingInput demo ───────────────────────────────────────────────
let lingValue = $state<LingRecord>({ es: 'Hola mundo', en: '' });
let lingInvalid = $state(false);
// ── JSON output ─────────────────────────────────────────────────
let showJson = $state(false);
let darkMode = $state(false);
const locales = ['es', 'en', 'fr'];
const defaultLocale = 'es';
</script>
<svelte:head>
<title>meta-content · Demo</title>
<link rel="preconnect" href="https://fonts.googleapis.com" />
7 months ago
<link href="https://fonts.googleapis.com/css2?family=Poppins:wght@400;500;600;700&display=swap" rel="stylesheet" />
7 months ago
</svelte:head>
<div class={['min-h-screen transition-colors duration-300', darkMode ? 'dark bg-zinc-950' : 'bg-zinc-50'].join(' ')}
7 months ago
style="font-family: 'Poppins', sans-serif;">
7 months ago
<!-- ── Background grid ────────────────────────────────────────── -->
<div class="pointer-events-none fixed inset-0 opacity-[0.03] dark:opacity-[0.06]"
style="background-image: linear-gradient(#000 1px, transparent 1px), linear-gradient(90deg, #000 1px, transparent 1px); background-size: 32px 32px;">
</div>
<!-- ── Header ─────────────────────────────────────────────────── -->
<header class="relative border-b border-zinc-200 bg-white/80 backdrop-blur dark:border-zinc-800 dark:bg-zinc-950/80">
<div class="mx-auto flex max-w-5xl items-center justify-between px-6 py-4">
<div class="flex items-center gap-3">
<div class="flex h-8 w-8 items-center justify-center rounded-lg bg-zinc-900 dark:bg-white">
<svg class="h-4 w-4 text-white dark:text-zinc-900" viewBox="0 0 16 16" fill="currentColor">
<rect x="1" y="1" width="6" height="6" rx="1"/>
<rect x="9" y="1" width="6" height="6" rx="1"/>
<rect x="1" y="9" width="6" height="6" rx="1"/>
<rect x="9" y="9" width="6" height="6" rx="1" opacity="0.4"/>
</svg>
</div>
<div>
<span class="font-mono text-[11px] font-bold uppercase tracking-[0.2em] text-zinc-800 dark:text-zinc-200">
meta-content
</span>
<span class="ml-2 rounded border border-zinc-200 px-1.5 py-0.5 font-mono text-[9px] text-zinc-400 dark:border-zinc-700 dark:text-zinc-500">
v0.1.0
</span>
</div>
</div>
<div class="flex items-center gap-3">
<button
7 months ago
onclick={() => showJson = !showJson}
class={[
7 months ago
'rounded-lg border px-3 py-1.5 font-mono text-[11px] font-medium transition-all duration-150',
showJson
? 'border-zinc-800 bg-zinc-900 text-white dark:border-zinc-200 dark:bg-white dark:text-zinc-900'
: 'border-zinc-200 text-zinc-500 hover:border-zinc-300 dark:border-zinc-700 dark:text-zinc-400',
].join(' ')}
>
{showJson ? '← editor' : 'JSON →'}
</button>
<button
7 months ago
onclick={() => darkMode = !darkMode}
class="flex h-8 w-8 items-center justify-center rounded-lg border border-zinc-200
7 months ago
text-zinc-400 transition-colors hover:border-zinc-300 hover:text-zinc-600
dark:border-zinc-700 dark:text-zinc-500 dark:hover:border-zinc-600 dark:hover:text-zinc-300"
>
{#if darkMode}
<svg class="h-4 w-4" viewBox="0 0 16 16" fill="currentColor">
<path d="M8 1a.75.75 0 0 1 .75.75v1a.75.75 0 0 1-1.5 0v-1A.75.75 0 0 1 8 1zM8 11a3 3 0 1 1 0-6 3 3 0 0 1 0 6zm5.657-8.157a.75.75 0 0 1 0 1.06l-.707.708a.75.75 0 0 1-1.06-1.06l.707-.708a.75.75 0 0 1 1.06 0zM3.05 4.904a.75.75 0 0 1-1.06-1.06l.707-.708a.75.75 0 0 1 1.06 1.06l-.707.708zM15 8a.75.75 0 0 1-.75.75h-1a.75.75 0 0 1 0-1.5h1A.75.75 0 0 1 15 8zM2.75 8a.75.75 0 0 1-.75.75H.75a.75.75 0 0 1 0-1.5H2A.75.75 0 0 1 2.75 8zM12.95 11.096a.75.75 0 0 1 1.06 1.06l-.707.708a.75.75 0 0 1-1.06-1.06l.707-.708zm-9.9 0l.707.708a.75.75 0 0 1-1.06 1.06l-.707-.707a.75.75 0 0 1 1.06-1.061z"/>
</svg>
{:else}
<svg class="h-4 w-4" viewBox="0 0 16 16" fill="currentColor">
<path d="M6 .278a.75.75 0 0 1 .18.83A7 7 0 1 0 14.85 9.5a.75.75 0 0 1 .83.18A8.5 8.5 0 1 1 6 .278z"/>
</svg>
{/if}
</button>
</div>
</div>
</header>
<!-- ── Main ───────────────────────────────────────────────────── -->
<main class="mx-auto max-w-5xl px-6 py-10">
<!-- Hero -->
<div class="mb-10">
<h1 class="text-[42px] font-extrabold leading-none tracking-tight text-zinc-900 dark:text-zinc-100">
Metadata<br/>
<span class="text-zinc-300 dark:text-zinc-700">Editor</span>
</h1>
<p class="mt-3 font-mono text-[12px] text-zinc-400 dark:text-zinc-500">
Campos tipados · Validación en tiempo real · Drag & drop · Dark mode
</p>
</div>
{#if showJson}
<!-- ── JSON view ─────────────────────────────────────── -->
<div class="rounded-xl border border-zinc-200 bg-white dark:border-zinc-800 dark:bg-zinc-900">
<div class="flex items-center justify-between border-b border-zinc-100 px-4 py-3 dark:border-zinc-800">
<span class="font-mono text-[10px] font-bold uppercase tracking-widest text-zinc-400">
metadata · serializado
</span>
<button
7 months ago
onclick={() => navigator.clipboard.writeText(JSON.stringify(metadata, null, 2))}
class="rounded border border-zinc-200 px-2 py-1 font-mono text-[10px] text-zinc-400
7 months ago
hover:border-zinc-300 hover:text-zinc-600 dark:border-zinc-700 dark:hover:border-zinc-600"
>
copiar
</button>
</div>
<pre class="overflow-auto p-4 font-mono text-[11px] leading-relaxed text-zinc-600 dark:text-zinc-400"
>{JSON.stringify(metadata, null, 2)}</pre>
</div>
{:else}
7 months ago
<div class="grid grid-cols-1 gap-8 lg:grid-cols-[minmax(0,1fr)_320px]">
7 months ago
<!-- ── Left: MetadataEditor ──────────────────────── -->
<div class="flex flex-col gap-2">
<div class="flex items-center gap-2 px-0.5">
<span class="font-mono text-[10px] font-bold uppercase tracking-[0.16em] text-zinc-400 dark:text-zinc-600">
MetadataEditor
</span>
7 months ago
<div class="h-px flex-1 bg-zinc-100 dark:bg-zinc-800 w-full"></div>
7 months ago
</div>
<MetadataEditor
7 months ago
bind:value={metadata}
{locales}
attributes={['hidden','required']}
{defaultLocale}
dark={darkMode}
onUpdate={(m) => metadata = m}
7 months ago
/>
</div>
<!-- ── Right: LingInput demo + stats ─────────────── -->
<div class="flex flex-col gap-6">
<!-- LingInput showcase -->
<div class="flex flex-col gap-2">
<div class="flex items-center gap-2 px-0.5">
<span class="font-mono text-[10px] font-bold uppercase tracking-[0.16em] text-zinc-400 dark:text-zinc-600">
LingInput
</span>
<div class="h-px flex-1 bg-zinc-100 dark:bg-zinc-800"></div>
</div>
<div class="flex flex-col gap-4 rounded-xl border border-zinc-200 bg-white p-4
dark:border-zinc-800 dark:bg-zinc-900">
<!-- textarea mode -->
<LingInput
7 months ago
value={lingValue}
{locales}
{defaultLocale}
label="Descripción"
placeholder="Escribe aquí…"
required={true}
invalid={lingInvalid}
as="textarea"
rows={3}
onUpdate={(v) => lingValue = v}
7 months ago
/>
<!-- input mode -->
<LingInput
7 months ago
value={lingValue}
{locales}
{defaultLocale}
label="Título"
as="input"
onUpdate={(v) => lingValue = v}
7 months ago
/>
<!-- Invalid toggle -->
<div class="flex items-center gap-2 border-t border-zinc-100 pt-3 dark:border-zinc-800">
<span class="font-mono text-[10px] text-zinc-400">Simular inválido</span>
<button
7 months ago
onclick={() => lingInvalid = !lingInvalid}
class={[
7 months ago
'relative h-5 w-9 rounded-full transition-colors duration-200',
lingInvalid ? 'bg-red-400' : 'bg-zinc-200 dark:bg-zinc-700',
].join(' ')}
>
<span class={[
'absolute top-0.5 h-4 w-4 rounded-full bg-white shadow transition-transform duration-200',
lingInvalid ? 'translate-x-4' : 'translate-x-0.5',
].join(' ')}></span>
</button>
</div>
</div>
</div>
<!-- Stats card -->
<div class="rounded-xl border border-zinc-200 bg-white p-4 dark:border-zinc-800 dark:bg-zinc-900">
<p class="mb-3 font-mono text-[10px] font-bold uppercase tracking-[0.16em] text-zinc-400 dark:text-zinc-600">
Estado actual
</p>
<div class="flex flex-col gap-2">
{#each [
{ label: 'campos', value: Object.keys(metadata).length },
{ label: 'tipos únicos', value: new Set(Object.values(metadata).map((f: any) => f.type)).size },
{ label: 'con hidden', value: Object.values(metadata).filter((f: any) => f.hidden).length },
] as stat}
<div class="flex items-center justify-between">
<span class="font-mono text-[11px] text-zinc-400 dark:text-zinc-500">{stat.label}</span>
<span class="font-mono text-[13px] font-bold text-zinc-700 dark:text-zinc-300">{stat.value}</span>
</div>
{/each}
</div>
</div>
<!-- Type legend -->
<div class="rounded-xl border border-zinc-200 bg-white p-4 dark:border-zinc-800 dark:bg-zinc-900">
<p class="mb-3 font-mono text-[10px] font-bold uppercase tracking-[0.16em] text-zinc-400 dark:text-zinc-600">
Tipos disponibles
</p>
<div class="flex flex-wrap gap-1.5">
7 months ago
{#each ['string','number','boolean','ling','date','datetime','url','currency','image','video','audio','pdf','file','group'] as type}
7 months ago
{@const colors: Record<string, string> = {
string: 'bg-sky-50 text-sky-500 border-sky-200 dark:bg-sky-900/20 dark:text-sky-400 dark:border-sky-800',
number: 'bg-violet-50 text-violet-500 border-violet-200 dark:bg-violet-900/20 dark:text-violet-400 dark:border-violet-800',
boolean: 'bg-amber-50 text-amber-500 border-amber-200 dark:bg-amber-900/20 dark:text-amber-400 dark:border-amber-800',
7 months ago
ling: 'bg-emerald-50 text-emerald-500 border-emerald-200 dark:bg-emerald-900/20 dark:text-emerald-400 dark:border-emerald-800',
7 months ago
date: 'bg-orange-50 text-orange-500 border-orange-200 dark:bg-orange-900/20 dark:text-orange-400 dark:border-orange-800',
datetime: 'bg-rose-50 text-rose-500 border-rose-200 dark:bg-rose-900/20 dark:text-rose-400 dark:border-rose-800',
url: 'bg-cyan-50 text-cyan-500 border-cyan-200 dark:bg-cyan-900/20 dark:text-cyan-400 dark:border-cyan-800',
currency: 'bg-lime-50 text-lime-500 border-lime-200 dark:bg-lime-900/20 dark:text-lime-400 dark:border-lime-800',
image: 'bg-pink-50 text-pink-500 border-pink-200 dark:bg-pink-900/20 dark:text-pink-400 dark:border-pink-800',
video: 'bg-red-50 text-red-500 border-red-200 dark:bg-red-900/20 dark:text-red-400 dark:border-red-800',
audio: 'bg-purple-50 text-purple-500 border-purple-200 dark:bg-purple-900/20 dark:text-purple-400 dark:border-purple-800',
pdf: 'bg-zinc-100 text-zinc-500 border-zinc-200 dark:bg-zinc-800 dark:text-zinc-400 dark:border-zinc-700',
file: 'bg-teal-50 text-teal-500 border-teal-200 dark:bg-teal-900/20 dark:text-teal-400 dark:border-teal-800',
group: 'bg-indigo-50 text-indigo-500 border-indigo-200 dark:bg-indigo-900/20 dark:text-indigo-400 dark:border-indigo-800',
}}
<span class={[
'rounded border px-1.5 py-0.5 font-mono text-[9px] font-bold uppercase tracking-widest',
colors[type] ?? '',
].join(' ')}>
{type}
</span>
{/each}
</div>
</div>
</div>
</div>
{/if}
</main>
<!-- ── Footer ─────────────────────────────────────────────────── -->
<footer class="border-t border-zinc-100 py-6 dark:border-zinc-800">
<p class="text-center font-mono text-[10px] text-zinc-300 dark:text-zinc-700">
meta-content · TypeScript · Svelte 5 · Tailwind CSS
</p>
</footer>
</div>
7 months ago
<style>
:global(*) {
font-family: 'Poppins', sans-serif !important;
}
:global(code), :global(pre) {
font-family: 'Poppins', sans-serif !important;
}
</style>

Powered by TurnKey Linux.