mirror of
https://github.com/harvester/harvester-ui-extension.git
synced 2026-11-15 13:26:15 +08:00
fix: show VNC console dialogs in full screen and restore deleting custom shortcut keys (#1222)
* fix: show the VNC console's dialogs in full screen
Only the console went full screen and dialogs render outside of it, so the
Ctrl+Alt+Del and Soft Reboot confirmations, among others, stayed hidden behind
it until full screen was left. Put the whole page in full screen instead.
Signed-off-by: Marcelo Fukumoto <marcelo.fukumoto@suse.com>
* fix: bring back deleting saved VNC console shortcut keys
The Vue 3 migration dropped the close button from the list of saved shortcuts,
which left no way to delete one. Restore it, and remove by position in the list:
the old code parsed the displayed HTML to find the shortcut, which cannot match
one with the Space key in it, such as Ctrl+Space.
Signed-off-by: Marcelo Fukumoto <marcelo.fukumoto@suse.com>
* fix: tidy the VNC console's custom shortcut keys
Give the saved shortcuts in the menu the same space as the separator above
them, and name the dialog's buttons Cancel and Save, which say what they do.
Signed-off-by: Marcelo Fukumoto <marcelo.fukumoto@suse.com>
---------
Signed-off-by: Marcelo Fukumoto <marcelo.fukumoto@suse.com>
(cherry picked from commit 73eaccc2ad)
This commit is contained in:
@@ -136,20 +136,15 @@ export default {
|
||||
this.addedShortcutKeys.push([].concat(this.keysRecord));
|
||||
},
|
||||
|
||||
removeKey(keys) {
|
||||
const key = keys.replace(/(\s*)<code>|<\/code>/g, '').replace(/\s*,\s*/g, ',');
|
||||
// The index is the one in displayedKeys, which lists the added shortcuts first.
|
||||
removeKey(index) {
|
||||
const added = this.addedShortcutKeys.length;
|
||||
|
||||
this.addedShortcutKeys = this.addedShortcutKeys.filter((item) => {
|
||||
const formatkey = item.map((K) => K.key.charAt(0).toUpperCase() + K.key.slice(1)).join(',');
|
||||
|
||||
return formatkey !== key;
|
||||
});
|
||||
|
||||
this.preferredShortcutKeys = this.preferredShortcutKeys.filter((item) => {
|
||||
const formatkey = item.map((K) => K.key.charAt(0).toUpperCase() + K.key.slice(1)).join(',');
|
||||
|
||||
return formatkey !== key;
|
||||
});
|
||||
if (index < added) {
|
||||
this.addedShortcutKeys.splice(index, 1);
|
||||
} else {
|
||||
this.preferredShortcutKeys.splice(index - added, 1);
|
||||
}
|
||||
},
|
||||
|
||||
async saveKeys(buttonCb) {
|
||||
@@ -220,6 +215,8 @@ export default {
|
||||
<Banner
|
||||
v-for="(keys,index) in displayedKeys"
|
||||
:key="index"
|
||||
:closable="true"
|
||||
@close="removeKey(index)"
|
||||
>
|
||||
<span v-clean-html="keys"></span>
|
||||
</Banner>
|
||||
@@ -233,10 +230,10 @@ export default {
|
||||
class="btn role-secondary mr-20"
|
||||
@click.prevent="closeRecordingModal"
|
||||
>
|
||||
<t k="generic.close" />
|
||||
<t k="generic.cancel" />
|
||||
</button>
|
||||
<AsyncButton
|
||||
mode="done"
|
||||
mode="edit"
|
||||
@click="saveKeys"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -428,7 +428,8 @@ export default {
|
||||
if (document.fullscreenElement) {
|
||||
await document.exitFullscreen();
|
||||
} else {
|
||||
await this.$refs.consoleContainer.requestFullscreen();
|
||||
// The whole page and not only the console, because dialogs render outside of it.
|
||||
await document.documentElement.requestFullscreen();
|
||||
// Keyboard lock lets shortcuts such as Ctrl+W and F11 reach the guest.
|
||||
await navigator.keyboard?.lock?.(['KeyW', 'F11']);
|
||||
}
|
||||
@@ -454,10 +455,7 @@ export default {
|
||||
|
||||
<template>
|
||||
<div id="app">
|
||||
<div
|
||||
ref="consoleContainer"
|
||||
class="vm-console"
|
||||
>
|
||||
<div class="vm-console">
|
||||
<div class="combination-keys">
|
||||
<div class="toolbar-group">
|
||||
<v-dropdown
|
||||
@@ -518,6 +516,7 @@ export default {
|
||||
<div
|
||||
v-for="(keys, index) in preferredShortcutKeys"
|
||||
:key="index"
|
||||
class="custom-shortcut"
|
||||
>
|
||||
<button
|
||||
class="btn btn-sm bg-primary"
|
||||
@@ -735,6 +734,11 @@ export default {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
// The space around the separator above the list.
|
||||
.custom-shortcut + .custom-shortcut {
|
||||
margin-top: 7px;
|
||||
}
|
||||
|
||||
.console-status {
|
||||
color: #fff;
|
||||
font-size: 12px;
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
import { shallowMount } from '@vue/test-utils';
|
||||
import Banner from '@components/Banner/Banner.vue';
|
||||
import NovncConsoleCustomKeys from '../NovncConsoleCustomKeys.vue';
|
||||
|
||||
const PREFERED_SHORTCUT_KEYS = 'prefered-shortcut-keys';
|
||||
|
||||
const key = (value) => ({ key: value, code: value });
|
||||
|
||||
const CTRL_F2 = [key('Control'), key('F2')];
|
||||
const CTRL_SPACE = [key('Control'), key(' ')];
|
||||
const ALT_T = [key('Alt'), key('t')];
|
||||
|
||||
const mountKeys = (saved = []) => {
|
||||
const preference = {
|
||||
data: { [PREFERED_SHORTCUT_KEYS]: JSON.stringify(saved) },
|
||||
save: jest.fn().mockResolvedValue(),
|
||||
};
|
||||
|
||||
const wrapper = shallowMount(NovncConsoleCustomKeys, {
|
||||
global: {
|
||||
mocks: { $store: { getters: { 'management/all': () => [preference] } } },
|
||||
stubs: { ModalWithCard: { template: '<div><slot name="content" /></div>' } },
|
||||
},
|
||||
});
|
||||
|
||||
return { wrapper, preference };
|
||||
};
|
||||
|
||||
const listed = (wrapper) => wrapper.find('.displayed-banners').findAllComponents(Banner);
|
||||
|
||||
const closeBanner = async(wrapper, index) => {
|
||||
listed(wrapper)[index].vm.$emit('close');
|
||||
await wrapper.vm.$nextTick();
|
||||
};
|
||||
|
||||
const savedAfterDone = async(wrapper, preference) => {
|
||||
await wrapper.vm.saveKeys(jest.fn());
|
||||
|
||||
return JSON.parse(preference.data[PREFERED_SHORTCUT_KEYS]);
|
||||
};
|
||||
|
||||
describe('component: NovncConsoleCustomKeys', () => {
|
||||
it('should offer to remove every listed shortcut', () => {
|
||||
const { wrapper } = mountKeys([CTRL_F2, ALT_T]);
|
||||
|
||||
expect(listed(wrapper).map((banner) => banner.props('closable'))).toStrictEqual([true, true]);
|
||||
});
|
||||
|
||||
it('should save without the shortcut that was removed', async() => {
|
||||
const { wrapper, preference } = mountKeys([CTRL_F2, ALT_T]);
|
||||
|
||||
await closeBanner(wrapper, 0);
|
||||
|
||||
expect(listed(wrapper)).toHaveLength(1);
|
||||
expect(preference.save).not.toHaveBeenCalled();
|
||||
|
||||
await expect(savedAfterDone(wrapper, preference)).resolves.toStrictEqual([ALT_T]);
|
||||
expect(preference.save).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('should remove a shortcut with the Space key in it', async() => {
|
||||
const { wrapper, preference } = mountKeys([CTRL_SPACE, ALT_T]);
|
||||
|
||||
await closeBanner(wrapper, 0);
|
||||
|
||||
await expect(savedAfterDone(wrapper, preference)).resolves.toStrictEqual([ALT_T]);
|
||||
});
|
||||
|
||||
it('should tell a shortcut that was just added from the saved ones', async() => {
|
||||
const { wrapper, preference } = mountKeys([CTRL_F2]);
|
||||
|
||||
wrapper.vm.keysRecord = ALT_T;
|
||||
wrapper.vm.addShortcutKey();
|
||||
await wrapper.vm.$nextTick();
|
||||
|
||||
// Just added shortcuts are listed first.
|
||||
await closeBanner(wrapper, 1);
|
||||
|
||||
await expect(savedAfterDone(wrapper, preference)).resolves.toStrictEqual([ALT_T]);
|
||||
});
|
||||
|
||||
it('should keep the saved shortcuts when it is closed without saving', async() => {
|
||||
const { wrapper, preference } = mountKeys([CTRL_F2, ALT_T]);
|
||||
|
||||
await closeBanner(wrapper, 0);
|
||||
wrapper.vm.closeRecordingModal();
|
||||
|
||||
expect(wrapper.emitted('close')).toHaveLength(1);
|
||||
expect(preference.save).not.toHaveBeenCalled();
|
||||
expect(JSON.parse(preference.data[PREFERED_SHORTCUT_KEYS])).toStrictEqual([CTRL_F2, ALT_T]);
|
||||
});
|
||||
});
|
||||
@@ -92,4 +92,17 @@ describe('component: NovncConsoleWrapper', () => {
|
||||
|
||||
expect(wrapper.vm.confirmAction).toBeNull();
|
||||
});
|
||||
|
||||
it('should put the whole page in full screen, so that dialogs rendered outside the console show', async() => {
|
||||
const wrapper = mountWrapper();
|
||||
const requestFullscreen = jest.fn().mockResolvedValue();
|
||||
|
||||
document.documentElement.requestFullscreen = requestFullscreen;
|
||||
|
||||
await wrapper.vm.toggleFullscreen();
|
||||
|
||||
expect(requestFullscreen).toHaveBeenCalledTimes(1);
|
||||
|
||||
delete document.documentElement.requestFullscreen;
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user