Add essential view templates for mods and worlds functionality
- views/mods/details.ejs: Detailed mod information and management page - views/mods/index.ejs: Main mod management interface with world selection - views/worlds/details.ejs: World configuration and settings page - views/worlds/new.ejs: New world creation form These templates were previously ignored but are required for core functionality. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
150
views/mods/details.ejs
Normal file
150
views/mods/details.ejs
Normal file
@@ -0,0 +1,150 @@
|
||||
<%
|
||||
const body = `
|
||||
<div class="page-header">
|
||||
<div class="breadcrumb">
|
||||
<a href="/mods">Mods</a>
|
||||
<span>${mod.title}</span>
|
||||
</div>
|
||||
<h2>${mod.title}</h2>
|
||||
${mod.description ? `<p class="text-secondary">${mod.description}</p>` : ''}
|
||||
</div>
|
||||
|
||||
<div class="grid">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h3>Mod Information</h3>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="info-grid">
|
||||
<div><strong>Name:</strong> ${mod.name}</div>
|
||||
<div><strong>Author:</strong> ${mod.author || 'Unknown'}</div>
|
||||
<div><strong>Files:</strong> ${mod.fileCount} files</div>
|
||||
<div><strong>Size:</strong> ${(mod.totalSize / 1024).toFixed(2)} KB</div>
|
||||
<div><strong>Created:</strong> ${new Date(mod.created).toLocaleString()}</div>
|
||||
<div><strong>Modified:</strong> ${new Date(mod.lastModified).toLocaleString()}</div>
|
||||
${mod.min_minetest_version ? `<div><strong>Min Version:</strong> ${mod.min_minetest_version}</div>` : ''}
|
||||
${mod.max_minetest_version ? `<div><strong>Max Version:</strong> ${mod.max_minetest_version}</div>` : ''}
|
||||
</div>
|
||||
|
||||
${mod.depends.length > 0 ? `
|
||||
<div style="margin-top: 16px;">
|
||||
<strong>Dependencies:</strong>
|
||||
<div class="tag-list">
|
||||
${mod.depends.map(dep => `<span class="tag tag-required">${dep}</span>`).join('')}
|
||||
</div>
|
||||
</div>
|
||||
` : ''}
|
||||
|
||||
${mod.optional_depends.length > 0 ? `
|
||||
<div style="margin-top: 16px;">
|
||||
<strong>Optional Dependencies:</strong>
|
||||
<div class="tag-list">
|
||||
${mod.optional_depends.map(dep => `<span class="tag tag-optional">${dep}</span>`).join('')}
|
||||
</div>
|
||||
</div>
|
||||
` : ''}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
${mod.installedWorlds.length > 0 ? `
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h3>Installed in Worlds (${mod.installedWorlds.length})</h3>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="world-list">
|
||||
${mod.installedWorlds.map(world => `
|
||||
<div class="world-item">
|
||||
<div>
|
||||
<strong>${world.displayName}</strong>
|
||||
<span class="text-secondary">(${world.name})</span>
|
||||
</div>
|
||||
<div class="world-actions">
|
||||
<form method="POST" action="/mods/remove/${world.name}/${mod.name}" style="display: inline;" onsubmit="return confirm('Remove this mod from ${world.displayName}?')">
|
||||
${typeof csrfToken !== 'undefined' && csrfToken ? `<input type="hidden" name="_csrf" value="${csrfToken}">` : ''}
|
||||
<button type="submit" class="btn btn-sm btn-danger">Remove</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
`).join('')}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
` : ''}
|
||||
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h3>Actions</h3>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="action-buttons">
|
||||
<a href="/mods" class="btn btn-outline">← Back to Mods</a>
|
||||
<form method="POST" action="/mods/delete/${mod.name}" style="display: inline;" onsubmit="return confirm('Permanently delete this mod and remove it from all worlds?')">
|
||||
${typeof csrfToken !== 'undefined' && csrfToken ? `<input type="hidden" name="_csrf" value="${csrfToken}">` : ''}
|
||||
<button type="submit" class="btn btn-danger">Delete Mod</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.info-grid {
|
||||
display: grid;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.tag-list {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 6px;
|
||||
margin-top: 6px;
|
||||
}
|
||||
|
||||
.tag {
|
||||
font-size: 12px;
|
||||
padding: 4px 8px;
|
||||
border-radius: 4px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.tag-required {
|
||||
background: var(--error-color);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.tag-optional {
|
||||
background: var(--warning-color);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.world-list {
|
||||
display: grid;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.world-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 12px;
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 6px;
|
||||
background: var(--bg-secondary);
|
||||
}
|
||||
|
||||
.world-actions {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.action-buttons {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
</style>
|
||||
`;
|
||||
%>
|
||||
|
||||
<%- include('../layout', { body: body, currentPage: 'mods', title: title }) %>
|
275
views/mods/index.ejs
Normal file
275
views/mods/index.ejs
Normal file
@@ -0,0 +1,275 @@
|
||||
<%
|
||||
const body = `
|
||||
<div class="page-header">
|
||||
<h2>Mod Management</h2>
|
||||
<p class="text-secondary">Install and manage mods for your worlds</p>
|
||||
</div>
|
||||
|
||||
${typeof req !== 'undefined' && req.query && req.query.enabled ? `
|
||||
<div class="alert alert-success">
|
||||
Mod "${req.query.enabled}" enabled for ${selectedWorld}!
|
||||
</div>
|
||||
` : ''}
|
||||
|
||||
${typeof req !== 'undefined' && req.query && req.query.disabled ? `
|
||||
<div class="alert alert-success">
|
||||
Mod "${req.query.disabled}" disabled for ${selectedWorld}!
|
||||
</div>
|
||||
` : ''}
|
||||
|
||||
${typeof req !== 'undefined' && req.query && req.query.copied ? `
|
||||
<div class="alert alert-success">
|
||||
Mod "${req.query.copied}" copied to ${selectedWorld}!
|
||||
</div>
|
||||
` : ''}
|
||||
|
||||
${typeof req !== 'undefined' && req.query && req.query.removed ? `
|
||||
<div class="alert alert-success">
|
||||
Mod "${req.query.removed}" removed from ${selectedWorld}!
|
||||
</div>
|
||||
` : ''}
|
||||
|
||||
${typeof req !== 'undefined' && req.query && req.query.deleted ? `
|
||||
<div class="alert alert-success">
|
||||
Mod "${req.query.deleted}" deleted permanently!
|
||||
</div>
|
||||
` : ''}
|
||||
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h3>Select World</h3>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<form method="GET">
|
||||
<div style="display: flex; gap: 12px; align-items: end;">
|
||||
<div style="flex: 1;">
|
||||
<label>World</label>
|
||||
<select name="world" class="form-control">
|
||||
<option value="">Select a world...</option>
|
||||
${worlds.map(world => `
|
||||
<option value="${world.name}" ${selectedWorld === world.name ? 'selected' : ''}>
|
||||
${world.displayName}
|
||||
</option>
|
||||
`).join('')}
|
||||
</select>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary">View Mods</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
${selectedWorld ? `
|
||||
<div class="grid">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h3>World Mods (${worldMods.length})</h3>
|
||||
<p class="text-secondary">Mods installed specifically for ${selectedWorld}</p>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
${worldMods.length > 0 ? `
|
||||
<div class="mod-grid">
|
||||
${worldMods.map(mod => `
|
||||
<div class="mod-card">
|
||||
<div class="mod-header">
|
||||
<h4>${mod.title}</h4>
|
||||
${mod.location === 'global-enabled' ? `
|
||||
<span class="badge badge-success">Enabled</span>
|
||||
` : mod.location === 'world-installed' ? `
|
||||
<span class="badge badge-primary">World Copy</span>
|
||||
` : mod.location === 'global-missing' ? `
|
||||
<span class="badge badge-danger">Missing</span>
|
||||
` : `
|
||||
<span class="badge badge-secondary">Unknown</span>
|
||||
`}
|
||||
</div>
|
||||
${mod.description ? `<p class="mod-description">${mod.description}</p>` : ''}
|
||||
<div class="mod-meta">
|
||||
${mod.author ? `<span><strong>Author:</strong> ${mod.author}</span>` : ''}
|
||||
${mod.depends && mod.depends.length > 0 ? `<span><strong>Depends:</strong> ${mod.depends.join(', ')}</span>` : ''}
|
||||
<span><strong>Type:</strong> ${
|
||||
mod.location === 'global-enabled' ? 'Global mod (enabled via config)' :
|
||||
mod.location === 'world-installed' ? 'World-specific installation' :
|
||||
mod.location === 'global-missing' ? 'Missing global mod' : 'Unknown'
|
||||
}</span>
|
||||
</div>
|
||||
<div class="mod-actions">
|
||||
${mod.location === 'global-enabled' ? `
|
||||
<form method="POST" action="/mods/disable/${selectedWorld}/${mod.name}" style="display: inline;" onsubmit="return confirm('Disable this mod for the world?')">
|
||||
${typeof csrfToken !== 'undefined' && csrfToken ? `<input type="hidden" name="_csrf" value="${csrfToken}">` : ''}
|
||||
<button type="submit" class="btn btn-sm btn-warning">Disable</button>
|
||||
</form>
|
||||
` : mod.location === 'world-installed' ? `
|
||||
<form method="POST" action="/mods/remove/${selectedWorld}/${mod.name}" style="display: inline;" onsubmit="return confirm('Delete this mod copy from the world?')">
|
||||
${typeof csrfToken !== 'undefined' && csrfToken ? `<input type="hidden" name="_csrf" value="${csrfToken}">` : ''}
|
||||
<button type="submit" class="btn btn-sm btn-danger">Delete Copy</button>
|
||||
</form>
|
||||
` : mod.location === 'global-missing' ? `
|
||||
<span class="text-danger">Missing from global mods</span>
|
||||
` : ''}
|
||||
</div>
|
||||
</div>
|
||||
`).join('')}
|
||||
</div>
|
||||
` : `
|
||||
<p class="text-secondary">No mods enabled for this world.</p>
|
||||
`}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h3>Available Global Mods (${globalMods.length})</h3>
|
||||
<p class="text-secondary">Install these mods to ${selectedWorld}</p>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
${globalMods.length > 0 ? `
|
||||
<div class="mod-grid">
|
||||
${globalMods.map(mod => {
|
||||
const isEnabled = worldMods.some(wm => wm.name === mod.name && wm.location === 'global-enabled');
|
||||
const isCopied = worldMods.some(wm => wm.name === mod.name && wm.location === 'world-installed');
|
||||
const isInUse = isEnabled || isCopied;
|
||||
return `
|
||||
<div class="mod-card ${isInUse ? 'mod-installed' : ''}">
|
||||
<div class="mod-header">
|
||||
<h4>${mod.title}</h4>
|
||||
<span class="badge ${isInUse ? 'badge-success' : 'badge-secondary'}">
|
||||
${isEnabled ? 'Enabled' : isCopied ? 'Copied' : 'Available'}
|
||||
</span>
|
||||
</div>
|
||||
${mod.description ? `<p class="mod-description">${mod.description}</p>` : ''}
|
||||
<div class="mod-meta">
|
||||
${mod.author ? `<span><strong>Author:</strong> ${mod.author}</span>` : ''}
|
||||
${mod.depends && mod.depends.length > 0 ? `<span><strong>Depends:</strong> ${mod.depends.join(', ')}</span>` : ''}
|
||||
</div>
|
||||
<div class="mod-actions">
|
||||
${!isEnabled ? `
|
||||
<form method="POST" action="/mods/enable/${selectedWorld}/${mod.name}" style="display: inline;">
|
||||
${typeof csrfToken !== 'undefined' && csrfToken ? `<input type="hidden" name="_csrf" value="${csrfToken}">` : ''}
|
||||
<button type="submit" class="btn btn-sm btn-primary">Enable</button>
|
||||
</form>
|
||||
` : ''}
|
||||
${!isCopied ? `
|
||||
<form method="POST" action="/mods/copy/${selectedWorld}/${mod.name}" style="display: inline;">
|
||||
${typeof csrfToken !== 'undefined' && csrfToken ? `<input type="hidden" name="_csrf" value="${csrfToken}">` : ''}
|
||||
<button type="submit" class="btn btn-sm btn-secondary">Copy to World</button>
|
||||
</form>
|
||||
` : ''}
|
||||
<a href="/mods/${mod.name}" class="btn btn-sm btn-outline">Details</a>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}).join('')}
|
||||
</div>
|
||||
` : `
|
||||
<p class="text-secondary">No global mods found. Upload mods to the mods directory to see them here.</p>
|
||||
`}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
` : `
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h3>Global Mods (${globalMods.length})</h3>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
${globalMods.length > 0 ? `
|
||||
<div class="mod-grid">
|
||||
${globalMods.map(mod => `
|
||||
<div class="mod-card">
|
||||
<div class="mod-header">
|
||||
<h4>${mod.title}</h4>
|
||||
<span class="badge badge-secondary">Global</span>
|
||||
</div>
|
||||
${mod.description ? `<p class="mod-description">${mod.description}</p>` : ''}
|
||||
<div class="mod-meta">
|
||||
${mod.author ? `<span><strong>Author:</strong> ${mod.author}</span>` : ''}
|
||||
${mod.depends.length > 0 ? `<span><strong>Depends:</strong> ${mod.depends.join(', ')}</span>` : ''}
|
||||
</div>
|
||||
<div class="mod-actions">
|
||||
<a href="/mods/${mod.name}" class="btn btn-sm btn-outline">Details</a>
|
||||
<form method="POST" action="/mods/delete/${mod.name}" style="display: inline;" onsubmit="return confirm('Permanently delete this mod?')">
|
||||
${typeof csrfToken !== 'undefined' && csrfToken ? `<input type="hidden" name="_csrf" value="${csrfToken}">` : ''}
|
||||
<button type="submit" class="btn btn-sm btn-danger">Delete</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
`).join('')}
|
||||
</div>
|
||||
` : `
|
||||
<p class="text-secondary">No global mods found. Upload mods to the mods directory to see them here.</p>
|
||||
`}
|
||||
</div>
|
||||
</div>
|
||||
`}
|
||||
|
||||
<style>
|
||||
.mod-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.mod-card {
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 8px;
|
||||
padding: 16px;
|
||||
background: var(--card-bg);
|
||||
}
|
||||
|
||||
.mod-card.mod-installed {
|
||||
border-color: var(--success-color);
|
||||
background: var(--success-bg);
|
||||
}
|
||||
|
||||
.mod-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.mod-header h4 {
|
||||
margin: 0;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.mod-description {
|
||||
font-size: 14px;
|
||||
color: var(--text-secondary);
|
||||
margin-bottom: 12px;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.mod-meta {
|
||||
font-size: 12px;
|
||||
color: var(--text-secondary);
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.mod-meta span {
|
||||
display: block;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.mod-actions {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.badge {
|
||||
font-size: 11px;
|
||||
padding: 4px 8px;
|
||||
border-radius: 4px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.badge-primary { background: var(--primary-color); color: white; }
|
||||
.badge-secondary { background: var(--text-secondary); color: white; }
|
||||
.badge-success { background: var(--success-color); color: white; }
|
||||
</style>
|
||||
`;
|
||||
%>
|
||||
|
||||
<%- include('../layout', { body: body, currentPage: 'mods', title: title }) %>
|
Reference in New Issue
Block a user