Zum Hauptinhalt springen

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
warnung

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;
}
}