class VirtualDisplayManager {
constructor() {
this.displays = new Map();
this.activeDisplayId = 'default';
// Properties for the peek functionality
this._peekTimeout = null;
this._prePeekId = null;
this._initDOM();
}
_initDOM() {
const styleId = 'v-display-manager-styles';
if (!document.getElementById(styleId)) {
const style = document.createElement('style');
style.id = styleId;
style.textContent = `
#v-display-root { position: fixed; top: 0; left: 0; width: 100vw;
height: 100vh; z-index: 999999; background: #ffffff; display: none; }
#v-display-root.is-active { display: block; }
.v-display-view { position: absolute; top: 0; left: 0; width: 100%;
height: 100%; display: none; overflow-y: auto; background: #ffffff; }
.v-display-view.is-active { display: block; }
.v-display-iframe { width: 100%; height: 100%; border: none; display:
block; }
`;
document.head.appendChild(style);
}
if (!document.getElementById('v-display-root')) {
this.rootContainer = document.createElement('div');
this.rootContainer.id = 'v-display-root';
document.body.appendChild(this.rootContainer);
} else {
this.rootContainer = document.getElementById('v-display-root');
}
}
add(id, content) {
if (id === 'default') return;
if (this.displays.has(id)) this.remove(id);
const viewWrapper = document.createElement('div');
viewWrapper.className = 'v-display-view';
if (typeof content === 'string') viewWrapper.innerHTML = content;
else if (content instanceof HTMLElement) viewWrapper.appendChild(content);
this.rootContainer.appendChild(viewWrapper);
this.displays.set(id, viewWrapper);
}
remove(id) {
if (!this.displays.has(id)) return;
const viewWrapper = this.displays.get(id);
if (viewWrapper && viewWrapper.parentNode) viewWrapper.parentNode.removeChi
ld(viewWrapper);
this.displays.delete(id);
if (this.activeDisplayId === id) this.switchTo('default');
}
switchTo(id) {
if (id !== 'default' && !this.displays.has(id)) {
console.error(`Switch failed: Display "${id}" not found.`);
return;
}
// If the user manually switches displays, cancel any active peek
countdown
if (this._peekTimeout) {
clearTimeout(this._peekTimeout);
this._peekTimeout = null;
this._prePeekId = null;
}
this.activeDisplayId = id;
if (id === 'default') {
this.rootContainer.classList.remove('is-active');
this.displays.forEach(view => view.classList.remove('is-active'));
} else {
this.rootContainer.classList.add('is-active');
this.displays.forEach((view, key) => view.classList.toggle('is-active',
key === id));
}
}
list() { return ['default', ...Array.from(this.displays.keys())]; }
getCurrent() { return this.activeDisplayId; }
// =========================================
// NEW HELPER ROUTINES
// =========================================
/**
* Loads a URL into a virtual display in the background using an iframe.
* If the ID already exists, it overwrites it.
*
* @param {string} id - The named/numbered window ID
* @param {string} url - The web address to load
*/
loadUrl(id, url) {
const iframe = document.createElement('iframe');
iframe.src = url;
iframe.className = 'v-display-iframe';
this.add(id, iframe);
console.info(`Loaded URL [${url}] into display "${id}" in the
background.`);
}
/**
* Temporarily switches to a display for a given number of seconds,
* then returns to whatever display was previously active.
*
* @param {string} id - The display ID to peek at
* @param {number} seconds - How long to stay before reverting
*/
peek(id, seconds = 3) {
if (id !== 'default' && !this.displays.has(id)) {
console.error(`Cannot peek: Display "${id}" not found.`);
return;
}
// If we are already in the middle of a peek, clear the old timer,
// but KEEP the original return destination.
if (this._peekTimeout) {
clearTimeout(this._peekTimeout);
} else {
// Record where we came from so we can go back
this._prePeekId = this.getCurrent();
}
console.info(`Peeking at "${id}" for ${seconds} seconds...`);
// Bypass the normal this.switchTo() to avoid clearing our prePeekId
this.activeDisplayId = id;
if (id === 'default') {
this.rootContainer.classList.remove('is-active');
this.displays.forEach(view => view.classList.remove('is-active'));
} else {
this.rootContainer.classList.add('is-active');
this.displays.forEach((view, key) => view.classList.toggle('is-active',
key === id));
}
// Set the return timer
this._peekTimeout = setTimeout(() => {
console.info(`Peek finished. Returning to "${this._prePeekId}".`);
this.switchTo(this._prePeekId);
}, seconds * 1000);
}
}