UNPKG

guify

Version:

A simple GUI for inspecting and changing JS variables

296 lines (243 loc) 9.95 kB
let uuid = require('uuid/v4'); let css = require('dom-css'); let isstring = require('is-string'); var themes = require('./themes'); import styles from './scss/container.scss'; export default class GUI { constructor(opts, componentsList = []) { this.opts = opts; this.hasRoot = opts.root !== undefined; opts = opts || {}; opts.width = opts.width || 300; opts.theme = isstring(opts.theme) ? themes[opts.theme] : themes.dark; opts.root = opts.root || document.body; opts.align = (opts.align || 'right').toLowerCase(); // Can be 'left' or 'right' opts.opacity = opts.opacity || 1.0; opts.useMenuBar = opts.useMenuBar || false; opts.barMode = (opts.barMode || 'overlay').toLowerCase(); // Can be 'above', 'offset', or 'overlay' this.styling = { barHeight: 36 } this.uuid = uuid(); this._LoadStyles(); this._ConstructElements(); this.components = { 'title': require('./components/title'), 'range': require('./components/range'), 'button': require('./components/button'), 'checkbox': require('./components/checkbox'), 'select': require('./components/select'), 'text': require('./components/text'), 'color': require('./components/color'), } // If any objects in `componentsList`, create components from them for(let componentOpts of componentsList) { this.Register(componentOpts); } this.SetPanelVisible(true); } /** * Load any runtime styling information needed here. */ _LoadStyles() { // Load the font we'll be using and append it to the head var elem = document.createElement('style'); elem.setAttribute('type', 'text/css'); elem.setAttribute('rel', 'stylesheet'); elem.setAttribute('href', '//cdn.jsdelivr.net/font-hack/2.019/css/hack.min.css'); document.getElementsByTagName('head')[0].appendChild(elem); } /** * Create container, bar, panel, and toastContainer */ _ConstructElements() { // Create the container that all the other elements will be contained within this.container = document.createElement('div'); this.container.id = 'guify'; // Throw on a unique ID for extra specificity this.container.classList.add('reset'); this.container.classList.add('guify-container'); css(this.container, { top: this.opts.barMode == 'above' && this.hasRoot ? '-36px' : '0', }); this.opts.root.appendChild(this.container); // Create menu bar this.bar = document.createElement('div'); this.bar.className = 'guify-bar'; this.container.appendChild(this.bar); css(this.bar, { background: this.opts.theme.background1 }) if (this.opts.title) { // Create a text label inside of the bar let text = this.bar.appendChild(document.createElement('div')); text.className = 'guify-bar-title'; text.innerHTML = this.opts.title; css(text, { 'color': this.opts.theme.text1 }) } // Make the menu collapse button let menuButton = this.bar.appendChild(document.createElement('button')); menuButton.className = 'guify-bar-button'; menuButton.innerHTML = 'Controls'; css(menuButton, { left: this.opts.align == 'left' ? '8px' : 'unset', right: this.opts.align == 'left' ? 'unset' : '8px', }) menuButton.onclick = () => { this.TogglePanelVisible(); } if (this.opts.barMode == 'offset') { // Make a div that will be the size of the bar and make it first in the container this.opts.root.style.height = this.opts.root.offsetHeight + this.bar.offsetHeight + 'px'; let offsetDiv = document.createElement('div'); offsetDiv.style.width = '100%'; offsetDiv.style.height = this.bar.offsetHeight; this.opts.root.insertBefore(offsetDiv, this.opts.root.childNodes[0]); } // Create panel this.panel = this.container.appendChild(document.createElement('div')); this.panel.className = 'guify-panel'; css(this.panel, { background: this.opts.theme.background1, width: this.opts.width, opacity: this.opts.opacity || 1.0, left: this.opts.align == 'left' ? '0px' : 'unset', right: this.opts.align == 'left' ? 'unset' : '0px', }); if (this.opts.title && !this.opts.useMenuBar) require('./components/partials/header')(this.panel, this.opts.title, this.opts.theme) if(this.opts.useMenuBar) { this.SetPanelVisible(false); } else { this.bar.style.display = 'none'; this.SetPanelVisible(true); } // Make toast area this.toastContainer = this.container.appendChild(document.createElement('div')); css(this.toastContainer, { position: 'absolute', 'width': '100%', }) } /** * Makes the panel visible based on the truthiness of `show`. * @param {Bool} [show] */ SetPanelVisible(show) { if(show) this.panel.style.display = 'block'; else this.panel.style.display = 'none'; } /** * Toggles the visibility of the panel. */ TogglePanelVisible() { if(this.panel.style.display != 'none') this.SetPanelVisible(false); else this.SetPanelVisible(true); } /** * Creates new component in the panel based on the options provided. * * @param {Object} [opts] Options for the component */ Register(opts) { if (opts.object && opts.property) if (opts.object[opts.property] === undefined) throw new Error(`Object ${opts.object} has no property '${opts.property}'`); // Set opts properties from the input if(opts.object && opts.property) { opts.initial = opts.object[opts.property]; // If no label is specified, generate it from property name //opts.label = opts.label || property; } if(this.components[opts.type] === undefined) { throw new Error(`No component type named '${opts.type}' exists.`) } let component = new this.components[opts.type](this.panel, opts, this.opts.theme, this.uuid); if(component.on) { component.on('initialized', function (data) { if(opts.onInitialize) opts.onInitialize(data); }); component.on('input', (data) => { if(opts.object && opts.property) opts.object[opts.property] = data; if(opts.onChange) { opts.onChange(data); } }); } } /** * Makes a message that appears under the menu bar. Transitions out * over `transitionMS` milliseconds after `stayMS` milliseconds have passed. */ Toast(message, stayMS = 5000, transitionMS = 100) { console.log('[Toast] ' + message); var toast = this.toastContainer.appendChild(document.createElement('div')); //toast.zIndex = this.container.zIndex - 1; css(toast, { 'box-sizing': 'border-box', 'background-color': this.opts.theme.background2, 'color': this.opts.theme.text1, 'position': 'relative', 'width': '100%', //'height': '20px', 'padding': '8px', 'padding-left': '20px', 'padding-right': '20px', 'text-align': 'center', 'font-family': `Hack', monospace`, 'font-size': '11px', 'z-index': this.container.zIndex - 100, // Animation stuff // '-webkit-transition': 'opacity ' + transitionMS + 'ms linear', // 'transition': 'opacity ' + transitionMS + 'ms linear', }); toast.innerHTML = message; // Make close button in toast var closeButton = toast.appendChild(document.createElement('button')); closeButton.innerHTML = '✖' css(closeButton, { background: 'rgba(0, 0, 0, 0)', 'color': this.opts.theme.text1, position: 'absolute', textAlign: 'center', 'margin-top': 'auto', 'margin-bottom': 'auto', border: 'none', cursor: 'pointer', fontFamily: 'inherit', top: '0', bottom: '0', right: '8px', }) let timeout; let TransitionOut = () => { css(toast, { //'transform-style': 'flat', //'transform-style': 'preserve-3d', // Slide up // '-webkit-transition': '-webkit-transform ' + transitionMS + 'ms linear', // 'transition': 'transform ' + transitionMS + 'ms linear', // '-webkit-transform': 'translate3d(0, -100%, 0)', // 'transform:': 'translate3d(0, -100%, 0)', // Fade out '-webkit-transition': '-webkit-opacity ' + transitionMS + 'ms linear', 'transition': 'opacity ' + transitionMS + 'ms linear', 'opacity': '0', }); clearTimeout(timeout); timeout = setTimeout(() => { if(toast) toast.parentNode.removeChild(toast); }, transitionMS); } timeout = setTimeout(TransitionOut, stayMS); closeButton.onclick = TransitionOut; } }