Created
May 19, 2025 14:45
-
-
Save jerrylususu/d085e374d05589a7bee4edff72d46a24 to your computer and use it in GitHub Desktop.
typescript matcher playground
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!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