lumina-slides 8.9.4 → 9.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LUMINA_LLM_EXAMPLES.json +234 -0
- package/README.md +18 -18
- package/dist/lumina-slides.js +13207 -12659
- package/dist/lumina-slides.umd.cjs +215 -215
- package/dist/style.css +1 -1
- package/package.json +5 -4
- package/src/App.vue +16 -0
- package/src/animation/index.ts +11 -0
- package/src/animation/registry.ts +126 -0
- package/src/animation/stagger.ts +95 -0
- package/src/animation/types.ts +53 -0
- package/src/components/LandingPage.vue +229 -0
- package/src/components/LuminaDeck.vue +224 -0
- package/src/components/LuminaSpeakerNotes.vue +701 -0
- package/src/components/base/BaseSlide.vue +122 -0
- package/src/components/base/LuminaElement.vue +67 -0
- package/src/components/base/VideoPlayer.vue +204 -0
- package/src/components/layouts/LayoutAuto.vue +71 -0
- package/src/components/layouts/LayoutChart.vue +287 -0
- package/src/components/layouts/LayoutCustom.vue +92 -0
- package/src/components/layouts/LayoutDiagram.vue +253 -0
- package/src/components/layouts/LayoutFeatures.vue +121 -0
- package/src/components/layouts/LayoutFlex.vue +172 -0
- package/src/components/layouts/LayoutFree.vue +62 -0
- package/src/components/layouts/LayoutHalf.vue +127 -0
- package/src/components/layouts/LayoutStatement.vue +74 -0
- package/src/components/layouts/LayoutSteps.vue +106 -0
- package/src/components/layouts/LayoutTimeline.vue +104 -0
- package/src/components/layouts/LayoutVideo.vue +41 -0
- package/src/components/parts/FlexBullets.vue +45 -0
- package/src/components/parts/FlexButton.vue +132 -0
- package/src/components/parts/FlexImage.vue +54 -0
- package/src/components/parts/FlexOrdered.vue +44 -0
- package/src/components/parts/FlexSpacer.vue +13 -0
- package/src/components/parts/FlexStepper.vue +59 -0
- package/src/components/parts/FlexText.vue +29 -0
- package/src/components/parts/FlexTimeline.vue +67 -0
- package/src/components/parts/FlexTitle.vue +39 -0
- package/src/components/parts/LuminaBackground.vue +100 -0
- package/src/components/site/LivePreview.vue +101 -0
- package/src/components/site/SiteApi.vue +301 -0
- package/src/components/site/SiteDashboard.vue +604 -0
- package/src/components/site/SiteDocs.vue +3267 -0
- package/src/components/site/SiteExamples.vue +65 -0
- package/src/components/site/SiteFooter.vue +6 -0
- package/src/components/site/SiteHome.vue +362 -0
- package/src/components/site/SiteNavBar.vue +122 -0
- package/src/components/site/SitePlayground.vue +389 -0
- package/src/components/site/SitePromptBuilder.vue +266 -0
- package/src/components/site/SiteUserMenu.vue +90 -0
- package/src/components/studio/ActionEditor.vue +108 -0
- package/src/components/studio/ArrayEditor.vue +124 -0
- package/src/components/studio/CollapsibleSection.vue +33 -0
- package/src/components/studio/ColorField.vue +22 -0
- package/src/components/studio/EditorCanvas.vue +326 -0
- package/src/components/studio/EditorLayoutFeatures.vue +18 -0
- package/src/components/studio/EditorLayoutFixed.vue +46 -0
- package/src/components/studio/EditorLayoutFlex.vue +133 -0
- package/src/components/studio/EditorLayoutHalf.vue +18 -0
- package/src/components/studio/EditorLayoutStatement.vue +18 -0
- package/src/components/studio/EditorLayoutSteps.vue +18 -0
- package/src/components/studio/EditorLayoutTimeline.vue +18 -0
- package/src/components/studio/EditorNode.vue +89 -0
- package/src/components/studio/FieldEditor.vue +133 -0
- package/src/components/studio/IconPicker.vue +109 -0
- package/src/components/studio/LayerItem.vue +117 -0
- package/src/components/studio/LuminaStudio.vue +30 -0
- package/src/components/studio/SaveSuccessModal.vue +138 -0
- package/src/components/studio/SlideNavigator.vue +373 -0
- package/src/components/studio/SliderField.vue +44 -0
- package/src/components/studio/StudioInspector.vue +595 -0
- package/src/components/studio/StudioJsonEditor.vue +191 -0
- package/src/components/studio/StudioLayers.vue +145 -0
- package/src/components/studio/StudioSettings.vue +514 -0
- package/src/components/studio/StudioSidebar.vue +29 -0
- package/src/components/studio/StudioToolbar.vue +222 -0
- package/src/components/studio/fieldLabels.ts +224 -0
- package/src/components/studio/inspectors/DiagramEdgeEditor.vue +77 -0
- package/src/components/studio/inspectors/DiagramNodeEditor.vue +117 -0
- package/src/components/studio/nodes/StudioDiagramNode.vue +138 -0
- package/src/composables/useAuth.ts +87 -0
- package/src/composables/useEditor.ts +224 -0
- package/src/composables/useElementState.ts +81 -0
- package/src/composables/useFlexLayout.ts +122 -0
- package/src/composables/useKeyboard.ts +45 -0
- package/src/composables/useLumina.ts +32 -0
- package/src/composables/useStudio.ts +87 -0
- package/src/composables/useSwipeNav.ts +53 -0
- package/src/composables/useTransition.ts +373 -0
- package/src/core/Lumina.ts +819 -0
- package/src/core/animationConfig.ts +251 -0
- package/src/core/compression.ts +34 -0
- package/src/core/elementController.ts +170 -0
- package/src/core/elementId.ts +27 -0
- package/src/core/elementResolver.ts +207 -0
- package/src/core/events.ts +53 -0
- package/src/core/fonts.ts +100 -0
- package/src/core/presets.ts +231 -0
- package/src/core/prompts.ts +272 -0
- package/src/core/schema.ts +478 -0
- package/src/core/speaker-channel.ts +250 -0
- package/src/core/store.ts +461 -0
- package/src/core/theme.ts +666 -0
- package/src/core/types.ts +1611 -0
- package/src/directives/vStudio.ts +45 -0
- package/src/index.ts +175 -0
- package/src/main.ts +17 -0
- package/src/router/index.ts +92 -0
- package/src/style/main.css +462 -0
- package/src/utils/deep.ts +127 -0
- package/src/utils/firebase.ts +184 -0
- package/src/utils/streaming.ts +134 -0
- package/src/views/DashboardView.vue +32 -0
- package/src/views/DeckView.vue +289 -0
- package/src/views/HomeView.vue +17 -0
- package/src/views/SiteLayout.vue +21 -0
- package/src/views/StudioView.vue +61 -0
- package/src/vite-env.d.ts +6 -0
- package/IMPLEMENTATION.md +0 -418
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="absolute inset-0 z-0 overflow-hidden pointer-events-none select-none" ref="containerRef">
|
|
3
|
+
<!-- Ambient Orb -->
|
|
4
|
+
<div ref="orbRef" class="ambient-orb" :style="orbStyle"></div>
|
|
5
|
+
<!-- Noise Texture -->
|
|
6
|
+
<div class="absolute inset-0 opacity-[0.04]"
|
|
7
|
+
style="background-image: url('https://grainy-gradients.vercel.app/noise.svg');"></div>
|
|
8
|
+
</div>
|
|
9
|
+
</template>
|
|
10
|
+
|
|
11
|
+
<script setup lang="ts">
|
|
12
|
+
import { computed, onMounted, onUnmounted, ref, watch } from 'vue';
|
|
13
|
+
import gsap from 'gsap';
|
|
14
|
+
|
|
15
|
+
const props = withDefaults(defineProps<{
|
|
16
|
+
orbColor?: string;
|
|
17
|
+
orbPos?: { top: string; left: string };
|
|
18
|
+
orbOpacity?: number;
|
|
19
|
+
orbBlur?: string;
|
|
20
|
+
orbSize?: string;
|
|
21
|
+
}>(), {
|
|
22
|
+
orbColor: 'var(--lumina-color-primary, #3b82f6)',
|
|
23
|
+
orbPos: () => ({ top: '-20%', left: '-10%' }),
|
|
24
|
+
orbSize: 'var(--lumina-orb-size, 60vw)',
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
const orbRef = ref<HTMLElement | null>(null);
|
|
28
|
+
const containerRef = ref<HTMLElement | null>(null);
|
|
29
|
+
let ctx: gsap.Context | null = null; // GSAP Context for cleanup
|
|
30
|
+
|
|
31
|
+
const orbStyle = computed(() => {
|
|
32
|
+
return {
|
|
33
|
+
backgroundColor: props.orbColor,
|
|
34
|
+
width: props.orbSize,
|
|
35
|
+
height: props.orbSize,
|
|
36
|
+
top: props.orbPos.top,
|
|
37
|
+
left: props.orbPos.left,
|
|
38
|
+
transition: 'top 1.2s cubic-bezier(0.2, 0.8, 0.2, 1), left 1.2s cubic-bezier(0.2, 0.8, 0.2, 1)' // Smoother transition
|
|
39
|
+
};
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
// Setup GSAP Animations
|
|
43
|
+
onMounted(() => {
|
|
44
|
+
ctx = gsap.context(() => {
|
|
45
|
+
if (!orbRef.value) return;
|
|
46
|
+
|
|
47
|
+
// 1. Breathing Animation (Subtle Pulse)
|
|
48
|
+
gsap.to(orbRef.value, {
|
|
49
|
+
scale: 1.1,
|
|
50
|
+
opacity: '+=0.1',
|
|
51
|
+
duration: 8,
|
|
52
|
+
ease: 'sine.inOut',
|
|
53
|
+
yoyo: true,
|
|
54
|
+
repeat: -1
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
// 2. Mouse Parallax Effect
|
|
58
|
+
const onMouseMove = (e: MouseEvent) => {
|
|
59
|
+
const { innerWidth, innerHeight } = window;
|
|
60
|
+
const x = (e.clientX / innerWidth - 0.5) * 50; // Move 50px max
|
|
61
|
+
const y = (e.clientY / innerHeight - 0.5) * 50;
|
|
62
|
+
|
|
63
|
+
gsap.to(orbRef.value, {
|
|
64
|
+
x: x,
|
|
65
|
+
y: y,
|
|
66
|
+
duration: 2,
|
|
67
|
+
ease: 'power2.out',
|
|
68
|
+
overwrite: 'auto'
|
|
69
|
+
});
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
window.addEventListener('mousemove', onMouseMove);
|
|
73
|
+
|
|
74
|
+
// Cleanup listener inside context cleanup? No, GSAP context doesn't auto-remove event listeners.
|
|
75
|
+
// We need to remove it manually.
|
|
76
|
+
// But we can store the remove function to call it in onUnmounted.
|
|
77
|
+
return () => window.removeEventListener('mousemove', onMouseMove);
|
|
78
|
+
|
|
79
|
+
}, containerRef.value || undefined);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
onUnmounted(() => {
|
|
83
|
+
ctx?.revert(); // Restores original state and kills animations
|
|
84
|
+
});
|
|
85
|
+
</script>
|
|
86
|
+
|
|
87
|
+
<style scoped>
|
|
88
|
+
/* Ensure the orb style is available locally even if global css fails (fallback) */
|
|
89
|
+
.ambient-orb {
|
|
90
|
+
position: absolute;
|
|
91
|
+
border-radius: 50%;
|
|
92
|
+
/* Use global variables injected by ThemeManager */
|
|
93
|
+
filter: blur(var(--lumina-orb-blur, 120px));
|
|
94
|
+
opacity: var(--lumina-orb-opacity-effective, 0.20);
|
|
95
|
+
pointer-events: none;
|
|
96
|
+
will-change: transform, top, left;
|
|
97
|
+
/* Hint to browser */
|
|
98
|
+
transform-origin: center center;
|
|
99
|
+
}
|
|
100
|
+
</style>
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="flex flex-col lg:flex-row gap-6 h-[600px] lg:h-[400px] not-prose">
|
|
3
|
+
<!-- Editor -->
|
|
4
|
+
<div class="w-full lg:w-1/3 flex flex-col bg-[#0f0f0f] border border-white/10 rounded-xl overflow-hidden">
|
|
5
|
+
<div class="bg-white/5 px-4 py-2 border-b border-white/10 flex justify-between items-center">
|
|
6
|
+
<span class="text-xs font-bold text-white/50 uppercase tracking-wider">JSON Editor</span>
|
|
7
|
+
<span v-if="error" class="text-xs text-red-400">{{ error }}</span>
|
|
8
|
+
</div>
|
|
9
|
+
<textarea v-model="code"
|
|
10
|
+
class="flex-1 bg-transparent p-4 font-mono text-xs text-blue-300 resize-none focus:outline-none leading-relaxed"
|
|
11
|
+
spellcheck="false"></textarea>
|
|
12
|
+
</div>
|
|
13
|
+
|
|
14
|
+
<!-- Preview -->
|
|
15
|
+
<div class="w-full lg:w-2/3 bg-black border border-white/10 rounded-xl overflow-hidden relative group">
|
|
16
|
+
<div :id="containerId" class="w-full h-full"></div>
|
|
17
|
+
|
|
18
|
+
<!-- Reset Button (Visible on hover) -->
|
|
19
|
+
<button @click="reset"
|
|
20
|
+
class="absolute top-4 right-4 px-3 py-1.5 bg-black/50 hover:bg-black/80 text-white/50 hover:text-white text-xs rounded-lg backdrop-blur-sm border border-white/10 transition opacity-0 group-hover:opacity-100">
|
|
21
|
+
<i class="ph-thin ph-arrow-counter-clockwise mr-1"></i> Reset
|
|
22
|
+
</button>
|
|
23
|
+
</div>
|
|
24
|
+
</div>
|
|
25
|
+
</template>
|
|
26
|
+
|
|
27
|
+
<script setup lang="ts">
|
|
28
|
+
import { ref, onMounted, onUnmounted, watch, nextTick } from 'vue';
|
|
29
|
+
import { Lumina } from '../../core/Lumina';
|
|
30
|
+
import { parsePartialJson } from '../../utils/streaming';
|
|
31
|
+
|
|
32
|
+
const props = defineProps<{
|
|
33
|
+
initialCode: string;
|
|
34
|
+
}>();
|
|
35
|
+
|
|
36
|
+
const code = ref(props.initialCode);
|
|
37
|
+
const error = ref<string | null>(null);
|
|
38
|
+
const containerId = `preview-${Math.random().toString(36).substr(2, 9)}`;
|
|
39
|
+
let engine: Lumina | null = null;
|
|
40
|
+
|
|
41
|
+
function initEngine() {
|
|
42
|
+
if (engine) engine.destroy();
|
|
43
|
+
|
|
44
|
+
// Clear container
|
|
45
|
+
const el = document.getElementById(containerId);
|
|
46
|
+
if (el) el.innerHTML = '';
|
|
47
|
+
|
|
48
|
+
// Initialize
|
|
49
|
+
engine = new Lumina(`#${containerId}`, {
|
|
50
|
+
ui: { visible: true, showControls: true, showProgressBar: false },
|
|
51
|
+
keyboard: false,
|
|
52
|
+
animation: { enabled: true, durationIn: 0.5 }
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
updateSlide();
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function updateSlide() {
|
|
59
|
+
if (!engine) return;
|
|
60
|
+
|
|
61
|
+
try {
|
|
62
|
+
const parsed = parsePartialJson(code.value);
|
|
63
|
+
if (parsed && typeof parsed === 'object') {
|
|
64
|
+
// Force container sizing for docs
|
|
65
|
+
if (Array.isArray(parsed.slides)) {
|
|
66
|
+
parsed.slides.forEach((s: any) => s.sizing = 'container');
|
|
67
|
+
engine.load(parsed);
|
|
68
|
+
} else {
|
|
69
|
+
// If it's a single slide object, wrap it
|
|
70
|
+
engine.load({
|
|
71
|
+
meta: { title: "Preview" },
|
|
72
|
+
slides: [{ ...parsed, sizing: 'container' }]
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
error.value = null;
|
|
76
|
+
}
|
|
77
|
+
} catch (e) {
|
|
78
|
+
// Silent fail on partial parse, but maybe show error if strictly invalid JSON?
|
|
79
|
+
// parsePartialJson is very forgiving.
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function reset() {
|
|
84
|
+
code.value = props.initialCode;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
watch(code, () => {
|
|
88
|
+
updateSlide();
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
onMounted(() => {
|
|
92
|
+
// Delay init to ensure DOM is ready
|
|
93
|
+
setTimeout(() => {
|
|
94
|
+
initEngine();
|
|
95
|
+
}, 100);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
onUnmounted(() => {
|
|
99
|
+
if (engine) engine.destroy();
|
|
100
|
+
});
|
|
101
|
+
</script>
|
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="min-h-screen pt-32 px-8 max-w-7xl mx-auto pb-32">
|
|
3
|
+
<!-- Unified Header -->
|
|
4
|
+
<div class="text-center mb-16 max-w-3xl mx-auto">
|
|
5
|
+
<h1 class="text-4xl md:text-5xl font-black mb-4 tracking-tight text-white">API Reference</h1>
|
|
6
|
+
<p class="text-lg text-white/50 leading-relaxed">Technical documentation and module reference for the Lumina
|
|
7
|
+
Engine.</p>
|
|
8
|
+
</div>
|
|
9
|
+
|
|
10
|
+
<div v-if="loading" class="text-white/50 animate-pulse text-center">Generated docs loading...</div>
|
|
11
|
+
<div v-else-if="error" class="text-red-400 text-center">Failed to load API docs. {{ error }}</div>
|
|
12
|
+
|
|
13
|
+
<div v-else class="space-y-24">
|
|
14
|
+
<div v-for="kind in apiData" :key="kind.name" class="space-y-12">
|
|
15
|
+
<div class="border-b border-white/10 pb-12 mb-12">
|
|
16
|
+
<h2 class="text-3xl font-bold text-white mb-2">{{ kind.name }}</h2>
|
|
17
|
+
<p class="text-blue-400 font-mono text-sm tracking-widest uppercase">@PailletJuanPablo/lumina-slides
|
|
18
|
+
</p>
|
|
19
|
+
</div>
|
|
20
|
+
|
|
21
|
+
<!-- 1. Classes -->
|
|
22
|
+
<div v-if="hasChildren(kind, [128])" class="space-y-16">
|
|
23
|
+
<div v-for="cls in getChildren(kind, [128])" :key="cls.id" class="space-y-6">
|
|
24
|
+
<div class="flex items-baseline gap-4">
|
|
25
|
+
<h3 class="text-3xl font-bold text-emerald-400">{{ cls.name }}</h3>
|
|
26
|
+
<span
|
|
27
|
+
class="px-3 py-1 rounded-full bg-emerald-500/10 text-emerald-300 text-xs font-bold uppercase tracking-wider">Class</span>
|
|
28
|
+
</div>
|
|
29
|
+
|
|
30
|
+
<div class="prose prose-invert max-w-none text-white/80 text-lg leading-relaxed"
|
|
31
|
+
v-html="renderComment(cls.comment)"></div>
|
|
32
|
+
|
|
33
|
+
<!-- Examples -->
|
|
34
|
+
<div v-if="getDbTags(cls.comment, '@example').length" class="space-y-4 my-8">
|
|
35
|
+
<div v-for="(example, i) in getDbTags(cls.comment, '@example')" :key="i"
|
|
36
|
+
class="rounded-xl overflow-hidden border border-white/10 bg-[#0A0A0A]">
|
|
37
|
+
<div
|
|
38
|
+
class="px-4 py-2 bg-white/5 border-b border-white/5 text-xs font-bold text-white/40 uppercase">
|
|
39
|
+
Example
|
|
40
|
+
</div>
|
|
41
|
+
<div class="p-6 font-mono text-sm text-blue-200 whitespace-pre-wrap overflow-x-auto"
|
|
42
|
+
v-html="renderMarkdown(example)"></div>
|
|
43
|
+
</div>
|
|
44
|
+
</div>
|
|
45
|
+
|
|
46
|
+
<!-- Constructor -->
|
|
47
|
+
<div class="bg-white/5 rounded-2xl border border-white/10 overflow-hidden">
|
|
48
|
+
<div class="px-8 py-6 border-b border-white/5">
|
|
49
|
+
<h4 class="text-xl font-bold">Constructor</h4>
|
|
50
|
+
</div>
|
|
51
|
+
<div class="p-8 space-y-8">
|
|
52
|
+
<div v-for="method in getChildren(cls, [512])" :key="method.id">
|
|
53
|
+
<code class="text-lg md:text-xl font-bold text-emerald-200 block mb-4">
|
|
54
|
+
new {{ cls.name }}(<span v-html="renderParams(method.signatures[0])"></span>)
|
|
55
|
+
</code>
|
|
56
|
+
<div class="text-white/70 mb-4"
|
|
57
|
+
v-html="renderComment(method.signatures[0].comment)"></div>
|
|
58
|
+
<div v-if="method.signatures[0].parameters" class="space-y-2">
|
|
59
|
+
<div v-for="param in method.signatures[0].parameters" :key="param.id"
|
|
60
|
+
class="flex gap-4 text-sm">
|
|
61
|
+
<div class="w-32 shrink-0 font-mono text-purple-300">{{ param.name }}:</div>
|
|
62
|
+
<div class="text-white/50">{{ renderType(param.type) }}</div>
|
|
63
|
+
<div class="text-white/70" v-html="renderComment(param.comment)"></div>
|
|
64
|
+
</div>
|
|
65
|
+
</div>
|
|
66
|
+
</div>
|
|
67
|
+
</div>
|
|
68
|
+
</div>
|
|
69
|
+
|
|
70
|
+
<!-- Methods -->
|
|
71
|
+
<div v-if="getChildren(cls, [2048]).length">
|
|
72
|
+
<h4 class="text-2xl font-bold mb-6 mt-12">Methods</h4>
|
|
73
|
+
<div class="grid gap-6">
|
|
74
|
+
<div v-for="method in getChildren(cls, [2048])" :key="method.id"
|
|
75
|
+
class="bg-white/5 p-8 rounded-2xl border border-white/10 hover:border-emerald-500/30 transition duration-300">
|
|
76
|
+
<div class="flex items-start justify-between mb-4">
|
|
77
|
+
<code class="text-xl font-bold text-emerald-200">
|
|
78
|
+
{{ method.name }}(<span v-html="renderParams(method.signatures[0])"></span>)
|
|
79
|
+
</code>
|
|
80
|
+
<span class="text-xs font-mono text-white/40">Points: {{ method.id }}</span>
|
|
81
|
+
</div>
|
|
82
|
+
|
|
83
|
+
<div class="prose prose-invert max-w-none text-white/70 mb-4"
|
|
84
|
+
v-html="renderComment(method.signatures[0].comment)"></div>
|
|
85
|
+
|
|
86
|
+
<!-- Method Params Table -->
|
|
87
|
+
<div v-if="method.signatures[0].parameters"
|
|
88
|
+
class="mt-6 pt-6 border-t border-white/5">
|
|
89
|
+
<h5 class="text-xs font-bold uppercase text-white/40 mb-3">Parameters</h5>
|
|
90
|
+
<div class="space-y-3">
|
|
91
|
+
<div v-for="param in method.signatures[0].parameters" :key="param.id"
|
|
92
|
+
class="grid grid-cols-[120px_1fr] md:grid-cols-[150px_200px_1fr] gap-4 text-sm">
|
|
93
|
+
<div class="font-mono text-purple-300">{{ param.name }}</div>
|
|
94
|
+
<div class="font-mono text-white/50 truncate"
|
|
95
|
+
v-html="renderType(param.type, true)"></div>
|
|
96
|
+
<div class="text-white/70" v-html="renderComment(param.comment)"></div>
|
|
97
|
+
</div>
|
|
98
|
+
</div>
|
|
99
|
+
</div>
|
|
100
|
+
</div>
|
|
101
|
+
</div>
|
|
102
|
+
</div>
|
|
103
|
+
</div>
|
|
104
|
+
</div>
|
|
105
|
+
|
|
106
|
+
<!-- 2. Interfaces -->
|
|
107
|
+
<div v-if="hasChildren(kind, [256])" class="space-y-12">
|
|
108
|
+
<h3 class="text-3xl font-bold text-pink-400 border-b border-white/10 pb-4">Interfaces & Types</h3>
|
|
109
|
+
<div class="grid gap-12">
|
|
110
|
+
<div v-for="iface in getChildren(kind, [256])" :key="iface.id" class="space-y-6">
|
|
111
|
+
<div class="flex items-baseline gap-4">
|
|
112
|
+
<h4 class="text-2xl font-bold text-pink-300">{{ iface.name }}</h4>
|
|
113
|
+
<span
|
|
114
|
+
class="px-2 py-0.5 rounded bg-pink-500/10 text-pink-300 text-xs font-bold uppercase">Interface</span>
|
|
115
|
+
</div>
|
|
116
|
+
<div class="prose prose-invert max-w-none text-white/80"
|
|
117
|
+
v-html="renderComment(iface.comment)"></div>
|
|
118
|
+
|
|
119
|
+
<!-- Examples -->
|
|
120
|
+
<div v-if="getDbTags(iface.comment, '@example').length" class="space-y-4 my-6">
|
|
121
|
+
<div v-for="(example, i) in getDbTags(iface.comment, '@example')" :key="i"
|
|
122
|
+
class="rounded-xl overflow-hidden border border-white/10 bg-[#0A0A0A]">
|
|
123
|
+
<div
|
|
124
|
+
class="px-4 py-2 bg-white/5 border-b border-white/5 text-xs font-bold text-white/40 uppercase">
|
|
125
|
+
Example Structure
|
|
126
|
+
</div>
|
|
127
|
+
<div class="p-6 font-mono text-xs md:text-sm text-blue-200 whitespace-pre-wrap overflow-x-auto"
|
|
128
|
+
v-html="renderMarkdown(example)"></div>
|
|
129
|
+
</div>
|
|
130
|
+
</div>
|
|
131
|
+
|
|
132
|
+
<!-- Interface Properties -->
|
|
133
|
+
<div v-if="getChildren(iface, [1024]).length"
|
|
134
|
+
class="bg-white/5 rounded-xl border border-white/10 overflow-hidden">
|
|
135
|
+
<div
|
|
136
|
+
class="grid grid-cols-[200px_1fr_1fr] gap-0 text-sm font-bold bg-white/5 border-b border-white/10 p-4 text-white/40 uppercase tracking-wider">
|
|
137
|
+
<div>Property</div>
|
|
138
|
+
<div>Type</div>
|
|
139
|
+
<div>Description</div>
|
|
140
|
+
</div>
|
|
141
|
+
<div class="divide-y divide-white/5">
|
|
142
|
+
<div v-for="prop in getChildren(iface, [1024])" :key="prop.id"
|
|
143
|
+
class="grid grid-cols-[200px_1fr_1fr] gap-4 p-4 hover:bg-white/5 transition">
|
|
144
|
+
<div class="font-mono text-purple-300">{{ prop.name }}<span
|
|
145
|
+
v-if="prop.flags.isOptional" class="text-white/30">?</span></div>
|
|
146
|
+
<div class="font-mono text-white/60 break-words"
|
|
147
|
+
v-html="renderType(prop.type, true)"></div>
|
|
148
|
+
<div class="text-white/70" v-html="renderComment(prop.comment)"></div>
|
|
149
|
+
</div>
|
|
150
|
+
</div>
|
|
151
|
+
</div>
|
|
152
|
+
</div>
|
|
153
|
+
</div>
|
|
154
|
+
</div>
|
|
155
|
+
|
|
156
|
+
</div>
|
|
157
|
+
</div>
|
|
158
|
+
</div>
|
|
159
|
+
</template>
|
|
160
|
+
|
|
161
|
+
<script setup lang="ts">
|
|
162
|
+
import { ref, onMounted } from 'vue';
|
|
163
|
+
|
|
164
|
+
const apiData = ref<any[]>([]);
|
|
165
|
+
const loading = ref(true);
|
|
166
|
+
const error = ref<string | null>(null);
|
|
167
|
+
|
|
168
|
+
// Kind Mapping for TypeDoc
|
|
169
|
+
const KIND_MAP: Record<number, string> = {
|
|
170
|
+
32: 'Variable',
|
|
171
|
+
64: 'Function',
|
|
172
|
+
128: 'Class',
|
|
173
|
+
256: 'Interface',
|
|
174
|
+
512: 'Constructor',
|
|
175
|
+
1024: 'Property',
|
|
176
|
+
2048: 'Method',
|
|
177
|
+
4194304: 'Type'
|
|
178
|
+
};
|
|
179
|
+
const hasChildren = (node: any, kinds: number[]) => node.children?.some((c: any) => kinds.includes(c.kind));
|
|
180
|
+
const getChildren = (node: any, kinds: number[]) => node.children ? node.children.filter((c: any) => kinds.includes(c.kind)) : [];
|
|
181
|
+
|
|
182
|
+
// --- Content Rendering Helpers ---
|
|
183
|
+
|
|
184
|
+
const renderComment = (comment: any) => {
|
|
185
|
+
if (!comment?.summary) return '';
|
|
186
|
+
const text = comment.summary.map((s: any) => s.text).join('');
|
|
187
|
+
return renderMarkdown(text);
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
// Simple Markdown Parser (Bold, Code, Links)
|
|
191
|
+
const renderMarkdown = (text: string) => {
|
|
192
|
+
if (!text) return '';
|
|
193
|
+
return text
|
|
194
|
+
.replace(/```([\s\S]*?)```/g, '<pre class="bg-black/50 p-4 rounded-lg my-2"><code class="text-sm">$1</code></pre>') // Code Blocks
|
|
195
|
+
.replace(/`([^`]+)`/g, '<code class="bg-white/10 px-1.5 py-0.5 rounded text-sm font-mono text-blue-200">$1</code>') // Inline Code
|
|
196
|
+
.replace(/\*\*(.*?)\*\*/g, '<strong class="text-white">$1</strong>') // Bold
|
|
197
|
+
.replace(/\n/g, '<br>'); // Line Breaks
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
const getDbTags = (comment: any, tagName: string) => {
|
|
201
|
+
if (!comment?.blockTags) return [];
|
|
202
|
+
return comment.blockTags
|
|
203
|
+
.filter((t: any) => t.tag === tagName)
|
|
204
|
+
.map((t: any) => t.content.map((c: any) => c.text).join(''));
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
// --- Type Resolution & Rendering ---
|
|
208
|
+
const findTypeByName = (name: string) => {
|
|
209
|
+
// Search top-level children for the type definition
|
|
210
|
+
if (!apiData.value[0]?.children) return null;
|
|
211
|
+
return apiData.value[0].children.find((c: any) => c.name === name);
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
const renderType = (type: any, expandRefs = false, depth = 0): string => {
|
|
215
|
+
if (!type) return 'void';
|
|
216
|
+
if (depth > 2) return '<span class="text-white/30">...</span>'; // Prevent infinite recursion
|
|
217
|
+
|
|
218
|
+
// String Logic
|
|
219
|
+
if (type.type === 'intrinsic') return `<span class="text-blue-300 font-mono">${type.name}</span>`;
|
|
220
|
+
if (type.type === 'literal') return `<span class="text-green-300 font-mono">'${type.value}'</span>`;
|
|
221
|
+
|
|
222
|
+
// References (Links or Expanded)
|
|
223
|
+
if (type.type === 'reference') {
|
|
224
|
+
const typeName = type.name;
|
|
225
|
+
|
|
226
|
+
// Always expand specific complex types inline for better DX
|
|
227
|
+
const typesToExpand = ['SlideType', 'LuminaEventType', 'ThemeConfig', 'LuminaKeyBindings', 'LuminaUIOptions', 'LuminaAnimationOptions'];
|
|
228
|
+
if (expandRefs || typesToExpand.includes(typeName)) {
|
|
229
|
+
const def = findTypeByName(typeName);
|
|
230
|
+
if (def) {
|
|
231
|
+
// If it's a type alias (union/reflection), render it
|
|
232
|
+
if (def.kind === 4194304 && def.type) return renderType(def.type, false, depth + 1);
|
|
233
|
+
// If it's an interface (likely has properties)
|
|
234
|
+
if (def.kind === 256) {
|
|
235
|
+
// Check if it has children properties we can render as an object literal
|
|
236
|
+
const props = getChildren(def, [1024]);
|
|
237
|
+
if (props && props.length) {
|
|
238
|
+
// Build synthetic reflection to reuse object rendering logic
|
|
239
|
+
return renderType({
|
|
240
|
+
type: 'reflection',
|
|
241
|
+
declaration: { children: props }
|
|
242
|
+
}, false, depth + 1);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
return `<span class="text-yellow-300 font-bold border-b border-white/10 decoration-dotted cursor-help" title="Reference">${typeName}</span>`;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// Unions (e.g. 'a' | 'b')
|
|
252
|
+
if (type.type === 'union') {
|
|
253
|
+
return type.types.map((t: any) => renderType(t, expandRefs, depth)).join('<span class="text-white/40 mx-2">|</span>');
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// Arrays (e.g. Slide[])
|
|
257
|
+
if (type.type === 'array') {
|
|
258
|
+
return `${renderType(type.elementType, expandRefs, depth)}<span class="text-white/60">[]</span>`;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// Reflection (Object literals: { a: string })
|
|
262
|
+
if (type.type === 'reflection' && type.declaration) {
|
|
263
|
+
if (type.declaration.children) {
|
|
264
|
+
const props = type.declaration.children.map((c: any) => {
|
|
265
|
+
return `<span class="text-purple-300">${c.name}</span>: ${renderType(c.type, false, depth + 1)}`;
|
|
266
|
+
});
|
|
267
|
+
return `{ <span class="text-white/80">${props.join(', ')}</span> }`;
|
|
268
|
+
}
|
|
269
|
+
return 'object';
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
return 'any';
|
|
273
|
+
};
|
|
274
|
+
|
|
275
|
+
const renderParams = (sig: any) => {
|
|
276
|
+
if (!sig?.parameters) return '';
|
|
277
|
+
return sig.parameters.map((p: any) => `<span class="text-purple-300">${p.name}</span>: ${renderType(p.type)}`).join(', ');
|
|
278
|
+
};
|
|
279
|
+
|
|
280
|
+
onMounted(async () => {
|
|
281
|
+
try {
|
|
282
|
+
// Use relative path to ensure it works on both local and GH pages
|
|
283
|
+
// The base URL is handled by the browser resolving relative paths
|
|
284
|
+
const res = await fetch('./api-docs.json');
|
|
285
|
+
if (!res.ok) {
|
|
286
|
+
console.error('Fetch failed:', res.status, res.statusText, res.url);
|
|
287
|
+
throw new Error(`Docs not found (${res.status})`);
|
|
288
|
+
}
|
|
289
|
+
const json = await res.json();
|
|
290
|
+
// TypeDoc structure often wraps everything in a root object
|
|
291
|
+
// We ensure we have an array for the loop
|
|
292
|
+
if (json.children) apiData.value = [json];
|
|
293
|
+
else apiData.value = [];
|
|
294
|
+
|
|
295
|
+
} catch (e) {
|
|
296
|
+
error.value = (e as Error).message;
|
|
297
|
+
} finally {
|
|
298
|
+
loading.value = false;
|
|
299
|
+
}
|
|
300
|
+
});
|
|
301
|
+
</script>
|