const template = document.createElement('template'); template.innerHTML = ` NAV2 BEARING NAV2 COURSE NAV2 DEVIATION NAV1 BEARING NAV1 COURSE NAV1 DEVIATION HEADING SELECT W E 30 12 33 15 N S 3 21 6 24 NAV1 NAV2 `; class HorizontalSituationIndicator extends HTMLElement { static get observedAttributes() { return [ 'debug', 'fix-north', 'heading', 'heading-select', 'nav1-label', 'nav1-course', 'nav1-deviation', 'nav1-bearing', 'nav1-to', 'nav2-label', 'nav2-course', 'nav2-deviation', 'nav2-bearing', 'nav2-to' ]; } constructor() { super(); let shadowRoot = this.attachShadow({ mode: 'open' }); shadowRoot.appendChild(document.importNode(template.content, true)); const degreeAttributes = [ 'heading', 'heading-select', 'nav1-course', 'nav1-bearing', 'nav2-course', 'nav2-bearing' ]; this._elements = { 'top': shadowRoot.getElementById('top') }; this._svg = { centerX: 67.733/2, centerY: 67.733/2 }; /* eslint-disable wc/no-constructor-attributes */ this.constructor.observedAttributes.forEach((attrName) => { const matches = this._getStructuredAttributeName(attrName); Object.defineProperty(this, attrName, { get() { if (!this.hasAttribute(attrName)) { return null; } return (matches[2] !== 'label') ? Number(this.getAttribute(attrName)) : this.getAttribute(attrName); }, set(attrValue) { if (attrValue !== null && attrValue !== undefined) { attrValue = Number(attrValue); if (degreeAttributes.indexOf(attrName) > -1) { attrValue = this._limitDeg(attrValue); } this.setAttribute(attrName, attrValue); if (matches[2] === 'course' || matches[2] === 'bearing') { this._calculateDeviation(matches[1]); } } else { this.removeAttribute(attrName); } this._log('warn', 'Set', attrName, attrValue); } }); const element = shadowRoot.getElementById(attrName); if (element) { this._log('Registering DOM element for ' + attrName, element); this._elements[attrName] = element; this._elements[attrName].setAttribute('display', 'none'); } }); } attributeChangedCallback(attrName, oldValue, newValue) { this._log('Manipulate stuff via attributeChangedCallback', attrName, newValue); const matches = this._getStructuredAttributeName(attrName); if (attrName === 'fix-north') { this._log('Switching mode'); this._rotateSvgElement(this._elements['top'], 0); this._rotateSvgElement(this._elements['heading'], 0); this.heading = this.heading; } const el = this._elements[attrName]; if (!el) { return; } if (oldValue === null) { el.removeAttribute('display'); } if (newValue === null) { el.setAttribute('display', 'none'); return; } let rotate = null; switch (matches[2]) { case 'label': el.querySelector('tspan').textContent = this[attrName]; break; case 'deviation': let translate = this[attrName]; if (translate < -90) { translate += 180; translate *= -1; } else if (translate > +90) { translate -= 180; translate *= -1; } translate = Math.max(-10, Math.min(10, translate)); el.setAttribute('transform','translate(' + (translate * -1.45) + ' 0)'); translate = Math.abs(translate); if (this[matches[1] + '-bearing'] !== null) { if (translate < 10) { this._elements[matches[1] + '-bearing'].setAttribute('opacity', translate / 10); } else { this._elements[matches[1] + '-bearing'].removeAttribute('opacity'); } } break; case 'to': rotate = this[attrName] > 0 ? 0 : 180; break; case 'heading': if (this['fix-north']) { this._rotateSvgElement(this._elements['top'], this[attrName]); } else { rotate = -this[attrName]; } break; default: rotate = this[attrName]; break; } if (rotate !== null) { this._rotateSvgElement(el, rotate); } this._setTitle(el, attrName.replace(/-/g, ' ').toUpperCase() + ': ' + this[attrName]+ '°'); } connectedCallback() { ['nav1', 'nav2'].forEach((source) => { this._calculateDeviation(source); }); } disconnectedCallback() { } _setTitle(el, title) { const elTitle = el.querySelector('title'); if (elTitle) { elTitle.textContent = title; } } _getStructuredAttributeName(attrName) { return attrName.match(/^(nav\d)-(\S+)/) || [attrName, '', attrName]; } _calculateDeviation(source) { if (this[source + '-course'] === null || this[source + '-bearing'] === null) { return; } const deviation = this._limitDeg(this[source + '-course'] - this[source + '-bearing'], -180, 180) this[source + '-deviation'] = deviation; this[source + '-to'] = (deviation >= -90 && deviation <= 90) ? 1 : -1; this._log('Setting derived values', { deviation: this[source + '-deviation'], to: this[source + '-to'] }); } _degToRad(deg) { return deg * (Math.PI / 180); } _limitDeg(deg, min = 0, max = 360) { while (deg >= max) { deg -= 360; } while (deg < min) { deg += 360; } return deg; } _rotateSvgElement(element, degrees) { element.setAttribute('transform','rotate(' + [degrees, this._svg.centerX, this._svg.centerY].join(' ') + ')'); } _log(...theArgs) { let method = 'log'; if (theArgs[0] === 'warn' || theArgs[0] === 'error') { method = theArgs.shift(); } if (this.debug) { console[method](...theArgs); } } }; customElements.define('horizontal-situation-indicator', HorizontalSituationIndicator);