Initial commit — Meridian OS browser desktop demo

This commit is contained in:
2026-04-19 23:18:55 +00:00
commit 8e30dd3597
30 changed files with 6277 additions and 0 deletions

184
js/apps/postman.js Normal file
View File

@@ -0,0 +1,184 @@
// Postman spoof — static-but-believable HTTP client UI.
const RESPONSES = {
GET: `{
"id": 1,
"name": "Alice Freeman",
"email": "alice@meridian.dev",
"role": "developer",
"active": true
}`,
POST: `{
"id": 42,
"name": "New Resource",
"created": "2025-03-14T10:30:00Z",
"status": "created",
"message": "Resource created successfully"
}`,
PUT: `{
"id": 42,
"name": "Updated Resource",
"updated": "2025-03-14T10:32:00Z",
"status": "updated",
"message": "Resource updated successfully"
}`,
DELETE: `{
"id": 42,
"status": "deleted",
"message": "Resource deleted successfully"
}`,
};
const METHODS = ['GET', 'POST', 'PUT', 'DELETE'];
export default {
id: 'postman',
title: 'Postman',
render(container, api) {
let method = 'GET';
let sending = false;
const root = document.createElement('div');
root.style.cssText = 'height:100%;display:flex;flex-direction:column;background:var(--bg-1);';
// Request bar: method select + URL input + Send button
const reqBar = document.createElement('div');
reqBar.style.cssText = 'display:flex;align-items:center;gap:0;padding:8px 12px;border-bottom:1px solid var(--border);background:var(--bg-2);flex-shrink:0;';
const methodSelect = document.createElement('select');
methodSelect.style.cssText = 'padding:6px 10px;background:var(--bg-3);border:1px solid var(--border);border-right:none;border-radius:4px 0 0 4px;color:var(--accent);font-weight:600;font-size:13px;outline:none;cursor:pointer;min-width:70px;';
METHODS.forEach((m) => {
const opt = document.createElement('option');
opt.value = m;
opt.textContent = m;
if (m === method) opt.selected = true;
methodSelect.appendChild(opt);
});
methodSelect.addEventListener('change', () => { method = methodSelect.value; });
const urlInput = document.createElement('input');
urlInput.type = 'text';
urlInput.value = 'https://api.meridian.dev/v1/users';
urlInput.style.cssText = 'flex:1;padding:6px 12px;background:var(--bg-3);border:1px solid var(--border);color:var(--text);font-size:13px;font-family:monospace;outline:none;';
urlInput.placeholder = 'Enter request URL';
const sendBtn = document.createElement('button');
sendBtn.style.cssText = 'padding:6px 20px;background:var(--accent);color:#0f1114;border:none;border-radius:0 4px 4px 0;font-size:13px;font-weight:600;cursor:pointer;';
sendBtn.textContent = 'Send';
sendBtn.addEventListener('click', () => {
if (sending) return;
sending = true;
sendBtn.textContent = 'Sending...';
sendBtn.disabled = true;
setTimeout(() => {
sendBtn.textContent = 'Send';
sendBtn.disabled = false;
sending = false;
showResponse();
}, 200);
});
reqBar.appendChild(methodSelect);
reqBar.appendChild(urlInput);
reqBar.appendChild(sendBtn);
root.appendChild(reqBar);
// Tabs
const tabNames = ['Params', 'Headers', 'Body'];
let activeTab = 'Params';
const tabBtns = [];
const tabBar = document.createElement('div');
tabBar.style.cssText = 'display:flex;gap:0;border-bottom:1px solid var(--border);background:var(--bg-2);flex-shrink:0;';
tabNames.forEach((tab) => {
const btn = document.createElement('button');
const isActive = activeTab === tab;
btn.style.cssText = 'padding:8px 16px;background:transparent;border:none;border-right:1px solid var(--border);color:' + (isActive ? 'var(--text)' : 'var(--text-dim)') + ';font-size:12px;cursor:pointer;transition:color 150ms;';
btn.textContent = tab;
btn.addEventListener('click', () => {
activeTab = tab;
tabArea.innerHTML = contentPanels[tab];
tabBtns.forEach((b) => { b.style.color = 'var(--text-dim)'; });
btn.style.color = 'var(--text)';
});
tabBtns.push(btn);
tabBar.appendChild(btn);
});
root.appendChild(tabBar);
// Tab content panels
const contentPanels = {
Params: '<div style="font-size:12px;line-height:2.4;padding:4px 0;">' +
'<div style="display:flex;gap:8px;margin-bottom:4px;">' +
'<span style="color:var(--accent);font-weight:500;width:100px;flex-shrink:0;">Authorization</span>' +
'<input style="flex:1;background:var(--bg-1);border:1px solid var(--border);padding:5px 10px;border-radius:3px;color:var(--text);font-size:12px;font-family:monospace;" value="Bearer eyJhbGciOiJIUzI1NiJ9..." />' +
'</div>' +
'<div style="display:flex;gap:8px;margin-bottom:4px;">' +
'<span style="color:var(--accent);font-weight:500;width:100px;flex-shrink:0;">Content-Type</span>' +
'<input style="flex:1;background:var(--bg-1);border:1px solid var(--border);padding:5px 10px;border-radius:3px;color:var(--text);font-size:12px;font-family:monospace;" value="application/json" />' +
'</div>' +
'<div style="display:flex;gap:8px;">' +
'<span style="color:var(--accent);font-weight:500;width:100px;flex-shrink:0;">X-Request-ID</span>' +
'<input style="flex:1;background:var(--bg-1);border:1px solid var(--border);padding:5px 10px;border-radius:3px;color:var(--text);font-size:12px;font-family:monospace;" value="" placeholder="Auto-generated" />' +
'</div>' +
'</div>',
Headers: '<div style="font-size:12px;line-height:2.4;padding:4px 0;">' +
'<div style="display:flex;gap:8px;margin-bottom:4px;">' +
'<span style="color:var(--accent);font-weight:500;width:100px;flex-shrink:0;">User-Agent</span>' +
'<span style="color:var(--text);font-family:monospace;">MeridianClient/1.0</span>' +
'</div>' +
'<div style="display:flex;gap:8px;margin-bottom:4px;">' +
'<span style="color:var(--accent);font-weight:500;width:100px;flex-shrink:0;">Accept</span>' +
'<span style="color:var(--text);font-family:monospace;">application/json</span>' +
'</div>' +
'<div style="display:flex;gap:8px;">' +
'<span style="color:var(--accent);font-weight:500;width:100px;flex-shrink:0;">Cache-Control</span>' +
'<span style="color:var(--text);font-family:monospace;">no-cache</span>' +
'</div>' +
'</div>',
Body: '<div style="padding:4px 0;">' +
'<textarea id="postman-body" style="width:100%;height:140px;background:var(--bg-1);border:1px solid var(--border);padding:10px;border-radius:3px;color:var(--text);font-size:12px;font-family:monospace;resize:none;outline:none;"></textarea>' +
'</div>',
};
// Build body content separately since it has real JSON
const postmanBody = JSON.stringify({ name: 'Alice Freeman', email: 'alice@meridian.dev', role: 'developer' }, null, 2);
const tabArea = document.createElement('div');
tabArea.style.cssText = 'flex:1;overflow-y:auto;background:var(--bg-2);';
tabArea.innerHTML = contentPanels[activeTab];
root.appendChild(tabArea);
// Response label
const respLabel = document.createElement('div');
respLabel.style.cssText = 'padding:8px 12px;font-size:11px;font-weight:600;color:var(--text-dim);text-transform:uppercase;letter-spacing:0.05em;border-bottom:1px solid var(--border);background:var(--bg-2);flex-shrink:0;';
respLabel.textContent = 'Response';
// Response pre
const respArea = document.createElement('pre');
respArea.style.cssText = 'flex:1;overflow:auto;padding:12px 16px;margin:0;font-size:12px;line-height:1.6;color:var(--text);font-family:monospace;background:var(--bg-1);';
respArea.textContent = 'Click Send to execute the request';
// Status line
const statusLine = document.createElement('div');
statusLine.style.cssText = 'padding:4px 12px;font-size:11px;color:var(--text-dim);border-top:1px solid var(--border);background:var(--bg-2);flex-shrink:0;';
statusLine.textContent = 'Ready';
root.appendChild(respLabel);
root.appendChild(respArea);
root.appendChild(statusLine);
container.appendChild(root);
function showResponse() {
const resp = RESPONSES[method] || RESPONSES.GET;
const time = Math.floor(Math.random() * 60) + 40;
const size = resp.length + 2;
respArea.textContent = resp;
statusLine.textContent = 'Status: 200 OK \u{B7} Time: ' + time + 'ms \u{B7} Size: ' + size + ' B';
}
},
};