feat(electron): implement inline metadata editing with save options modal
- Remove Edit button from header, add pencil icons next to editable fields - Add inline editing for Version, Author, and Comment metadata fields - Implement save options modal with overwrite/save-as-new functionality - Add file dialog integration for save-as-new option - Fix backend MANIFEST_FILENAME error by using correct string literals - Add CSS styling for editable fields and save options modal - Clean up debug logging code BREAKING CHANGE: Edit button removed from UI in favor of inline editing
This commit is contained in:
@@ -31,11 +31,11 @@
|
|||||||
<header class="content-header">
|
<header class="content-header">
|
||||||
<h2 id="bundle-title"></h2>
|
<h2 id="bundle-title"></h2>
|
||||||
<div class="action-buttons">
|
<div class="action-buttons">
|
||||||
<button id="edit-bundle-btn">Edit</button>
|
|
||||||
<button id="sign-bundle-btn">Sign</button>
|
<button id="sign-bundle-btn">Sign</button>
|
||||||
<button id="validate-bundle-btn">Validate</button>
|
<button id="validate-bundle-btn">Validate</button>
|
||||||
<button id="fill-bundle-btn">Fill</button>
|
<button id="fill-bundle-btn">Fill</button>
|
||||||
<button id="clear-bundle-btn">Clear</button>
|
<button id="clear-bundle-btn">Clear</button>
|
||||||
|
<button id="save-metadata-btn" class="hidden">Save Changes</button>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
@@ -73,6 +73,35 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Spinner Overlay -->
|
||||||
|
<div id="spinner-overlay" class="spinner-overlay hidden">
|
||||||
|
<div class="spinner"></div>
|
||||||
|
<p>Processing...</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Save Options Modal -->
|
||||||
|
<div id="save-options-modal" class="modal-container hidden">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h3>Save Metadata Changes</h3>
|
||||||
|
<button class="modal-close" onclick="hideSaveOptionsModal()">×</button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<p>How would you like to save your metadata changes?</p>
|
||||||
|
<div class="save-options">
|
||||||
|
<button id="overwrite-bundle-btn" class="save-option-btn primary">
|
||||||
|
<strong>Overwrite Current Bundle</strong>
|
||||||
|
<small>Update the existing bundle file</small>
|
||||||
|
</button>
|
||||||
|
<button id="save-as-new-btn" class="save-option-btn secondary">
|
||||||
|
<strong>Save As New Bundle</strong>
|
||||||
|
<small>Create a new bundle file with changes</small>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Fill Modal -->
|
<!-- Fill Modal -->
|
||||||
<div id="fill-modal" class="modal">
|
<div id="fill-modal" class="modal">
|
||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
|
|||||||
@@ -297,3 +297,19 @@ ipcMain.handle('open-bundle', async (event, bundlePath) => {
|
|||||||
// Return error as-is since it's already in the correct format
|
// Return error as-is since it's already in the correct format
|
||||||
return result || { success: false, error: 'An unknown error occurred while opening the bundle.' };
|
return result || { success: false, error: 'An unknown error occurred while opening the bundle.' };
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Handle show save dialog
|
||||||
|
ipcMain.handle('show-save-dialog', async (event, options) => {
|
||||||
|
const result = await dialog.showSaveDialog(BrowserWindow.fromWebContents(event.sender), options);
|
||||||
|
return result;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Handle file copying
|
||||||
|
ipcMain.handle('copy-file', async (event, { source, destination }) => {
|
||||||
|
try {
|
||||||
|
await fs.copy(source, destination);
|
||||||
|
return { success: true };
|
||||||
|
} catch (error) {
|
||||||
|
return { success: false, error: error.message };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|||||||
@@ -15,11 +15,16 @@ const openBundleBtn = document.getElementById('open-bundle-btn');
|
|||||||
const createBundleBtn = document.getElementById('create-bundle-btn');
|
const createBundleBtn = document.getElementById('create-bundle-btn');
|
||||||
|
|
||||||
// Bundle action buttons
|
// Bundle action buttons
|
||||||
const editBundleBtn = document.getElementById('edit-bundle-btn');
|
|
||||||
const signBundleBtn = document.getElementById('sign-bundle-btn');
|
const signBundleBtn = document.getElementById('sign-bundle-btn');
|
||||||
const validateBundleBtn = document.getElementById('validate-bundle-btn');
|
const validateBundleBtn = document.getElementById('validate-bundle-btn');
|
||||||
const fillBundleBtn = document.getElementById('fill-bundle-btn');
|
const fillBundleBtn = document.getElementById('fill-bundle-btn');
|
||||||
const clearBundleBtn = document.getElementById('clear-bundle-btn');
|
const clearBundleBtn = document.getElementById('clear-bundle-btn');
|
||||||
|
const saveMetadataBtn = document.getElementById('save-metadata-btn');
|
||||||
|
|
||||||
|
// Save options modal elements
|
||||||
|
const saveOptionsModal = document.getElementById('save-options-modal');
|
||||||
|
const overwriteBundleBtn = document.getElementById('overwrite-bundle-btn');
|
||||||
|
const saveAsNewBtn = document.getElementById('save-as-new-btn');
|
||||||
|
|
||||||
// Bundle display
|
// Bundle display
|
||||||
const bundleTitle = document.getElementById('bundle-title');
|
const bundleTitle = document.getElementById('bundle-title');
|
||||||
@@ -52,6 +57,8 @@ const fillProgressView = document.getElementById('fill-progress-view');
|
|||||||
const fillProgressList = document.getElementById('fill-progress-list');
|
const fillProgressList = document.getElementById('fill-progress-list');
|
||||||
|
|
||||||
let currentBundlePath = null;
|
let currentBundlePath = null;
|
||||||
|
let hasUnsavedChanges = false;
|
||||||
|
let originalMetadata = {};
|
||||||
|
|
||||||
// --- INITIALIZATION ---
|
// --- INITIALIZATION ---
|
||||||
document.addEventListener('DOMContentLoaded', async () => {
|
document.addEventListener('DOMContentLoaded', async () => {
|
||||||
@@ -93,6 +100,9 @@ function setupEventListeners() {
|
|||||||
signBundleBtn.addEventListener('click', handleSignBundle);
|
signBundleBtn.addEventListener('click', handleSignBundle);
|
||||||
validateBundleBtn.addEventListener('click', handleValidateBundle);
|
validateBundleBtn.addEventListener('click', handleValidateBundle);
|
||||||
clearBundleBtn.addEventListener('click', handleClearBundle);
|
clearBundleBtn.addEventListener('click', handleClearBundle);
|
||||||
|
saveMetadataBtn.addEventListener('click', showSaveOptionsModal);
|
||||||
|
overwriteBundleBtn.addEventListener('click', () => handleSaveMetadata(false));
|
||||||
|
saveAsNewBtn.addEventListener('click', () => handleSaveMetadata(true));
|
||||||
fillBundleBtn.addEventListener('click', async () => {
|
fillBundleBtn.addEventListener('click', async () => {
|
||||||
if (!currentBundlePath) {
|
if (!currentBundlePath) {
|
||||||
showModal('Error', 'No bundle is currently open.');
|
showModal('Error', 'No bundle is currently open.');
|
||||||
@@ -415,6 +425,15 @@ function displayBundleInfo(report) {
|
|||||||
|
|
||||||
const { manifest, signature, validation, plugins } = report;
|
const { manifest, signature, validation, plugins } = report;
|
||||||
|
|
||||||
|
// Store original metadata for comparison
|
||||||
|
originalMetadata = {
|
||||||
|
bundleVersion: manifest.bundleVersion || '',
|
||||||
|
bundleAuthor: manifest.bundleAuthor || '',
|
||||||
|
bundleComment: manifest.bundleComment || ''
|
||||||
|
};
|
||||||
|
hasUnsavedChanges = false;
|
||||||
|
updateSaveButtonVisibility();
|
||||||
|
|
||||||
// Set bundle title
|
// Set bundle title
|
||||||
bundleTitle.textContent = manifest.bundleName || 'Untitled Bundle';
|
bundleTitle.textContent = manifest.bundleName || 'Untitled Bundle';
|
||||||
|
|
||||||
@@ -443,16 +462,19 @@ function displayBundleInfo(report) {
|
|||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-header"><h3>Manifest Details</h3></div>
|
<div class="card-header"><h3>Manifest Details</h3></div>
|
||||||
<div class="card-content">
|
<div class="card-content">
|
||||||
<p><strong>Version:</strong> ${manifest.bundleVersion || 'N/A'}</p>
|
${createEditableField('Version', 'bundleVersion', manifest.bundleVersion || 'N/A')}
|
||||||
<p><strong>Author:</strong> ${manifest.bundleAuthor || 'N/A'}</p>
|
${createEditableField('Author', 'bundleAuthor', manifest.bundleAuthor || 'N/A')}
|
||||||
<p><strong>Bundled On:</strong> ${manifest.bundledOn || 'N/A'}</p>
|
<p><strong>Bundled On:</strong> ${manifest.bundledOn || 'N/A'}</p>
|
||||||
<p><strong>Comment:</strong> ${manifest.bundleComment || 'N/A'}</p>
|
${createEditableField('Comment', 'bundleComment', manifest.bundleComment || 'N/A')}
|
||||||
${manifest.bundleAuthorKeyFingerprint ? `<p><strong>Author Key:</strong> ${manifest.bundleAuthorKeyFingerprint}</p>` : ''}
|
${manifest.bundleAuthorKeyFingerprint ? `<p><strong>Author Key:</strong> ${manifest.bundleAuthorKeyFingerprint}</p>` : ''}
|
||||||
${manifest.bundleSignature ? `<p><strong>Signature:</strong> <span class="signature">${manifest.bundleSignature}</span></p>` : ''}
|
${manifest.bundleSignature ? `<p><strong>Signature:</strong> <span class="signature">${manifest.bundleSignature}</span></p>` : ''}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
// Add event listeners for edit functionality
|
||||||
|
setupEditableFieldListeners();
|
||||||
|
|
||||||
// --- Plugins Tab ---
|
// --- Plugins Tab ---
|
||||||
pluginsList.innerHTML = '';
|
pluginsList.innerHTML = '';
|
||||||
if (plugins && Object.keys(plugins).length > 0) {
|
if (plugins && Object.keys(plugins).length > 0) {
|
||||||
@@ -493,3 +515,230 @@ function displayBundleInfo(report) {
|
|||||||
// Reset to overview tab by default
|
// Reset to overview tab by default
|
||||||
switchTab('overview-tab');
|
switchTab('overview-tab');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Helper function to create editable fields with pencil icons
|
||||||
|
function createEditableField(label, fieldName, value) {
|
||||||
|
const displayValue = value === 'N/A' ? '' : value;
|
||||||
|
return `
|
||||||
|
<p class="editable-field">
|
||||||
|
<strong>${label}:</strong>
|
||||||
|
<span class="field-display" data-field="${fieldName}">${value}</span>
|
||||||
|
<span class="field-edit hidden" data-field="${fieldName}">
|
||||||
|
<input type="text" class="field-input" data-field="${fieldName}" value="${displayValue}">
|
||||||
|
</span>
|
||||||
|
<button class="edit-icon" data-field="${fieldName}" title="Edit ${label}">✏️</button>
|
||||||
|
</p>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Setup event listeners for editable fields
|
||||||
|
function setupEditableFieldListeners() {
|
||||||
|
const editIcons = document.querySelectorAll('.edit-icon');
|
||||||
|
const fieldInputs = document.querySelectorAll('.field-input');
|
||||||
|
|
||||||
|
editIcons.forEach(icon => {
|
||||||
|
icon.addEventListener('click', (e) => {
|
||||||
|
const fieldName = e.target.dataset.field;
|
||||||
|
toggleFieldEdit(fieldName, true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
fieldInputs.forEach(input => {
|
||||||
|
input.addEventListener('blur', (e) => {
|
||||||
|
const fieldName = e.target.dataset.field;
|
||||||
|
saveFieldEdit(fieldName);
|
||||||
|
});
|
||||||
|
|
||||||
|
input.addEventListener('keydown', (e) => {
|
||||||
|
if (e.key === 'Enter') {
|
||||||
|
const fieldName = e.target.dataset.field;
|
||||||
|
saveFieldEdit(fieldName);
|
||||||
|
} else if (e.key === 'Escape') {
|
||||||
|
const fieldName = e.target.dataset.field;
|
||||||
|
cancelFieldEdit(fieldName);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
input.addEventListener('input', () => {
|
||||||
|
checkForChanges();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Toggle between display and edit mode for a field
|
||||||
|
function toggleFieldEdit(fieldName, editMode) {
|
||||||
|
const displaySpan = document.querySelector(`.field-display[data-field="${fieldName}"]`);
|
||||||
|
const editSpan = document.querySelector(`.field-edit[data-field="${fieldName}"]`);
|
||||||
|
const input = document.querySelector(`.field-input[data-field="${fieldName}"]`);
|
||||||
|
const icon = document.querySelector(`.edit-icon[data-field="${fieldName}"]`);
|
||||||
|
|
||||||
|
if (editMode) {
|
||||||
|
displaySpan.classList.add('hidden');
|
||||||
|
editSpan.classList.remove('hidden');
|
||||||
|
icon.textContent = '✅';
|
||||||
|
icon.title = 'Save';
|
||||||
|
input.focus();
|
||||||
|
input.select();
|
||||||
|
} else {
|
||||||
|
displaySpan.classList.remove('hidden');
|
||||||
|
editSpan.classList.add('hidden');
|
||||||
|
icon.textContent = '✏️';
|
||||||
|
icon.title = `Edit ${fieldName}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save field edit and update display
|
||||||
|
function saveFieldEdit(fieldName) {
|
||||||
|
const input = document.querySelector(`.field-input[data-field="${fieldName}"]`);
|
||||||
|
const displaySpan = document.querySelector(`.field-display[data-field="${fieldName}"]`);
|
||||||
|
|
||||||
|
const newValue = input.value.trim();
|
||||||
|
const displayValue = newValue || 'N/A';
|
||||||
|
|
||||||
|
displaySpan.textContent = displayValue;
|
||||||
|
toggleFieldEdit(fieldName, false);
|
||||||
|
checkForChanges();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cancel field edit and restore original value
|
||||||
|
function cancelFieldEdit(fieldName) {
|
||||||
|
const input = document.querySelector(`.field-input[data-field="${fieldName}"]`);
|
||||||
|
const originalValue = originalMetadata[fieldName] || '';
|
||||||
|
|
||||||
|
input.value = originalValue;
|
||||||
|
toggleFieldEdit(fieldName, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if any fields have been modified
|
||||||
|
function checkForChanges() {
|
||||||
|
const inputs = document.querySelectorAll('.field-input');
|
||||||
|
let hasChanges = false;
|
||||||
|
|
||||||
|
inputs.forEach(input => {
|
||||||
|
const fieldName = input.dataset.field;
|
||||||
|
const currentValue = input.value.trim();
|
||||||
|
const originalValue = originalMetadata[fieldName] || '';
|
||||||
|
|
||||||
|
if (currentValue !== originalValue) {
|
||||||
|
hasChanges = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
hasUnsavedChanges = hasChanges;
|
||||||
|
updateSaveButtonVisibility();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show/hide save button based on changes
|
||||||
|
function updateSaveButtonVisibility() {
|
||||||
|
if (hasUnsavedChanges) {
|
||||||
|
saveMetadataBtn.classList.remove('hidden');
|
||||||
|
} else {
|
||||||
|
saveMetadataBtn.classList.add('hidden');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show save options modal
|
||||||
|
function showSaveOptionsModal() {
|
||||||
|
if (!currentBundlePath || !hasUnsavedChanges) return;
|
||||||
|
saveOptionsModal.classList.remove('hidden');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hide save options modal
|
||||||
|
function hideSaveOptionsModal() {
|
||||||
|
saveOptionsModal.classList.add('hidden');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle save metadata with option for save as new
|
||||||
|
async function handleSaveMetadata(saveAsNew = false) {
|
||||||
|
if (!currentBundlePath || !hasUnsavedChanges) return;
|
||||||
|
|
||||||
|
// Hide the modal first
|
||||||
|
hideSaveOptionsModal();
|
||||||
|
|
||||||
|
const inputs = document.querySelectorAll('.field-input');
|
||||||
|
const updatedMetadata = {};
|
||||||
|
|
||||||
|
inputs.forEach(input => {
|
||||||
|
const fieldName = input.dataset.field;
|
||||||
|
const value = input.value.trim();
|
||||||
|
if (value !== originalMetadata[fieldName]) {
|
||||||
|
// Convert camelCase to snake_case for backend
|
||||||
|
const backendFieldName = fieldName.replace(/([A-Z])/g, '_$1').toLowerCase();
|
||||||
|
updatedMetadata[backendFieldName] = value;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (Object.keys(updatedMetadata).length === 0) {
|
||||||
|
hasUnsavedChanges = false;
|
||||||
|
updateSaveButtonVisibility();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let targetPath = currentBundlePath;
|
||||||
|
|
||||||
|
if (saveAsNew) {
|
||||||
|
// Show file save dialog for new bundle
|
||||||
|
const result = await ipcRenderer.invoke('show-save-dialog', {
|
||||||
|
defaultPath: currentBundlePath.replace('.fbundle', '_edited.fbundle'),
|
||||||
|
filters: [{ name: 'Bundle Files', extensions: ['fbundle'] }]
|
||||||
|
});
|
||||||
|
|
||||||
|
if (result.canceled) {
|
||||||
|
return; // User canceled the save dialog
|
||||||
|
}
|
||||||
|
|
||||||
|
targetPath = result.filePath;
|
||||||
|
|
||||||
|
// Copy the original bundle to the new location first
|
||||||
|
try {
|
||||||
|
await ipcRenderer.invoke('copy-file', {
|
||||||
|
source: currentBundlePath,
|
||||||
|
destination: targetPath
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
showModal('Copy Error', `Failed to copy bundle: ${error.message}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
showSpinner();
|
||||||
|
const result = await ipcRenderer.invoke('edit-bundle', {
|
||||||
|
bundlePath: targetPath,
|
||||||
|
updatedManifest: updatedMetadata
|
||||||
|
});
|
||||||
|
hideSpinner();
|
||||||
|
|
||||||
|
if (result.success) {
|
||||||
|
// Update original metadata to reflect saved changes
|
||||||
|
Object.keys(updatedMetadata).forEach(backendKey => {
|
||||||
|
const frontendKey = backendKey.replace(/_([a-z])/g, (match, letter) => letter.toUpperCase());
|
||||||
|
originalMetadata[frontendKey] = updatedMetadata[backendKey];
|
||||||
|
});
|
||||||
|
|
||||||
|
hasUnsavedChanges = false;
|
||||||
|
updateSaveButtonVisibility();
|
||||||
|
|
||||||
|
if (saveAsNew) {
|
||||||
|
showModal('Success', `New bundle created successfully at: ${targetPath}`);
|
||||||
|
// Open the new bundle
|
||||||
|
currentBundlePath = targetPath;
|
||||||
|
await reloadCurrentBundle();
|
||||||
|
} else {
|
||||||
|
showModal('Success', 'Metadata updated successfully!');
|
||||||
|
// Reload current bundle to reflect changes
|
||||||
|
await reloadCurrentBundle();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
showModal('Save Error', `Failed to save metadata: ${result.error}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper function to reload current bundle
|
||||||
|
async function reloadCurrentBundle() {
|
||||||
|
if (!currentBundlePath) return;
|
||||||
|
|
||||||
|
const result = await ipcRenderer.invoke('open-bundle', currentBundlePath);
|
||||||
|
if (result.success) {
|
||||||
|
displayBundleInfo(result.report);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -370,6 +370,99 @@ body {
|
|||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Inline Editing Styles */
|
||||||
|
.editable-field {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.edit-icon {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 14px;
|
||||||
|
padding: 4px;
|
||||||
|
border-radius: 4px;
|
||||||
|
transition: background-color 0.2s;
|
||||||
|
opacity: 0.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.edit-icon:hover {
|
||||||
|
background-color: var(--border-color);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.field-input {
|
||||||
|
padding: 4px 8px;
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 14px;
|
||||||
|
font-family: var(--font-family);
|
||||||
|
background-color: white;
|
||||||
|
min-width: 200px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.field-input:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: var(--primary-color);
|
||||||
|
box-shadow: 0 0 0 2px rgba(52, 152, 219, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.field-display {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hidden {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Save Options Modal */
|
||||||
|
.save-options {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 12px;
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.save-option-btn {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
padding: 16px;
|
||||||
|
border: 2px solid var(--border-color);
|
||||||
|
border-radius: 8px;
|
||||||
|
background: white;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.save-option-btn:hover {
|
||||||
|
border-color: var(--primary-color);
|
||||||
|
background-color: rgba(52, 152, 219, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.save-option-btn.primary {
|
||||||
|
border-color: var(--primary-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.save-option-btn.secondary {
|
||||||
|
border-color: var(--text-light);
|
||||||
|
}
|
||||||
|
|
||||||
|
.save-option-btn strong {
|
||||||
|
font-size: 16px;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
color: var(--text-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.save-option-btn small {
|
||||||
|
font-size: 14px;
|
||||||
|
color: var(--text-light);
|
||||||
|
}
|
||||||
|
|
||||||
.progress-indicator.spinner-icon {
|
.progress-indicator.spinner-icon {
|
||||||
border: 2px solid var(--text-color-light);
|
border: 2px solid var(--text-color-light);
|
||||||
border-top: 2px solid var(--primary-color);
|
border-top: 2px solid var(--primary-color);
|
||||||
|
|||||||
@@ -283,10 +283,10 @@ def edit_bundle_metadata(bundle_path: Path, metadata: dict, progress_callback: O
|
|||||||
raise FileNotFoundError("Bundle is not a valid zip file.")
|
raise FileNotFoundError("Bundle is not a valid zip file.")
|
||||||
|
|
||||||
with zipfile.ZipFile(bundle_path, 'a') as zf:
|
with zipfile.ZipFile(bundle_path, 'a') as zf:
|
||||||
if MANIFEST_FILENAME not in zf.namelist():
|
if "manifest.yaml" not in zf.namelist():
|
||||||
raise FileNotFoundError(f"{MANIFEST_FILENAME} not found in bundle.")
|
raise FileNotFoundError("manifest.yaml not found in bundle.")
|
||||||
|
|
||||||
with zf.open(MANIFEST_FILENAME, 'r') as f:
|
with zf.open("manifest.yaml", 'r') as f:
|
||||||
manifest = yaml.safe_load(f)
|
manifest = yaml.safe_load(f)
|
||||||
|
|
||||||
_progress("Updating manifest...")
|
_progress("Updating manifest...")
|
||||||
@@ -305,14 +305,14 @@ def edit_bundle_metadata(bundle_path: Path, metadata: dict, progress_callback: O
|
|||||||
temp_bundle_path = bundle_path.with_suffix('.zip.tmp')
|
temp_bundle_path = bundle_path.with_suffix('.zip.tmp')
|
||||||
with zipfile.ZipFile(temp_bundle_path, 'w', zipfile.ZIP_DEFLATED) as temp_zf:
|
with zipfile.ZipFile(temp_bundle_path, 'w', zipfile.ZIP_DEFLATED) as temp_zf:
|
||||||
for item in zf.infolist():
|
for item in zf.infolist():
|
||||||
if item.filename == MANIFEST_FILENAME:
|
if item.filename == "manifest.yaml":
|
||||||
continue # Skip old manifest
|
continue # Skip old manifest
|
||||||
buffer = zf.read(item.filename)
|
buffer = zf.read(item.filename)
|
||||||
temp_zf.writestr(item, buffer)
|
temp_zf.writestr(item, buffer)
|
||||||
|
|
||||||
# Write the updated manifest
|
# Write the updated manifest
|
||||||
new_manifest_content = yaml.dump(manifest, Dumper=yaml.SafeDumper)
|
new_manifest_content = yaml.dump(manifest, Dumper=yaml.SafeDumper)
|
||||||
temp_zf.writestr(MANIFEST_FILENAME, new_manifest_content)
|
temp_zf.writestr("manifest.yaml", new_manifest_content)
|
||||||
|
|
||||||
# Replace the original bundle with the updated one
|
# Replace the original bundle with the updated one
|
||||||
shutil.move(temp_bundle_path, bundle_path)
|
shutil.move(temp_bundle_path, bundle_path)
|
||||||
|
|||||||
Reference in New Issue
Block a user