atuin
Version:
An RDF and SPARQL editor with graph visualization
240 lines (198 loc) • 6.47 kB
JavaScript
/**
* Split pane manager for responsive layout
* @module ui/SplitPaneManager
*/
/**
* Manages split pane layout
*/
export class SplitPaneManager {
/**
* Create a new SplitPaneManager
* @param {Object} options - Configuration options
* @param {HTMLElement} options.container - Container element
* @param {HTMLElement} options.leftPane - Left pane element
* @param {HTMLElement} options.rightPane - Right pane element
* @param {HTMLElement} options.divider - Divider element
*/
constructor(options) {
this.container = options.container
this.leftPane = options.leftPane
this.rightPane = options.rightPane
this.divider = options.divider
this.isResizing = false
this.startX = 0
this.startLeftWidth = 0
this.minLeftWidth = 200
this.minRightWidth = 200
this._initializeEvents()
}
/**
* Initialize event listeners
* @private
*/
_initializeEvents() {
// Mouse events for dragging
this.divider.addEventListener('mousedown', this._onMouseDown.bind(this))
document.addEventListener('mousemove', this._onMouseMove.bind(this))
document.addEventListener('mouseup', this._onMouseUp.bind(this))
// Touch events for mobile
this.divider.addEventListener('touchstart', this._onTouchStart.bind(this))
document.addEventListener('touchmove', this._onTouchMove.bind(this))
document.addEventListener('touchend', this._onTouchEnd.bind(this))
// Window resize event
window.addEventListener('resize', this._onWindowResize.bind(this))
// Initial layout
this._setInitialSizes()
}
/**
* Set initial sizes
* @private
*/
_setInitialSizes() {
const containerWidth = this.container.clientWidth
const initialLeftWidth = Math.max(containerWidth * 0.5, this.minLeftWidth)
this.leftPane.style.width = `${initialLeftWidth}px`
}
/**
* Handle mouse down on divider
* @private
* @param {MouseEvent} e - Mouse event
*/
_onMouseDown(e) {
e.preventDefault()
this.isResizing = true
this.startX = e.clientX
this.startLeftWidth = this.leftPane.getBoundingClientRect().width
this.container.classList.add('resizing')
}
/**
* Handle mouse move while resizing
* @private
* @param {MouseEvent} e - Mouse event
*/
_onMouseMove(e) {
if (!this.isResizing) return
const containerRect = this.container.getBoundingClientRect()
const containerWidth = containerRect.width
const delta = e.clientX - this.startX
let newLeftWidth = this.startLeftWidth + delta
// Apply constraints
newLeftWidth = Math.max(this.minLeftWidth, newLeftWidth)
newLeftWidth = Math.min(containerWidth - this.minRightWidth, newLeftWidth)
this.leftPane.style.width = `${newLeftWidth}px`
// Trigger resize event on graph container
window.dispatchEvent(new Event('resize'))
}
/**
* Handle mouse up after resizing
* @private
*/
_onMouseUp() {
this.isResizing = false
this.container.classList.remove('resizing')
// Trigger resize event on graph container
window.dispatchEvent(new Event('resize'))
}
/**
* Handle touch start on divider
* @private
* @param {TouchEvent} e - Touch event
*/
_onTouchStart(e) {
if (e.touches.length === 1) {
e.preventDefault()
this.isResizing = true
this.startX = e.touches[0].clientX
this.startLeftWidth = this.leftPane.getBoundingClientRect().width
this.container.classList.add('resizing')
}
}
/**
* Handle touch move while resizing
* @private
* @param {TouchEvent} e - Touch event
*/
_onTouchMove(e) {
if (!this.isResizing || e.touches.length !== 1) return
const containerRect = this.container.getBoundingClientRect()
const containerWidth = containerRect.width
const delta = e.touches[0].clientX - this.startX
let newLeftWidth = this.startLeftWidth + delta
// Apply constraints
newLeftWidth = Math.max(this.minLeftWidth, newLeftWidth)
newLeftWidth = Math.min(containerWidth - this.minRightWidth, newLeftWidth)
this.leftPane.style.width = `${newLeftWidth}px`
// Trigger resize event on graph container
window.dispatchEvent(new Event('resize'))
}
/**
* Handle touch end after resizing
* @private
*/
_onTouchEnd() {
this.isResizing = false
this.container.classList.remove('resizing')
// Trigger resize event on graph container
window.dispatchEvent(new Event('resize'))
}
/**
* Handle window resize
* @private
*/
_onWindowResize() {
const containerWidth = this.container.clientWidth
let leftWidth = this.leftPane.getBoundingClientRect().width
// If left pane is too wide, adjust it
if (leftWidth + this.minRightWidth > containerWidth) {
leftWidth = containerWidth - this.minRightWidth
this.leftPane.style.width = `${leftWidth}px`
}
}
/**
* Set the split position
* @param {number} percentage - Percentage (0-100) for left pane width
*/
setSplitPosition(percentage) {
if (percentage < 0 || percentage > 100) {
throw new Error('Percentage must be between 0 and 100')
}
const containerWidth = this.container.clientWidth
const leftWidth = (containerWidth * percentage) / 100
// Apply constraints
const constrainedWidth = Math.max(
this.minLeftWidth,
Math.min(containerWidth - this.minRightWidth, leftWidth)
)
this.leftPane.style.width = `${constrainedWidth}px`
// Trigger resize event on graph container
window.dispatchEvent(new Event('resize'))
}
/**
* Toggle between editor and graph views
* @param {string} view - View to show ('editor', 'graph', 'split')
*/
setView(view) {
const containerWidth = this.container.clientWidth
switch (view) {
case 'editor':
this.leftPane.style.width = `${containerWidth}px`
this.rightPane.style.display = 'none'
this.divider.style.display = 'none'
break
case 'graph':
this.leftPane.style.display = 'none'
this.rightPane.style.display = 'block'
this.divider.style.display = 'none'
break
case 'split':
default:
this.leftPane.style.display = 'block'
this.rightPane.style.display = 'block'
this.divider.style.display = 'block'
this.setSplitPosition(50)
break
}
// Trigger resize event on graph container
window.dispatchEvent(new Event('resize'))
}
}