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,45 @@
|
|
|
1
|
+
import { Directive, DirectiveBinding } from 'vue';
|
|
2
|
+
|
|
3
|
+
export const vStudio: Directive = {
|
|
4
|
+
mounted(el: HTMLElement, binding: DirectiveBinding) {
|
|
5
|
+
// binding.value should be the path string e.g. "elements.0.title"
|
|
6
|
+
|
|
7
|
+
// We use a data attribute to store the path for debugging/logic
|
|
8
|
+
el.dataset.studioPath = binding.value;
|
|
9
|
+
|
|
10
|
+
// Hover Effect
|
|
11
|
+
el.addEventListener('mouseenter', () => {
|
|
12
|
+
// Only if studio mode is enabled?
|
|
13
|
+
// We can check a global class on body or just always emit and let parent decide.
|
|
14
|
+
// Better: check if a specific class exists on a parent, or use a store if possible (hard in directive).
|
|
15
|
+
// For now, let's just add a class that we can style in CSS only when .studio-mode is active.
|
|
16
|
+
el.classList.add('studio-hover');
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
el.addEventListener('mouseleave', () => {
|
|
20
|
+
el.classList.remove('studio-hover');
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
// Click Handler
|
|
24
|
+
el.addEventListener('click', (e) => {
|
|
25
|
+
// We want to stop propagation so we select the most specific element
|
|
26
|
+
e.stopPropagation();
|
|
27
|
+
|
|
28
|
+
const rect = el.getBoundingClientRect();
|
|
29
|
+
|
|
30
|
+
const event = new CustomEvent('studio-select', {
|
|
31
|
+
bubbles: true,
|
|
32
|
+
detail: {
|
|
33
|
+
path: binding.value,
|
|
34
|
+
rect: rect
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
el.dispatchEvent(event);
|
|
39
|
+
});
|
|
40
|
+
},
|
|
41
|
+
|
|
42
|
+
updated(el: HTMLElement, binding: DirectiveBinding) {
|
|
43
|
+
el.dataset.studioPath = binding.value;
|
|
44
|
+
}
|
|
45
|
+
};
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lumina-slides — Renders declarative JSON into animated slide decks. Vanilla JS; Vue not required.
|
|
3
|
+
*
|
|
4
|
+
* ---
|
|
5
|
+
* ## Quick reference for LLMs and code agents
|
|
6
|
+
*
|
|
7
|
+
* **Entry:** `import { Lumina } from "lumina-slides"; import "lumina-slides/style.css";`
|
|
8
|
+
* `const engine = new Lumina("#app", options); engine.load(deck);`
|
|
9
|
+
*
|
|
10
|
+
* **Deck:** `{ meta: { title, initialElementState?, elementControl?, effects? }, slides: [...] }`
|
|
11
|
+
* Slide types: statement, features, half, timeline, steps, flex, chart, diagram, custom, video.
|
|
12
|
+
*
|
|
13
|
+
* **Element control:** `engine.element(id)` or `engine.element(slideIndex, path)` → ElementController:
|
|
14
|
+
* `.show()`, `.hide()`, `.toggle()`, `.opacity(n)`, `.transform(s)`, `.animate({ preset?, from?, to?, duration?, ease? })`.
|
|
15
|
+
* Presets: fadeUp, fadeIn, scaleIn, slideLeft, slideRight, zoomIn, blurIn, spring, drop, fadeOut. `to` optional when using preset.
|
|
16
|
+
* Ids: `engine.elements(slideIndex)` or `s{N}-{path}` (e.g. s0-tag, s1-features-0). `meta.initialElementState`:
|
|
17
|
+
* `{ [id]: { visible?: false } }` to start hidden; then `engine.element(id).show()`.
|
|
18
|
+
*
|
|
19
|
+
* **Helpers:** `hideAll(slideIndex?)`, `showAll(slideIndex?)`, `showOnly(ids?)`, `hideAllExcept(exceptIds?)`,
|
|
20
|
+
* `resetSlide(slideIndex?)`, `delay(ms)`, `revealInSequence(slideIndex?, { delayMs?, staggerMode?, preset?, from?, to?, duration?, ease?, hideFirst?, only?, exclude? })`.
|
|
21
|
+
* StaggerMode: sequential, center-out, ends-in, wave, random. Animation: `getPreset`, `registerPreset`, `computeStagger`, `BUILTIN_PRESETS`.
|
|
22
|
+
*
|
|
23
|
+
* **Timeline (Remotion-style):** `slide.timelineTracks` = `{ [id]: { "0": { opacity?, x?, y?, scale?, rotate?, visible? }, "0.3": {...}, "1": {...} } }`.
|
|
24
|
+
* `type: "free"` layout: `elements: [{ type: "text"|"image"|"box", text?, src?, width?, height?, ... }]` — absolute positioning, storytelling.
|
|
25
|
+
* `engine.seekTo(progress)`, `engine.playTimeline(duration?)` → `[Promise, cancel]`, `engine.timelineProgress`, `element(id).animateToProgress(progress, keyframes)`. Example: *Animation: Timeline* in Examples.
|
|
26
|
+
*
|
|
27
|
+
* **Animation config:** All timings/eases overridable via `options.animation`, `deck.meta.effects`, `slide.meta.effects`.
|
|
28
|
+
* `resolveTransitionConfig(store, slide?)` returns merged config. `ANIMATION_DEFAULTS` has all keys.
|
|
29
|
+
* Keys: durationIn, durationOut, stagger, ease, revealDelayMs, revealDuration, revealEase, elementDuration, elementEase, easeOut, etc.
|
|
30
|
+
*
|
|
31
|
+
* **Events:** `engine.on("ready"|"slideChange"|"action"|"error"|"patch"|"destroy"|"navigate"|"themeChange"|"speakerNotesOpen"|"speakerNotesClose"|"timelineSeek"|"timelineComplete"|"revealStart"|"revealComplete"|"revealElement", handler)`. `engine.emitError(err)` to forward errors.
|
|
32
|
+
* **State for LLM:** `engine.exportState()` → `{ currentSlide, narrative, history }`.
|
|
33
|
+
* **Key-value store:** `engine.data.get(key)`, `engine.data.set(key, value)`, `engine.data.has(key)`, `engine.data.delete(key)`, `engine.data.clear()`, `engine.data.keys()`. Persists across deck loads.
|
|
34
|
+
*
|
|
35
|
+
* **Streaming:** `parsePartialJson(buffer)` then `engine.load(json)`. Or `createDebouncedLoader(engine.load, ms)`.
|
|
36
|
+
* **Prompts:** `generateSystemPrompt({ mode, includeSchema, includeTheming })`, `generateThemePrompt()`.
|
|
37
|
+
* **Schema:** `getLuminaJsonSchema()`.
|
|
38
|
+
*
|
|
39
|
+
* **More:** IMPLEMENTATION.md, AGENTS.md, ELEMENT_IDS.md. Full JSDoc: this package includes `src/`; read
|
|
40
|
+
* `src/core/Lumina.ts`, `src/core/animationConfig.ts`, `src/core/types.ts`. Minified `dist/` strips comments.
|
|
41
|
+
*/
|
|
42
|
+
export { Lumina } from './core/Lumina';
|
|
43
|
+
|
|
44
|
+
export type {
|
|
45
|
+
LuminaOptions,
|
|
46
|
+
LuminaDataStore,
|
|
47
|
+
Deck,
|
|
48
|
+
BaseSlideData as SlideData,
|
|
49
|
+
ThemeConfig,
|
|
50
|
+
LuminaEventMap,
|
|
51
|
+
// Detailed Types for Docs
|
|
52
|
+
DeckMeta,
|
|
53
|
+
// Slide Interfaces
|
|
54
|
+
SlideStatement,
|
|
55
|
+
SlideFeatures,
|
|
56
|
+
SlideHalf,
|
|
57
|
+
SlideTimeline,
|
|
58
|
+
SlideSteps,
|
|
59
|
+
SlideFlex,
|
|
60
|
+
SlideChart,
|
|
61
|
+
SlideFree,
|
|
62
|
+
SlideGeneric,
|
|
63
|
+
SlideCustom,
|
|
64
|
+
FreeElement,
|
|
65
|
+
FeatureItem,
|
|
66
|
+
StepItem,
|
|
67
|
+
TimelineItem,
|
|
68
|
+
SlideMeta,
|
|
69
|
+
LuminaEventType,
|
|
70
|
+
SlideChangePayload,
|
|
71
|
+
ActionPayload,
|
|
72
|
+
PatchPayload,
|
|
73
|
+
NavigatePayload,
|
|
74
|
+
ThemeChangePayload,
|
|
75
|
+
TimelineSeekPayload,
|
|
76
|
+
TimelineCompletePayload,
|
|
77
|
+
RevealStartPayload,
|
|
78
|
+
RevealCompletePayload,
|
|
79
|
+
RevealElementPayload,
|
|
80
|
+
LuminaUIOptions,
|
|
81
|
+
LuminaAnimationOptions,
|
|
82
|
+
LuminaKeyBindings,
|
|
83
|
+
// Chart Types
|
|
84
|
+
ChartType,
|
|
85
|
+
ChartData,
|
|
86
|
+
ChartDataset,
|
|
87
|
+
// Flex Types
|
|
88
|
+
FlexElement,
|
|
89
|
+
FlexChildElement,
|
|
90
|
+
FlexSize,
|
|
91
|
+
SpacingToken,
|
|
92
|
+
VAlign,
|
|
93
|
+
HAlign,
|
|
94
|
+
TextAlign,
|
|
95
|
+
FlexElementImage,
|
|
96
|
+
FlexElementContent,
|
|
97
|
+
FlexElementTitle,
|
|
98
|
+
FlexElementText,
|
|
99
|
+
FlexElementBullets,
|
|
100
|
+
FlexElementOrdered,
|
|
101
|
+
FlexElementButton,
|
|
102
|
+
FlexElementTimeline,
|
|
103
|
+
FlexElementStepper,
|
|
104
|
+
FlexElementSpacer,
|
|
105
|
+
// Speaker Notes
|
|
106
|
+
SpeakerSyncPayload,
|
|
107
|
+
// Theme Types (NEW)
|
|
108
|
+
ThemeColors,
|
|
109
|
+
ThemeTypography,
|
|
110
|
+
ThemeSpacing,
|
|
111
|
+
ThemeBorderRadius,
|
|
112
|
+
ThemeEffects,
|
|
113
|
+
ThemeComponents,
|
|
114
|
+
} from './core/types';
|
|
115
|
+
|
|
116
|
+
// Events
|
|
117
|
+
export { bus, EventBus, type Handler } from './core/events';
|
|
118
|
+
|
|
119
|
+
// Speaker Notes
|
|
120
|
+
export { SpeakerChannel, type MessageHandler } from './core/speaker-channel';
|
|
121
|
+
|
|
122
|
+
// Theme System (NEW)
|
|
123
|
+
export { ThemeManager, DEFAULT_THEME } from './core/theme';
|
|
124
|
+
export { THEME_PRESETS } from './core/presets';
|
|
125
|
+
export { getLuminaJsonSchema, getThemeJsonSchema, ThemeConfigSchema } from './core/schema';
|
|
126
|
+
|
|
127
|
+
// Prompt Generation
|
|
128
|
+
export { generateSystemPrompt, generateThemePrompt, type SystemPromptOptions } from './core/prompts';
|
|
129
|
+
|
|
130
|
+
// Streaming (for LLM token streams and partial JSON)
|
|
131
|
+
export { parsePartialJson, createDebouncedLoader, isSlideReady } from './utils/streaming';
|
|
132
|
+
|
|
133
|
+
// Animation config: centralized defaults and resolution (options / meta.effects / slide.meta.effects)
|
|
134
|
+
export { resolveTransitionConfig, ANIMATION_DEFAULTS, type ResolvedTransitionConfig } from './core/animationConfig';
|
|
135
|
+
|
|
136
|
+
// Animation: presets, stagger, resolver (ANIMATION_IDEAS.md, ANIMATION_STRATEGY.md)
|
|
137
|
+
export {
|
|
138
|
+
BUILTIN_PRESETS,
|
|
139
|
+
getPreset,
|
|
140
|
+
registerPreset,
|
|
141
|
+
resolveAnimationFromInput,
|
|
142
|
+
computeStagger,
|
|
143
|
+
type StaggerMode,
|
|
144
|
+
type StaggerOptions,
|
|
145
|
+
type StaggerStep,
|
|
146
|
+
type PresetDef,
|
|
147
|
+
} from './animation';
|
|
148
|
+
|
|
149
|
+
// Element control: resolution and discovery (for engine.element(id), engine.element(slideIndex, path), engine.elements(slideIndex))
|
|
150
|
+
export { elemId } from './core/elementId';
|
|
151
|
+
export {
|
|
152
|
+
resolveId,
|
|
153
|
+
parsePath,
|
|
154
|
+
pathToKey,
|
|
155
|
+
getElementPaths,
|
|
156
|
+
getElementIds,
|
|
157
|
+
registerElementPaths,
|
|
158
|
+
type ElementPath,
|
|
159
|
+
type PathGenerator
|
|
160
|
+
} from './core/elementResolver';
|
|
161
|
+
|
|
162
|
+
export type {
|
|
163
|
+
ElementState,
|
|
164
|
+
ElementController,
|
|
165
|
+
AnimateOptions,
|
|
166
|
+
RevealInSequenceOptions,
|
|
167
|
+
InitialElementState,
|
|
168
|
+
TimelineKeyframes,
|
|
169
|
+
TimelineKeyframeState,
|
|
170
|
+
TimelineTracks,
|
|
171
|
+
} from './core/types';
|
|
172
|
+
|
|
173
|
+
export { default as LuminaDeck } from './components/LuminaDeck.vue';
|
|
174
|
+
export { default as LuminaStudio } from './components/studio/LuminaStudio.vue';
|
|
175
|
+
import './style/main.css';
|
package/src/main.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { createApp } from 'vue'
|
|
2
|
+
import App from './App.vue'
|
|
3
|
+
import './style/main.css'
|
|
4
|
+
import { initFirebase } from './utils/firebase'
|
|
5
|
+
|
|
6
|
+
// Initialize Infrastructure
|
|
7
|
+
try {
|
|
8
|
+
initFirebase();
|
|
9
|
+
} catch (e) {
|
|
10
|
+
console.error("Failed to initialize Firebase:", e);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
import router from './router'
|
|
14
|
+
|
|
15
|
+
const app = createApp(App)
|
|
16
|
+
app.use(router)
|
|
17
|
+
app.mount('#app')
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router';
|
|
2
|
+
import { getFirebaseAuth } from '../utils/firebase';
|
|
3
|
+
|
|
4
|
+
const routes: RouteRecordRaw[] = [
|
|
5
|
+
{
|
|
6
|
+
path: '/',
|
|
7
|
+
component: () => import('../views/SiteLayout.vue'),
|
|
8
|
+
children: [
|
|
9
|
+
{
|
|
10
|
+
path: '',
|
|
11
|
+
name: 'home',
|
|
12
|
+
component: () => import('../components/site/SiteHome.vue')
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
path: 'dashboard',
|
|
16
|
+
name: 'dashboard',
|
|
17
|
+
component: () => import('../components/site/SiteDashboard.vue'),
|
|
18
|
+
meta: { requiresAuth: true }
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
path: 'examples',
|
|
22
|
+
name: 'examples',
|
|
23
|
+
component: () => import('../components/site/SiteExamples.vue')
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
path: 'docs',
|
|
27
|
+
name: 'docs',
|
|
28
|
+
component: () => import('../components/site/SiteDocs.vue')
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
path: 'playground',
|
|
32
|
+
name: 'playground',
|
|
33
|
+
component: () => import('../components/site/SitePlayground.vue')
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
path: 'prompt-builder',
|
|
37
|
+
name: 'prompt-builder',
|
|
38
|
+
component: () => import('../components/site/SitePromptBuilder.vue')
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
path: 'api',
|
|
42
|
+
name: 'api',
|
|
43
|
+
component: () => import('../components/site/SiteApi.vue')
|
|
44
|
+
}
|
|
45
|
+
]
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
path: '/studio/:id?',
|
|
49
|
+
name: 'studio',
|
|
50
|
+
component: () => import('../views/StudioView.vue'),
|
|
51
|
+
meta: { requiresAuth: true }
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
path: '/deck/:id',
|
|
55
|
+
name: 'deck',
|
|
56
|
+
component: () => import('../views/DeckView.vue')
|
|
57
|
+
}
|
|
58
|
+
];
|
|
59
|
+
|
|
60
|
+
const router = createRouter({
|
|
61
|
+
history: createWebHistory(import.meta.env.BASE_URL),
|
|
62
|
+
routes,
|
|
63
|
+
scrollBehavior(_to, _from, savedPosition) {
|
|
64
|
+
if (savedPosition) {
|
|
65
|
+
return savedPosition;
|
|
66
|
+
}
|
|
67
|
+
return { top: 0 };
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
// Navigation Guard
|
|
72
|
+
router.beforeEach(async (to, _from, next) => {
|
|
73
|
+
const requiresAuth = to.matched.some(record => record.meta.requiresAuth);
|
|
74
|
+
|
|
75
|
+
// Simple check - in a real app wait for auth init
|
|
76
|
+
if (requiresAuth) {
|
|
77
|
+
try {
|
|
78
|
+
const auth = getFirebaseAuth();
|
|
79
|
+
if (!auth.currentUser) {
|
|
80
|
+
// If not logged in, redirect home or show login
|
|
81
|
+
// For now, we allow access but components might handle logic,
|
|
82
|
+
// or we could redirect to home with a query param
|
|
83
|
+
// next({ name: 'home' });
|
|
84
|
+
}
|
|
85
|
+
} catch (e) {
|
|
86
|
+
// Auth not initialized yet, proceed (components handle it)
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
next();
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
export default router;
|