feat(electron): added plugin specific tools to ui
This commit is contained in:
@@ -34,7 +34,7 @@ class FourdstEncoder(json.JSONEncoder):
|
||||
project_root = Path(__file__).resolve().parent.parent
|
||||
sys.path.insert(0, str(project_root))
|
||||
|
||||
from fourdst.core import bundle, keys
|
||||
from fourdst.core import bundle, keys, plugin
|
||||
|
||||
def main():
|
||||
# Use stderr for all logging to avoid interfering with JSON output on stdout
|
||||
@@ -74,9 +74,18 @@ def main():
|
||||
'sync_remotes', 'get_remote_sources', 'add_remote_source', 'remove_remote_source'
|
||||
]
|
||||
|
||||
plugin_commands = [
|
||||
'parse_cpp_interface', 'generate_plugin_project', 'validate_bundle_directory',
|
||||
'pack_bundle_directory', 'extract_plugin_from_bundle', 'compare_plugin_sources',
|
||||
'validate_plugin_project'
|
||||
]
|
||||
|
||||
if command in key_commands:
|
||||
func = getattr(keys, command)
|
||||
module_name = "keys"
|
||||
elif command in plugin_commands:
|
||||
func = getattr(plugin, command)
|
||||
module_name = "plugin"
|
||||
else:
|
||||
func = getattr(bundle, command)
|
||||
module_name = "bundle"
|
||||
|
||||
@@ -63,6 +63,16 @@
|
||||
<button id="open-bundle-btn" class="nav-button active">Open Bundle</button>
|
||||
<button id="create-bundle-btn" class="nav-button">Create Bundle</button>
|
||||
</nav>
|
||||
|
||||
<div class="sidebar-header" style="margin-top: 30px;">
|
||||
<h3>Plugin Tools</h3>
|
||||
</div>
|
||||
<nav class="sidebar-nav">
|
||||
<button id="init-plugin-btn" class="nav-button">Initialize Plugin</button>
|
||||
<button id="validate-plugin-btn" class="nav-button">Validate Plugin</button>
|
||||
<button id="extract-plugin-btn" class="nav-button">Extract Plugin</button>
|
||||
<button id="diff-plugin-btn" class="nav-button">Compare Plugins</button>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<!-- libconstants content (empty for now) -->
|
||||
@@ -480,6 +490,150 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Plugin Management Views -->
|
||||
<div id="plugin-view" class="hidden">
|
||||
<!-- Plugin Initialize View -->
|
||||
<div id="plugin-init-view" class="plugin-management-view">
|
||||
<div class="plugin-header">
|
||||
<h3>Initialize New Plugin</h3>
|
||||
<p>Create a new Meson-based C++ plugin project from an interface header.</p>
|
||||
</div>
|
||||
<div class="plugin-form">
|
||||
<div class="form-group">
|
||||
<label for="plugin-project-name">Project Name:</label>
|
||||
<input type="text" id="plugin-project-name" placeholder="my_plugin" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="plugin-header-file">Interface Header File:</label>
|
||||
<div class="file-input-group">
|
||||
<input type="file" id="plugin-header-file" accept=".h,.hpp" style="display: none;">
|
||||
<button id="plugin-header-browse-btn" class="browse-button">Browse...</button>
|
||||
<span id="plugin-header-filename" class="filename-display">No file selected</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group" id="plugin-interface-selection" style="display: none;">
|
||||
<label for="plugin-interface-select">Select Interface to Implement:</label>
|
||||
<select id="plugin-interface-select" class="form-select">
|
||||
<option value="">-- Select an Interface --</option>
|
||||
</select>
|
||||
<div id="plugin-interface-methods" class="interface-methods-preview" style="display: none;">
|
||||
<h5>Methods to implement:</h5>
|
||||
<ul id="plugin-methods-list"></ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="plugin-directory">Output Directory:</label>
|
||||
<div class="file-input-group">
|
||||
<input type="text" id="plugin-directory" placeholder="." readonly>
|
||||
<button id="plugin-directory-browse-btn" class="browse-button">Browse...</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="plugin-version">Version:</label>
|
||||
<input type="text" id="plugin-version" placeholder="0.1.0" value="0.1.0">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="plugin-libplugin-rev">libplugin Revision:</label>
|
||||
<input type="text" id="plugin-libplugin-rev" placeholder="main" value="main">
|
||||
</div>
|
||||
<div class="form-actions">
|
||||
<button id="plugin-init-execute-btn" class="action-button primary" disabled>Initialize Plugin</button>
|
||||
</div>
|
||||
</div>
|
||||
<div id="plugin-init-results" class="plugin-results hidden"></div>
|
||||
</div>
|
||||
|
||||
<!-- Plugin Validate View -->
|
||||
<div id="plugin-validate-view" class="plugin-management-view hidden">
|
||||
<div class="plugin-header">
|
||||
<h3>Validate Plugin Project</h3>
|
||||
<p>Check a plugin project's structure and meson.build file for correctness.</p>
|
||||
</div>
|
||||
<div class="plugin-form">
|
||||
<div class="form-group">
|
||||
<label for="validate-plugin-path">Plugin Directory:</label>
|
||||
<div class="file-input-group">
|
||||
<input type="text" id="validate-plugin-path" placeholder="." readonly>
|
||||
<button id="validate-plugin-browse-btn" class="browse-button">Browse...</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-actions">
|
||||
<button id="plugin-validate-execute-btn" class="action-button primary">Validate Plugin</button>
|
||||
</div>
|
||||
</div>
|
||||
<div id="plugin-validate-results" class="plugin-results hidden"></div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<!-- Plugin Extract View -->
|
||||
<div id="plugin-extract-view" class="plugin-management-view hidden">
|
||||
<div class="plugin-header">
|
||||
<h3>Extract Plugin from Bundle</h3>
|
||||
<p>Extract a plugin's source code from a bundle.</p>
|
||||
</div>
|
||||
<div class="plugin-form">
|
||||
<div class="form-group">
|
||||
<label for="extract-plugin-name">Plugin Name:</label>
|
||||
<input type="text" id="extract-plugin-name" placeholder="plugin_name" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="extract-bundle-file">Bundle File:</label>
|
||||
<div class="file-input-group">
|
||||
<input type="file" id="extract-bundle-file" accept=".fbundle" style="display: none;">
|
||||
<button id="extract-bundle-browse-btn" class="browse-button">Browse...</button>
|
||||
<span id="extract-bundle-filename" class="filename-display">No file selected</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="extract-output-dir">Output Directory:</label>
|
||||
<div class="file-input-group">
|
||||
<input type="text" id="extract-output-dir" placeholder="." readonly>
|
||||
<button id="extract-output-browse-btn" class="browse-button">Browse...</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-actions">
|
||||
<button id="plugin-extract-execute-btn" class="action-button primary" disabled>Extract Plugin</button>
|
||||
</div>
|
||||
</div>
|
||||
<div id="plugin-extract-results" class="plugin-results hidden"></div>
|
||||
</div>
|
||||
|
||||
<!-- Plugin Diff View -->
|
||||
<div id="plugin-diff-view" class="plugin-management-view hidden">
|
||||
<div class="plugin-header">
|
||||
<h3>Compare Plugin Sources</h3>
|
||||
<p>Compare the source code of a plugin between two different bundles.</p>
|
||||
</div>
|
||||
<div class="plugin-form">
|
||||
<div class="form-group">
|
||||
<label for="diff-plugin-name">Plugin Name:</label>
|
||||
<input type="text" id="diff-plugin-name" placeholder="plugin_name" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="diff-bundle-a">First Bundle:</label>
|
||||
<div class="file-input-group">
|
||||
<input type="file" id="diff-bundle-a" accept=".fbundle" style="display: none;">
|
||||
<button id="diff-bundle-a-browse-btn" class="browse-button">Browse...</button>
|
||||
<span id="diff-bundle-a-filename" class="filename-display">No file selected</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="diff-bundle-b">Second Bundle:</label>
|
||||
<div class="file-input-group">
|
||||
<input type="file" id="diff-bundle-b" accept=".fbundle" style="display: none;">
|
||||
<button id="diff-bundle-b-browse-btn" class="browse-button">Browse...</button>
|
||||
<span id="diff-bundle-b-filename" class="filename-display">No file selected</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-actions">
|
||||
<button id="plugin-diff-execute-btn" class="action-button primary" disabled>Compare Plugins</button>
|
||||
</div>
|
||||
</div>
|
||||
<div id="plugin-diff-results" class="plugin-results hidden"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Key Management Views -->
|
||||
<div id="keys-view" class="hidden">
|
||||
<!-- Trusted Keys View -->
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
// Import modular components
|
||||
const { setupAppEventHandlers, setupThemeHandlers } = require('./main/app-lifecycle');
|
||||
const { setupFileDialogHandlers } = require('./main/file-dialogs');
|
||||
const { setupBundleIPCHandlers, setupKeyIPCHandlers } = require('./main/ipc-handlers');
|
||||
const { setupBundleIPCHandlers, setupKeyIPCHandlers, setupPluginIPCHandlers } = require('./main/ipc-handlers');
|
||||
|
||||
// Initialize all modules in the correct order
|
||||
function initializeMainProcess() {
|
||||
@@ -24,6 +24,9 @@ function initializeMainProcess() {
|
||||
// Setup key management IPC handlers
|
||||
setupKeyIPCHandlers();
|
||||
|
||||
// Setup plugin management IPC handlers
|
||||
setupPluginIPCHandlers();
|
||||
|
||||
console.log('[MAIN_PROCESS] All modules initialized successfully');
|
||||
}
|
||||
|
||||
|
||||
@@ -231,9 +231,92 @@ const setupBundleIPCHandlers = () => {
|
||||
return { success: false, error: error.message };
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
};
|
||||
|
||||
const setupPluginIPCHandlers = () => {
|
||||
// Parse C++ interface handler
|
||||
ipcMain.handle('parse-cpp-interface', async (event, { headerContent, fileName }) => {
|
||||
try {
|
||||
// Write header content to a temporary file since parse_cpp_interface expects a file path
|
||||
const os = require('os');
|
||||
const tempDir = os.tmpdir();
|
||||
const tempFilePath = path.join(tempDir, fileName || 'temp_interface.h');
|
||||
|
||||
await fs.writeFile(tempFilePath, headerContent);
|
||||
|
||||
const kwargs = {
|
||||
header_path: tempFilePath
|
||||
};
|
||||
|
||||
const result = await runPythonCommand('parse_cpp_interface', kwargs, event);
|
||||
|
||||
// Clean up temporary file
|
||||
try {
|
||||
await fs.unlink(tempFilePath);
|
||||
} catch (cleanupError) {
|
||||
console.warn('[IPC_HANDLER] Failed to clean up temp file:', cleanupError);
|
||||
}
|
||||
|
||||
return result;
|
||||
} catch (error) {
|
||||
console.error('[IPC_HANDLER] Error in parse-cpp-interface:', error);
|
||||
return { success: false, error: error.message };
|
||||
}
|
||||
});
|
||||
|
||||
// Generate plugin project handler
|
||||
ipcMain.handle('generate-plugin-project', async (event, projectConfig) => {
|
||||
// The function expects a single 'config' parameter containing all the configuration
|
||||
// Note: directory and header_path need to be converted to Path objects in Python
|
||||
const kwargs = {
|
||||
config: {
|
||||
project_name: projectConfig.project_name,
|
||||
chosen_interface: projectConfig.chosen_interface,
|
||||
interfaces: projectConfig.interfaces,
|
||||
directory: projectConfig.output_directory, // Will be converted to Path in Python
|
||||
version: projectConfig.version,
|
||||
libplugin_rev: projectConfig.libplugin_revision,
|
||||
header_path: projectConfig.header_path // Will be converted to Path in Python
|
||||
}
|
||||
};
|
||||
return runPythonCommand('generate_plugin_project', kwargs, event);
|
||||
});
|
||||
|
||||
// Validate plugin project handler
|
||||
ipcMain.handle('validate-plugin-project', async (event, { plugin_directory }) => {
|
||||
const kwargs = {
|
||||
project_path: plugin_directory // Function expects 'project_path', not 'plugin_directory'
|
||||
};
|
||||
return runPythonCommand('validate_plugin_project', kwargs, event);
|
||||
});
|
||||
|
||||
|
||||
|
||||
// Extract plugin from bundle handler
|
||||
ipcMain.handle('extract-plugin-from-bundle', async (event, { plugin_name, bundle_path, output_directory }) => {
|
||||
const kwargs = {
|
||||
plugin_name: plugin_name,
|
||||
bundle_path: bundle_path,
|
||||
output_directory: output_directory
|
||||
};
|
||||
return runPythonCommand('extract_plugin_from_bundle', kwargs, event);
|
||||
});
|
||||
|
||||
// Compare plugin sources handler
|
||||
ipcMain.handle('compare-plugin-sources', async (event, { plugin_name, bundle_a_path, bundle_b_path }) => {
|
||||
const kwargs = {
|
||||
plugin_name: plugin_name,
|
||||
bundle_a_path: bundle_a_path,
|
||||
bundle_b_path: bundle_b_path
|
||||
};
|
||||
return runPythonCommand('compare_plugin_sources', kwargs, event);
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
setupBundleIPCHandlers,
|
||||
setupKeyIPCHandlers
|
||||
setupKeyIPCHandlers,
|
||||
setupPluginIPCHandlers
|
||||
};
|
||||
|
||||
@@ -9,6 +9,9 @@ const stateManager = require('./renderer/state-manager');
|
||||
const domManager = require('./renderer/dom-manager');
|
||||
const bundleOperations = require('./renderer/bundle-operations');
|
||||
const keyOperations = require('./renderer/key-operations');
|
||||
console.log('[RENDERER] Loading plugin operations module...');
|
||||
const pluginOperations = require('./renderer/plugin-operations');
|
||||
console.log('[RENDERER] Plugin operations module loaded:', !!pluginOperations);
|
||||
const uiComponents = require('./renderer/ui-components');
|
||||
const eventHandlers = require('./renderer/event-handlers');
|
||||
const opatHandler = require('./renderer/opat-handler');
|
||||
@@ -23,6 +26,7 @@ function initializeModules() {
|
||||
domManager,
|
||||
bundleOperations,
|
||||
keyOperations,
|
||||
pluginOperations,
|
||||
uiComponents,
|
||||
eventHandlers,
|
||||
opatHandler,
|
||||
@@ -33,6 +37,7 @@ function initializeModules() {
|
||||
// Initialize each module with its dependencies
|
||||
bundleOperations.initializeDependencies(deps);
|
||||
keyOperations.initializeDependencies(deps);
|
||||
pluginOperations.initializeDependencies(deps);
|
||||
uiComponents.initializeDependencies(deps);
|
||||
eventHandlers.initializeDependencies(deps);
|
||||
opatHandler.initializeDependencies(deps);
|
||||
@@ -87,6 +92,7 @@ window.stateManager = stateManager;
|
||||
window.domManager = domManager;
|
||||
window.bundleOperations = bundleOperations;
|
||||
window.keyOperations = keyOperations;
|
||||
window.pluginOperations = pluginOperations;
|
||||
window.uiComponents = uiComponents;
|
||||
window.eventHandlers = eventHandlers;
|
||||
window.opatHandler = opatHandler;
|
||||
|
||||
@@ -2,8 +2,9 @@
|
||||
// Extracted from renderer.js to centralize DOM element handling and view management
|
||||
|
||||
// --- DOM ELEMENTS (will be initialized in initializeDOMElements) ---
|
||||
let welcomeScreen, bundleView, keysView, createBundleForm;
|
||||
let welcomeScreen, bundleView, keysView, createBundleForm, pluginView;
|
||||
let openBundleBtn, createBundleBtn;
|
||||
let pluginInitBtn, pluginValidateBtn, pluginPackBtn, pluginExtractBtn, pluginDiffBtn;
|
||||
let signBundleBtn, validateBundleBtn, clearBundleBtn, saveMetadataBtn;
|
||||
let saveOptionsModal, overwriteBundleBtn, saveAsNewBtn;
|
||||
let signatureWarningModal, signatureWarningCancel, signatureWarningContinue;
|
||||
@@ -45,10 +46,17 @@ function initializeDOMElements() {
|
||||
bundleView = document.getElementById('bundle-view');
|
||||
keysView = document.getElementById('keys-view');
|
||||
createBundleForm = document.getElementById('create-bundle-form');
|
||||
pluginView = document.getElementById('plugin-view');
|
||||
|
||||
// Sidebar buttons
|
||||
openBundleBtn = document.getElementById('open-bundle-btn');
|
||||
createBundleBtn = document.getElementById('create-bundle-btn');
|
||||
|
||||
// Plugin management buttons
|
||||
initPluginBtn = document.getElementById('init-plugin-btn');
|
||||
validatePluginBtn = document.getElementById('validate-plugin-btn');
|
||||
extractPluginBtn = document.getElementById('extract-plugin-btn');
|
||||
diffPluginBtn = document.getElementById('diff-plugin-btn');
|
||||
|
||||
// Bundle action buttons
|
||||
signBundleBtn = document.getElementById('sign-bundle-btn');
|
||||
@@ -90,10 +98,16 @@ function showView(viewId) {
|
||||
const opatView = document.getElementById('opat-view');
|
||||
|
||||
// Hide main content views
|
||||
[welcomeScreen, bundleView, keysView, createBundleForm].forEach(view => {
|
||||
[welcomeScreen, bundleView, keysView, createBundleForm, pluginView].forEach(view => {
|
||||
view.classList.toggle('hidden', view.id !== viewId);
|
||||
});
|
||||
|
||||
// When switching away from plugin view, hide all plugin management sub-views
|
||||
if (viewId !== 'plugin-view') {
|
||||
const pluginManagementViews = document.querySelectorAll('.plugin-management-view');
|
||||
pluginManagementViews.forEach(view => view.classList.add('hidden'));
|
||||
}
|
||||
|
||||
// Handle OPAT view separately since it's not in the main views array
|
||||
if (opatView) {
|
||||
opatView.classList.toggle('hidden', viewId !== 'opat-view');
|
||||
@@ -117,6 +131,15 @@ function showView(viewId) {
|
||||
if (libpluginView) {
|
||||
libpluginView.classList.remove('hidden');
|
||||
}
|
||||
} else if (viewId === 'plugin-view') {
|
||||
// When switching to plugin view, show the default plugin sub-view (init view)
|
||||
const pluginManagementViews = document.querySelectorAll('.plugin-management-view');
|
||||
pluginManagementViews.forEach(view => view.classList.add('hidden'));
|
||||
|
||||
const defaultPluginView = document.getElementById('plugin-init-view');
|
||||
if (defaultPluginView) {
|
||||
defaultPluginView.classList.remove('hidden');
|
||||
}
|
||||
} else if (viewId === 'opat-view') {
|
||||
// Ensure OPAT view is visible and properly initialized
|
||||
if (opatView) {
|
||||
@@ -135,6 +158,12 @@ function switchTab(tabId) {
|
||||
tabLinks.forEach(link => {
|
||||
link.classList.toggle('active', link.dataset.tab === tabId);
|
||||
});
|
||||
|
||||
// When switching away from libplugin tab, hide all plugin management views
|
||||
if (tabId !== 'libplugin') {
|
||||
const pluginManagementViews = document.querySelectorAll('.plugin-management-view');
|
||||
pluginManagementViews.forEach(view => view.classList.add('hidden'));
|
||||
}
|
||||
}
|
||||
|
||||
function showSpinner() {
|
||||
@@ -174,8 +203,13 @@ module.exports = {
|
||||
bundleView,
|
||||
keysView,
|
||||
createBundleForm,
|
||||
pluginView,
|
||||
openBundleBtn,
|
||||
createBundleBtn,
|
||||
initPluginBtn,
|
||||
validatePluginBtn,
|
||||
extractPluginBtn,
|
||||
diffPluginBtn,
|
||||
signBundleBtn,
|
||||
validateBundleBtn,
|
||||
clearBundleBtn,
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
const { ipcRenderer } = require('electron');
|
||||
|
||||
// Import dependencies (these will be injected when integrated)
|
||||
let stateManager, domManager, bundleOperations, keyOperations, fillWorkflow, uiComponents, opatHandler;
|
||||
let stateManager, domManager, bundleOperations, keyOperations, pluginOperations, fillWorkflow, uiComponents, opatHandler;
|
||||
|
||||
// --- EVENT LISTENERS SETUP ---
|
||||
function setupEventListeners() {
|
||||
@@ -66,6 +66,9 @@ function setupEventListeners() {
|
||||
domManager.showModal('Not Implemented', 'The create bundle form will be moved to a modal dialog.');
|
||||
});
|
||||
|
||||
// Plugin management navigation is handled by plugin-operations.js
|
||||
// No duplicate event listeners needed here
|
||||
|
||||
// Tab navigation
|
||||
elements.tabLinks.forEach(link => {
|
||||
link.addEventListener('click', () => domManager.switchTab(link.dataset.tab));
|
||||
@@ -89,6 +92,17 @@ function setupEventListeners() {
|
||||
// Key Management event listeners
|
||||
setupKeyManagementEventListeners();
|
||||
|
||||
// Plugin Management event listeners
|
||||
console.log('[EVENT_HANDLERS] Setting up plugin event listeners...');
|
||||
console.log('[EVENT_HANDLERS] pluginOperations available:', !!pluginOperations);
|
||||
console.log('[EVENT_HANDLERS] setupPluginEventListeners method available:', !!(pluginOperations && pluginOperations.setupPluginEventListeners));
|
||||
|
||||
if (pluginOperations && pluginOperations.setupPluginEventListeners) {
|
||||
pluginOperations.setupPluginEventListeners();
|
||||
} else {
|
||||
console.warn('[EVENT_HANDLERS] Plugin operations not available for event listener setup');
|
||||
}
|
||||
|
||||
// Signature warning modal event listeners
|
||||
elements.signatureWarningCancel.addEventListener('click', () => {
|
||||
elements.signatureWarningModal.classList.add('hidden');
|
||||
@@ -172,6 +186,14 @@ function setupCategoryNavigation() {
|
||||
|
||||
// Show category home screen
|
||||
showCategoryHomeScreen(category);
|
||||
|
||||
// Set up plugin button listeners when libplugin category is shown
|
||||
if (category === 'libplugin' && pluginOperations) {
|
||||
// Use setTimeout to ensure DOM is ready
|
||||
setTimeout(() => {
|
||||
pluginOperations.ensurePluginButtonListeners();
|
||||
}, 100);
|
||||
}
|
||||
}
|
||||
|
||||
// Update welcome screen
|
||||
@@ -210,7 +232,7 @@ function showCategoryHomeScreen(category) {
|
||||
const views = [
|
||||
'welcome-screen', 'libplugin-home', 'opat-home',
|
||||
'libconstants-home', 'serif-home', 'opat-view', 'libplugin-view',
|
||||
'bundle-view', 'keys-view', 'create-bundle-form'
|
||||
'bundle-view', 'keys-view', 'create-bundle-form', 'plugin-view'
|
||||
];
|
||||
|
||||
// Hide all views
|
||||
@@ -512,6 +534,7 @@ function initializeDependencies(deps) {
|
||||
domManager = deps.domManager;
|
||||
bundleOperations = deps.bundleOperations;
|
||||
keyOperations = deps.keyOperations;
|
||||
pluginOperations = deps.pluginOperations;
|
||||
fillWorkflow = deps.fillWorkflow;
|
||||
uiComponents = deps.uiComponents;
|
||||
opatHandler = deps.opatHandler;
|
||||
|
||||
509
electron/renderer/plugin-operations.js
Normal file
509
electron/renderer/plugin-operations.js
Normal file
@@ -0,0 +1,509 @@
|
||||
// Plugin Operations Module
|
||||
// Handles all plugin-related functionality in the GUI
|
||||
|
||||
const { ipcRenderer } = require('electron');
|
||||
|
||||
class PluginOperations {
|
||||
constructor() {
|
||||
this.parsedInterfaces = null;
|
||||
this.headerFilePath = null;
|
||||
this.deps = null;
|
||||
this.currentView = 'plugin-init-view';
|
||||
}
|
||||
|
||||
initializeDependencies(deps) {
|
||||
this.deps = deps;
|
||||
console.log('[PLUGIN_OPERATIONS] Dependencies initialized');
|
||||
console.log('[PLUGIN_OPERATIONS] Available deps:', Object.keys(deps));
|
||||
}
|
||||
|
||||
// Show specific plugin view
|
||||
showPluginView(viewId) {
|
||||
// Show the main plugin view container first
|
||||
this.deps.domManager.showView('plugin-view');
|
||||
|
||||
// Ensure plugin button event listeners are set up now that buttons are visible
|
||||
this.ensurePluginButtonListeners();
|
||||
|
||||
// Hide all plugin views
|
||||
const pluginViews = document.querySelectorAll('.plugin-management-view');
|
||||
pluginViews.forEach(view => view.classList.add('hidden'));
|
||||
|
||||
// Show the requested view
|
||||
const targetView = document.getElementById(viewId);
|
||||
if (targetView) {
|
||||
targetView.classList.remove('hidden');
|
||||
this.currentView = viewId;
|
||||
}
|
||||
}
|
||||
|
||||
// Parse interface header file and populate interface selection
|
||||
async parseInterfaceHeader(headerFile) {
|
||||
try {
|
||||
this.showPluginLoading('Parsing interface header file...');
|
||||
|
||||
// Read header file content
|
||||
const headerContent = await this.readFileAsText(headerFile);
|
||||
|
||||
// Call backend to parse interfaces using specific IPC handler
|
||||
const result = await ipcRenderer.invoke('parse-cpp-interface', {
|
||||
headerContent: headerContent,
|
||||
fileName: headerFile.name
|
||||
});
|
||||
|
||||
if (result.success) {
|
||||
this.populateInterfaceSelection(result.data);
|
||||
this.headerFilePath = headerFile.path; // Store the header file path for later use
|
||||
this.hidePluginResults();
|
||||
} else {
|
||||
this.showPluginError(`Failed to parse header file: ${result.error}`);
|
||||
}
|
||||
} catch (error) {
|
||||
this.showPluginError(`Failed to parse header file: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Populate interface selection dropdown
|
||||
populateInterfaceSelection(interfaces) {
|
||||
const interfaceSelection = document.getElementById('plugin-interface-selection');
|
||||
const interfaceSelect = document.getElementById('plugin-interface-select');
|
||||
const methodsPreview = document.getElementById('plugin-interface-methods');
|
||||
const methodsList = document.getElementById('plugin-methods-list');
|
||||
|
||||
// Clear existing options
|
||||
interfaceSelect.innerHTML = '<option value="">-- Select an Interface --</option>';
|
||||
|
||||
// Add interfaces to dropdown
|
||||
Object.keys(interfaces).forEach(interfaceName => {
|
||||
const option = document.createElement('option');
|
||||
option.value = interfaceName;
|
||||
option.textContent = interfaceName;
|
||||
interfaceSelect.appendChild(option);
|
||||
});
|
||||
|
||||
// Show interface selection
|
||||
interfaceSelection.style.display = 'block';
|
||||
|
||||
// Store interfaces data for later use
|
||||
this.parsedInterfaces = interfaces;
|
||||
|
||||
// Setup interface selection change handler
|
||||
interfaceSelect.onchange = () => {
|
||||
const selectedInterface = interfaceSelect.value;
|
||||
if (selectedInterface && interfaces[selectedInterface]) {
|
||||
// Show methods preview
|
||||
methodsList.innerHTML = '';
|
||||
interfaces[selectedInterface].forEach(method => {
|
||||
const li = document.createElement('li');
|
||||
li.textContent = method.signature;
|
||||
methodsList.appendChild(li);
|
||||
});
|
||||
methodsPreview.style.display = 'block';
|
||||
} else {
|
||||
methodsPreview.style.display = 'none';
|
||||
}
|
||||
this.updateInitButtonState();
|
||||
};
|
||||
|
||||
this.updateInitButtonState();
|
||||
}
|
||||
|
||||
// Initialize Plugin Project
|
||||
async initializePlugin() {
|
||||
const projectName = document.getElementById('plugin-project-name').value.trim();
|
||||
const headerFile = document.getElementById('plugin-header-file').files[0];
|
||||
const selectedInterface = document.getElementById('plugin-interface-select').value;
|
||||
const outputDir = document.getElementById('plugin-directory').value.trim();
|
||||
const version = document.getElementById('plugin-version').value.trim();
|
||||
const libpluginRev = document.getElementById('plugin-libplugin-rev').value.trim();
|
||||
|
||||
if (!projectName || !headerFile || !selectedInterface || !outputDir) {
|
||||
this.showPluginError('Please fill in all required fields and select an interface.');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
this.showPluginLoading('Initializing plugin project...');
|
||||
|
||||
// Call backend to initialize plugin using specific IPC handler
|
||||
const result = await ipcRenderer.invoke('generate-plugin-project', {
|
||||
project_name: projectName,
|
||||
chosen_interface: selectedInterface,
|
||||
interfaces: this.parsedInterfaces,
|
||||
output_directory: outputDir,
|
||||
version: version,
|
||||
libplugin_revision: libpluginRev,
|
||||
header_path: this.headerFilePath
|
||||
});
|
||||
|
||||
this.handlePluginResult(result, 'Plugin project initialized successfully!');
|
||||
} catch (error) {
|
||||
this.showPluginError(`Failed to initialize plugin: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Validate Plugin Project
|
||||
async validatePlugin() {
|
||||
const pluginPath = document.getElementById('validate-plugin-path').value.trim();
|
||||
|
||||
if (!pluginPath) {
|
||||
this.showPluginError('Please select a plugin directory.');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
this.showPluginLoading('Validating plugin project...');
|
||||
|
||||
const result = await ipcRenderer.invoke('validate-plugin-project', {
|
||||
plugin_directory: pluginPath
|
||||
});
|
||||
|
||||
this.handlePluginResult(result, 'Plugin validation completed!');
|
||||
} catch (error) {
|
||||
this.showPluginError(`Failed to validate plugin: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Extract Plugin from Bundle
|
||||
async extractPlugin() {
|
||||
const pluginName = document.getElementById('extract-plugin-name').value.trim();
|
||||
const bundleFile = document.getElementById('extract-bundle-file').files[0];
|
||||
const outputDir = document.getElementById('extract-output-dir').value.trim();
|
||||
|
||||
if (!pluginName || !bundleFile || !outputDir) {
|
||||
this.showPluginError('Please fill in all required fields.');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
this.showPluginLoading('Extracting plugin from bundle...');
|
||||
|
||||
const result = await ipcRenderer.invoke('extract-plugin-from-bundle', {
|
||||
plugin_name: pluginName,
|
||||
bundle_path: bundleFile.path,
|
||||
output_directory: outputDir
|
||||
});
|
||||
|
||||
this.handlePluginResult(result, 'Plugin extracted successfully!');
|
||||
} catch (error) {
|
||||
this.showPluginError(`Failed to extract plugin: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Compare Plugin Sources
|
||||
async comparePlugins() {
|
||||
const pluginName = document.getElementById('diff-plugin-name').value.trim();
|
||||
const bundleA = document.getElementById('diff-bundle-a').files[0];
|
||||
const bundleB = document.getElementById('diff-bundle-b').files[0];
|
||||
|
||||
if (!pluginName || !bundleA || !bundleB) {
|
||||
this.showPluginError('Please fill in all required fields.');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
this.showPluginLoading('Comparing plugin sources...');
|
||||
|
||||
const result = await ipcRenderer.invoke('compare-plugin-sources', {
|
||||
plugin_name: pluginName,
|
||||
bundle_a_path: bundleA.path,
|
||||
bundle_b_path: bundleB.path
|
||||
});
|
||||
|
||||
this.handlePluginResult(result, 'Plugin comparison completed!');
|
||||
} catch (error) {
|
||||
this.showPluginError(`Failed to compare plugins: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Helper Methods
|
||||
async readFileAsText(file) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const reader = new FileReader();
|
||||
reader.onload = e => resolve(e.target.result);
|
||||
reader.onerror = e => reject(new Error('Failed to read file'));
|
||||
reader.readAsText(file);
|
||||
});
|
||||
}
|
||||
|
||||
showPluginLoading(message) {
|
||||
const resultsDiv = document.querySelector(`#${this.currentView} .plugin-results`);
|
||||
if (resultsDiv) {
|
||||
resultsDiv.classList.remove('hidden');
|
||||
resultsDiv.innerHTML = `
|
||||
<div class="loading-state">
|
||||
<div class="loading-spinner"></div>
|
||||
<p>${message}</p>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
showPluginError(message) {
|
||||
const resultsDiv = document.querySelector(`#${this.currentView} .plugin-results`);
|
||||
if (resultsDiv) {
|
||||
resultsDiv.classList.remove('hidden');
|
||||
resultsDiv.innerHTML = `
|
||||
<div class="error-state">
|
||||
<h4>Error</h4>
|
||||
<p>${message}</p>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
hidePluginResults() {
|
||||
const resultsDiv = document.querySelector(`#${this.currentView} .plugin-results`);
|
||||
if (resultsDiv) {
|
||||
resultsDiv.classList.add('hidden');
|
||||
}
|
||||
}
|
||||
|
||||
handlePluginResult(result, successMessage) {
|
||||
const resultsDiv = document.querySelector(`#${this.currentView} .plugin-results`);
|
||||
if (!resultsDiv) return;
|
||||
|
||||
resultsDiv.classList.remove('hidden');
|
||||
|
||||
if (result.success) {
|
||||
let content = `<div class="success-state"><h4>${successMessage}</h4>`;
|
||||
|
||||
if (result.data) {
|
||||
if (result.data.output) {
|
||||
content += `<pre class="command-output">${result.data.output}</pre>`;
|
||||
}
|
||||
if (result.data.diff) {
|
||||
content += `<pre class="diff-output">${result.data.diff}</pre>`;
|
||||
}
|
||||
if (result.data.validation_results) {
|
||||
content += `<div class="validation-results">`;
|
||||
result.data.validation_results.forEach(item => {
|
||||
content += `<p><strong>${item.check}:</strong> ${item.status}</p>`;
|
||||
});
|
||||
content += `</div>`;
|
||||
}
|
||||
}
|
||||
|
||||
content += `</div>`;
|
||||
resultsDiv.innerHTML = content;
|
||||
} else {
|
||||
resultsDiv.innerHTML = `
|
||||
<div class="error-state">
|
||||
<h4>Operation Failed</h4>
|
||||
<p>${result.error}</p>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
// Setup event listeners for plugin operations (called when plugin buttons are visible)
|
||||
setupPluginEventListeners() {
|
||||
// Don't set up listeners during initial app startup - wait until plugin buttons are visible
|
||||
// This method will be called from showPluginView() when needed
|
||||
}
|
||||
|
||||
// Setup plugin button event listeners when they're actually in the DOM
|
||||
ensurePluginButtonListeners() {
|
||||
if (this.listenersSetup) return; // Avoid duplicate setup
|
||||
|
||||
// Plugin navigation buttons with active state management
|
||||
const pluginButtons = [
|
||||
{ id: 'init-plugin-btn', view: 'plugin-init-view' },
|
||||
{ id: 'validate-plugin-btn', view: 'plugin-validate-view' },
|
||||
{ id: 'extract-plugin-btn', view: 'plugin-extract-view' },
|
||||
{ id: 'diff-plugin-btn', view: 'plugin-diff-view' }
|
||||
];
|
||||
|
||||
let allButtonsFound = true;
|
||||
pluginButtons.forEach(({ id, view }) => {
|
||||
const button = document.getElementById(id);
|
||||
|
||||
if (button) {
|
||||
button.addEventListener('click', () => {
|
||||
// Remove active class from all plugin buttons
|
||||
pluginButtons.forEach(({ id: btnId }) => {
|
||||
document.getElementById(btnId)?.classList.remove('active');
|
||||
});
|
||||
|
||||
// Add active class to clicked button
|
||||
button.classList.add('active');
|
||||
|
||||
// Show the plugin view
|
||||
this.showPluginView(view);
|
||||
});
|
||||
} else {
|
||||
allButtonsFound = false;
|
||||
}
|
||||
});
|
||||
|
||||
if (allButtonsFound) {
|
||||
this.listenersSetup = true;
|
||||
}
|
||||
|
||||
// Plugin Initialize form
|
||||
document.getElementById('plugin-header-browse-btn')?.addEventListener('click', () => {
|
||||
document.getElementById('plugin-header-file').click();
|
||||
});
|
||||
|
||||
document.getElementById('plugin-header-file')?.addEventListener('change', async (e) => {
|
||||
const file = e.target.files[0];
|
||||
const filename = file?.name || 'No file selected';
|
||||
document.getElementById('plugin-header-filename').textContent = filename;
|
||||
|
||||
// Hide interface selection and reset state when new file is selected
|
||||
const interfaceSelection = document.getElementById('plugin-interface-selection');
|
||||
const interfaceSelect = document.getElementById('plugin-interface-select');
|
||||
const methodsPreview = document.getElementById('plugin-interface-methods');
|
||||
|
||||
if (interfaceSelection) interfaceSelection.style.display = 'none';
|
||||
if (interfaceSelect) interfaceSelect.value = '';
|
||||
if (methodsPreview) methodsPreview.style.display = 'none';
|
||||
|
||||
this.parsedInterfaces = null;
|
||||
this.updateInitButtonState();
|
||||
|
||||
// Parse interface file if one was selected
|
||||
if (file) {
|
||||
await this.parseInterfaceHeader(file);
|
||||
}
|
||||
});
|
||||
|
||||
document.getElementById('plugin-directory-browse-btn')?.addEventListener('click', async () => {
|
||||
const result = await ipcRenderer.invoke('select-directory');
|
||||
if (result) {
|
||||
document.getElementById('plugin-directory').value = result;
|
||||
this.updateInitButtonState();
|
||||
}
|
||||
});
|
||||
|
||||
document.getElementById('plugin-project-name')?.addEventListener('input', () => {
|
||||
this.updateInitButtonState();
|
||||
});
|
||||
|
||||
document.getElementById('plugin-init-execute-btn')?.addEventListener('click', () => {
|
||||
this.initializePlugin();
|
||||
});
|
||||
|
||||
// Plugin Validate form
|
||||
document.getElementById('validate-plugin-browse-btn')?.addEventListener('click', async () => {
|
||||
const result = await ipcRenderer.invoke('select-directory');
|
||||
if (result) {
|
||||
document.getElementById('validate-plugin-path').value = result;
|
||||
}
|
||||
});
|
||||
|
||||
document.getElementById('plugin-validate-execute-btn')?.addEventListener('click', () => {
|
||||
this.validatePlugin();
|
||||
});
|
||||
|
||||
// Plugin Pack form
|
||||
document.getElementById('pack-plugin-browse-btn')?.addEventListener('click', async () => {
|
||||
const result = await ipcRenderer.invoke('select-directory');
|
||||
if (result) {
|
||||
document.getElementById('pack-plugin-path').value = result;
|
||||
}
|
||||
});
|
||||
|
||||
document.getElementById('plugin-pack-execute-btn')?.addEventListener('click', () => {
|
||||
this.packPlugin();
|
||||
});
|
||||
|
||||
// Plugin Extract form
|
||||
document.getElementById('extract-bundle-browse-btn')?.addEventListener('click', () => {
|
||||
document.getElementById('extract-bundle-file').click();
|
||||
});
|
||||
|
||||
document.getElementById('extract-bundle-file')?.addEventListener('change', (e) => {
|
||||
const filename = e.target.files[0]?.name || 'No file selected';
|
||||
document.getElementById('extract-bundle-filename').textContent = filename;
|
||||
this.updateExtractButtonState();
|
||||
});
|
||||
|
||||
document.getElementById('extract-output-browse-btn')?.addEventListener('click', async () => {
|
||||
const result = await ipcRenderer.invoke('select-directory');
|
||||
if (result) {
|
||||
document.getElementById('extract-output-dir').value = result;
|
||||
this.updateExtractButtonState();
|
||||
}
|
||||
});
|
||||
|
||||
document.getElementById('extract-plugin-name')?.addEventListener('input', () => {
|
||||
this.updateExtractButtonState();
|
||||
});
|
||||
|
||||
document.getElementById('plugin-extract-execute-btn')?.addEventListener('click', () => {
|
||||
this.extractPlugin();
|
||||
});
|
||||
|
||||
// Plugin Diff form
|
||||
document.getElementById('diff-bundle-a-browse-btn')?.addEventListener('click', () => {
|
||||
document.getElementById('diff-bundle-a').click();
|
||||
});
|
||||
|
||||
document.getElementById('diff-bundle-a')?.addEventListener('change', (e) => {
|
||||
const filename = e.target.files[0]?.name || 'No file selected';
|
||||
document.getElementById('diff-bundle-a-filename').textContent = filename;
|
||||
this.updateDiffButtonState();
|
||||
});
|
||||
|
||||
document.getElementById('diff-bundle-b-browse-btn')?.addEventListener('click', () => {
|
||||
document.getElementById('diff-bundle-b').click();
|
||||
});
|
||||
|
||||
document.getElementById('diff-bundle-b')?.addEventListener('change', (e) => {
|
||||
const filename = e.target.files[0]?.name || 'No file selected';
|
||||
document.getElementById('diff-bundle-b-filename').textContent = filename;
|
||||
this.updateDiffButtonState();
|
||||
});
|
||||
|
||||
document.getElementById('diff-plugin-name')?.addEventListener('input', () => {
|
||||
this.updateDiffButtonState();
|
||||
});
|
||||
|
||||
document.getElementById('plugin-diff-execute-btn')?.addEventListener('click', () => {
|
||||
this.comparePlugins();
|
||||
});
|
||||
|
||||
console.log('[PLUGIN_OPERATIONS] Event listeners setup complete');
|
||||
}
|
||||
|
||||
// Button state management
|
||||
updateInitButtonState() {
|
||||
const projectName = document.getElementById('plugin-project-name').value.trim();
|
||||
const headerFile = document.getElementById('plugin-header-file').files[0];
|
||||
const selectedInterface = document.getElementById('plugin-interface-select').value;
|
||||
const outputDir = document.getElementById('plugin-directory').value.trim();
|
||||
const button = document.getElementById('plugin-init-execute-btn');
|
||||
|
||||
if (button) {
|
||||
// Button is enabled only when all required fields are filled AND an interface is selected
|
||||
button.disabled = !projectName || !headerFile || !selectedInterface || !outputDir;
|
||||
}
|
||||
}
|
||||
|
||||
updateExtractButtonState() {
|
||||
const pluginName = document.getElementById('extract-plugin-name').value.trim();
|
||||
const bundleFile = document.getElementById('extract-bundle-file').files[0];
|
||||
const outputDir = document.getElementById('extract-output-dir').value.trim();
|
||||
const button = document.getElementById('plugin-extract-execute-btn');
|
||||
|
||||
if (button) {
|
||||
button.disabled = !pluginName || !bundleFile || !outputDir;
|
||||
}
|
||||
}
|
||||
|
||||
updateDiffButtonState() {
|
||||
const pluginName = document.getElementById('diff-plugin-name').value.trim();
|
||||
const bundleA = document.getElementById('diff-bundle-a').files[0];
|
||||
const bundleB = document.getElementById('diff-bundle-b').files[0];
|
||||
const button = document.getElementById('plugin-diff-execute-btn');
|
||||
|
||||
if (button) {
|
||||
button.disabled = !pluginName || !bundleA || !bundleB;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = new PluginOperations();
|
||||
@@ -298,6 +298,79 @@ body.dark-mode .app-title {
|
||||
flex-shrink: 0;
|
||||
transition: all 0.2s ease;
|
||||
box-shadow: inset -1px 0 0 rgba(0, 0, 0, 0.05);
|
||||
height: 100vh;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
|
||||
/* Custom scrollbar for secondary sidebar */
|
||||
.secondary-sidebar::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
}
|
||||
|
||||
.secondary-sidebar::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.secondary-sidebar::-webkit-scrollbar-thumb {
|
||||
background: rgba(0, 0, 0, 0.1);
|
||||
border-radius: 3px;
|
||||
transition: background 0.2s ease;
|
||||
}
|
||||
|
||||
.secondary-sidebar::-webkit-scrollbar-thumb:hover {
|
||||
background: rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
/* Responsive sidebar content spacing */
|
||||
.sidebar-content {
|
||||
padding: 1rem;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 0; /* Allow content to shrink */
|
||||
}
|
||||
|
||||
/* Responsive button spacing based on available height */
|
||||
@media (max-height: 700px) {
|
||||
.sidebar-nav {
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.nav-button {
|
||||
padding: 0.5rem 0.75rem;
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
.sidebar-header {
|
||||
margin-top: 1rem !important;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.sidebar-header h3 {
|
||||
font-size: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-height: 600px) {
|
||||
.sidebar-nav {
|
||||
gap: 0.25rem;
|
||||
}
|
||||
|
||||
.nav-button {
|
||||
padding: 0.375rem 0.5rem;
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
.sidebar-header {
|
||||
margin-top: 0.75rem !important;
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
.sidebar-header h3 {
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
}
|
||||
|
||||
body.dark-mode .secondary-sidebar {
|
||||
@@ -306,6 +379,15 @@ body.dark-mode .secondary-sidebar {
|
||||
box-shadow: inset -1px 0 0 rgba(255, 255, 255, 0.05);
|
||||
}
|
||||
|
||||
/* Dark mode scrollbar */
|
||||
body.dark-mode .secondary-sidebar::-webkit-scrollbar-thumb {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
body.dark-mode .secondary-sidebar::-webkit-scrollbar-thumb:hover {
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
|
||||
/* Responsive secondary sidebar */
|
||||
@media (min-width: 1280px) {
|
||||
.secondary-sidebar {
|
||||
@@ -914,7 +996,16 @@ body.dark-mode .opat-table-container ::-webkit-scrollbar-thumb:hover {
|
||||
background: var(--background-color);
|
||||
}
|
||||
|
||||
/* Individual Key Management Views */
|
||||
/* Main Content Area */
|
||||
.main-content {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: 2rem;
|
||||
background: var(--background-color);
|
||||
max-height: calc(100vh - 60px); /* Fix scrolling by setting max height */
|
||||
}
|
||||
|
||||
/* Individual Key Management Styles */
|
||||
.key-management-view {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
@@ -922,6 +1013,98 @@ body.dark-mode .opat-table-container ::-webkit-scrollbar-thumb:hover {
|
||||
background: var(--background-color);
|
||||
}
|
||||
|
||||
/* Plugin Management Styles */
|
||||
.plugin-management-view {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: 20px;
|
||||
background: var(--background-color);
|
||||
max-height: calc(100vh - 120px); /* Fix scrolling by setting max height */
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* File Input Group Styling */
|
||||
.file-input-group {
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
gap: 0;
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
background: white;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.file-input-group:focus-within {
|
||||
border-color: var(--primary-color);
|
||||
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
|
||||
}
|
||||
|
||||
.file-input-group input {
|
||||
flex: 1;
|
||||
border: none !important;
|
||||
border-radius: 0 !important;
|
||||
margin: 0;
|
||||
box-shadow: none !important;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.file-input-group input:focus {
|
||||
box-shadow: none !important;
|
||||
border: none !important;
|
||||
}
|
||||
|
||||
/* Modern Browse Button Styling */
|
||||
.browse-button {
|
||||
background: linear-gradient(135deg, var(--primary-color) 0%, var(--secondary-color) 100%);
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 0.75rem 1.25rem;
|
||||
font-size: 0.9rem;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
border-radius: 0;
|
||||
white-space: nowrap;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.browse-button::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: -100%;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent);
|
||||
transition: left 0.5s ease;
|
||||
}
|
||||
|
||||
.browse-button:hover {
|
||||
background: linear-gradient(135deg, var(--secondary-color) 0%, #1d4ed8 100%);
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 4px 12px rgba(59, 130, 246, 0.3);
|
||||
}
|
||||
|
||||
.browse-button:hover::before {
|
||||
left: 100%;
|
||||
}
|
||||
|
||||
.browse-button:active {
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
/* Filename Display Styling */
|
||||
.filename-display {
|
||||
color: var(--text-light);
|
||||
font-size: 0.9rem;
|
||||
font-style: italic;
|
||||
margin-left: 0.5rem;
|
||||
padding: 0.75rem 0;
|
||||
}
|
||||
|
||||
/* Key Management Headers */
|
||||
.keys-header,
|
||||
.generate-key-header,
|
||||
@@ -937,6 +1120,13 @@ body.dark-mode .opat-table-container ::-webkit-scrollbar-thumb:hover {
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
/* Plugin Management Headers */
|
||||
.plugin-header {
|
||||
margin-bottom: 2rem;
|
||||
padding-bottom: 1rem;
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
.keys-header .keys-actions {
|
||||
display: flex;
|
||||
gap: 0.75rem;
|
||||
@@ -965,7 +1155,8 @@ body.dark-mode .opat-table-container ::-webkit-scrollbar-thumb:hover {
|
||||
|
||||
/* Form Styling */
|
||||
.generate-key-form,
|
||||
.add-key-form {
|
||||
.add-key-form,
|
||||
.plugin-form {
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
padding: 2rem;
|
||||
@@ -1549,6 +1740,29 @@ body.dark-mode .info-banner svg {
|
||||
color: #60a5fa;
|
||||
}
|
||||
|
||||
/* Dark Mode Support for File Input Groups and Browse Buttons */
|
||||
body.dark-mode .file-input-group {
|
||||
background: #1e293b;
|
||||
border-color: #334155;
|
||||
}
|
||||
|
||||
body.dark-mode .file-input-group input {
|
||||
background: transparent;
|
||||
color: #e2e8f0;
|
||||
}
|
||||
|
||||
body.dark-mode .browse-button {
|
||||
background: linear-gradient(135deg, #3b82f6 0%, #1d4ed8 100%);
|
||||
}
|
||||
|
||||
body.dark-mode .browse-button:hover {
|
||||
background: linear-gradient(135deg, #1d4ed8 0%, #1e40af 100%);
|
||||
}
|
||||
|
||||
body.dark-mode .filename-display {
|
||||
color: #94a3b8;
|
||||
}
|
||||
|
||||
/* Responsive Design */
|
||||
@media (max-width: 1024px) {
|
||||
.key-management-view {
|
||||
|
||||
Reference in New Issue
Block a user