Enhance repository page with dynamic favicon, detailed plugin view, and improved UI components

This commit is contained in:
2025-02-14 12:48:26 -05:00
parent 0955765680
commit 3acdccf9a7
3 changed files with 1288 additions and 129 deletions

View File

@@ -5,6 +5,22 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>HRIStudio Robot Plugins</title>
<link rel="stylesheet" href="assets/style.css">
<link rel="icon" type="image/png" id="favicon">
<script>
// Update favicon when logo is loaded
async function updateFavicon() {
try {
const response = await fetch('repository.json');
const data = await response.json();
if (data.assets?.logo) {
document.getElementById('favicon').href = data.assets.logo;
}
} catch (error) {
console.error('Failed to load favicon:', error);
}
}
updateFavicon();
</script>
</head>
<body>
<div class="container">
@@ -20,8 +36,11 @@
<div class="card-content">
<div class="header-content">
<img id="repoIcon" alt="Repository Icon" class="header-icon hidden">
<div>
<h1 id="repoName" class="title"></h1>
<div class="flex-1">
<div class="flex items-center justify-between">
<h1 id="repoName" class="title"></h1>
<div id="repoLinks" class="button-group"></div>
</div>
<p id="repoDescription" class="description"></p>
</div>
</div>
@@ -42,16 +61,63 @@
<h2>Author</h2>
</div>
<div class="card-content">
<div>
<span>Name:</span>
<div class="card-row">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"></path>
<circle cx="12" cy="7" r="4"></circle>
</svg>
<span class="label">Name:</span>
<span id="authorName"></span>
</div>
<div id="authorOrgContainer" class="hidden">
<span>Organization:</span>
<div id="authorOrgContainer" class="card-row hidden">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<rect x="2" y="7" width="20" height="14" rx="2" ry="2"></rect>
<path d="M16 21V5a2 2 0 0 0-2-2h-4a2 2 0 0 0-2 2v16"></path>
</svg>
<span class="label">Organization:</span>
<span id="authorOrg"></span>
</div>
<div id="authorUrlContainer" class="hidden">
<a id="authorUrl" target="_blank">View Profile</a>
<div id="authorEmailContainer" class="card-row hidden">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"></path>
<polyline points="22,6 12,13 2,6"></polyline>
</svg>
<span class="label">Email:</span>
<a id="authorEmail" target="_blank" class="text-primary hover:underline"></a>
</div>
<div id="authorUrlContainer" class="card-row hidden">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path>
<path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path>
</svg>
<span class="label">Website:</span>
<a id="authorUrl" target="_blank" class="text-primary hover:underline">View</a>
</div>
<div id="maintainersContainer" class="card-row hidden">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"></path>
<circle cx="9" cy="7" r="4"></circle>
<path d="M23 21v-2a4 4 0 0 0-3-3.87"></path>
<path d="M16 3.13a4 4 0 0 1 0 7.75"></path>
</svg>
<span class="label">Maintainers:</span>
<div id="maintainersList"></div>
</div>
<div class="card-row">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2"></path>
<rect x="8" y="2" width="8" height="4" rx="1" ry="1"></rect>
</svg>
<span class="label">License:</span>
<span id="license"></span>
</div>
<div class="card-row">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="10"></circle>
<polyline points="12 6 12 12 16 14"></polyline>
</svg>
<span class="label">Last Updated:</span>
<span id="lastUpdated"></span>
</div>
</div>
</div>
@@ -62,22 +128,32 @@
<h2>Compatibility</h2>
</div>
<div class="card-content">
<div>
<h3>HRIStudio</h3>
<div>
<span>Min Version:</span>
<div class="card-section">
<h3>
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-bot"><path d="M12 8V4H8"/><rect width="16" height="12" x="4" y="8" rx="2"/><path d="M2 14h2"/><path d="M20 14h2"/><path d="M15 13v2"/><path d="M9 13v2"/></svg>
HRIStudio
</h3>
<div class="card-row">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-circle-minus"><circle cx="12" cy="12" r="10"/><path d="M8 12h8"/></svg>
<span class="label">Minimum Version:</span>
<span id="hriMin"></span>
</div>
<div id="hriRecommendedContainer" class="hidden">
<span>Recommended:</span>
<div id="hriRecommendedContainer" class="card-row hidden">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-circle-check-big"><path d="M21.801 10A10 10 0 1 1 17 3.335"/><path d="m9 11 3 3L22 4"/></svg>
<span class="label">Recommended:</span>
<span id="hriRecommended"></span>
</div>
</div>
<div id="ros2Container" class="hidden">
<h3>ROS 2</h3>
<div id="ros2Distributions" style="margin: 0.5rem 0;"></div>
<div id="ros2RecommendedContainer" class="hidden">
<span>Recommended:</span>
<div id="ros2Container" class="card-section hidden">
<h3>
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-grip"><circle cx="12" cy="5" r="1"/><circle cx="19" cy="5" r="1"/><circle cx="5" cy="5" r="1"/><circle cx="12" cy="12" r="1"/><circle cx="19" cy="12" r="1"/><circle cx="5" cy="12" r="1"/><circle cx="12" cy="19" r="1"/><circle cx="19" cy="19" r="1"/><circle cx="5" cy="19" r="1"/></svg>
ROS 2
</h3>
<div id="ros2Distributions" class="flex flex-wrap gap-1 mb-2"></div>
<div id="ros2RecommendedContainer" class="card-row hidden">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-circle-check-big"><path d="M21.801 10A10 10 0 1 1 17 3.335"/><path d="m9 11 3 3L22 4"/></svg>
<span class="label">Recommended:</span>
<span id="ros2Recommended"></span>
</div>
</div>
@@ -100,14 +176,157 @@
<div class="tab-content" role="tabpanel" data-tab="plugins" aria-hidden="true">
<div class="card">
<div class="card-header">
<div style="display: flex; justify-content: space-between; align-items: center;">
<h2 style="margin: 0;">Available Plugins</h2>
<div class="flex items-center justify-between">
<div>
<h2>Available Plugins</h2>
<p class="text-sm text-muted-foreground mt-1">
Browse robot plugins from this repository
</p>
</div>
<span class="badge badge-secondary" id="pluginCount"></span>
</div>
</div>
<div class="card-content">
<div id="pluginGrid" class="plugin-grid">
<!-- Plugins will be loaded here -->
<div class="plugin-layout">
<!-- Plugin List -->
<div class="plugin-list">
<div id="pluginList"></div>
</div>
<!-- Plugin Details -->
<div class="plugin-details">
<div id="pluginDetails" class="hidden">
<div class="plugin-details-header">
<div class="mb-4 flex items-start justify-between">
<div class="flex items-center gap-4">
<div class="plugin-details-icon">
<img id="detailsIcon" alt="" class="plugin-icon">
</div>
<div class="flex-1 min-w-0">
<h3 id="detailsTitle" class="text-xl font-semibold"></h3>
<p id="detailsDescription" class="mt-1 text-muted-foreground"></p>
<div class="mt-4 flex items-center gap-2">
<span id="detailsSpeed" class="badge badge-secondary"></span>
<span id="detailsBattery" class="badge badge-secondary"></span>
<span id="detailsWeight" class="badge badge-secondary"></span>
</div>
</div>
</div>
<div class="flex items-center gap-2">
<a id="detailsDocsButton" href="#" target="_blank" rel="noopener noreferrer" class="button button-outline">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M2 3h6a4 4 0 0 1 4 4v14a3 3 0 0 0-3-3H2z"></path>
<path d="M22 3h-6a4 4 0 0 0-4 4v14a3 3 0 0 1 3-3h7z"></path>
</svg>
Documentation
</a>
</div>
</div>
</div>
<div class="p-6">
<div class="plugin-details-tabs">
<div class="plugin-details-tabs-list" role="tablist">
<button class="plugin-details-tab" role="tab" aria-selected="true" data-state="active" data-plugin-tab="plugin-overview">Overview</button>
<button class="plugin-details-tab" role="tab" aria-selected="false" data-plugin-tab="plugin-specs">Specifications</button>
<button class="plugin-details-tab" role="tab" aria-selected="false" data-plugin-tab="plugin-actions">Actions</button>
</div>
<div class="plugin-details-tab-content" data-state="active" role="tabpanel" data-plugin-tab="plugin-overview">
<div id="detailsImages" class="relative mb-6">
<!-- Images will be loaded here -->
</div>
<div class="card-secondary">
<h4>Documentation</h4>
<div class="grid gap-2 text-sm">
<a id="detailsMainDocs" href="#" target="_blank" rel="noopener noreferrer" class="text-primary hover:underline">
User Manual
</a>
<a id="detailsApiDocs" href="#" target="_blank" rel="noopener noreferrer" class="text-primary hover:underline hidden">
API Reference
</a>
</div>
</div>
</div>
<div class="plugin-details-tab-content" role="tabpanel" data-plugin-tab="plugin-specs">
<div class="space-y-6">
<div class="card-secondary">
<h4>Physical Specifications</h4>
<div class="grid gap-4 md:grid-cols-2">
<div class="flex items-center gap-2">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M19 21l2-2v-6"/><path d="M12 3v18"/><path d="m5 21-2-2v-6"/>
<path d="M3 7V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2v2"/>
<path d="M3 17v2a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-2"/>
</svg>
<span id="detailsDimensions" class="text-sm"></span>
</div>
<div class="flex items-center gap-2">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="m5 8 6 6"/><path d="m4 14 6 6 10-10-6-6-10 10z"/>
</svg>
<span id="detailsSpeedFull" class="text-sm"></span>
</div>
<div class="flex items-center gap-2">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M3 3v18h18"/><path d="m19 9-5 5-4-4-3 3"/>
</svg>
<span id="detailsBatteryFull" class="text-sm"></span>
</div>
</div>
</div>
<div class="card-secondary">
<h4>Capabilities</h4>
<div id="detailsCapabilities" class="flex flex-wrap gap-2">
<!-- Capabilities will be loaded here -->
</div>
</div>
<div class="card-secondary">
<h4>ROS 2 Configuration</h4>
<div class="grid gap-3 text-sm">
<div>
<span class="text-muted-foreground">Namespace: </span>
<code id="detailsNamespace" class="rounded bg-muted px-1.5 py-0.5"></code>
</div>
<div>
<span class="text-muted-foreground">Node Prefix: </span>
<code id="detailsNodePrefix" class="rounded bg-muted px-1.5 py-0.5"></code>
</div>
<div class="grid gap-2">
<span class="text-muted-foreground">Default Topics:</span>
<div id="detailsTopics" class="pl-4">
<!-- Topics will be loaded here -->
</div>
</div>
</div>
</div>
</div>
</div>
<div class="plugin-details-tab-content" role="tabpanel" data-plugin-tab="plugin-actions">
<div id="detailsActions" class="space-y-4">
<!-- Actions will be loaded here -->
</div>
</div>
</div>
</div>
</div>
<div id="emptyState" class="absolute inset-0 flex flex-col items-center justify-center p-6 text-center">
<div class="flex h-16 w-16 items-center justify-center rounded-lg bg-muted mb-4">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="text-muted-foreground">
<path d="M12 8V4H8"/><rect width="16" height="12" x="4" y="8" rx="2"/>
<path d="M2 14h2"/><path d="M20 14h2"/><path d="M15 13v2"/><path d="M9 13v2"/>
</svg>
</div>
<h3 class="text-lg font-medium">No Plugin Selected</h3>
<p class="text-sm text-muted-foreground mt-1">
Select a plugin from the list to view its details
</p>
</div>
</div>
</div>
</div>
</div>
@@ -119,10 +338,10 @@
</div>
<script>
// Tab management
// Separate tab management for root and plugin details
document.querySelectorAll('.tab').forEach(tab => {
tab.addEventListener('click', () => {
// Update tab states
// Update root tab states
document.querySelectorAll('.tab').forEach(t => {
t.setAttribute('data-state', '');
t.setAttribute('aria-selected', 'false');
@@ -130,7 +349,7 @@
tab.setAttribute('data-state', 'active');
tab.setAttribute('aria-selected', 'true');
// Update content states
// Update root content states
const tabId = tab.getAttribute('data-tab');
document.querySelectorAll('.tab-content').forEach(content => {
const isActive = content.getAttribute('data-tab') === tabId;
@@ -140,6 +359,27 @@
});
});
// Add separate tab management for plugin details
document.querySelectorAll('.plugin-details-tab').forEach(tab => {
tab.addEventListener('click', () => {
// Update plugin details tab states
document.querySelectorAll('.plugin-details-tab').forEach(t => {
t.setAttribute('data-state', '');
t.setAttribute('aria-selected', 'false');
});
tab.setAttribute('data-state', 'active');
tab.setAttribute('aria-selected', 'true');
// Update plugin details content states
const tabId = tab.getAttribute('data-plugin-tab');
document.querySelectorAll('.plugin-details-tab-content').forEach(content => {
const isActive = content.getAttribute('data-plugin-tab') === tabId;
content.setAttribute('data-state', isActive ? 'active' : '');
content.setAttribute('aria-hidden', isActive ? 'false' : 'true');
});
});
});
// Repository data loading
async function loadRepositoryData() {
try {
@@ -153,8 +393,175 @@
document.getElementById('repoName').textContent = data.name;
document.getElementById('repoDescription').textContent = data.description || '';
// Update stats
document.getElementById('pluginCount').textContent = `${data.stats?.plugins || 0} plugins`;
// Add repository links
const repoLinks = document.getElementById('repoLinks');
if (data.urls?.git) {
const gitButton = document.createElement('a');
gitButton.href = data.urls.git;
gitButton.target = '_blank';
gitButton.rel = 'noopener noreferrer';
gitButton.className = 'button button-outline';
gitButton.innerHTML = `
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-folder-git-2"><path d="M9 20H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h3.9a2 2 0 0 1 1.69.9l.81 1.2a2 2 0 0 0 1.67.9H20a2 2 0 0 1 2 2v5"/><circle cx="13" cy="12" r="2"/><path d="M18 19c-2.8 0-5-2.2-5-5v8"/><circle cx="20" cy="19" r="2"/></svg>
View Git Repository
`;
repoLinks.appendChild(gitButton);
}
// Load plugins
const pluginsResponse = await fetch('plugins/index.json');
const pluginFiles = await pluginsResponse.json();
// Update plugin count
document.getElementById('pluginCount').textContent = `${pluginFiles.length} ${pluginFiles.length === 1 ? 'plugin' : 'plugins'}`;
// Clear existing plugins
const pluginList = document.getElementById('pluginList');
pluginList.innerHTML = '';
// Load each plugin
for (const pluginFile of pluginFiles) {
const pluginResponse = await fetch(`plugins/${pluginFile}`);
const plugin = await pluginResponse.json();
const card = document.createElement('div');
card.className = 'plugin-list-item';
card.innerHTML = `
<div class="plugin-list-icon">
<img src="${plugin.assets.thumbnailUrl}" alt="${plugin.name}" class="plugin-icon">
</div>
<div class="plugin-list-info">
<div class="flex items-center justify-between">
<div class="plugin-list-title">${plugin.name}</div>
<span class="badge badge-secondary">${plugin.platform}</span>
</div>
<div class="plugin-list-description">${plugin.description || ''}</div>
</div>
`;
// Add click handler
card.addEventListener('click', () => {
// Remove selected class from all items
document.querySelectorAll('.plugin-list-item').forEach(item => {
item.classList.remove('selected');
});
// Add selected class to clicked item
card.classList.add('selected');
// Show plugin details
document.getElementById('pluginDetails').classList.remove('hidden');
document.getElementById('emptyState').classList.add('hidden');
// Update details
document.getElementById('detailsIcon').src = plugin.assets.thumbnailUrl;
document.getElementById('detailsTitle').textContent = plugin.name;
document.getElementById('detailsDescription').textContent = plugin.description || '';
document.getElementById('detailsDocsButton').href = plugin.documentation.mainUrl;
document.getElementById('detailsSpeed').textContent = `${plugin.specs.maxSpeed}m/s`;
document.getElementById('detailsBattery').textContent = `${plugin.specs.batteryLife}h`;
document.getElementById('detailsWeight').textContent = `${plugin.specs.dimensions.weight}kg`;
// Load images in the overview tab
const imagesContainer = document.getElementById('detailsImages');
imagesContainer.innerHTML = `
<div class="image-grid">
<div class="image-item main">
<img src="${plugin.assets.images.main}" alt="${plugin.name} main view">
</div>
${plugin.assets.images.angles ? Object.entries(plugin.assets.images.angles)
.filter(([_, url]) => url)
.map(([angle, url]) => `
<div class="image-item angle">
<img src="${url}" alt="${plugin.name} ${angle} view">
<div class="image-label">${angle} View</div>
</div>
`).join('') : ''}
</div>
`;
// Update specifications
document.getElementById('detailsDimensions').textContent =
`${plugin.specs.dimensions.length}m × ${plugin.specs.dimensions.width}m × ${plugin.specs.dimensions.height}m`;
document.getElementById('detailsSpeedFull').textContent = `${plugin.specs.maxSpeed}m/s`;
document.getElementById('detailsBatteryFull').textContent = `${plugin.specs.batteryLife}h`;
// Update capabilities
const capsContainer = document.getElementById('detailsCapabilities');
capsContainer.innerHTML = '';
plugin.specs.capabilities.forEach(cap => {
const badge = document.createElement('span');
badge.className = 'badge badge-secondary';
badge.textContent = cap;
capsContainer.appendChild(badge);
});
// Update ROS2 config
document.getElementById('detailsNamespace').textContent = plugin.ros2Config.namespace;
document.getElementById('detailsNodePrefix').textContent = plugin.ros2Config.nodePrefix;
const topicsContainer = document.getElementById('detailsTopics');
topicsContainer.innerHTML = '';
Object.entries(plugin.ros2Config.defaultTopics).forEach(([name, topic]) => {
const div = document.createElement('div');
div.innerHTML = `
<span class="text-muted-foreground">${name}: </span>
<code class="rounded bg-muted px-1.5 py-0.5">${topic}</code>
`;
topicsContainer.appendChild(div);
});
// Update documentation links
document.getElementById('detailsMainDocs').href = plugin.documentation.mainUrl;
const apiDocs = document.getElementById('detailsApiDocs');
if (plugin.documentation.apiReference) {
apiDocs.href = plugin.documentation.apiReference;
apiDocs.classList.remove('hidden');
} else {
apiDocs.classList.add('hidden');
}
// Update actions
const actionsContainer = document.getElementById('detailsActions');
actionsContainer.innerHTML = '';
plugin.actions.forEach(action => {
const div = document.createElement('div');
div.className = 'card';
div.innerHTML = `
<div class="card-content">
<div class="mb-3 flex items-center justify-between">
<h4 class="font-medium">${action.title}</h4>
<span class="badge badge-secondary">${action.type}</span>
</div>
<p class="mb-4 text-sm text-muted-foreground">
${action.description}
</p>
<div class="grid gap-2">
<h5 class="text-sm font-medium text-muted-foreground">Parameters:</h5>
<div class="grid gap-2 pl-4">
${Object.entries(action.parameters.properties).map(([name, prop]) => `
<div class="text-sm">
<span class="font-medium">${prop.title}</span>
${prop.unit ? `<span class="text-muted-foreground"> (${prop.unit})</span>` : ''}
${prop.description ? `<p class="mt-0.5 text-muted-foreground">${prop.description}</p>` : ''}
</div>
`).join('')}
</div>
</div>
</div>
`;
actionsContainer.appendChild(div);
});
// Trigger click on the Overview tab to show it by default
document.querySelector('.plugin-details-tabs-list .plugin-details-tab[data-plugin-tab="plugin-overview"]').click();
});
pluginList.appendChild(card);
}
// Show empty state initially
document.getElementById('pluginDetails').classList.add('hidden');
document.getElementById('emptyState').classList.remove('hidden');
// Update author info
document.getElementById('authorName').textContent = data.author.name;
@@ -162,11 +569,28 @@
document.getElementById('authorOrgContainer').classList.remove('hidden');
document.getElementById('authorOrg').textContent = data.author.organization;
}
if (data.author.email) {
document.getElementById('authorEmailContainer').classList.remove('hidden');
const emailLink = document.getElementById('authorEmail');
emailLink.href = `mailto:${data.author.email}`;
emailLink.textContent = data.author.email;
}
if (data.author.url) {
document.getElementById('authorUrlContainer').classList.remove('hidden');
document.getElementById('authorUrl').href = data.author.url;
}
// Update maintainers
if (data.maintainers && data.maintainers.length > 0) {
document.getElementById('maintainersContainer').classList.remove('hidden');
const maintainersList = document.getElementById('maintainersList');
maintainersList.innerHTML = data.maintainers.map(maintainer =>
`<span>${maintainer.name}${maintainer.url ?
` (<a href="${maintainer.url}" target="_blank" rel="noopener noreferrer">Git</a>)` :
''}</span>`
).join('');
}
// Update compatibility
document.getElementById('hriMin').textContent = data.compatibility.hristudio.min;
if (data.compatibility.hristudio.recommended) {
@@ -212,31 +636,14 @@
banner.querySelector('img').src = data.assets.banner;
}
// Load plugins
const pluginGrid = document.getElementById('pluginGrid');
const pluginsResponse = await fetch('plugins/index.json');
const pluginFiles = await pluginsResponse.json();
for (const pluginFile of pluginFiles) {
const pluginResponse = await fetch(`plugins/${pluginFile}`);
const plugin = await pluginResponse.json();
const card = document.createElement('div');
card.className = 'plugin-card';
card.innerHTML = `
<div class="plugin-header">
<img src="${plugin.assets.thumbnailUrl}" alt="${plugin.name}" class="plugin-icon">
<div class="plugin-info">
<div class="plugin-title">${plugin.name}</div>
<div class="plugin-description">${plugin.description || ''}</div>
</div>
</div>
<div>
<span class="badge badge-secondary">${plugin.platform}</span>
</div>
`;
pluginGrid.appendChild(card);
}
// Update license and last updated
document.getElementById('license').textContent = data.license;
const lastUpdated = new Date(data.lastUpdated);
document.getElementById('lastUpdated').textContent = lastUpdated.toLocaleDateString(undefined, {
year: 'numeric',
month: 'long',
day: 'numeric'
});
// Show content
document.getElementById('loading').classList.add('hidden');