Skip to content

Instantly share code, notes, and snippets.

@jerrylususu
Created May 19, 2025 14:45
Show Gist options
  • Save jerrylususu/d085e374d05589a7bee4edff72d46a24 to your computer and use it in GitHub Desktop.
Save jerrylususu/d085e374d05589a7bee4edff72d46a24 to your computer and use it in GitHub Desktop.
typescript matcher playground
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>TypeScript Match Engine Playground</title>
<style>
body { font-family: sans-serif; display: flex; flex-direction: column; height: 100vh; margin: 0; }
.main-container { display: flex; flex: 1; overflow: hidden; } /* Changed from .container to .main-container */
.column {
display: flex;
flex-direction: column;
padding: 10px;
box-sizing: border-box;
overflow: hidden; /* Prevent content from breaking layout */
}
.column-left {
flex: 1; /* Each column takes equal width initially */
border-right: 1px solid #ccc;
}
.column-middle {
flex: 2; /* Editor column can be wider */
border-right: 1px solid #ccc;
min-width: 0; /* Necessary for flex items to shrink properly */
}
.column-right {
flex: 1;
min-width: 0; /* Necessary for flex items to shrink properly */
}
.input-area, .output-area-json, .log-area {
margin-bottom: 15px; /* Space between sections in a column */
flex: 1; /* Allow these areas to grow and shrink */
display: flex;
flex-direction: column;
min-height: 0; /* Critical for nested flex children */
}
/* Remove margin from the last element in a column if needed */
.column > div:last-child {
margin-bottom: 0;
}
textarea.json-input-textarea, textarea#output-log { /* Renamed #output to #output-log */
width: 100%;
flex: 1;
box-sizing: border-box;
padding: 5px;
border: 1px solid #ccc;
resize: none;
}
.json-viewer-display {
flex: 1;
overflow: auto;
border: 1px solid #ccc;
padding: 5px;
box-sizing: border-box;
display: none; /* Initially hidden */
background-color: #f9f9f9;
}
/* Ensure the json-viewer custom element takes full width */
json-viewer {
width: 100%;
display: block;
}
#editor {
width: 100%;
flex: 1;
min-height: 0;
border: 1px solid #ccc;
}
button { padding: 8px 15px; margin-bottom: 10px; cursor: pointer; background-color: #007bff; color: white; border: none; border-radius: 4px; }
button:hover { background-color: #0056b3; }
h3, h4 { margin-top: 0; margin-bottom: 5px; }
/* Specific styling for elements within their new containers */
#dependency-status-container {
padding-bottom: 10px;
border-bottom: 1px solid #eee;
margin-bottom: 10px;
}
.editor-controls { /* Container for button above editor */
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 5px;
}
</style>
</head>
<body>
<div class="main-container">
<div class="column column-left">
<div class="input-area" id="supply-input-container">
<h3>Supply (as 'supply')</h3>
<button id="toggle-supply-view-button" style="margin-bottom: 5px;">View as Tree</button>
<textarea id="supply-input" class="json-input-textarea" placeholder="Enter Supply JSON..."></textarea>
<div id="supply-viewer-display" class="json-viewer-display"></div>
</div>
<div class="input-area" id="demand-input-container">
<h3>Demand (as 'demand')</h3>
<button id="toggle-demand-view-button" style="margin-bottom: 5px;">View as Tree</button>
<textarea id="demand-input" class="json-input-textarea" placeholder="Enter Demand JSON..."></textarea>
<div id="demand-viewer-display" class="json-viewer-display"></div>
</div>
</div>
<div class="column column-middle">
<div id="dependency-status-container">
<h4 style="margin-top:0; margin-bottom: 5px;">Dependency Status:</h4>
<span id="zod-status-indicator">Zod: ⌛ Initializing...</span>
</div>
<div class="editor-controls">
<h3>TypeScript Code</h3>
<button id="run-button">Run Code</button>
</div>
<div id="editor"></div>
</div>
<div class="column column-right">
<div class="output-area-json" id="output-json-container">
<h3>Output (from 'output')</h3>
<button id="toggle-output-view-button" style="margin-bottom: 5px;">View as Tree</button>
<textarea id="output-json-text" class="json-input-textarea" readonly placeholder="Script output will appear here..."></textarea>
<div id="output-viewer-display" class="json-viewer-display" style="display: none;"></div>
</div>
<div class="log-area" id="log-container">
<h3>Console Log</h3>
<textarea id="output-log" readonly></textarea> {/* Renamed from #output */}
</div>
</div>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.30.1/min/vs/loader.min.js"></script>
<script type="module">
import { z } from 'https://esm.sh/[email protected]';
window.z = z;
</script>
<script src="https://unpkg.com/@alenaksu/[email protected]/dist/json-viewer.bundle.js"></script>
<script>
let editor;
let supplyJson = {}; // Variable for parsed Supply JSON
let demandJson = {}; // Variable for parsed Demand JSON
let scriptOutput = {}; // Variable for script output, to be displayed in its own JSON viewer
let zod = undefined;
const zodStatusEl = document.getElementById('zod-status-indicator');
function updateZodStatus(status, isError = false) {
if (zodStatusEl) {
zodStatusEl.textContent = status;
zodStatusEl.style.color = isError ? 'red' : 'green'; // Green for success, red for error
zodStatusEl.style.fontWeight = isError ? 'bold' : 'normal';
}
}
updateZodStatus('Zod: ⌛ Loading...');
function checkZod() {
if (window.z) {
zod = window.z;
console.log('Zod initialized successfully via ESM');
updateZodStatus('Zod: ✅ Loaded');
} else {
requestAnimationFrame(checkZod);
}
}
requestAnimationFrame(checkZod);
setTimeout(() => {
if (!window.z) {
console.error('CRITICAL: Failed to load Zod ESM module');
updateZodStatus('Zod: ❌ Failed to Load', true);
}
}, 5000);
require.config({ paths: { 'vs': 'https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.30.1/min/vs' }});
require(['vs/editor/editor.main'], function() {
monaco.languages.typescript.typescriptDefaults.addExtraLib(
`
declare var supply: any;
declare var demand: any;
declare var output: any; // User script will write to this
declare var zod: any;
`,
'global.d.ts'
);
editor = monaco.editor.create(document.getElementById('editor'), {
value: [
'// Welcome to the Match Engine Playground!',
"// Use 'supply', 'demand' (parsed JSON) and 'zod'.",
"// Assign results to the 'output' variable.",
'console.log("Supply data:", supply);',
'console.log("Demand data:", demand);',
'',
'// Example: Find matching items by ID',
'output.matches = [];',
'if (Array.isArray(supply.items) && Array.isArray(demand.requests)) {',
' for (const item of supply.items) {',
' for (const request of demand.requests) {',
' if (item.id === request.itemId) {',
' output.matches.push({ item, request, matchDetails: "Matched by ID" });',
' }',
' }',
' }',
'}',
"console.log(\"Processing complete. Check the 'output' view.\");",
].join('\n'),
language: 'typescript',
theme: 'vs-dark',
automaticLayout: true,
});
});
const supplyInputEl = document.getElementById('supply-input');
const demandInputEl = document.getElementById('demand-input');
const outputLogEl = document.getElementById('output-log'); // Renamed
const runButton = document.getElementById('run-button');
const toggleSupplyViewButton = document.getElementById('toggle-supply-view-button');
const supplyViewerDisplay = document.getElementById('supply-viewer-display');
let isSupplyViewMode = false;
const toggleDemandViewButton = document.getElementById('toggle-demand-view-button');
const demandViewerDisplay = document.getElementById('demand-viewer-display');
let isDemandViewMode = false;
const outputViewerDisplay = document.getElementById('output-viewer-display'); // For script output
const outputJsonTextEl = document.getElementById('output-json-text');
const toggleOutputViewButton = document.getElementById('toggle-output-view-button');
let isOutputViewMode = false; // false = text, true = tree
// Generic function to handle JSON input and update the variable
supplyInputEl.addEventListener('input', () => {
try {
supplyJson = JSON.parse(supplyInputEl.value);
supplyInputEl.style.borderColor = '#ccc';
} catch (e) {
supplyInputEl.style.borderColor = 'red';
console.warn(`Invalid JSON in ${supplyInputEl.id}:`, e.message);
// supplyJson will retain its last valid value or initial {}
}
});
demandInputEl.addEventListener('input', () => {
try {
demandJson = JSON.parse(demandInputEl.value);
demandInputEl.style.borderColor = '#ccc';
} catch (e) {
demandInputEl.style.borderColor = 'red';
console.warn(`Invalid JSON in ${demandInputEl.id}:`, e.message);
// demandJson will retain its last valid value or initial {}
}
});
// Generic function to update a JSON viewer
function updateGenericJsonViewer(viewerDisplayEl, jsonData, inputTextAreaElForBorder) {
viewerDisplayEl.innerHTML = ''; // Clear previous viewer
try {
const viewerElement = document.createElement('json-viewer');
viewerElement.data = jsonData; // Assumes jsonData is already a parsed object
viewerDisplayEl.appendChild(viewerElement);
// Expand all nodes after data is set
if (viewerElement.expandAll) {
viewerElement.expandAll();
} else if (typeof viewerElement.expand === 'function') { // Fallback to expand('**')
viewerElement.expand('**');
} else {
console.warn("json-viewer element created, but expandAll method not found. Expansion might not occur.");
}
if (inputTextAreaElForBorder) inputTextAreaElForBorder.style.borderColor = '#ccc';
} catch (e) {
viewerDisplayEl.textContent = 'Error displaying JSON data.';
if (inputTextAreaElForBorder) inputTextAreaElForBorder.style.borderColor = 'red';
console.error("Error updating JSON viewer:", e);
}
}
// Generic function to toggle between textarea and JSON tree view
function toggleJsonView(isViewModeFlagRef, textareaEl, viewerDisplayEl, buttonEl, jsonDataObjectRef, dataKey, viewAsTreeText, editJsonText) {
isViewModeFlagRef[dataKey] = !isViewModeFlagRef[dataKey];
if (isViewModeFlagRef[dataKey]) { // View as Tree
try {
// Ensure current text area value is parsed for the viewer
const currentData = JSON.parse(textareaEl.value);
textareaEl.style.display = 'none';
viewerDisplayEl.style.display = 'flex';
updateGenericJsonViewer(viewerDisplayEl, currentData, textareaEl);
buttonEl.textContent = editJsonText;
textareaEl.style.borderColor = '#ccc';
} catch (e) {
isViewModeFlagRef[dataKey] = false; // Revert mode
alert("JSON is currently invalid. Please correct it before viewing as a tree.");
textareaEl.style.borderColor = 'red';
}
} else { // Edit JSON
textareaEl.style.display = 'block';
viewerDisplayEl.style.display = 'none';
buttonEl.textContent = viewAsTreeText;
}
}
// Store view modes in an object to pass by reference
let viewModes = { supply: false, demand: false };
toggleSupplyViewButton.addEventListener('click', () => {
// Pass null for jsonDataObjectRef as it's not used for data retrieval in toggleJsonView
toggleJsonView(viewModes, supplyInputEl, supplyViewerDisplay, toggleSupplyViewButton, null, 'supply', 'View Supply as Tree', 'Edit Supply JSON');
});
toggleDemandViewButton.addEventListener('click', () => {
// Pass null for jsonDataObjectRef as it's not used for data retrieval in toggleJsonView
toggleJsonView(viewModes, demandInputEl, demandViewerDisplay, toggleDemandViewButton, null, 'demand', 'View Demand as Tree', 'Edit Demand JSON');
});
toggleOutputViewButton.addEventListener('click', () => {
isOutputViewMode = !isOutputViewMode;
if (isOutputViewMode) { // View as Tree
outputJsonTextEl.style.display = 'none';
outputViewerDisplay.style.display = 'flex';
// Data is already in scriptOutput, just re-render and expand
// displayScriptOutput will handle the viewer creation and expansion
displayScriptOutput(scriptOutput); // Re-render to ensure viewer is active and expanded
toggleOutputViewButton.textContent = 'View as Text';
} else { // View as Text
outputJsonTextEl.style.display = 'block';
outputViewerDisplay.style.display = 'none';
toggleOutputViewButton.textContent = 'View as Tree';
// Textarea is already updated by displayScriptOutput
}
});
// Function to update the main output JSON viewer (for 'output' variable from script)
function displayScriptOutput(data) {
// Update the textarea for raw JSON view
try {
outputJsonTextEl.value = JSON.stringify(data, null, 2);
outputJsonTextEl.style.borderColor = '#ccc';
} catch (e) {
outputJsonTextEl.value = "Error stringifying script output.";
outputJsonTextEl.style.borderColor = 'red';
}
// Update the JSON viewer (if it's the active view or becomes active)
outputViewerDisplay.innerHTML = ''; // Clear previous
// outputViewerDisplay.style.display = 'flex'; // Visibility handled by toggle button
try {
const viewerElement = document.createElement('json-viewer');
viewerElement.data = data;
outputViewerDisplay.appendChild(viewerElement);
// Expand all nodes after data is set
if (viewerElement.expandAll) {
viewerElement.expandAll();
} else {
// Fallback or log if expandAll is not available, though it should be
console.warn("json-viewer element created, but expandAll method not found immediately. Expansion might rely on component's internal timing or might not occur if the method name is different (e.g. ccollapseAll as per docs - checking that too).");
// Trying with the documented `ccollapseAll` - oops, that's for collapsing. It should be `expandAll` or similar.
// The user provided doc says `expandAll () => void | Alias for expand('**')`
// Let's assume `expandAll` is correct. If not, the user will need to provide the correct method.
// The user mentioned `ccollapseAll` - this is likely a typo and meant `expandAll`.
// The provided documentation snippet also lists `expandAll`.
// Let's ensure it is called if present.
if (typeof viewerElement.expandAll === 'function') {
viewerElement.expandAll();
} else if (typeof viewerElement.expand === 'function') { // Fallback to expand('**') if expandAll not directly there
viewerElement.expand('**');
}
}
} catch (e) {
outputViewerDisplay.textContent = 'Error displaying script output JSON.';
console.error("Error rendering script output in JSON viewer:", e);
}
}
// Initialize input fields with some default JSON
supplyInputEl.value = JSON.stringify({
items: [
{ id: "s1", product: "Apple", quantity: 100, price: 1.2 },
{ id: "s2", product: "Banana", quantity: 150, price: 0.8 }
],
metadata: { source: "SystemA", timestamp: new Date().toISOString() }
}, null, 2);
demandInputEl.value = JSON.stringify({
requests: [
{ id: "d1", itemId: "s1", quantity: 10 },
{ id: "d2", itemId: "s3", quantity: 5 } // s3 does not exist in supply
],
customerInfo: { id: "cust123", priority: "high" }
}, null, 2);
// Trigger input events to parse initial JSON
supplyInputEl.dispatchEvent(new Event('input'));
demandInputEl.dispatchEvent(new Event('input'));
// Override console.log/error etc.
const originalConsoleLog = console.log;
const originalConsoleError = console.error;
const originalConsoleWarn = console.warn;
const originalConsoleInfo = console.info;
const originalConsoleDebug = console.debug;
function appendToOutputLog(args, type = 'log') {
const message = args.map(arg => {
if (arg instanceof Error) {
let errorString = `${arg.name}: ${arg.message}`;
if (arg.stack) {
const stackLines = arg.stack.split('\n');
errorString += `\nStack (partial):\n ${stackLines.slice(1, 4).join('\n ')}`;
}
return errorString;
} else if (typeof arg === 'object' && arg !== null) {
try {
return JSON.stringify(arg, null, 2);
} catch (e_stringify) {
return arg.toString();
}
}
return String(arg);
}).join(' ');
outputLogEl.value += `[${type.toUpperCase()}] ${message}\n`;
outputLogEl.scrollTop = outputLogEl.scrollHeight;
}
console.log = (...args) => { originalConsoleLog.apply(console, args); appendToOutputLog(args, 'log'); };
console.error = (...args) => { originalConsoleError.apply(console, args); appendToOutputLog(args, 'error'); };
console.warn = (...args) => { originalConsoleWarn.apply(console, args); appendToOutputLog(args, 'warn'); };
console.info = (...args) => { originalConsoleInfo.apply(console, args); appendToOutputLog(args, 'info'); };
console.debug = (...args) => { originalConsoleDebug.apply(console, args); appendToOutputLog(args, 'debug'); };
runButton.addEventListener('click', () => {
const userCode = editor.getValue();
outputLogEl.value = ''; // Clear previous log output
scriptOutput = {}; // Reset script output object for each run
const currentZodStatusEl = document.getElementById('zod-status-indicator');
if (!zod) {
const msg = "Zod is not initialized. Cannot execute code. Check console and dependency status.";
console.error(msg);
if (currentZodStatusEl) {
currentZodStatusEl.textContent = 'Zod: ❌ Not Ready for Execution!';
currentZodStatusEl.style.color = 'red';
currentZodStatusEl.style.fontWeight = 'bold';
}
return;
}
if (currentZodStatusEl) { // Ensure status is positive if Zod is loaded
updateZodStatus('Zod: ✅ Ready', false);
}
try {
// Use global supplyJson and demandJson directly
(function(supply, demand, output, z) {
eval(userCode);
})(supplyJson, demandJson, scriptOutput, zod); // Changed from window.supplyJson/demandJson
// After script execution, display the content of 'scriptOutput'
// This will update both textarea and viewer (if visible)
displayScriptOutput(scriptOutput);
// Adjust visibility based on current view mode
if (isOutputViewMode) { // Tree view
outputJsonTextEl.style.display = 'none';
outputViewerDisplay.style.display = 'flex';
} else { // Text view
outputJsonTextEl.style.display = 'block';
outputViewerDisplay.style.display = 'none';
}
} catch (e) {
console.error("Error executing code:", e);
// Optionally, display this error in the script output viewer as well or instead
displayScriptOutput({ error: e.name, message: e.message, stack: e.stack ? e.stack.split('\n').slice(0,5).join('\n') : "No stack" });
}
});
// Initial display for output viewer (empty)
displayScriptOutput({});
// Ensure initial state of output view is text
outputJsonTextEl.style.display = 'block';
outputViewerDisplay.style.display = 'none';
toggleOutputViewButton.textContent = 'View as Tree';
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment