spytial 0.2.0

Drop-in replacement for std::dbg! that opens an interactive diagram of Rust values (Spytial for Rust).
Documentation
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>sPyTial</title>
    
    <style>/*__SPYTIAL_CORE_CSS__*/</style>
    <style>/*__REACT_COMPONENTS_CSS__*/</style>
    <script>/*__SPYTIAL_CORE_JS__*/</script>
    <script>/*__REACT_COMPONENTS_JS__*/</script>
    
    <style>
        body {
            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
            margin: 0;
            padding: 0;
            background: transparent;
            height: 100vh;
            display: flex;
            flex-direction: column;
        }
        
        .container {
            position: relative;
            border: 1px solid #e1e5e9;
            border-radius: 6px;
            overflow: hidden;
            background: white;
            flex: 1;
            display: flex;
            flex-direction: column;
        }
        
        .graph-wrapper {
            position: relative;
            min-height: 400px; /* Ensure minimum height */
            flex: 1;
            display: flex;
        }
        
        #graph-container {
            width: 100%;
            height: 100%;
            min-height: 400px;
            display: block;
            flex: 1;
        }
        
        .reset-btn {
            background: #0366d6;
            color: white;
            border: none;
            padding: 8px 8px;
            border-radius: 3px;
            font-size: 12px;
            cursor: pointer;
            transition: background-color 0.15s ease;
            line-height: 1.2;
            margin-left: 8px;
        }
        
        .reset-btn:hover {
            background: #0256cc;
        }
    </style>
</head>

