forked from jsnbuchanan/crowd-funder-for-time-pwa
- Remove duplicate NOTIFY_INVITE_MISSING and NOTIFY_INVITE_PROCESSING_ERROR exports - Update InviteOneAcceptView.vue to use correct NOTIFY_INVITE_TRUNCATED_DATA constant - Migrate ContactsView to PlatformServiceMixin and extract into modular sub-components - Resolves TypeScript compilation errors preventing web build
306 lines
7.5 KiB
Vue
306 lines
7.5 KiB
Vue
<template>
|
|
<div class="lazy-loading-example">
|
|
<!-- Loading state with Suspense -->
|
|
<Suspense>
|
|
<template #default>
|
|
<!-- Main content with lazy-loaded components -->
|
|
<div class="content">
|
|
<h1>Lazy Loading Example</h1>
|
|
|
|
<!-- Lazy-loaded heavy component -->
|
|
<LazyHeavyComponent
|
|
v-if="showHeavyComponent"
|
|
:data="heavyComponentData"
|
|
@data-processed="handleDataProcessed"
|
|
/>
|
|
|
|
<!-- Conditionally loaded components -->
|
|
<LazyQRScanner v-if="showQRScanner" @qr-detected="handleQRDetected" />
|
|
|
|
<LazyThreeJSViewer
|
|
v-if="showThreeJS"
|
|
:model-url="threeJSModelUrl"
|
|
@model-loaded="handleModelLoaded"
|
|
/>
|
|
|
|
<!-- Route-based lazy loading -->
|
|
<router-view v-slot="{ Component }">
|
|
<component :is="Component" />
|
|
</router-view>
|
|
</div>
|
|
</template>
|
|
|
|
<!-- Loading fallback -->
|
|
<template #fallback>
|
|
<div class="loading-fallback">
|
|
<div class="spinner"></div>
|
|
<p>Loading components...</p>
|
|
</div>
|
|
</template>
|
|
</Suspense>
|
|
|
|
<!-- Control buttons -->
|
|
<div class="controls">
|
|
<button @click="toggleHeavyComponent">
|
|
{{ showHeavyComponent ? "Hide" : "Show" }} Heavy Component
|
|
</button>
|
|
<button @click="toggleQRScanner">
|
|
{{ showQRScanner ? "Hide" : "Show" }} QR Scanner
|
|
</button>
|
|
<button @click="toggleThreeJS">
|
|
{{ showThreeJS ? "Hide" : "Show" }} 3D Viewer
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script lang="ts">
|
|
import { Component, Vue, Prop, Watch } from "vue-facing-decorator";
|
|
import { defineAsyncComponent } from "vue";
|
|
|
|
/**
|
|
* Lazy Loading Example Component
|
|
*
|
|
* Demonstrates various lazy loading patterns with vue-facing-decorator:
|
|
* - defineAsyncComponent for heavy components
|
|
* - Conditional loading based on user interaction
|
|
* - Suspense for loading states
|
|
* - Route-based lazy loading
|
|
*
|
|
* @author Matthew Raymer
|
|
* @version 1.0.0
|
|
*/
|
|
@Component({
|
|
name: "LazyLoadingExample",
|
|
components: {
|
|
// Lazy-loaded components with loading and error states
|
|
LazyHeavyComponent: defineAsyncComponent({
|
|
loader: () => import("./sub-components/HeavyComponent.vue"),
|
|
loadingComponent: {
|
|
template: '<div class="loading">Loading heavy component...</div>',
|
|
},
|
|
errorComponent: {
|
|
template: '<div class="error">Failed to load heavy component</div>',
|
|
},
|
|
delay: 200, // Show loading component after 200ms
|
|
timeout: 10000, // Timeout after 10 seconds
|
|
}),
|
|
|
|
LazyQRScanner: defineAsyncComponent({
|
|
loader: () => import("./sub-components/QRScannerComponent.vue"),
|
|
loadingComponent: {
|
|
template: '<div class="loading">Initializing QR scanner...</div>',
|
|
},
|
|
errorComponent: {
|
|
template: '<div class="error">QR scanner not available</div>',
|
|
},
|
|
}),
|
|
|
|
LazyThreeJSViewer: defineAsyncComponent({
|
|
loader: () => import("./sub-components/ThreeJSViewer.vue"),
|
|
loadingComponent: {
|
|
template: '<div class="loading">Loading 3D viewer...</div>',
|
|
},
|
|
errorComponent: {
|
|
template: '<div class="error">3D viewer failed to load</div>',
|
|
},
|
|
}),
|
|
},
|
|
})
|
|
export default class LazyLoadingExample extends Vue {
|
|
// Component state
|
|
@Prop({ default: false }) readonly initialLoadHeavy!: boolean;
|
|
|
|
// Reactive properties
|
|
showHeavyComponent = false;
|
|
showQRScanner = false;
|
|
showThreeJS = false;
|
|
|
|
// Component data
|
|
heavyComponentData = {
|
|
items: Array.from({ length: 1000 }, (_, i) => ({
|
|
id: i,
|
|
name: `Item ${i}`,
|
|
})),
|
|
filters: { category: "all", status: "active" },
|
|
sortBy: "name",
|
|
};
|
|
|
|
threeJSModelUrl = "/models/lupine_plant/scene.gltf";
|
|
|
|
// Computed properties
|
|
get isLoadingAnyComponent(): boolean {
|
|
return this.showHeavyComponent || this.showQRScanner || this.showThreeJS;
|
|
}
|
|
|
|
get componentCount(): number {
|
|
let count = 0;
|
|
if (this.showHeavyComponent) count++;
|
|
if (this.showQRScanner) count++;
|
|
if (this.showThreeJS) count++;
|
|
return count;
|
|
}
|
|
|
|
// Lifecycle hooks
|
|
mounted(): void {
|
|
console.log("[LazyLoadingExample] Component mounted");
|
|
|
|
// Initialize based on props
|
|
if (this.initialLoadHeavy) {
|
|
this.showHeavyComponent = true;
|
|
}
|
|
|
|
// Preload critical components
|
|
this.preloadCriticalComponents();
|
|
}
|
|
|
|
// Methods
|
|
toggleHeavyComponent(): void {
|
|
this.showHeavyComponent = !this.showHeavyComponent;
|
|
console.log(
|
|
"[LazyLoadingExample] Heavy component toggled:",
|
|
this.showHeavyComponent,
|
|
);
|
|
}
|
|
|
|
toggleQRScanner(): void {
|
|
this.showQRScanner = !this.showQRScanner;
|
|
console.log("[LazyLoadingExample] QR scanner toggled:", this.showQRScanner);
|
|
}
|
|
|
|
toggleThreeJS(): void {
|
|
this.showThreeJS = !this.showThreeJS;
|
|
console.log(
|
|
"[LazyLoadingExample] ThreeJS viewer toggled:",
|
|
this.showThreeJS,
|
|
);
|
|
}
|
|
|
|
handleDataProcessed(data: any): void {
|
|
console.log("[LazyLoadingExample] Data processed:", data);
|
|
// Handle processed data from heavy component
|
|
}
|
|
|
|
handleQRDetected(qrData: string): void {
|
|
console.log("[LazyLoadingExample] QR code detected:", qrData);
|
|
// Handle QR code data
|
|
}
|
|
|
|
handleModelLoaded(modelInfo: any): void {
|
|
console.log("[LazyLoadingExample] 3D model loaded:", modelInfo);
|
|
// Handle 3D model loaded event
|
|
}
|
|
|
|
/**
|
|
* Preload critical components for better UX
|
|
*/
|
|
private preloadCriticalComponents(): void {
|
|
// Preload components that are likely to be used
|
|
if (process.env.NODE_ENV === "production") {
|
|
// In production, preload based on user behavior patterns
|
|
this.preloadComponent(
|
|
() => import("./sub-components/HeavyComponent.vue"),
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Preload a component without rendering it
|
|
*/
|
|
private preloadComponent(componentLoader: () => Promise<any>): void {
|
|
componentLoader().catch((error) => {
|
|
console.warn("[LazyLoadingExample] Preload failed:", error);
|
|
});
|
|
}
|
|
|
|
// Watchers
|
|
@Watch("showHeavyComponent")
|
|
onHeavyComponentToggle(newValue: boolean): void {
|
|
if (newValue) {
|
|
// Component is being shown - could trigger analytics
|
|
console.log("[LazyLoadingExample] Heavy component shown");
|
|
}
|
|
}
|
|
|
|
@Watch("componentCount")
|
|
onComponentCountChange(newCount: number): void {
|
|
console.log("[LazyLoadingExample] Active component count:", newCount);
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<style scoped>
|
|
.lazy-loading-example {
|
|
padding: 20px;
|
|
max-width: 1200px;
|
|
margin: 0 auto;
|
|
}
|
|
|
|
.content {
|
|
margin-bottom: 20px;
|
|
}
|
|
|
|
.controls {
|
|
display: flex;
|
|
gap: 10px;
|
|
flex-wrap: wrap;
|
|
margin-top: 20px;
|
|
}
|
|
|
|
.controls button {
|
|
padding: 10px 20px;
|
|
border: 1px solid #ccc;
|
|
border-radius: 4px;
|
|
background: #f8f9fa;
|
|
cursor: pointer;
|
|
transition: background-color 0.2s;
|
|
}
|
|
|
|
.controls button:hover {
|
|
background: #e9ecef;
|
|
}
|
|
|
|
.loading-fallback {
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
justify-content: center;
|
|
padding: 40px;
|
|
text-align: center;
|
|
}
|
|
|
|
.spinner {
|
|
width: 40px;
|
|
height: 40px;
|
|
border: 4px solid #f3f3f3;
|
|
border-top: 4px solid #3498db;
|
|
border-radius: 50%;
|
|
animation: spin 1s linear infinite;
|
|
margin-bottom: 20px;
|
|
}
|
|
|
|
@keyframes spin {
|
|
0% {
|
|
transform: rotate(0deg);
|
|
}
|
|
100% {
|
|
transform: rotate(360deg);
|
|
}
|
|
}
|
|
|
|
.loading {
|
|
padding: 20px;
|
|
text-align: center;
|
|
color: #666;
|
|
}
|
|
|
|
.error {
|
|
padding: 20px;
|
|
text-align: center;
|
|
color: #dc3545;
|
|
background: #f8d7da;
|
|
border: 1px solid #f5c6cb;
|
|
border-radius: 4px;
|
|
}
|
|
</style>
|