Update.
This commit is contained in:
parent
da30590080
commit
1bc7bbe81e
235
src/snek/static/toolbar-menu.js
Normal file
235
src/snek/static/toolbar-menu.js
Normal file
@ -0,0 +1,235 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
class ToolbarMenu extends HTMLElement {
|
||||
constructor() {
|
||||
super();
|
||||
this.attachShadow({ mode: 'open' });
|
||||
this._isOpen = false;
|
||||
this._sttButton = null;
|
||||
this._ttsButton = null;
|
||||
this._uploadButton = null;
|
||||
this._observer = null;
|
||||
this._boundClickOutside = this._handleClickOutside.bind(this);
|
||||
|
||||
const style = document.createElement('style');
|
||||
style.textContent = `
|
||||
:host {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.toolbar-container {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.menu-toggle {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 10px 20px;
|
||||
margin-left: 10px;
|
||||
background-color: #000000;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
font-size: 18px;
|
||||
position: relative;
|
||||
overflow: visible;
|
||||
transition: background-color 0.2s ease, transform 0.1s ease;
|
||||
}
|
||||
|
||||
.menu-toggle:hover {
|
||||
background-color: #222222;
|
||||
}
|
||||
|
||||
.menu-toggle:active {
|
||||
transform: scale(0.95);
|
||||
}
|
||||
|
||||
:host([open]) .menu-toggle {
|
||||
background-color: #222222;
|
||||
}
|
||||
|
||||
.status-indicator {
|
||||
position: absolute;
|
||||
top: 4px;
|
||||
right: 4px;
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 50%;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.status-indicator.listening {
|
||||
display: block;
|
||||
background-color: #d32f2f;
|
||||
animation: pulse-indicator 1.5s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.status-indicator.tts-enabled {
|
||||
display: block;
|
||||
background-color: #2e7d32;
|
||||
}
|
||||
|
||||
@keyframes pulse-indicator {
|
||||
0%, 100% {
|
||||
box-shadow: 0 0 0 0 rgba(211, 47, 47, 0.6);
|
||||
}
|
||||
50% {
|
||||
box-shadow: 0 0 0 4px rgba(211, 47, 47, 0);
|
||||
}
|
||||
}
|
||||
|
||||
.menu-panel {
|
||||
position: absolute;
|
||||
bottom: 100%;
|
||||
right: 0;
|
||||
margin-bottom: 8px;
|
||||
background-color: #111111;
|
||||
border: 1px solid #333333;
|
||||
border-radius: 8px;
|
||||
padding: 8px;
|
||||
display: none;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
min-width: 60px;
|
||||
box-shadow: 0 -4px 12px rgba(0, 0, 0, 0.4);
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
:host([open]) .menu-panel {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.menu-item {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
`;
|
||||
|
||||
const container = document.createElement('div');
|
||||
container.className = 'toolbar-container';
|
||||
|
||||
this._menuPanel = document.createElement('div');
|
||||
this._menuPanel.className = 'menu-panel';
|
||||
|
||||
this._toggleButton = document.createElement('button');
|
||||
this._toggleButton.className = 'menu-toggle';
|
||||
this._toggleButton.setAttribute('aria-label', 'Toggle toolbar menu');
|
||||
this._toggleButton.innerHTML = '☰';
|
||||
|
||||
this._statusIndicator = document.createElement('span');
|
||||
this._statusIndicator.className = 'status-indicator';
|
||||
this._toggleButton.appendChild(this._statusIndicator);
|
||||
|
||||
this._toggleButton.addEventListener('click', (e) => {
|
||||
e.stopPropagation();
|
||||
this._toggle();
|
||||
});
|
||||
|
||||
container.appendChild(this._menuPanel);
|
||||
container.appendChild(this._toggleButton);
|
||||
this.shadowRoot.append(style, container);
|
||||
}
|
||||
|
||||
_toggle() {
|
||||
if (this._isOpen) {
|
||||
this._close();
|
||||
} else {
|
||||
this._open();
|
||||
}
|
||||
}
|
||||
|
||||
_open() {
|
||||
this._isOpen = true;
|
||||
this.setAttribute('open', '');
|
||||
setTimeout(() => {
|
||||
document.addEventListener('click', this._boundClickOutside);
|
||||
}, 0);
|
||||
}
|
||||
|
||||
_close() {
|
||||
this._isOpen = false;
|
||||
this.removeAttribute('open');
|
||||
document.removeEventListener('click', this._boundClickOutside);
|
||||
}
|
||||
|
||||
_handleClickOutside(event) {
|
||||
const path = event.composedPath();
|
||||
if (!path.includes(this)) {
|
||||
this._close();
|
||||
}
|
||||
}
|
||||
|
||||
_observeButtonStates() {
|
||||
if (this._observer) {
|
||||
this._observer.disconnect();
|
||||
}
|
||||
|
||||
this._observer = new MutationObserver(() => {
|
||||
this._updateStatusIndicator();
|
||||
});
|
||||
|
||||
if (this._sttButton) {
|
||||
this._observer.observe(this._sttButton, {
|
||||
attributes: true,
|
||||
attributeFilter: ['listening']
|
||||
});
|
||||
}
|
||||
|
||||
if (this._ttsButton) {
|
||||
this._observer.observe(this._ttsButton, {
|
||||
attributes: true,
|
||||
attributeFilter: ['enabled']
|
||||
});
|
||||
}
|
||||
|
||||
this._updateStatusIndicator();
|
||||
}
|
||||
|
||||
_updateStatusIndicator() {
|
||||
this._statusIndicator.classList.remove('listening', 'tts-enabled');
|
||||
|
||||
if (this._sttButton?.hasAttribute('listening')) {
|
||||
this._statusIndicator.classList.add('listening');
|
||||
} else if (this._ttsButton?.hasAttribute('enabled')) {
|
||||
this._statusIndicator.classList.add('tts-enabled');
|
||||
}
|
||||
}
|
||||
|
||||
addButton(button, type) {
|
||||
const wrapper = document.createElement('div');
|
||||
wrapper.className = 'menu-item';
|
||||
|
||||
if (button.style) {
|
||||
button.style.marginLeft = '0';
|
||||
}
|
||||
|
||||
wrapper.appendChild(button);
|
||||
this._menuPanel.appendChild(wrapper);
|
||||
|
||||
if (type === 'stt') {
|
||||
this._sttButton = button;
|
||||
} else if (type === 'tts') {
|
||||
this._ttsButton = button;
|
||||
} else if (type === 'upload') {
|
||||
this._uploadButton = button;
|
||||
}
|
||||
|
||||
this._observeButtonStates();
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
this._observeButtonStates();
|
||||
}
|
||||
|
||||
disconnectedCallback() {
|
||||
document.removeEventListener('click', this._boundClickOutside);
|
||||
if (this._observer) {
|
||||
this._observer.disconnect();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define('toolbar-menu', ToolbarMenu);
|
||||
Loading…
Reference in New Issue
Block a user