Add Master Visibility Toggle to Layers Panel

Introduce a three-state checkbox in CanvasLayersPanel header to control visibility of all layers at once. Supports automatic state updates and integrates with renderLayers() for seamless layer management.
This commit is contained in:
Dariusz L
2025-08-09 16:15:11 +02:00
parent 00cf74a3c2
commit c9860cac9e
4 changed files with 295 additions and 1 deletions

View File

@@ -103,6 +103,7 @@ export class CanvasLayersPanel {
this.container.tabIndex = 0; // Umożliwia fokus na panelu
this.container.innerHTML = `
<div class="layers-panel-header">
<div class="master-visibility-toggle" title="Toggle all layers visibility"></div>
<span class="layers-panel-title">Layers</span>
<div class="layers-panel-controls">
<button class="layers-btn" id="delete-layer-btn" title="Delete layer"></button>
@@ -115,6 +116,7 @@ export class CanvasLayersPanel {
this.layersContainer = this.container.querySelector('#layers-container');
// Setup event listeners dla przycisków
this.setupControlButtons();
this.setupMasterVisibilityToggle();
// Dodaj listener dla klawiatury, aby usuwanie działało z panelu
this.container.addEventListener('keydown', (e) => {
if (e.key === 'Delete' || e.key === 'Backspace') {
@@ -142,6 +144,67 @@ export class CanvasLayersPanel {
// Initial button state update
this.updateButtonStates();
}
setupMasterVisibilityToggle() {
if (!this.container)
return;
const toggleContainer = this.container.querySelector('.master-visibility-toggle');
if (!toggleContainer)
return;
const updateToggleState = () => {
const total = this.canvas.layers.length;
const visibleCount = this.canvas.layers.filter(l => l.visible).length;
toggleContainer.innerHTML = '';
const checkboxContainer = document.createElement('div');
checkboxContainer.className = 'checkbox-container';
const checkbox = document.createElement('input');
checkbox.type = 'checkbox';
checkbox.id = 'master-visibility-checkbox';
const customCheckbox = document.createElement('span');
customCheckbox.className = 'custom-checkbox';
checkboxContainer.appendChild(checkbox);
checkboxContainer.appendChild(customCheckbox);
if (visibleCount === 0) {
checkbox.checked = false;
checkbox.indeterminate = false;
customCheckbox.classList.remove('checked', 'indeterminate');
}
else if (visibleCount === total) {
checkbox.checked = true;
checkbox.indeterminate = false;
customCheckbox.classList.add('checked');
customCheckbox.classList.remove('indeterminate');
}
else {
checkbox.checked = false;
checkbox.indeterminate = true;
customCheckbox.classList.add('indeterminate');
customCheckbox.classList.remove('checked');
}
checkboxContainer.addEventListener('click', (e) => {
e.stopPropagation();
let newVisible;
if (checkbox.indeterminate) {
newVisible = false; // hide all when mixed
}
else if (checkbox.checked) {
newVisible = false; // toggle to hide all
}
else {
newVisible = true; // toggle to show all
}
this.canvas.layers.forEach(layer => {
layer.visible = newVisible;
});
this.canvas.render();
this.canvas.requestSaveState();
updateToggleState();
this.renderLayers();
});
toggleContainer.appendChild(checkboxContainer);
};
updateToggleState();
this._updateMasterVisibilityToggle = updateToggleState;
}
renderLayers() {
if (!this.layersContainer) {
log.warn('Layers container not initialized');
@@ -158,6 +221,8 @@ export class CanvasLayersPanel {
if (this.layersContainer)
this.layersContainer.appendChild(layerElement);
});
if (this._updateMasterVisibilityToggle)
this._updateMasterVisibilityToggle();
log.debug(`Rendered ${sortedLayers.length} layers`);
}
createLayerElement(layer, index) {

View File

@@ -23,6 +23,85 @@
margin-bottom: 8px;
}
.checkbox-container {
display: flex;
align-items: center;
gap: 8px;
padding: 5px 0;
border-radius: 5px;
cursor: pointer;
transition: background-color 0.2s;
position: relative;
}
.checkbox-container:hover {
background-color: #4a4a4a;
}
.checkbox-container input[type="checkbox"] {
position: absolute;
opacity: 0;
cursor: pointer;
height: 0;
width: 0;
}
.checkbox-container .custom-checkbox {
height: 16px;
width: 16px;
background-color: #2a2a2a;
border: 1px solid #666;
border-radius: 3px;
transition: all 0.2s;
position: relative;
flex-shrink: 0;
}
.checkbox-container input:checked ~ .custom-checkbox {
background-color: #3a76d6;
border-color: #3a76d6;
}
.checkbox-container .custom-checkbox::after {
content: "";
position: absolute;
display: none;
left: 5px;
top: 1px;
width: 4px;
height: 9px;
border: solid white;
border-width: 0 2px 2px 0;
transform: rotate(45deg);
}
.checkbox-container input:checked ~ .custom-checkbox::after {
display: block;
}
.checkbox-container input:indeterminate ~ .custom-checkbox {
background-color: #3a76d6;
border-color: #3a76d6;
}
.checkbox-container input:indeterminate ~ .custom-checkbox::after {
display: block;
content: "";
position: absolute;
top: 7px;
left: 3px;
width: 8px;
height: 2px;
background-color: white;
border: none;
transform: none;
box-shadow: none;
}
.checkbox-container:hover {
background-color: #4a4a4a;
}
.layers-panel-title {
font-weight: bold;
color: #ffffff;

View File

@@ -121,6 +121,7 @@ export class CanvasLayersPanel {
this.container.tabIndex = 0; // Umożliwia fokus na panelu
this.container.innerHTML = `
<div class="layers-panel-header">
<div class="master-visibility-toggle" title="Toggle all layers visibility"></div>
<span class="layers-panel-title">Layers</span>
<div class="layers-panel-controls">
<button class="layers-btn" id="delete-layer-btn" title="Delete layer"></button>
@@ -135,6 +136,7 @@ export class CanvasLayersPanel {
// Setup event listeners dla przycisków
this.setupControlButtons();
this.setupMasterVisibilityToggle();
// Dodaj listener dla klawiatury, aby usuwanie działało z panelu
this.container.addEventListener('keydown', (e: KeyboardEvent) => {
@@ -169,6 +171,74 @@ export class CanvasLayersPanel {
this.updateButtonStates();
}
setupMasterVisibilityToggle(): void {
if (!this.container) return;
const toggleContainer = this.container.querySelector('.master-visibility-toggle') as HTMLElement;
if (!toggleContainer) return;
const updateToggleState = () => {
const total = this.canvas.layers.length;
const visibleCount = this.canvas.layers.filter(l => l.visible).length;
toggleContainer.innerHTML = '';
const checkboxContainer = document.createElement('div');
checkboxContainer.className = 'checkbox-container';
const checkbox = document.createElement('input');
checkbox.type = 'checkbox';
checkbox.id = 'master-visibility-checkbox';
const customCheckbox = document.createElement('span');
customCheckbox.className = 'custom-checkbox';
checkboxContainer.appendChild(checkbox);
checkboxContainer.appendChild(customCheckbox);
if (visibleCount === 0) {
checkbox.checked = false;
checkbox.indeterminate = false;
customCheckbox.classList.remove('checked', 'indeterminate');
} else if (visibleCount === total) {
checkbox.checked = true;
checkbox.indeterminate = false;
customCheckbox.classList.add('checked');
customCheckbox.classList.remove('indeterminate');
} else {
checkbox.checked = false;
checkbox.indeterminate = true;
customCheckbox.classList.add('indeterminate');
customCheckbox.classList.remove('checked');
}
checkboxContainer.addEventListener('click', (e) => {
e.stopPropagation();
let newVisible: boolean;
if (checkbox.indeterminate) {
newVisible = false; // hide all when mixed
} else if (checkbox.checked) {
newVisible = false; // toggle to hide all
} else {
newVisible = true; // toggle to show all
}
this.canvas.layers.forEach(layer => {
layer.visible = newVisible;
});
this.canvas.render();
this.canvas.requestSaveState();
updateToggleState();
this.renderLayers();
});
toggleContainer.appendChild(checkboxContainer);
};
updateToggleState();
this._updateMasterVisibilityToggle = updateToggleState;
}
private _updateMasterVisibilityToggle?: () => void;
renderLayers(): void {
if (!this.layersContainer) {
log.warn('Layers container not initialized');
@@ -186,10 +256,11 @@ export class CanvasLayersPanel {
sortedLayers.forEach((layer: Layer, index: number) => {
const layerElement = this.createLayerElement(layer, index);
if(this.layersContainer)
if (this.layersContainer)
this.layersContainer.appendChild(layerElement);
});
if (this._updateMasterVisibilityToggle) this._updateMasterVisibilityToggle();
log.debug(`Rendered ${sortedLayers.length} layers`);
}

View File

@@ -23,6 +23,85 @@
margin-bottom: 8px;
}
.checkbox-container {
display: flex;
align-items: center;
gap: 8px;
padding: 5px 0;
border-radius: 5px;
cursor: pointer;
transition: background-color 0.2s;
position: relative;
}
.checkbox-container:hover {
background-color: #4a4a4a;
}
.checkbox-container input[type="checkbox"] {
position: absolute;
opacity: 0;
cursor: pointer;
height: 0;
width: 0;
}
.checkbox-container .custom-checkbox {
height: 16px;
width: 16px;
background-color: #2a2a2a;
border: 1px solid #666;
border-radius: 3px;
transition: all 0.2s;
position: relative;
flex-shrink: 0;
}
.checkbox-container input:checked ~ .custom-checkbox {
background-color: #3a76d6;
border-color: #3a76d6;
}
.checkbox-container .custom-checkbox::after {
content: "";
position: absolute;
display: none;
left: 5px;
top: 1px;
width: 4px;
height: 9px;
border: solid white;
border-width: 0 2px 2px 0;
transform: rotate(45deg);
}
.checkbox-container input:checked ~ .custom-checkbox::after {
display: block;
}
.checkbox-container input:indeterminate ~ .custom-checkbox {
background-color: #3a76d6;
border-color: #3a76d6;
}
.checkbox-container input:indeterminate ~ .custom-checkbox::after {
display: block;
content: "";
position: absolute;
top: 7px;
left: 3px;
width: 8px;
height: 2px;
background-color: white;
border: none;
transform: none;
box-shadow: none;
}
.checkbox-container:hover {
background-color: #4a4a4a;
}
.layers-panel-title {
font-weight: bold;
color: #ffffff;