Compare commits
3 Commits
47b4e6e182
...
56ca497041
Author | SHA1 | Date | |
---|---|---|---|
|
56ca497041 | ||
|
4b708f1f40 | ||
|
b891929956 |
@@ -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 = () => {
|
||||||
emit('toggle-list');
|
// 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);
|
||||||
|
}
|
||||||
|
|
||||||
// When toggling the list, ensure search text is preserved
|
// Close the drawer
|
||||||
const savedSearchText = localStorage.getItem('vmSearchText');
|
emit('toggle-list');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (!props.showList) {
|
// Toggle filtering mode
|
||||||
// When opening the list, restore search text if available
|
isFilterActive.value = !isFilterActive.value;
|
||||||
if (savedSearchText && !inputValue.value) {
|
|
||||||
|
// If we're turning filtering on, make sure the search text is applied
|
||||||
|
if (isFilterActive.value && searchText.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>
|
Reference in New Issue
Block a user