You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1286 lines
19 KiB

2 years ago
class UIElement {
constructor( dom ) {
this.dom = dom;
}
add() {
for ( let i = 0; i < arguments.length; i ++ ) {
const argument = arguments[ i ];
if ( argument instanceof UIElement ) {
this.dom.appendChild( argument.dom );
} else {
console.error( 'UIElement:', argument, 'is not an instance of UIElement.' );
}
}
return this;
}
remove() {
for ( let i = 0; i < arguments.length; i ++ ) {
const argument = arguments[ i ];
if ( argument instanceof UIElement ) {
this.dom.removeChild( argument.dom );
} else {
console.error( 'UIElement:', argument, 'is not an instance of UIElement.' );
}
}
return this;
}
clear() {
while ( this.dom.children.length ) {
this.dom.removeChild( this.dom.lastChild );
}
}
setId( id ) {
this.dom.id = id;
return this;
}
getId() {
return this.dom.id;
}
setClass( name ) {
this.dom.className = name;
return this;
}
addClass( name ) {
this.dom.classList.add( name );
return this;
}
removeClass( name ) {
this.dom.classList.remove( name );
return this;
}
setStyle( style, array ) {
for ( let i = 0; i < array.length; i ++ ) {
this.dom.style[ style ] = array[ i ];
}
return this;
}
setDisabled( value ) {
this.dom.disabled = value;
return this;
}
setTextContent( value ) {
this.dom.textContent = value;
return this;
}
setInnerHTML( value ) {
this.dom.innerHTML = value;
}
getIndexOfChild( element ) {
return Array.prototype.indexOf.call( this.dom.children, element.dom );
}
}
// properties
const properties = [ 'position', 'left', 'top', 'right', 'bottom', 'width', 'height',
'display', 'verticalAlign', 'overflow', 'color', 'background', 'backgroundColor', 'opacity',
'border', 'borderLeft', 'borderTop', 'borderRight', 'borderBottom', 'borderColor',
'margin', 'marginLeft', 'marginTop', 'marginRight', 'marginBottom',
'padding', 'paddingLeft', 'paddingTop', 'paddingRight', 'paddingBottom',
'fontSize', 'fontWeight', 'textAlign', 'textDecoration', 'textTransform', 'cursor', 'zIndex' ];
properties.forEach( function ( property ) {
const method = 'set' + property.substr( 0, 1 ).toUpperCase() + property.substr( 1, property.length );
UIElement.prototype[ method ] = function () {
this.setStyle( property, arguments );
return this;
};
} );
// events
const events = [ 'KeyUp', 'KeyDown', 'MouseOver', 'MouseOut', 'Click', 'DblClick', 'Change', 'Input' ];
events.forEach( function ( event ) {
const method = 'on' + event;
UIElement.prototype[ method ] = function ( callback ) {
this.dom.addEventListener( event.toLowerCase(), callback.bind( this ) );
return this;
};
} );
class UISpan extends UIElement {
constructor() {
super( document.createElement( 'span' ) );
}
}
class UIDiv extends UIElement {
constructor() {
super( document.createElement( 'div' ) );
}
}
class UIRow extends UIDiv {
constructor() {
super();
this.dom.className = 'Row';
}
}
class UIPanel extends UIDiv {
constructor() {
super();
this.dom.className = 'Panel';
}
}
class UIText extends UISpan {
constructor( text ) {
super();
this.dom.className = 'Text';
this.dom.style.cursor = 'default';
this.dom.style.display = 'inline-block';
this.setValue( text );
}
getValue() {
return this.dom.textContent;
}
setValue( value ) {
if ( value !== undefined ) {
this.dom.textContent = value;
}
return this;
}
}
class UIInput extends UIElement {
constructor( text ) {
super( document.createElement( 'input' ) );
this.dom.className = 'Input';
this.dom.style.padding = '2px';
this.dom.style.border = '1px solid transparent';
this.dom.setAttribute( 'autocomplete', 'off' );
this.dom.addEventListener( 'keydown', function ( event ) {
event.stopPropagation();
} );
this.setValue( text );
}
getValue() {
return this.dom.value;
}
setValue( value ) {
this.dom.value = value;
return this;
}
}
class UITextArea extends UIElement {
constructor() {
super( document.createElement( 'textarea' ) );
this.dom.className = 'TextArea';
this.dom.style.padding = '2px';
this.dom.spellcheck = false;
this.dom.setAttribute( 'autocomplete', 'off' );
this.dom.addEventListener( 'keydown', function ( event ) {
event.stopPropagation();
if ( event.keyCode === 9 ) {
event.preventDefault();
const cursor = this.selectionStart;
this.value = this.value.substring( 0, cursor ) + '\t' + this.value.substring( cursor );
this.selectionStart = cursor + 1;
this.selectionEnd = this.selectionStart;
}
} );
}
getValue() {
return this.dom.value;
}
setValue( value ) {
this.dom.value = value;
return this;
}
}
class UISelect extends UIElement {
constructor() {
super( document.createElement( 'select' ) );
this.dom.className = 'Select';
this.dom.style.padding = '2px';
this.dom.setAttribute( 'autocomplete', 'off' );
}
setMultiple( boolean ) {
this.dom.multiple = boolean;
return this;
}
setOptions( options ) {
const selected = this.dom.value;
while ( this.dom.children.length > 0 ) {
this.dom.removeChild( this.dom.firstChild );
}
for ( const key in options ) {
const option = document.createElement( 'option' );
option.value = key;
option.innerHTML = options[ key ];
this.dom.appendChild( option );
}
this.dom.value = selected;
return this;
}
getValue() {
return this.dom.value;
}
setValue( value ) {
value = String( value );
if ( this.dom.value !== value ) {
this.dom.value = value;
}
return this;
}
}
class UICheckbox extends UIElement {
constructor( boolean ) {
super( document.createElement( 'input' ) );
this.dom.className = 'Checkbox';
this.dom.type = 'checkbox';
this.setValue( boolean );
}
getValue() {
return this.dom.checked;
}
setValue( value ) {
if ( value !== undefined ) {
this.dom.checked = value;
}
return this;
}
}
class UIColor extends UIElement {
constructor() {
super( document.createElement( 'input' ) );
this.dom.className = 'Color';
this.dom.style.width = '32px';
this.dom.style.height = '16px';
this.dom.style.border = '0px';
this.dom.style.padding = '2px';
this.dom.style.backgroundColor = 'transparent';
this.dom.setAttribute( 'autocomplete', 'off' );
try {
this.dom.type = 'color';
this.dom.value = '#ffffff';
} catch ( exception ) {}
}
getValue() {
return this.dom.value;
}
getHexValue() {
return parseInt( this.dom.value.substr( 1 ), 16 );
}
setValue( value ) {
this.dom.value = value;
return this;
}
setHexValue( hex ) {
this.dom.value = '#' + ( '000000' + hex.toString( 16 ) ).slice( - 6 );
return this;
}
}
class UINumber extends UIElement {
constructor( number ) {
super( document.createElement( 'input' ) );
this.dom.style.cursor = 'ns-resize';
this.dom.className = 'Number';
this.dom.value = '0.00';
this.dom.setAttribute( 'autocomplete', 'off' );
this.value = 0;
this.min = - Infinity;
this.max = Infinity;
this.precision = 2;
this.step = 1;
this.unit = '';
this.nudge = 0.01;
this.setValue( number );
const scope = this;
const changeEvent = document.createEvent( 'HTMLEvents' );
changeEvent.initEvent( 'change', true, true );
let distance = 0;
let onMouseDownValue = 0;
const pointer = { x: 0, y: 0 };
const prevPointer = { x: 0, y: 0 };
function onMouseDown( event ) {
event.preventDefault();
distance = 0;
onMouseDownValue = scope.value;
prevPointer.x = event.clientX;
prevPointer.y = event.clientY;
document.addEventListener( 'mousemove', onMouseMove );
document.addEventListener( 'mouseup', onMouseUp );
}
function onMouseMove( event ) {
const currentValue = scope.value;
pointer.x = event.clientX;
pointer.y = event.clientY;
distance += ( pointer.x - prevPointer.x ) - ( pointer.y - prevPointer.y );
let value = onMouseDownValue + ( distance / ( event.shiftKey ? 5 : 50 ) ) * scope.step;
value = Math.min( scope.max, Math.max( scope.min, value ) );
if ( currentValue !== value ) {
scope.setValue( value );
scope.dom.dispatchEvent( changeEvent );
}
prevPointer.x = event.clientX;
prevPointer.y = event.clientY;
}
function onMouseUp() {
document.removeEventListener( 'mousemove', onMouseMove );
document.removeEventListener( 'mouseup', onMouseUp );
if ( Math.abs( distance ) < 2 ) {
scope.dom.focus();
scope.dom.select();
}
}
function onTouchStart( event ) {
if ( event.touches.length === 1 ) {
distance = 0;
onMouseDownValue = scope.value;
prevPointer.x = event.touches[ 0 ].pageX;
prevPointer.y = event.touches[ 0 ].pageY;
document.addEventListener( 'touchmove', onTouchMove );
document.addEventListener( 'touchend', onTouchEnd );
}
}
function onTouchMove( event ) {
const currentValue = scope.value;
pointer.x = event.touches[ 0 ].pageX;
pointer.y = event.touches[ 0 ].pageY;
distance += ( pointer.x - prevPointer.x ) - ( pointer.y - prevPointer.y );
let value = onMouseDownValue + ( distance / ( event.shiftKey ? 5 : 50 ) ) * scope.step;
value = Math.min( scope.max, Math.max( scope.min, value ) );
if ( currentValue !== value ) {
scope.setValue( value );
scope.dom.dispatchEvent( changeEvent );
}
prevPointer.x = event.touches[ 0 ].pageX;
prevPointer.y = event.touches[ 0 ].pageY;
}
function onTouchEnd( event ) {
if ( event.touches.length === 0 ) {
document.removeEventListener( 'touchmove', onTouchMove );
document.removeEventListener( 'touchend', onTouchEnd );
}
}
function onChange() {
scope.setValue( scope.dom.value );
}
function onFocus() {
scope.dom.style.backgroundColor = '';
scope.dom.style.cursor = '';
}
function onBlur() {
scope.dom.style.backgroundColor = 'transparent';
scope.dom.style.cursor = 'ns-resize';
}
function onKeyDown( event ) {
event.stopPropagation();
switch ( event.keyCode ) {
case 13: // enter
scope.dom.blur();
break;
case 38: // up
event.preventDefault();
scope.setValue( scope.getValue() + scope.nudge );
scope.dom.dispatchEvent( changeEvent );
break;
case 40: // down
event.preventDefault();
scope.setValue( scope.getValue() - scope.nudge );
scope.dom.dispatchEvent( changeEvent );
break;
}
}
onBlur();
this.dom.addEventListener( 'keydown', onKeyDown );
this.dom.addEventListener( 'mousedown', onMouseDown );
this.dom.addEventListener( 'touchstart', onTouchStart );
this.dom.addEventListener( 'change', onChange );
this.dom.addEventListener( 'focus', onFocus );
this.dom.addEventListener( 'blur', onBlur );
}
getValue() {
return this.value;
}
setValue( value ) {
if ( value !== undefined ) {
value = parseFloat( value );
if ( value < this.min ) value = this.min;
if ( value > this.max ) value = this.max;
this.value = value;
this.dom.value = value.toFixed( this.precision );
if ( this.unit !== '' ) this.dom.value += ' ' + this.unit;
}
return this;
}
setPrecision( precision ) {
this.precision = precision;
return this;
}
setStep( step ) {
this.step = step;
return this;
}
setNudge( nudge ) {
this.nudge = nudge;
return this;
}
setRange( min, max ) {
this.min = min;
this.max = max;
return this;
}
setUnit( unit ) {
this.unit = unit;
return this;
}
}
class UIInteger extends UIElement {
constructor( number ) {
super( document.createElement( 'input' ) );
this.dom.style.cursor = 'ns-resize';
this.dom.className = 'Number';
this.dom.value = '0';
this.dom.setAttribute( 'autocomplete', 'off' );
this.value = 0;
this.min = - Infinity;
this.max = Infinity;
this.step = 1;
this.nudge = 1;
this.setValue( number );
const scope = this;
const changeEvent = document.createEvent( 'HTMLEvents' );
changeEvent.initEvent( 'change', true, true );
let distance = 0;
let onMouseDownValue = 0;
const pointer = { x: 0, y: 0 };
const prevPointer = { x: 0, y: 0 };
function onMouseDown( event ) {
event.preventDefault();
distance = 0;
onMouseDownValue = scope.value;
prevPointer.x = event.clientX;
prevPointer.y = event.clientY;
document.addEventListener( 'mousemove', onMouseMove );
document.addEventListener( 'mouseup', onMouseUp );
}
function onMouseMove( event ) {
const currentValue = scope.value;
pointer.x = event.clientX;
pointer.y = event.clientY;
distance += ( pointer.x - prevPointer.x ) - ( pointer.y - prevPointer.y );
let value = onMouseDownValue + ( distance / ( event.shiftKey ? 5 : 50 ) ) * scope.step;
value = Math.min( scope.max, Math.max( scope.min, value ) ) | 0;
if ( currentValue !== value ) {
scope.setValue( value );
scope.dom.dispatchEvent( changeEvent );
}
prevPointer.x = event.clientX;
prevPointer.y = event.clientY;
}
function onMouseUp() {
document.removeEventListener( 'mousemove', onMouseMove );
document.removeEventListener( 'mouseup', onMouseUp );
if ( Math.abs( distance ) < 2 ) {
scope.dom.focus();
scope.dom.select();
}
}
function onChange() {
scope.setValue( scope.dom.value );
}
function onFocus() {
scope.dom.style.backgroundColor = '';
scope.dom.style.cursor = '';
}
function onBlur() {
scope.dom.style.backgroundColor = 'transparent';
scope.dom.style.cursor = 'ns-resize';
}
function onKeyDown( event ) {
event.stopPropagation();
switch ( event.keyCode ) {
case 13: // enter
scope.dom.blur();
break;
case 38: // up
event.preventDefault();
scope.setValue( scope.getValue() + scope.nudge );
scope.dom.dispatchEvent( changeEvent );
break;
case 40: // down
event.preventDefault();
scope.setValue( scope.getValue() - scope.nudge );
scope.dom.dispatchEvent( changeEvent );
break;
}
}
onBlur();
this.dom.addEventListener( 'keydown', onKeyDown );
this.dom.addEventListener( 'mousedown', onMouseDown );
this.dom.addEventListener( 'change', onChange );
this.dom.addEventListener( 'focus', onFocus );
this.dom.addEventListener( 'blur', onBlur );
}
getValue() {
return this.value;
}
setValue( value ) {
if ( value !== undefined ) {
value = parseInt( value );
this.value = value;
this.dom.value = value;
}
return this;
}
setStep( step ) {
this.step = parseInt( step );
return this;
}
setNudge( nudge ) {
this.nudge = nudge;
return this;
}
setRange( min, max ) {
this.min = min;
this.max = max;
return this;
}
}
class UIBreak extends UIElement {
constructor() {
super( document.createElement( 'br' ) );
this.dom.className = 'Break';
}
}
class UIHorizontalRule extends UIElement {
constructor() {
super( document.createElement( 'hr' ) );
this.dom.className = 'HorizontalRule';
}
}
class UIButton extends UIElement {
constructor( value ) {
super( document.createElement( 'button' ) );
this.dom.className = 'Button';
this.dom.textContent = value;
}
}
class UIProgress extends UIElement {
constructor( value ) {
super( document.createElement( 'progress' ) );
this.dom.value = value;
}
setValue( value ) {
this.dom.value = value;
}
}
class UITabbedPanel extends UIDiv {
constructor() {
super();
this.dom.className = 'TabbedPanel';
this.tabs = [];
this.panels = [];
this.tabsDiv = new UIDiv();
this.tabsDiv.setClass( 'Tabs' );
this.panelsDiv = new UIDiv();
this.panelsDiv.setClass( 'Panels' );
this.add( this.tabsDiv );
this.add( this.panelsDiv );
this.selected = '';
}
select( id ) {
let tab;
let panel;
const scope = this;
// Deselect current selection
if ( this.selected && this.selected.length ) {
tab = this.tabs.find( function ( item ) {
return item.dom.id === scope.selected;
} );
panel = this.panels.find( function ( item ) {
return item.dom.id === scope.selected;
} );
if ( tab ) {
tab.removeClass( 'selected' );
}
if ( panel ) {
panel.setDisplay( 'none' );
}
}
tab = this.tabs.find( function ( item ) {
return item.dom.id === id;
} );
panel = this.panels.find( function ( item ) {
return item.dom.id === id;
} );
if ( tab ) {
tab.addClass( 'selected' );
}
if ( panel ) {
panel.setDisplay( '' );
}
this.selected = id;
return this;
}
addTab( id, label, items ) {
const tab = new UITab( label, this );
tab.setId( id );
this.tabs.push( tab );
this.tabsDiv.add( tab );
const panel = new UIDiv();
panel.setId( id );
panel.add( items );
panel.setDisplay( 'none' );
this.panels.push( panel );
this.panelsDiv.add( panel );
this.select( id );
}
}
class UITab extends UIText {
constructor( text, parent ) {
super( text );
this.dom.className = 'Tab';
this.parent = parent;
const scope = this;
this.dom.addEventListener( 'click', function () {
scope.parent.select( scope.dom.id );
} );
}
}
class UIListbox extends UIDiv {
constructor() {
super();
this.dom.className = 'Listbox';
this.dom.tabIndex = 0;
this.items = [];
this.listitems = [];
this.selectedIndex = 0;
this.selectedValue = null;
}
setItems( items ) {
if ( Array.isArray( items ) ) {
this.items = items;
}
this.render();
}
render( ) {
while ( this.listitems.length ) {
const item = this.listitems[ 0 ];
item.dom.remove();
this.listitems.splice( 0, 1 );
}
for ( let i = 0; i < this.items.length; i ++ ) {
const item = this.items[ i ];
const listitem = new ListboxItem( this );
listitem.setId( item.id || `Listbox-${i}` );
listitem.setTextContent( item.name || item.type );
this.add( listitem );
}
}
add() {
const items = Array.from( arguments );
this.listitems = this.listitems.concat( items );
UIElement.prototype.add.apply( this, items );
}
selectIndex( index ) {
if ( index >= 0 && index < this.items.length ) {
this.setValue( this.listitems[ index ].getId() );
}
this.selectedIndex = index;
}
getValue() {
return this.selectedValue;
}
setValue( value ) {
for ( let i = 0; i < this.listitems.length; i ++ ) {
const element = this.listitems[ i ];
if ( element.getId() === value ) {
element.addClass( 'active' );
} else {
element.removeClass( 'active' );
}
}
this.selectedValue = value;
const changeEvent = document.createEvent( 'HTMLEvents' );
changeEvent.initEvent( 'change', true, true );
this.dom.dispatchEvent( changeEvent );
}
}
class ListboxItem extends UIDiv {
constructor( parent ) {
super();
this.dom.className = 'ListboxItem';
this.parent = parent;
const scope = this;
function onClick() {
if ( scope.parent ) {
scope.parent.setValue( scope.getId( ) );
}
}
this.dom.addEventListener( 'click', onClick );
}
}
export { UIElement, UISpan, UIDiv, UIRow, UIPanel, UIText, UIInput, UITextArea, UISelect, UICheckbox, UIColor, UINumber, UIInteger, UIBreak, UIHorizontalRule, UIButton, UIProgress, UITabbedPanel, UIListbox, ListboxItem };