<body>
    <div class="container">
        <div class="graph-wrapper">
            <!-- WebCola Rust Graph Element -->
            <webcola-cnd-graph 
                id="graph-container"
                width="800" 
                height="600"
                layoutFormat="default"
                aria-label="Interactive graph visualization">
            </webcola-cnd-graph>
        </div>
        
    </div>
    
    <div id="error-message"></div>
    <div id="error-core"></div>

    <script>
        // Embedded data passed from Rust (using simple string replacement)
        const spytialSpec = `{{ spytial_spec }}`;
        
        const jsonData = `{{ json_data }}`;

        // Global variables for reset functionality
        let instanceLayout = null;
        let graphElement = null;

        /**
         * Reset the graph layout
         */
        async function resetLayout() {
            if (graphElement && instanceLayout) {
                await graphElement.renderLayout(instanceLayout);
            }
        }

        /**
         * Create and add reset button to the graph toolbar
         */
        function addResetButton() {
            if (graphElement && graphElement.addToolbarControl) {
                const resetButton = document.createElement('button');
                resetButton.className = 'reset-btn';
                resetButton.innerHTML = '↻ Reset';
                resetButton.title = 'Reset layout';
                resetButton.onclick = resetLayout;
                
                graphElement.addToolbarControl(resetButton);
            }
        }

        /**
         * Load and render the graph using the webcola-cnd-graph custom element
         */
        async function loadGraph() {
            graphElement = document.getElementById('graph-container');

            const coreApi = window.CndCore || window.CnDCore || window.spytialcore;

            // Check if Core is available
            if (!coreApi || typeof coreApi !== 'object') {
                console.error('Spytial core library is not available');
                showError('Spytial core library failed to load. The visualization cannot be displayed.');
                return;
            }

            // Step 1: Mount error message modal if available
            if (window.mountErrorMessageModal) {
                console.log('Mounting error message modal');
                window.mountErrorMessageModal('error-core');
            }

            try {


                // Parse JSON data if it's a string
                let parsedData;
                try {
                    parsedData = typeof jsonData === 'string' ? JSON.parse(jsonData) : jsonData;
                } catch (parseError) {
                    throw new Error(`Failed to parse JSON data: ${parseError.message}`);
                }
                console.log(parsedData)
                // Create a data instance from the JSON data
                if (typeof coreApi.JSONDataInstance !== 'function') {
                    throw new Error('Spytial core API mismatch: JSONDataInstance constructor is not available on the global namespace');
                }

                const dataInstance = new coreApi.JSONDataInstance(parsedData);
                window.dataInstance = dataInstance; // Expose for debugging

                // Initialize the evaluation context
                const evaluationContext = { sourceData: dataInstance };
                const sgqEvaluator = new coreApi.SGraphQueryEvaluator();
                sgqEvaluator.initialize(evaluationContext);
                console.log('Evaluation context initialized');

                window.evaluator = sgqEvaluator; // Expose evaluator for debugging

                // Parse the layout specification (empty specs are valid)
                const layoutSpec = coreApi.parseLayoutSpec(spytialSpec);
                console.log('Layout spec parsed');

                // Generate the layout
                const layoutInstance = new coreApi.LayoutInstance(layoutSpec, sgqEvaluator, 0, true);
                const layoutResult = layoutInstance.generateLayout(dataInstance, {});
                console.log('Layout generated');
                window.evaluator = sgqEvaluator; // Expose evaluator for debugging

                // Check for layout errors
                if (layoutResult.error) {
                    console.error('Layout generation error:', layoutResult.error);

                    // Check if this is a constraint conflict error
                    if (layoutResult.error.errorMessages) {
                        if (window.showPositionalError) {
                            window.showPositionalError(layoutResult.error.errorMessages);
                        } else {
                            showError(`Positional constraint conflict: ${layoutResult.error.message}`);
                        }
                    } else if (layoutResult.error.overlappingNodes) {
                        if (window.showGroupOverlapError) {
                            window.showGroupOverlapError(layoutResult.error.message);
                        } else {
                            showError(`Group overlap error: ${layoutResult.error.message}`);
                        }
                    } else {
                        if (window.showGeneralError) {
                            window.showGeneralError(`Layout generation error: ${layoutResult.error.message}`);
                        } else {
                            showError(`Layout generation error: ${layoutResult.error.message}`);
                        }
                    }

                    // Set the webcola-cnd-graph element to indicate unsat state
                    const webcolaGraphElement = document.getElementById('graph-container');
                    webcolaGraphElement.setAttribute('unsat', "");
                }  

                instanceLayout = layoutResult.layout;
                const webcolaGraphElement = document.getElementById('graph-container');
                //webcolaGraphElement.removeAttribute('unsat'); // Clear unsat state

                // Render the graph
                await graphElement.renderLayout(instanceLayout);
                
                // Add reset button to toolbar after successful rendering
                addResetButton();
            } catch (error) {
                console.error('Error rendering graph:', error);
                if (window.showGeneralError) {
                    window.showGeneralError(`Layout generation failed: ${error.message}`);
                } else {
                    showError(`Error rendering graph: ${error.message}`);
                }
            }
        }

        /**
         * Display an error message in the error-message div.
         * @param {string} message - The error message to display.
         */
        function showError(message) {
            const errorDiv = document.getElementById('error-message');
            if (errorDiv) {
                errorDiv.innerHTML = `
                    <div style="color: red; padding: 10px; border: 1px solid red; background-color: #ffe6e6; margin-top: 10px;">
                        <h3>Error</h3>
                        <p>${message}</p>
                    </div>
                `;
            } else {
                console.error('Error div not found. Error message:', message);
            }
        }

        /**
         * Display an info message in the error-message div.
         * @param {string} message - The info message to display.
         */
        function showInfo(message) {
            const errorDiv = document.getElementById('error-message');
            if (errorDiv) {
                errorDiv.innerHTML = `
                    <div style="color: #0366d6; padding: 20px; border: 1px solid #0366d6; background-color: #f1f8ff; margin-top: 10px; border-radius: 6px;">
                        <h3>🦀 Rust Data Structure</h3>
                        <p>${message}</p>
                        <details style="margin-top: 15px;">
                            <summary style="cursor: pointer; font-weight: bold;">Show JSON Data Structure</summary>
                            <pre style="background: #f6f8fa; padding: 10px; border-radius: 4px; margin-top: 10px; font-size: 12px; max-height: 300px; overflow-y: auto;">${JSON.stringify(JSON.parse(jsonData), null, 2)}</pre>
                        </details>
                    </div>
                `;
            } else {
                console.error('Error div not found. Info message:', message);
            }
        }

        // Auto-load when page loads
        window.addEventListener('load', loadGraph);
    </script>
</body>

</html>