qnce-engine
Version:
Core QNCE (Quantum Narrative Convergence Engine) - Framework agnostic narrative engine with performance optimization
365 lines • 11.7 kB
JavaScript
;
/**
* QNCE Engine React Integration
*
* React hooks and utilities for integrating QNCE Engine with React applications.
* Provides convenient hooks for managing narrative state, undo/redo operations,
* and autosave functionality.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.useQNCE = useQNCE;
exports.useUndoRedo = useUndoRedo;
exports.useAutosave = useAutosave;
const react_1 = require("react");
/**
* React hook for QNCE Engine integration
*
* Provides a complete interface for managing QNCE Engine state in React applications,
* including undo/redo functionality, autosave, and automatic re-renders.
*
* @param engine - The QNCE Engine instance
* @param config - Configuration options for the hook
* @returns Hook return object with state and actions
*
* @example
* ```tsx
* function NarrativeComponent() {
* const engine = useMemo(() => createQNCEEngine(DEMO_STORY), []);
* const {
* currentNode,
* availableChoices,
* selectChoice,
* undo,
* redo,
* canUndo,
* canRedo
* } = useQNCE(engine, {
* enableUndoRedo: true,
* enableAutosave: true,
* maxUndoEntries: 50
* });
*
* return (
* <div>
* <p>{currentNode?.text}</p>
*
* <div>
* {availableChoices.map(choice => (
* <button key={choice.text} onClick={() => selectChoice(choice)}>
* {choice.text}
* </button>
* ))}
* </div>
*
* <div>
* <button onClick={undo} disabled={!canUndo}>
* Undo
* </button>
* <button onClick={redo} disabled={!canRedo}>
* Redo
* </button>
* </div>
* </div>
* );
* }
* ```
*/
function useQNCE(engine, config = {}) {
const { autoUpdate = true, enableUndoRedo = true, maxUndoEntries = 50, maxRedoEntries = 25, enableAutosave = true, autosaveThrottleMs = 100 } = config;
// Force re-render counter
const [, setUpdateCounter] = (0, react_1.useState)(0);
// Initialize engine configuration
(0, react_1.useEffect)(() => {
if (enableUndoRedo) {
engine.configureUndoRedo({
enabled: true,
maxUndoEntries,
maxRedoEntries
});
}
if (enableAutosave) {
engine.configureAutosave({
enabled: true,
throttleMs: autosaveThrottleMs,
triggers: ['choice', 'flag-change', 'state-load']
});
}
}, [engine, enableUndoRedo, maxUndoEntries, maxRedoEntries, enableAutosave, autosaveThrottleMs]);
// Force re-render when autoUpdate is enabled
const refresh = (0, react_1.useCallback)(() => {
if (autoUpdate) {
setUpdateCounter(prev => prev + 1);
}
}, [autoUpdate]);
// Memoized state getters
const currentNode = (0, react_1.useMemo)(() => {
try {
return engine.getCurrentNode();
}
catch {
return null;
}
}, [engine, autoUpdate]);
const availableChoices = (0, react_1.useMemo)(() => {
try {
return engine.getAvailableChoices();
}
catch {
return [];
}
}, [engine, autoUpdate]);
const flags = (0, react_1.useMemo)(() => {
return engine.getState().flags;
}, [engine, autoUpdate]);
// Undo/Redo state
const canUndo = (0, react_1.useMemo)(() => engine.canUndo(), [engine, autoUpdate]);
const canRedo = (0, react_1.useMemo)(() => engine.canRedo(), [engine, autoUpdate]);
const undoCount = (0, react_1.useMemo)(() => engine.getUndoCount(), [engine, autoUpdate]);
const redoCount = (0, react_1.useMemo)(() => engine.getRedoCount(), [engine, autoUpdate]);
// Actions with automatic refresh
const selectChoice = (0, react_1.useCallback)(async (choice) => {
let choiceToSelect;
if (typeof choice === 'string') {
// Find choice by text
const foundChoice = availableChoices.find(c => c.text === choice);
if (!foundChoice) {
throw new Error(`Choice not found: ${choice}`);
}
choiceToSelect = foundChoice;
}
else {
choiceToSelect = choice;
}
engine.selectChoice(choiceToSelect);
refresh();
}, [engine, refresh, availableChoices]);
const setFlag = (0, react_1.useCallback)((key, value) => {
engine.setFlag(key, value);
refresh();
}, [engine, refresh]);
const resetNarrative = (0, react_1.useCallback)(() => {
engine.resetNarrative();
refresh();
}, [engine, refresh]);
const undo = (0, react_1.useCallback)(() => {
const result = engine.undo();
refresh();
return result;
}, [engine, refresh]);
const redo = (0, react_1.useCallback)(() => {
const result = engine.redo();
refresh();
return result;
}, [engine, refresh]);
const clearHistory = (0, react_1.useCallback)(() => {
engine.clearHistory();
refresh();
}, [engine, refresh]);
const autosave = (0, react_1.useCallback)(async () => {
await engine.manualAutosave();
}, [engine]);
const configureAutosave = (0, react_1.useCallback)((config) => {
engine.configureAutosave(config);
}, [engine]);
const saveState = (0, react_1.useCallback)(async () => {
return await engine.saveState();
}, [engine]);
const loadState = (0, react_1.useCallback)(async (serializedState) => {
await engine.loadState(serializedState);
refresh();
}, [engine, refresh]);
return {
// Core state
engine,
currentNode,
availableChoices,
flags,
// Actions
selectChoice,
setFlag,
resetNarrative,
// Undo/Redo
undo,
redo,
canUndo,
canRedo,
undoCount,
redoCount,
clearHistory,
// Autosave
autosave,
configureAutosave,
// State management
saveState,
loadState,
// Utility
refresh
};
}
/**
* Hook for managing just undo/redo functionality
*
* A lightweight hook focused specifically on undo/redo operations.
* Useful when you want to add undo/redo to an existing QNCE integration.
*
* @param engine - The QNCE Engine instance
* @param config - Undo/redo configuration
* @returns Undo/redo state and actions
*
* @example
* ```tsx
* function UndoRedoControls({ engine }: { engine: QNCEEngine }) {
* const { undo, redo, canUndo, canRedo, undoCount, redoCount } = useUndoRedo(engine);
*
* return (
* <div className="undo-redo-controls">
* <button onClick={undo} disabled={!canUndo}>
* ← Undo ({undoCount})
* </button>
* <button onClick={redo} disabled={!canRedo}>
* Redo ({redoCount}) →
* </button>
* </div>
* );
* }
* ```
*/
function useUndoRedo(engine, config = {}) {
const { maxUndoEntries = 50, maxRedoEntries = 25 } = config;
const [, setUpdateCounter] = (0, react_1.useState)(0);
// Configure undo/redo on mount
(0, react_1.useEffect)(() => {
engine.configureUndoRedo({
enabled: true,
maxUndoEntries,
maxRedoEntries
});
}, [engine, maxUndoEntries, maxRedoEntries]);
const refresh = (0, react_1.useCallback)(() => {
setUpdateCounter(prev => prev + 1);
}, []);
const undo = (0, react_1.useCallback)(() => {
const result = engine.undo();
refresh();
return result;
}, [engine, refresh]);
const redo = (0, react_1.useCallback)(() => {
const result = engine.redo();
refresh();
return result;
}, [engine, refresh]);
const clearHistory = (0, react_1.useCallback)(() => {
engine.clearHistory();
refresh();
}, [engine, refresh]);
const canUndo = (0, react_1.useMemo)(() => engine.canUndo(), [engine]);
const canRedo = (0, react_1.useMemo)(() => engine.canRedo(), [engine]);
const undoCount = (0, react_1.useMemo)(() => engine.getUndoCount(), [engine]);
const redoCount = (0, react_1.useMemo)(() => engine.getRedoCount(), [engine]);
const historySummary = (0, react_1.useMemo)(() => ({
undoCount,
redoCount,
canUndo,
canRedo
}), [undoCount, redoCount, canUndo, canRedo]);
return {
undo,
redo,
canUndo,
canRedo,
undoCount,
redoCount,
clearHistory,
historySummary
};
}
/**
* Hook for managing autosave functionality
*
* Provides control over the autosave system for fine-grained management.
*
* @param engine - The QNCE Engine instance
* @param config - Autosave configuration
* @returns Autosave state and actions
*
* @example
* ```tsx
* function AutosaveIndicator({ engine }: { engine: QNCEEngine }) {
* const { autosave, configure, isEnabled } = useAutosave(engine, {
* throttleMs: 200,
* triggers: ['choice', 'flag-change']
* });
*
* return (
* <div>
* <span>Autosave: {isEnabled ? 'ON' : 'OFF'}</span>
* <button onClick={autosave}>Save Now</button>
* </div>
* );
* }
* ```
*/
function useAutosave(engine, config = {}) {
const { throttleMs = 100, triggers = ['choice', 'flag-change', 'state-load'], enabled = true } = config;
const [lastAutosave, setLastAutosave] = (0, react_1.useState)(null);
const [isSaving, setIsSaving] = (0, react_1.useState)(false);
(0, react_1.useEffect)(() => {
engine.configureAutosave({
enabled,
throttleMs,
triggers: triggers
});
}, [engine, enabled, throttleMs, triggers]);
// Monitor autosave events by wrapping engine methods
(0, react_1.useEffect)(() => {
if (!engine)
return;
const originalSelectChoice = engine.selectChoice.bind(engine);
const originalSetFlag = engine.setFlag.bind(engine);
// Track autosave calls
const trackAutosave = async (originalMethod, ...args) => {
const result = originalMethod(...args);
if (enabled) {
setIsSaving(true);
try {
// Wait for potential autosave to complete
await new Promise(resolve => setTimeout(resolve, throttleMs + 50));
setLastAutosave(new Date());
}
finally {
setIsSaving(false);
}
}
return result;
};
// Override methods to track autosave
engine.selectChoice = (...args) => trackAutosave(originalSelectChoice, ...args);
engine.setFlag = (...args) => trackAutosave(originalSetFlag, ...args);
return () => {
// Restore original methods
engine.selectChoice = originalSelectChoice;
engine.setFlag = originalSetFlag;
};
}, [engine, enabled, throttleMs]);
const autosave = (0, react_1.useCallback)(async () => {
setIsSaving(true);
try {
await engine.manualAutosave();
setLastAutosave(new Date());
}
finally {
setIsSaving(false);
}
}, [engine]);
const configure = (0, react_1.useCallback)((config) => {
engine.configureAutosave(config);
}, [engine]);
return {
autosave,
configure,
isEnabled: enabled,
lastAutosave,
isSaving
};
}
//# sourceMappingURL=react.js.map