Last active
February 1, 2025 06:43
-
-
Save hbouhadji/0562f0c578d4acf6e977d89071e47998 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(function() { | |
'use strict'; | |
class ConfigurablePanel { | |
constructor() { | |
this.position = 'right'; | |
this.isDragging = false; | |
this.isResizing = false; | |
this.offset = { x: 0, y: 0 }; | |
this.createPanel(); | |
this.attachEvents(); | |
} | |
createPanel() { | |
// Création du container pour le shadow DOM | |
this.container = document.createElement('div'); | |
document.body.appendChild(this.container); | |
// Création du shadow DOM | |
this.shadow = this.container.attachShadow({ mode: 'open' }); | |
// Styles pour le panel | |
const style = document.createElement('style'); | |
style.textContent = ` | |
.panel { | |
position: fixed; | |
width: 300px; | |
height: 400px; | |
background: #1e1e1e; | |
border: 1px solid #333; | |
box-shadow: 0 0 10px rgba(0,0,0,0.3); | |
display: flex; | |
flex-direction: column; | |
z-index: 9999; | |
color: #e0e0e0; | |
} | |
.panel.detached { | |
position: fixed; | |
} | |
.panel-header { | |
padding: 10px; | |
background: #2d2d2d; | |
cursor: move; | |
display: flex; | |
justify-content: space-between; | |
align-items: center; | |
min-height: 40px; | |
box-sizing: border-box; | |
border-bottom: 1px solid #404040; | |
} | |
.panel-content { | |
flex-grow: 1; | |
overflow: auto; | |
padding: 10px; | |
} | |
.position-controls button { | |
margin: 0 5px; | |
padding: 2px 5px; | |
background: #404040; | |
color: #e0e0e0; | |
border: 1px solid #555; | |
cursor: pointer; | |
} | |
.position-controls button:hover { | |
background: #505050; | |
} | |
.close-button { | |
background: #404040; | |
color: #e0e0e0; | |
border: 1px solid #555; | |
cursor: pointer; | |
} | |
.close-button:hover { | |
background: #505050; | |
} | |
.resize-handle { | |
position: absolute; | |
width: 20px; | |
height: 20px; | |
cursor: se-resize; | |
background: linear-gradient(135deg, transparent 50%, #404040 50%); | |
transition: background 0.2s ease; | |
} | |
.resize-handle:hover { | |
background: linear-gradient(135deg, transparent 50%, #505050 50%); | |
} | |
.panel.left .resize-handle { | |
right: 0; | |
top: 50%; | |
transform: translateY(-50%); | |
width: 8px; | |
height: 60px; | |
cursor: ew-resize; | |
background: linear-gradient(90deg, transparent 0%, #404040 100%); | |
} | |
.panel.left .resize-handle:hover { | |
background: linear-gradient(90deg, transparent 0%, #505050 100%); | |
} | |
.panel.right .resize-handle { | |
left: 0; | |
top: 50%; | |
transform: translateY(-50%); | |
width: 8px; | |
height: 60px; | |
cursor: ew-resize; | |
background: linear-gradient(270deg, transparent 0%, #404040 100%); | |
} | |
.panel.right .resize-handle:hover { | |
background: linear-gradient(270deg, transparent 0%, #505050 100%); | |
} | |
.panel.top .resize-handle { | |
bottom: 0; | |
left: 50%; | |
transform: translateX(-50%); | |
width: 60px; | |
height: 8px; | |
cursor: ns-resize; | |
background: linear-gradient(180deg, transparent 0%, #404040 100%); | |
} | |
.panel.top .resize-handle:hover { | |
background: linear-gradient(180deg, transparent 0%, #505050 100%); | |
} | |
.panel.bottom .resize-handle { | |
top: 0; | |
left: 50%; | |
transform: translateX(-50%); | |
width: 60px; | |
height: 8px; | |
cursor: ns-resize; | |
background: linear-gradient(0deg, transparent 0%, #404040 100%); | |
} | |
.panel.bottom .resize-handle:hover { | |
background: linear-gradient(0deg, transparent 0%, #505050 100%); | |
} | |
.panel.detached .resize-handle { | |
bottom: 0; | |
right: 0; | |
width: 20px; | |
height: 20px; | |
transform: none; | |
cursor: se-resize; | |
background: linear-gradient(135deg, transparent 50%, #404040 50%); | |
} | |
.panel.detached .resize-handle:hover { | |
background: linear-gradient(135deg, transparent 50%, #505050 50%); | |
} | |
.panel.left { | |
left: 0; | |
right: auto; | |
top: 0; | |
height: 100vh !important; | |
} | |
.panel.right { | |
right: 0; | |
left: auto; | |
top: 0; | |
height: 100vh !important; | |
} | |
.panel.top { | |
top: 0; | |
bottom: auto; | |
left: 0; | |
width: 100vw !important; | |
height: 200px; | |
} | |
.panel.bottom { | |
bottom: 0; | |
top: auto; | |
left: 0; | |
width: 100vw !important; | |
height: 200px; | |
} | |
`; | |
// Création du panel | |
const panel = document.createElement('div'); | |
panel.className = 'panel'; | |
panel.innerHTML = ` | |
<div class="panel-header"> | |
<div class="position-controls"> | |
<button data-position="left">←</button> | |
<button data-position="right">→</button> | |
<button data-position="top">↑</button> | |
<button data-position="bottom">↓</button> | |
<button data-position="detach">⇱</button> | |
</div> | |
<button class="close-button">×</button> | |
</div> | |
<div class="panel-content"> | |
Contenu du panel | |
</div> | |
<div class="resize-handle"></div> | |
`; | |
this.shadow.appendChild(style); | |
this.shadow.appendChild(panel); | |
this.panel = panel; | |
// Stocker la référence à la poignée de redimensionnement | |
this.resizeHandle = this.shadow.querySelector('.resize-handle'); | |
this.setPosition('right'); | |
} | |
attachEvents() { | |
const header = this.shadow.querySelector('.panel-header'); | |
const closeButton = this.shadow.querySelector('.close-button'); | |
const positionButtons = this.shadow.querySelectorAll('.position-controls button'); | |
// Gestion du drag | |
header.addEventListener('mousedown', (e) => { | |
if (e.target === header) { | |
this.isDragging = true; | |
const rect = this.panel.getBoundingClientRect(); | |
this.offset = { | |
x: e.clientX - rect.left, | |
y: e.clientY - rect.top | |
}; | |
} | |
}); | |
// Gestion du resize | |
this.resizeHandle.addEventListener('mousedown', (e) => { | |
this.isResizing = true; | |
e.preventDefault(); | |
}); | |
// Gestion des événements de la souris | |
document.addEventListener('mousemove', (e) => { | |
if (this.isDragging && this.panel.classList.contains('detached')) { | |
this.panel.style.left = `${e.clientX - this.offset.x}px`; | |
this.panel.style.top = `${e.clientY - this.offset.y}px`; | |
} | |
if (this.isResizing) { | |
const rect = this.panel.getBoundingClientRect(); | |
if (this.position === 'left' || this.position === 'right') { | |
const newWidth = this.position === 'left' ? | |
e.clientX - rect.left : | |
rect.right - e.clientX; | |
this.panel.style.width = `${newWidth}px`; | |
this.adjustBodyMargins(`${newWidth}px`); | |
} else if (this.position === 'top' || this.position === 'bottom') { | |
const newHeight = this.position === 'top' ? | |
e.clientY - rect.top : | |
rect.bottom - e.clientY; | |
this.panel.style.height = `${newHeight}px`; | |
this.adjustBodyMargins(`${newHeight}px`); | |
} else if (this.position === 'detach') { | |
this.panel.style.width = `${e.clientX - rect.left}px`; | |
this.panel.style.height = `${e.clientY - rect.top}px`; | |
} | |
} | |
}); | |
document.addEventListener('mouseup', () => { | |
this.isDragging = false; | |
this.isResizing = false; | |
}); | |
// Gestion des positions | |
positionButtons.forEach(button => { | |
button.addEventListener('click', () => { | |
const position = button.dataset.position; | |
this.setPosition(position); | |
}); | |
}); | |
// Fermeture du panel | |
closeButton.addEventListener('click', () => { | |
this.container.remove(); | |
this.adjustBodyMargins('0px'); | |
}); | |
} | |
setPosition(position) { | |
// Réinitialiser toutes les positions et dimensions personnalisées | |
this.panel.style.left = ''; | |
this.panel.style.right = ''; | |
this.panel.style.top = ''; | |
this.panel.style.bottom = ''; | |
this.panel.style.width = ''; | |
this.panel.style.height = ''; | |
this.panel.className = 'panel'; | |
this.position = position; | |
if (position === 'detach') { | |
this.panel.classList.add('detached'); | |
// En mode détaché, on garde la dernière position mais on ajuste les marges | |
this.adjustBodyMargins('0px'); | |
this.panel.style.width = '300px'; | |
this.panel.style.height = '400px'; | |
if (!this.panel.style.top && !this.panel.style.left) { | |
this.panel.style.top = '20px'; | |
this.panel.style.right = '20px'; | |
} | |
this.resizeHandle.style.display = 'block'; | |
return; | |
} | |
this.panel.classList.add(position); | |
// Ajuster le body en fonction de la position | |
const panelSize = position === 'top' || position === 'bottom' ? | |
this.panel.offsetHeight : | |
this.panel.offsetWidth; | |
this.adjustBodyMargins(panelSize + 'px'); | |
} | |
adjustBodyMargins(offset) { | |
const body = document.body; | |
// Réinitialiser toutes les marges et les styles computés | |
body.style.removeProperty('width'); | |
body.style.removeProperty('margin-left'); | |
body.style.removeProperty('margin-right'); | |
body.style.removeProperty('margin-top'); | |
body.style.removeProperty('margin-bottom'); | |
body.style.removeProperty('height'); | |
body.style.removeProperty('min-height'); | |
// Appliquer la marge selon la position seulement si un offset est spécifié | |
if (offset !== '0px') { | |
const computedStyle = window.getComputedStyle(document.body); | |
offset = parseFloat(offset); | |
let newOffset; | |
switch (this.position) { | |
case 'left': | |
body.style.marginLeft = `${offset}px`; | |
body.style.width = `calc(100% - ${offset}px)`; | |
// newOffset = offset / 2 + parseFloat(computedStyle.marginLeft); | |
// body.style.marginLeft = `${newOffset}px`; | |
break; | |
case 'right': | |
// body.style.marginRight = (parseFloat(offset) + parseFloat(computedStyle.marginRight)) + 'px'; | |
// margin-right: calc(608px + 150px); | |
// console.log(`${parseFloat(offset)/2 + parseFloat(computedStyle.marginRight)}px`); | |
body.style.width = `calc(100% - ${offset}px)`; | |
// newOffset = offset / 2 + parseFloat(computedStyle.marginRight); | |
// body.style.marginRight = `${newOffset}px`; | |
break; | |
case 'top': | |
body.style.marginTop = `${offset}px`; | |
body.style.height = `calc(100% - ${offset}px)`; | |
// const mt = parseFloat(computedStyle.marginTop); | |
// newOffset = Math.max(offset / 2 + mt, offset); | |
// body.style.marginTop = `${newOffset}px`; | |
break; | |
case 'bottom': | |
// body.style.marginBottom = offset; | |
body.style.height = `calc(100% - ${offset}px)`; | |
// const mb = parseFloat(computedStyle.marginBottom); | |
// newOffset = Math.max(offset / 2 + mb, offset); | |
// body.style.marginBottom = `${newOffset}px`; | |
body.style.minHeight = '0px'; | |
break; | |
} | |
} | |
} | |
} | |
// Création d'un bouton pour ouvrir le panel | |
const openButton = document.createElement('button'); | |
openButton.textContent = 'Ouvrir Panel'; | |
openButton.style.position = 'fixed'; | |
openButton.style.top = '10px'; | |
openButton.style.right = '10px'; | |
openButton.style.zIndex = '9998'; | |
document.body.appendChild(openButton); | |
openButton.addEventListener('click', () => { | |
new ConfigurablePanel(); | |
openButton.remove(); | |
}); | |
})(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment