Logik & Events
Die Logic-Erweiterungen ermöglichen es, in den Datenfluss und die Event-Pipeline des Konfigurators einzugreifen. Sie können Konfigurationsdaten transformieren, Screenshots anpassen, und auf Lifecycle-Events reagieren.
Grundstruktur
Logic-Callbacks werden im logic Objekt registriert:
// In Plugin.tsx
export default {
// ...
logic: {
config: {
onUpdate: { fn: ..., description: "..." },
onSave: { fn: ..., description: "..." },
// weitere Config Callbacks
},
camera: {
onSetScreenshotCameras: { fn: ..., description: "..." },
// weitere Camera Callbacks
},
core: {
preprocessFullApp: { fn: ..., description: "..." },
// weitere Core Callbacks
}
}
}
Jeder Callback benötigt:
- fn: Die Callback-Funktion
- description: Beschreibung (String oder LocalizedString)
Config Callbacks
Config-Callbacks greifen in den Konfigurations-Lifecycle ein.
onUpdate
Wird bei jeder Konfigurationsänderung aufgerufen (z.B. bei jedem Merkmalswert-Wechsel).
config: {
onUpdate: {
fn: (config: K3Configuration) => {
// Custom Logik
console.log("Current config:", config);
// Konfiguration modifizieren
const modified = {
...config,
customField: "my-value"
};
return modified;
},
description: "Fügt Custom-Feld zu Konfiguration hinzu"
}
}
K3Configuration Interface:
interface K3Configuration {
id: number | string;
appId: number;
code: string; // Eindeutiger Konfig-Code
price: number | null; // Berechneter Preis
lang: string; // Sprachcode
json: {
variables: unknown[]; // Merkmalswerte
bom: unknown[]; // Stückliste
summary: unknown; // Zusammenfassung
};
files: Record<string, Record<string, string>>; // Screenshots
// Weitere Felder...
}
Anwendungsbeispiele:
- Custom Preisberechnungen
- Zusätzliche Metadaten anfügen
- Validierung und Constraints
- Analytics-Tracking
onSave
Wird vor dem Speichern einer Konfiguration aufgerufen (bei Bestellung, Warenkorb, PDF, etc.).
config: {
onSave: {
fn: (config: K3Configuration | K3NewConfiguration) => {
// Vor dem Speichern modifizieren
const enhanced = {
...config,
savedAt: new Date().toISOString(),
userAgent: navigator.userAgent
};
return enhanced;
},
description: "Fügt Timestamp und UserAgent hinzu"
}
}
Anwendungsbeispiele:
- Zusätzliche Metadaten vor dem Speichern anfügen
- Externe Systeme benachrichtigen
- Preise final verifizieren
onSaveFiles
Ermöglicht das Anpassen der Screenshots, die mit der Konfiguration gespeichert werden.
interface K3UploadFile {
key: string; // Kamera-Name oder Custom Key
file: Blob; // Die Bild-Datei
fileName?: string; // Optional: Dateiname
}
config: {
onSaveFiles: {
fn: (files: K3UploadFile[]) => {
// Zusätzliche Screenshots hinzufügen
const extraFile: K3UploadFile = {
key: "custom-view",
file: new Blob([/* ... */]),
fileName: "custom-screenshot.png"
};
return [...files, extraFile];
},
description: "Fügt zusätzlichen Screenshot hinzu"
}
}
Anwendungsbeispiele:
- Zusätzliche Screenshots aus Custom-Views
- Wasserzeichen auf Screenshots
- Screenshot-Qualität oder -Größe anpassen
- Screenshots filtern/entfernen
onSaveEvent
Wird nach erfolgreichem Speichern mit dem Event-Payload aufgerufen.
interface K3ConfigurationSavedEvent {
type: "K3ConfigurationSaved";
actionKey: string; // "cart", "pdf", "email", etc.
code: string; // Konfigurations-Code
customer?: {
email?: string;
firstName?: string;
lastName?: string;
};
}
config: {
onSaveEvent: {
fn: (payload: K3ConfigurationSavedEvent) => {
// Analytics Event
if (window.gtag) {
window.gtag("event", "configuration_saved", {
action: payload.actionKey,
code: payload.code
});
}
// Custom Fields
return {
...payload,
timestamp: Date.now()
};
},
description: "Sendet Analytics und fügt Timestamp hinzu"
}
}
Anwendungsbeispiele:
- Analytics/Tracking
- Externe Webhooks triggern
- Customer-Journey-Tracking
Camera Callbacks
Camera-Callbacks ermöglichen das Anpassen von Kameras und Screenshots.
onSetScreenshotCameras
Passt die Liste der Kameras an, die für automatische Screenshots verwendet werden.
interface K3Camera {
id: string;
name: string;
type: "PerspectiveCamera" | "OrthographicCamera";
position: [number, number, number];
lookAt: [number, number, number];
rotation: [number, number, number];
resolution?: { width: number; height: number };
zoom?: number;
scope?: K3CameraScope;
// Weitere Felder...
}
camera: {
onSetScreenshotCameras: {
fn: (cameras: K3Camera[]) => {
// Nur Front-View für Screenshots
return cameras.filter(cam => cam.name === "Front");
},
description: "Verwendet nur Front-Kamera für Screenshots"
}
}
Anwendungsbeispiele:
- Screenshot-Kameras filtern
- Zusätzliche Kameras für Screenshots
- Kamera-Reihenfolge ändern
- Kamera-Parameter überschreiben
onSetCameraList
Passt die vollständige Liste aller Kameras an, die im Viewer verfügbar sind.
camera: {
onSetCameraList: {
fn: (cameras: K3Camera[]) => {
// Custom Kamera hinzufügen
const customCamera: K3Camera = {
id: "custom-1",
name: "Detail-Ansicht",
type: "PerspectiveCamera",
position: [2, 2, 2],
lookAt: [0, 0, 0],
rotation: [0, 0, 0]
};
return [...cameras, customCamera];
},
description: "Fügt zusätzliche Detail-Kamera hinzu"
}
}
Anwendungsbeispiele:
- Programmierte Kamerapositionen hinzufügen
- Kameras aus externen Daten generieren
- Kameras basierend auf Konfiguration filtern
getScreenshotDimensions
Bestimmt die Pixel-Dimensionen für Screenshot-Rendering.
interface K3ScreenshotDimensions {
width: number;
height: number;
}
camera: {
getScreenshotDimensions: {
fn: (dim: K3ScreenshotDimensions) => {
// Höhere Auflösung für Screenshots
return {
width: dim.width * 2,
height: dim.height * 2
};
},
description: "Verdoppelt Screenshot-Auflösung"
}
}
Anwendungsbeispiele:
- Hochauflösende Screenshots für Druck
- Bildgröße basierend auf Lizenz anpassen
- Responsive Screenshot-Größen
Core Callbacks
Core-Callbacks greifen in zentrale App-Lifecycle-Events ein.
preprocessFullApp
Wird einmalig vor der Store-Initialisierung mit allen App-Daten aufgerufen.
interface K3FullApp {
app: { id: number; /* ... */ };
client: { id: number; /* ... */ };
groups: Array<{ id: number; /* ... */ }>;
variables: Array<{ id: number; /* ... */ }>;
values: Array<{ id: number | string; /* ... */ }>;
articles: Array<{ id: number; /* ... */ }>;
prices: Array<{ id: number; /* ... */ }>;
materials: Array<{ id: number; /* ... */ }>;
models: Array<{ id: number; /* ... */ }>;
images: Array<{ id: number; /* ... */ }>;
rules: Array<{ id: number; /* ... */ }>;
ruleItems: Array<{ id: number; /* ... */ }>;
}
core: {
preprocessFullApp: {
fn: (app: K3FullApp) => {
// App-Daten vor dem Laden modifizieren
const modifiedApp = {
...app,
variables: app.variables.map(v => ({
...v,
customFlag: true
}))
};
return modifiedApp;
},
description: "Fügt Custom-Flag zu allen Variablen hinzu"
}
}
Anwendungsbeispiele:
- Daten-Migration
- A/B-Testing (Features ein/ausschalten)
- White-Labeling (Branding ersetzen)
- Komplexe Datenvorverarbeitung
preprocessFullApp ist sehr mächtig und sollte mit Vorsicht verwendet werden. Fehler hier können den gesamten Konfigurator unbrauchbar machen.
onOpenPdf
Wird nach PDF-Generierung aufgerufen. Sie können das PDF-Öffnen übernehmen.
interface K3SaveResult {
code?: string;
price?: {
okay: boolean;
priceSent?: number;
priceCalculated?: number;
};
error?: boolean;
message?: string;
shopLink?: string;
pdf?: string; // URL zum PDF
pdfName?: string; // Vorgeschlagener Dateiname
}
core: {
onOpenPdf: {
fn: (result: K3SaveResult) => {
if (result.pdf) {
// Custom PDF-Handling
console.log("PDF generated:", result.pdf);
// Eigenes Modal statt direktem Download
showCustomPdfModal(result.pdf);
// Analytics
if (window.gtag) {
window.gtag("event", "pdf_generated", {
code: result.code
});
}
}
},
description: "Custom PDF-Handling mit Analytics"
}
}
Anwendungsbeispiele:
- Custom Download-Modal
- PDF in eigenem Viewer öffnen
- Analytics-Tracking
- Automatisches Hochladen zu externem Service
onExportAR
Ermöglicht benutzerdefinierten AR-Export.
interface K3ARExportContext {
platform: "iOS" | "AndroidOS";
scene: K3Scene; // Three.js Scene Graph
}
interface K3Scene {
type: string;
children: readonly K3Scene[];
userData: Record<string, unknown>;
traverse(callback: (object: K3Scene) => void): void;
getObjectByName(name: string): K3Scene | undefined;
}
core: {
onExportAR: {
fn: async (ctx: K3ARExportContext) => {
console.log("Exporting for platform:", ctx.platform);
// Scene traversieren
ctx.scene.traverse((obj) => {
console.log("Object:", obj.type, obj.userData);
});
// Custom GLTF/USDZ Export
const blob = await customARExporter(ctx.scene, ctx.platform);
return blob;
},
description: "Custom AR-Export-Logik"
}
}
Anwendungsbeispiele:
- Custom GLTF/USDZ-Export
- Szenen-Optimierungen für AR
- Plattform-spezifische Anpassungen
- Externe AR-Services
Vollständiges Beispiel
import type {
K3Configuration,
K3Camera,
K3FullApp,
K3UploadFile,
K3ConfigurationSavedEvent,
K3ScreenshotDimensions
} from "k3-plugin-api";
// Config Callbacks
const onUpdateCallback = (config: K3Configuration) => {
// Bei jeder Änderung tracken
if (window.analytics) {
window.analytics.track("config_updated", {
price: config.price,
variables: config.json.variables.length
});
}
return config;
};
const onSaveCallback = (config: K3Configuration) => {
// Zusätzliche Metadaten vor Speicherung
return {
...config,
metadata: {
savedAt: new Date().toISOString(),
userAgent: navigator.userAgent,
viewport: {
width: window.innerWidth,
height: window.innerHeight
}
}
};
};
const onSaveFilesCallback = (files: K3UploadFile[]) => {
// Nur hochauflösende Screenshots speichern
return files.filter(f =>
f.fileName?.includes("high-res")
);
};
const onSaveEventCallback = (payload: K3ConfigurationSavedEvent) => {
// Multi-Channel Analytics
if (window.gtag) {
window.gtag("event", "configuration_saved", {
action: payload.actionKey,
code: payload.code
});
}
if (window.fbq) {
window.fbq("track", "AddToCart");
}
return {
...payload,
tracked: true
};
};
// Camera Callbacks
const onSetScreenshotCamerasCallback = (cameras: K3Camera[]) => {
// Nur Kameras mit "Screenshot" im Namen
const filtered = cameras.filter(cam =>
cam.name.toLowerCase().includes("screenshot")
);
// Mindestens eine Kamera behalten
return filtered.length > 0 ? filtered : [cameras[0]];
};
const getScreenshotDimensionsCallback = (dim: K3ScreenshotDimensions) => {
// 4K Screenshots für Premium-User
const isPremium = checkPremiumStatus();
if (isPremium) {
return { width: 3840, height: 2160 };
}
return dim;
};
// Core Callbacks
const preprocessFullAppCallback = (app: K3FullApp) => {
// Feature-Flags basierend auf Kunde
const customerFeatures = getCustomerFeatures(app.client.id);
return {
...app,
app: {
...app.app,
features: customerFeatures
}
};
};
const onOpenPdfCallback = (result) => {
if (result.pdf && !result.error) {
// Custom PDF-Modal statt direktem Download
showPdfPreviewModal({
url: result.pdf,
filename: result.pdfName || "konfiguration.pdf",
onDownload: () => {
window.open(result.pdf, "_blank");
},
onEmail: () => {
openEmailDialog(result.pdf);
}
});
// Tracking
trackPdfGeneration(result.code);
}
};
// Im Plugin registrieren:
export default {
// ...
logic: {
config: {
onUpdate: {
fn: onUpdateCallback,
description: "Analytics-Tracking bei Updates"
},
onSave: {
fn: onSaveCallback,
description: "Fügt erweiterte Metadaten hinzu"
},
onSaveFiles: {
fn: onSaveFilesCallback,
description: "Filtert Low-Res Screenshots"
},
onSaveEvent: {
fn: onSaveEventCallback,
description: "Multi-Channel Analytics bei Save"
}
},
camera: {
onSetScreenshotCameras: {
fn: onSetScreenshotCamerasCallback,
description: "Filtert Screenshot-Kameras nach Namen"
},
getScreenshotDimensions: {
fn: getScreenshotDimensionsCallback,
description: "Höhere Auflösung für Premium-User"
}
},
core: {
preprocessFullApp: {
fn: preprocessFullAppCallback,
description: "Kundespezifische Feature-Flags"
},
onOpenPdf: {
fn: onOpenPdfCallback,
description: "Custom PDF-Vorschau-Modal"
}
}
}
};
Best Practices
1. Immutability beachten
Ändern Sie Objekte nicht direkt, sondern erstellen Sie Kopien:
// ❌ Schlecht
fn: (config) => {
config.customField = "value";
return config;
}
// ✅ Gut
fn: (config) => {
return {
...config,
customField: "value"
};
}
2. Error Handling
Callbacks können den Konfigurator brechen. Nutzen Sie try-catch:
fn: (config) => {
try {
// Ihre Logik
return enhanceConfig(config);
} catch (error) {
console.error("Plugin error:", error);
return config; // Original zurückgeben bei Fehler
}
}
3. Performance
onUpdate wird sehr häufig aufgerufen. Vermeiden Sie teure Operationen:
// ❌ Schlecht: Bei jedem Update API-Call
onUpdate: {
fn: async (config) => {
await fetch("/api/track", { /* ... */ });
return config;
}
}
// ✅ Gut: Debounced oder in onSave
let timeout: NodeJS.Timeout;
onUpdate: {
fn: (config) => {
clearTimeout(timeout);
timeout = setTimeout(() => {
fetch("/api/track", { /* ... */ });
}, 1000);
return config;
}
}
4. Type Safety
Nutzen Sie TypeScript für Typsicherheit:
import type {
K3Configuration,
K3Camera,
K3FullApp,
CallbackWithDescription
} from "k3-plugin-api";
const onUpdateCallback: CallbackWithDescription<
(config: K3Configuration) => K3Configuration
> = {
fn: (config) => config,
description: "My callback"
};
5. Side Effects dokumentieren
Wenn Ihr Callback Side Effects hat, dokumentieren Sie diese klar:
onSave: {
fn: (config) => {
// Side Effect: Externes System benachrichtigen
notifyExternalSystem(config);
return config;
},
description: "Speichert Konfiguration und benachrichtigt CRM-System"
}
Debugging
Loggen Sie ausführlich während der Entwicklung:
logic: {
config: {
onUpdate: {
fn: (config) => {
console.group("Config Update");
console.log("Config ID:", config.id);
console.log("Price:", config.price);
console.log("Variables:", config.json.variables);
console.groupEnd();
return config;
},
description: "Debug logging"
}
}
}
Async Callbacks
Die meisten Callbacks sollten synchron sein. Nur onExportAR ist explizit async:
// ✅ onExportAR: async erlaubt
onExportAR: {
fn: async (ctx) => {
const blob = await generateARFile(ctx);
return blob;
}
}
// ⚠️ Andere Callbacks: synchron halten
onUpdate: {
fn: (config) => {
// Async-Operationen in Promise starten, nicht awaiten
performAsyncOperation(config).catch(console.error);
return config;
}
}