Compare commits

..

3 Commits

Author SHA1 Message Date
Joshua Boniface
56ca497041 Tweak positioning of icons 2025-03-02 19:24:47 -05:00
Joshua Boniface
4b708f1f40 Fix search opening bug 2025-03-02 19:17:23 -05:00
Joshua Boniface
b891929956 First try at refactoring again 2025-03-02 19:08:03 -05:00

View File

@@ -13,8 +13,25 @@
<i class="fas fa-list"></i> List VMs <i class="fas fa-list"></i> List VMs
</button> </button>
<div class="search-box"> <!-- Search box - visible when drawer is open -->
<i class="fas fa-search search-icon"></i> <div v-if="showList" class="search-box">
<button
v-if="shouldShowClearButton"
class="btn-clear btn-clear-left"
@click.stop="handleClearButton"
title="Clear search"
>
<i class="fas fa-times"></i>
</button>
<button
v-else
class="btn-clear btn-clear-left disabled"
disabled
title="No search to clear"
>
<i class="fas fa-times"></i>
</button>
<input <input
type="text" type="text"
placeholder="Search VMs..." placeholder="Search VMs..."
@@ -26,14 +43,32 @@
:class="{ 'search-active': showList && isFilterActive }" :class="{ 'search-active': showList && isFilterActive }"
class="form-control search-input" class="form-control search-input"
> >
<i class="fas fa-search search-icon-right-open"></i>
</div>
<!-- VM Display - visible when drawer is closed -->
<div v-else class="vm-display" @click.stop="openSearchDrawer">
<button <button
v-if="shouldShowClearButton" v-if="selectedVMName"
class="btn-clear" class="btn-clear vm-clear-btn"
@click.stop="handleClearButton" @click.stop="clearSelectedVM"
:title="showList ? 'Clear search' : 'Clear selected VM'" title="Clear selected VM"
> >
<i class="fas fa-times"></i> <i class="fas fa-times"></i>
</button> </button>
<button
v-else
class="btn-clear vm-clear-btn disabled"
disabled
title="No VM selected"
>
<i class="fas fa-times"></i>
</button>
<i class="fas fa-desktop vm-icon"></i>
<span class="vm-name">{{ selectedVMName || 'Select a VM...' }}</span>
<i class="fas fa-search search-icon-right-closed"></i>
</div> </div>
<div class="filter-dropdown" ref="filterDropdown"> <div class="filter-dropdown" ref="filterDropdown">
@@ -197,6 +232,11 @@ const isVMSelected = (vmName) => {
localStorage.getItem('selectedVMId') === vmName; localStorage.getItem('selectedVMId') === vmName;
}; };
// Computed property for the selected VM name
const selectedVMName = computed(() => {
return props.selectedVM || props.vmFromUrl || localStorage.getItem('selectedVMId') || '';
});
// Initialize the component // Initialize the component
onMounted(() => { onMounted(() => {
// Set up click outside handler // Set up click outside handler
@@ -304,35 +344,75 @@ const handleBlur = (event) => {
// Handle click on search input // Handle click on search input
const handleSearchClick = () => { const handleSearchClick = () => {
if (props.showList) { // When clicking the search input, activate filtering mode
// When clicking the search input while list is open, activate filtering if (props.showList && !isFilterActive.value) {
isFilterActive.value = true; isFilterActive.value = true;
// Restore search text if available // Restore saved search text if available
if (searchText.value && searchText.value !== inputValue.value) { const savedSearchText = localStorage.getItem('vmSearchText');
inputValue.value = searchText.value; if (savedSearchText && inputValue.value !== savedSearchText) {
emit('update:modelValue', searchText.value); inputValue.value = savedSearchText;
emit('update:modelValue', savedSearchText);
} }
} }
}; };
// Toggle the VM list // Toggle the VM list
const toggleList = () => { const toggleList = () => {
// If the list is already open, toggle filtering instead of closing
if (props.showList) {
// If we're in list mode (not filtering) and the button is clicked, close the drawer
if (!isFilterActive.value) {
// Save the current search text before closing
if (props.modelValue) {
searchText.value = props.modelValue;
// Save to localStorage
localStorage.setItem('vmSearchText', props.modelValue);
}
// Close the drawer
emit('toggle-list'); emit('toggle-list');
return;
}
// When toggling the list, ensure search text is preserved // Toggle filtering mode
const savedSearchText = localStorage.getItem('vmSearchText'); isFilterActive.value = !isFilterActive.value;
if (!props.showList) { // If we're turning filtering on, make sure the search text is applied
// When opening the list, restore search text if available if (isFilterActive.value && searchText.value) {
if (savedSearchText && !inputValue.value) { inputValue.value = searchText.value;
emit('update:modelValue', searchText.value);
}
// If we're turning filtering off (switching to list mode), scroll to selected VM
if (!isFilterActive.value) {
nextTick(() => { nextTick(() => {
inputValue.value = savedSearchText; scrollToSelectedVM();
searchText.value = savedSearchText;
emit('update:modelValue', savedSearchText);
}); });
} }
// No need to emit toggle-list since we're not closing the list
return;
} }
// If the list is closed, open it without filtering
if (!props.showList) {
// If we're opening the list, deactivate filtering
isFilterActive.value = false;
// Restore search text in the input, but don't apply filtering
if (searchText.value) {
inputValue.value = searchText.value;
emit('update:modelValue', searchText.value);
}
// Schedule scrolling to selected VM after the list opens
nextTick(() => {
scrollToSelectedVM();
});
}
emit('toggle-list');
}; };
// Handle clear button click // Handle clear button click
@@ -372,23 +452,9 @@ const clearSearch = () => {
// Clear selected VM // Clear selected VM
const clearSelectedVM = () => { const clearSelectedVM = () => {
// Clear the input value
inputValue.value = '';
// Clear from localStorage // Clear from localStorage
localStorage.removeItem('selectedVMId'); localStorage.removeItem('selectedVMId');
// Don't clear vmSearchText - that's for search history
// Only clear if the input value matches the selected VM
const savedVMId = localStorage.getItem('selectedVMId');
const savedSearchText = localStorage.getItem('vmSearchText');
if (savedSearchText === savedVMId) {
localStorage.removeItem('vmSearchText');
}
// Show the VM list
emit('toggle-list');
// Emit event to parent component // Emit event to parent component
emit('clear-vm'); emit('clear-vm');
}; };
@@ -663,6 +729,42 @@ const loadFiltersFromLocalStorage = () => {
} }
} }
}; };
// Method to open the search drawer
const openSearchDrawer = (event) => {
// Prevent event propagation
event.stopPropagation();
console.log('Opening search drawer');
// Only open if it's not already open
if (!props.showList) {
// Set filter active to true to indicate we're in search mode
isFilterActive.value = true;
// Emit toggle-list to open the drawer
emit('toggle-list');
// Focus the search input after the drawer opens
nextTick(() => {
const searchInput = document.querySelector('.search-input');
if (searchInput) {
searchInput.focus();
// Restore saved search text if available
const savedSearchText = localStorage.getItem('vmSearchText');
if (savedSearchText) {
inputValue.value = savedSearchText;
emit('update:modelValue', savedSearchText);
} else {
// If no saved search, clear the input
inputValue.value = '';
emit('update:modelValue', '');
}
}
});
}
};
</script> </script>
<style scoped> <style scoped>
@@ -687,24 +789,18 @@ const loadFiltersFromLocalStorage = () => {
position: relative; position: relative;
flex: 1; flex: 1;
min-width: 200px; min-width: 200px;
} height: 38px;
.search-icon {
position: absolute;
left: 10px;
top: 50%;
transform: translateY(-50%);
color: #6c757d;
} }
.search-input { .search-input {
padding-left: 30px; height: 38px;
padding-left: 35px;
padding-right: 30px; padding-right: 30px;
} }
.btn-clear { .btn-clear-left {
position: absolute; position: absolute;
right: 10px; left: 10px;
top: 50%; top: 50%;
transform: translateY(-50%); transform: translateY(-50%);
background: none; background: none;
@@ -714,11 +810,11 @@ const loadFiltersFromLocalStorage = () => {
padding: 0; padding: 0;
font-size: 0.875rem; font-size: 0.875rem;
transition: color 0.2s; transition: color 0.2s;
z-index: 5; /* Ensure it's above other elements */ z-index: 5;
} }
.btn-clear:hover { .btn-clear-left:hover {
color: #dc3545; /* Red color on hover */ color: #dc3545;
} }
.list-toggle-btn { .list-toggle-btn {
@@ -836,7 +932,11 @@ const loadFiltersFromLocalStorage = () => {
.vm-name { .vm-name {
font-weight: 500; font-weight: 500;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
flex: 1; flex: 1;
padding-left: 0;
} }
.vm-state { .vm-state {
@@ -976,4 +1076,94 @@ const loadFiltersFromLocalStorage = () => {
border-color: #0d6efd; border-color: #0d6efd;
box-shadow: 0 0 0 0.25rem rgba(13, 110, 253, 0.25); box-shadow: 0 0 0 0.25rem rgba(13, 110, 253, 0.25);
} }
/* Add these styles for the VM display */
.vm-display {
position: relative;
flex: 1;
min-width: 200px;
height: 38px;
padding: 0.375rem 0.75rem;
padding-left: 35px;
padding-right: 30px;
background-color: #f8f9fa;
border: 1px solid #ced4da;
border-radius: 0.25rem;
display: flex;
align-items: center;
cursor: pointer;
transition: background-color 0.2s;
user-select: none;
z-index: 1;
}
/* Add a hover effect to make it clear it's clickable */
.vm-display:hover {
background-color: #e9ecef;
border-color: #adb5bd;
}
/* Add an active state for when it's clicked */
.vm-display:active {
background-color: #dee2e6;
border-color: #adb5bd;
}
.vm-clear-btn {
position: absolute;
left: 9px;
top: 50%;
transform: translateY(-50%);
background: none;
border: none;
color: #6c757d;
cursor: pointer;
padding: 0;
font-size: 0.875rem;
transition: color 0.2s;
z-index: 5;
}
.vm-clear-btn:hover {
color: #dc3545;
}
.vm-icon {
color: #6c757d;
margin-right: 0.5rem;
margin-left: -0.25rem;
}
.vm-name {
font-weight: 500;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
flex: 1;
padding-left: 0;
}
.search-icon-right-open {
position: absolute;
right: 10px;
top: 50%;
transform: translateY(-50%);
color: #6c757d;
}
.search-icon-right-closed {
position: absolute;
right: 9px;
top: 50%;
transform: translateY(-50%);
color: #6c757d;
}
/* Add disabled style for clear buttons */
.btn-clear-left.disabled,
.vm-clear-btn.disabled {
color: #ced4da;
cursor: default;
pointer-events: none;
}
</style> </style>