import {LitElement, svg, SVGTemplateResult, unsafeCSS} from 'lit'; import componentStyle from './compass-flat.css?inline'; import {customElement, property, state} from 'lit/decorators.js'; import {Tickmark, TickmarkType} from '../watch-flat/tickmark-flat'; import '../watch-flat/watch-flat'; export enum LabelPosition { top = -45, bottom = 50, } export enum LabelStyle { regular = 'var(--instrument-tick-mark-secondary-color)', } export interface Label { x: number; y: LabelPosition; text: string; } /** * * @ignition-base-height: 170px * @ignition-base-width: 512px */ @customElement('obc-compass-flat') export class ObcCompassFlat extends LitElement { @property({type: Boolean}) noPadding: boolean = true; @property({type: Boolean}) FOVIndicator: boolean = false; @property({type: Number}) padding: number = 16; @property({type: Number}) heading = 0; @property({type: Number}) courseOverGround = 0; @property({type: Number}) tickInterval = 5; @property({type: Number}) FOV = 45; @property({type: Number}) minFOV = 45; @property({type: Number}) maxFOV = 180; @state() containerWidth = 0; @state() maxContainerWidth = 0; private resizeObserver: ResizeObserver = new ResizeObserver((entries) => { for (const entry of entries) { // Made by chatGPT so that the text is inside the wrapper this.maxContainerWidth = -125.36 + 3.79 * entry.contentRect.height; this.containerWidth = Math.min( entry.contentRect.width, this.maxContainerWidth ); } }); override connectedCallback() { super.connectedCallback(); this.resizeObserver.observe(this); } override disconnectedCallback() { super.disconnectedCallback(); this.resizeObserver.unobserve(this); } generateLabels() { if (this.containerWidth < 192) { return []; } else if (this.containerWidth <= 300) { return [ {x: -180, y: LabelPosition.top, text: 'S'}, {x: -90, y: LabelPosition.top, text: 'W'}, {x: 0, y: LabelPosition.top, text: 'N'}, {x: 90, y: LabelPosition.top, text: 'E'}, {x: 180, y: LabelPosition.top, text: 'S'}, {x: 270, y: LabelPosition.top, text: 'W'}, {x: 360, y: LabelPosition.top, text: 'N'}, {x: 450, y: LabelPosition.top, text: 'E'}, {x: 540, y: LabelPosition.top, text: 'S'}, ]; } else { return [ {x: -180, y: LabelPosition.top, text: 'S'}, {x: -135, y: LabelPosition.top, text: 'SW'}, {x: -90, y: LabelPosition.top, text: 'W'}, {x: -45, y: LabelPosition.top, text: 'NW'}, {x: 0, y: LabelPosition.top, text: 'N'}, {x: 45, y: LabelPosition.top, text: 'NE'}, {x: 90, y: LabelPosition.top, text: 'E'}, {x: 135, y: LabelPosition.top, text: 'SE'}, {x: 180, y: LabelPosition.top, text: 'S'}, {x: 225, y: LabelPosition.top, text: 'SW'}, {x: 270, y: LabelPosition.top, text: 'W'}, {x: 315, y: LabelPosition.top, text: 'NW'}, {x: 360, y: LabelPosition.top, text: 'N'}, {x: 405, y: LabelPosition.top, text: 'NE'}, {x: 450, y: LabelPosition.top, text: 'E'}, {x: 495, y: LabelPosition.top, text: 'SE'}, {x: 540, y: LabelPosition.top, text: 'S'}, ]; } } private generateIntervalTickmarks(scale: number): Tickmark[] { const tickmarks: Tickmark[] = []; let cardinalInterval = 90; if (this.containerWidth > 300) { cardinalInterval = 45; } else if (this.containerWidth < 192) { cardinalInterval = 0; } for ( let angle = -180; angle < this.maxFOV * 3; angle += this.tickInterval ) { if (cardinalInterval !== 0 && angle % cardinalInterval === 0) { continue; } tickmarks.push({angle: angle * scale, type: TickmarkType.secondary}); } return tickmarks; } private generateCardinalTickmarks( scale: number, labels: Label[] ): Tickmark[] { const tickmarks: Tickmark[] = []; for (const label of labels) { tickmarks.push({angle: label.x * scale, type: TickmarkType.main}); } return tickmarks; } private generateTickmarks(scale: number, labels: Label[]): Tickmark[] { return [ ...this.generateCardinalTickmarks(scale, labels), ...this.generateIntervalTickmarks(scale), ]; } private renderFOVIndicator(): SVGTemplateResult[] { const indicators: SVGTemplateResult[] = []; const maxAdjustment = 10; const minContainerWidth = 300; const maxContainerWidth = 512; let yAdjustment = 0; if (this.containerWidth < maxContainerWidth) { const widthRange = maxContainerWidth - minContainerWidth; const scaleFactor = (maxContainerWidth - this.containerWidth) / widthRange; yAdjustment = scaleFactor * maxAdjustment; } const y = LabelPosition.bottom + yAdjustment; indicators.push(svg` ${-this.FOV}\u00B0 `); indicators.push(svg` ${this.heading}\u00B0 `); indicators.push(svg` ${this.FOV}\u00B0 `); return indicators; } private get HDGSvg(): SVGTemplateResult { return svg` `; } private COGSvg(translation: number): SVGTemplateResult { return svg` `; } override render() { let angleDiff = this.courseOverGround - this.heading; if (angleDiff > this.maxFOV) { angleDiff -= 360; } else if (angleDiff < -this.maxFOV) { angleDiff += 360; } this.FOV = Math.max(this.minFOV, Math.abs(angleDiff)); const baseOffset = 5; const translationScale = (baseOffset * 35) / this.FOV; const translation = angleDiff * translationScale; const labels = this.generateLabels(); const tickmarks = this.generateTickmarks(translationScale, labels); const scaledLabels = labels.map((l) => ({ ...l, x: l.x * translationScale, })); const viewBox = this.noPadding ? '-192 -128 384 128' : '-200 -144 400 144'; return svg`
${this.HDGSvg}${this.COGSvg(translation)}
`; } static override styles = unsafeCSS(componentStyle); } declare global { interface HTMLElementTagNameMap { 'obc-compass-flat': ObcCompassFlat; } }