khan-graphlql / index.html
Fede-rueen's picture
undefined - Initial Deployment
ffe8a58 verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>GraphQL Playground</title>
<script src="https://cdn.tailwindcss.com"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
.cm-editor {
height: 100%;
font-size: 14px;
line-height: 1.5;
}
.cm-scroller {
font-family: 'Fira Code', monospace;
}
.cm-gutters {
background-color: #f8fafc !important;
}
.json-viewer {
white-space: pre-wrap;
font-family: 'Fira Code', monospace;
font-size: 14px;
}
.graphiql-container {
--color-primary: #6366f1;
--color-secondary: #818cf8;
}
.tab-active {
border-bottom: 2px solid #6366f1;
color: #6366f1;
}
.query-history-item:hover {
background-color: #f1f5f9;
cursor: pointer;
}
</style>
</head>
<body class="bg-gray-50 min-h-screen">
<div class="container mx-auto px-4 py-8">
<!-- Header -->
<header class="mb-8">
<div class="flex flex-col md:flex-row justify-between items-start md:items-center gap-4">
<div>
<h1 class="text-3xl font-bold text-indigo-600 flex items-center gap-2">
<i class="fas fa-project-diagram"></i>
GraphQL Explorer
</h1>
<p class="text-gray-600">Interactive GraphQL API playground</p>
</div>
<div class="flex gap-2">
<button id="execute-btn" class="bg-indigo-600 hover:bg-indigo-700 text-white px-4 py-2 rounded-md flex items-center gap-2 transition-colors">
<i class="fas fa-play"></i> Execute
</button>
<button id="save-btn" class="bg-gray-200 hover:bg-gray-300 text-gray-800 px-4 py-2 rounded-md flex items-center gap-2 transition-colors">
<i class="fas fa-save"></i> Save
</button>
<button id="history-btn" class="bg-gray-200 hover:bg-gray-300 text-gray-800 px-4 py-2 rounded-md flex items-center gap-2 transition-colors">
<i class="fas fa-history"></i> History
</button>
</div>
</div>
</header>
<!-- Main Content -->
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
<!-- Left Panel - Query Editor -->
<div class="lg:col-span-2 bg-white rounded-lg shadow-md overflow-hidden">
<div class="border-b border-gray-200">
<div class="flex">
<button class="tab-btn px-4 py-3 font-medium text-gray-600 tab-active" data-tab="query">
<i class="fas fa-code mr-2"></i>Query
</button>
<button class="tab-btn px-4 py-3 font-medium text-gray-600" data-tab="variables">
<i class="fas fa-list mr-2"></i>Variables
</button>
<button class="tab-btn px-4 py-3 font-medium text-gray-600" data-tab="headers">
<i class="fas fa-heading mr-2"></i>Headers
</button>
</div>
</div>
<div class="tab-content" id="query-tab">
<div id="query-editor" class="h-96 p-4 font-mono">{
characters(page: 2, filter: { name: "rick" }) {
info {
count
}
results {
name
status
species
type
gender
}
}
}</div>
</div>
<div class="tab-content hidden" id="variables-tab">
<textarea id="variables-editor" class="w-full h-96 p-4 font-mono border-none focus:ring-0 resize-none" placeholder="Enter variables as JSON">{
"page": 1,
"filter": {
"name": "rick"
}
}</textarea>
</div>
<div class="tab-content hidden" id="headers-tab">
<textarea id="headers-editor" class="w-full h-96 p-4 font-mono border-none focus:ring-0 resize-none" placeholder="Enter headers as JSON">{
"Authorization": "Bearer YOUR_TOKEN",
"Content-Type": "application/json"
}</textarea>
</div>
</div>
<!-- Right Panel - Results and Documentation -->
<div class="space-y-6">
<!-- Results Panel -->
<div class="bg-white rounded-lg shadow-md overflow-hidden">
<div class="border-b border-gray-200 px-4 py-3 font-medium text-gray-600 flex justify-between items-center">
<span><i class="fas fa-chart-bar mr-2"></i>Results</span>
<div class="flex gap-2">
<button id="prettify-btn" class="text-gray-500 hover:text-indigo-600">
<i class="fas fa-align-left"></i>
</button>
<button id="copy-btn" class="text-gray-500 hover:text-indigo-600">
<i class="fas fa-copy"></i>
</button>
</div>
</div>
<div id="results-container" class="p-4 h-64 overflow-auto json-viewer bg-gray-50">
<p class="text-gray-400 italic">Results will appear here after execution</p>
</div>
</div>
<!-- Documentation Panel -->
<div class="bg-white rounded-lg shadow-md overflow-hidden">
<div class="border-b border-gray-200 px-4 py-3 font-medium text-gray-600">
<i class="fas fa-book mr-2"></i>Documentation
</div>
<div class="p-4 h-64 overflow-auto">
<div class="space-y-4">
<div>
<h3 class="font-medium text-indigo-600">Query</h3>
<ul class="ml-4 mt-2 space-y-1 text-sm text-gray-600">
<li><span class="font-mono">characters</span> - Get all characters</li>
<li><span class="font-mono">character(id: ID!)</span> - Get a specific character</li>
<li><span class="font-mono">locations</span> - Get all locations</li>
<li><span class="font-mono">location(id: ID!)</span> - Get a specific location</li>
</ul>
</div>
<div>
<h3 class="font-medium text-indigo-600">Types</h3>
<ul class="ml-4 mt-2 space-y-1 text-sm text-gray-600">
<li><span class="font-mono">Character</span> - A character in the series</li>
<li><span class="font-mono">Location</span> - A location in the series</li>
<li><span class="font-mono">Episode</span> - An episode in the series</li>
</ul>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Endpoint Configuration -->
<div class="mt-6 bg-white rounded-lg shadow-md p-4">
<div class="flex flex-col md:flex-row gap-4 items-start md:items-center">
<div class="flex-1">
<label class="block text-sm font-medium text-gray-700 mb-1">GraphQL Endpoint</label>
<input type="text" id="endpoint-input" value="https://rickandmortyapi.com/graphql" class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-indigo-500">
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Request Method</label>
<select id="method-select" class="px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-indigo-500">
<option value="POST">POST</option>
<option value="GET">GET</option>
</select>
</div>
</div>
</div>
<!-- History Modal -->
<div id="history-modal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden z-50">
<div class="bg-white rounded-lg shadow-xl w-full max-w-2xl max-h-[80vh] overflow-hidden">
<div class="border-b border-gray-200 px-6 py-4 flex justify-between items-center">
<h3 class="text-lg font-medium text-gray-900">Query History</h3>
<button id="close-history" class="text-gray-400 hover:text-gray-500">
<i class="fas fa-times"></i>
</button>
</div>
<div class="p-6 overflow-y-auto">
<ul id="history-list" class="divide-y divide-gray-200">
<!-- History items will be added here -->
</ul>
</div>
<div class="border-t border-gray-200 px-6 py-4 flex justify-end">
<button id="clear-history" class="text-red-600 hover:text-red-800 mr-4">Clear History</button>
<button id="close-history-btn" class="bg-indigo-600 hover:bg-indigo-700 text-white px-4 py-2 rounded-md">Close</button>
</div>
</div>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
// Tab switching
const tabButtons = document.querySelectorAll('.tab-btn');
const tabContents = document.querySelectorAll('.tab-content');
tabButtons.forEach(button => {
button.addEventListener('click', () => {
// Remove active class from all buttons
tabButtons.forEach(btn => btn.classList.remove('tab-active'));
// Add active class to clicked button
button.classList.add('tab-active');
// Hide all tab contents
tabContents.forEach(content => content.classList.add('hidden'));
// Show the selected tab content
const tabId = button.getAttribute('data-tab') + '-tab';
document.getElementById(tabId).classList.remove('hidden');
});
});
// Execute button
document.getElementById('execute-btn').addEventListener('click', executeQuery);
// History modal
const historyModal = document.getElementById('history-modal');
document.getElementById('history-btn').addEventListener('click', () => {
historyModal.classList.remove('hidden');
loadHistory();
});
document.getElementById('close-history').addEventListener('click', () => {
historyModal.classList.add('hidden');
});
document.getElementById('close-history-btn').addEventListener('click', () => {
historyModal.classList.add('hidden');
});
document.getElementById('clear-history').addEventListener('click', () => {
localStorage.removeItem('graphqlQueryHistory');
document.getElementById('history-list').innerHTML = '<p class="text-gray-500 py-4">No queries in history</p>';
});
// Copy button
document.getElementById('copy-btn').addEventListener('click', () => {
const results = document.getElementById('results-container').textContent;
navigator.clipboard.writeText(results).then(() => {
const copyBtn = document.getElementById('copy-btn');
copyBtn.innerHTML = '<i class="fas fa-check"></i>';
setTimeout(() => {
copyBtn.innerHTML = '<i class="fas fa-copy"></i>';
}, 2000);
});
});
// Prettify button
document.getElementById('prettify-btn').addEventListener('click', () => {
const resultsContainer = document.getElementById('results-container');
try {
const json = JSON.parse(resultsContainer.textContent);
resultsContainer.textContent = JSON.stringify(json, null, 2);
} catch (e) {
// Not valid JSON, do nothing
}
});
// Load CodeMirror for syntax highlighting
loadCodeMirror();
function executeQuery() {
const query = document.getElementById('query-editor').textContent;
const variables = document.getElementById('variables-editor').value;
const headers = document.getElementById('headers-editor').value;
const endpoint = document.getElementById('endpoint-input').value;
const method = document.getElementById('method-select').value;
const resultsContainer = document.getElementById('results-container');
resultsContainer.innerHTML = '<p class="text-gray-500 italic">Executing query...</p>';
try {
const requestBody = {
query: query
};
if (variables) {
requestBody.variables = JSON.parse(variables);
}
const requestHeaders = {
'Content-Type': 'application/json'
};
if (headers) {
try {
const headersObj = JSON.parse(headers);
Object.assign(requestHeaders, headersObj);
} catch (e) {
console.error('Invalid headers JSON');
}
}
const options = {
method: method,
headers: requestHeaders,
body: method === 'POST' ? JSON.stringify(requestBody) : null
};
let url = endpoint;
if (method === 'GET') {
url += `?query=${encodeURIComponent(query)}`;
if (variables) {
url += `&variables=${encodeURIComponent(variables)}`;
}
}
fetch(url, options)
.then(response => response.json())
.then(data => {
resultsContainer.textContent = JSON.stringify(data, null, 2);
saveToHistory(query, data);
})
.catch(error => {
resultsContainer.textContent = `Error: ${error.message}`;
});
} catch (error) {
resultsContainer.textContent = `Error: ${error.message}`;
}
}
function saveToHistory(query, response) {
const historyItem = {
query: query,
response: response,
timestamp: new Date().toISOString()
};
let history = JSON.parse(localStorage.getItem('graphqlQueryHistory') || '[]');
history.unshift(historyItem);
if (history.length > 10) {
history = history.slice(0, 10);
}
localStorage.setItem('graphqlQueryHistory', JSON.stringify(history));
}
function loadHistory() {
const historyList = document.getElementById('history-list');
const history = JSON.parse(localStorage.getItem('graphqlQueryHistory') || '[]');
if (history.length === 0) {
historyList.innerHTML = '<p class="text-gray-500 py-4">No queries in history</p>';
return;
}
historyList.innerHTML = '';
history.forEach((item, index) => {
const li = document.createElement('li');
li.className = 'py-4 query-history-item';
li.innerHTML = `
<div class="flex items-center justify-between">
<div class="flex items-center">
<span class="text-indigo-600 font-mono text-sm mr-3">#${index + 1}</span>
<div>
<p class="text-sm font-medium text-gray-900 truncate max-w-xs">${item.query.split('\n')[0].substring(0, 50)}...</p>
<p class="text-xs text-gray-500">${new Date(item.timestamp).toLocaleString()}</p>
</div>
</div>
<button class="text-indigo-600 hover:text-indigo-800" onclick="loadHistoryItem(${index})">
<i class="fas fa-eye"></i>
</button>
</div>
`;
historyList.appendChild(li);
});
}
window.loadHistoryItem = function(index) {
const history = JSON.parse(localStorage.getItem('graphqlQueryHistory') || '[]');
if (index >= 0 && index < history.length) {
const item = history[index];
document.getElementById('query-editor').textContent = item.query;
document.getElementById('results-container').textContent = JSON.stringify(item.response, null, 2);
historyModal.classList.add('hidden');
}
};
function loadCodeMirror() {
// In a real implementation, you would load CodeMirror here
// For this example, we'll just use textareas with monospace font
console.log('CodeMirror would be loaded here in a real implementation');
}
});
</script>
<p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=Fede-rueen/khan-graphlql" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body>
</html>