From 8734b18509f210401e76c65d32af803060f50a45 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ludvig=20=C3=98vrevik?= Date: Mon, 20 Jul 2026 13:51:41 +0200 Subject: [PATCH 1/6] feat(automation): add specialty-tank tiles Heat pump, hydraulic separator, and heat exchanger share one abstract base (ObcAbstractSpecialtyTank); subclasses override only the center icon and the hot/cold split geometry. Layout, tokens, divider, and glyph colors verified 1:1 against Figma nodes 18907-47623 / 18907-47658 / 18907-47693 in both hasMedium states. Co-Authored-By: Claude Fable 5 --- .../heat-exchanger/heat-exchanger.stories.ts | 110 ++++++ .../heat-exchanger/heat-exchanger.ts | 62 ++++ .../automation/heat-pump/heat-pump.stories.ts | 110 ++++++ .../src/automation/heat-pump/heat-pump.ts | 62 ++++ .../hydraulic-separator.stories.ts | 110 ++++++ .../hydraulic-separator.ts | 64 ++++ .../specialty-tank/abstract-specialty-tank.ts | 314 ++++++++++++++++++ .../specialty-tank/specialty-tank.css | 251 ++++++++++++++ 8 files changed, 1083 insertions(+) create mode 100644 packages/openbridge-webcomponents/src/automation/heat-exchanger/heat-exchanger.stories.ts create mode 100644 packages/openbridge-webcomponents/src/automation/heat-exchanger/heat-exchanger.ts create mode 100644 packages/openbridge-webcomponents/src/automation/heat-pump/heat-pump.stories.ts create mode 100644 packages/openbridge-webcomponents/src/automation/heat-pump/heat-pump.ts create mode 100644 packages/openbridge-webcomponents/src/automation/hydraulic-separator/hydraulic-separator.stories.ts create mode 100644 packages/openbridge-webcomponents/src/automation/hydraulic-separator/hydraulic-separator.ts create mode 100644 packages/openbridge-webcomponents/src/automation/specialty-tank/abstract-specialty-tank.ts create mode 100644 packages/openbridge-webcomponents/src/automation/specialty-tank/specialty-tank.css diff --git a/packages/openbridge-webcomponents/src/automation/heat-exchanger/heat-exchanger.stories.ts b/packages/openbridge-webcomponents/src/automation/heat-exchanger/heat-exchanger.stories.ts new file mode 100644 index 000000000..b4226f55e --- /dev/null +++ b/packages/openbridge-webcomponents/src/automation/heat-exchanger/heat-exchanger.stories.ts @@ -0,0 +1,110 @@ +import type {Meta, StoryObj} from '@storybook/web-components-vite'; +import {ObcHeatExchanger} from './heat-exchanger.js'; +import './heat-exchanger.js'; +import {html} from 'lit'; +import { + ObcAlertFrameStatus, + ObcAlertFrameThickness, + ObcAlertFrameType, +} from '../../components/alert-frame/alert-frame.js'; +import { + AutomationButtonBadgeAlert, + AutomationButtonBadgeCommandLocked, + AutomationButtonBadgeControl, + AutomationButtonBadgeInterlock, +} from '../automation-button/abstract-automation-button.js'; + +type StoryArgs = ObcHeatExchanger; + +const meta: Meta = { + title: 'Automation/Tanks/Heat Exchanger', + tags: ['autodocs', '6.1'], + component: 'obc-heat-exchanger', + args: { + medium: false, + showMediumIcons: true, + showTag: true, + tag: '#0000', + badgeControl: AutomationButtonBadgeControl.None, + badgeInterlock: AutomationButtonBadgeInterlock.None, + badgeCommandLocked: AutomationButtonBadgeCommandLocked.None, + badgeAlert: AutomationButtonBadgeAlert.None, + alert: false, + alertFrameType: ObcAlertFrameType.SmallSideFlip, + alertFrameThickness: ObcAlertFrameThickness.Small, + alertFrameStatus: ObcAlertFrameStatus.Alarm, + showAlertCategoryIcon: true, + showAlertIcon: false, + }, + argTypes: { + badgeControl: { + options: Object.values(AutomationButtonBadgeControl), + control: {type: 'select'}, + }, + badgeInterlock: { + options: Object.values(AutomationButtonBadgeInterlock), + control: {type: 'select'}, + }, + badgeCommandLocked: { + options: Object.values(AutomationButtonBadgeCommandLocked), + control: {type: 'select'}, + }, + badgeAlert: { + options: Object.values(AutomationButtonBadgeAlert), + control: {type: 'select'}, + }, + alertFrameType: { + options: Object.values(ObcAlertFrameType), + control: {type: 'select'}, + }, + alertFrameThickness: { + options: Object.values(ObcAlertFrameThickness), + control: {type: 'select'}, + }, + alertFrameStatus: { + options: Object.values(ObcAlertFrameStatus), + control: {type: 'select'}, + }, + }, + render: (args) => html` + + `, +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const Default: Story = {}; + +export const WithMedium: Story = { + args: {medium: true}, +}; + +export const WithBadges: Story = { + args: { + medium: true, + badgeControl: AutomationButtonBadgeControl.Auto, + badgeInterlock: AutomationButtonBadgeInterlock.Interlock, + badgeCommandLocked: AutomationButtonBadgeCommandLocked.CommandLocked, + badgeAlert: AutomationButtonBadgeAlert.Alarm, + }, +}; + +export const WithAlert: Story = { + args: {medium: true, alert: true}, +}; diff --git a/packages/openbridge-webcomponents/src/automation/heat-exchanger/heat-exchanger.ts b/packages/openbridge-webcomponents/src/automation/heat-exchanger/heat-exchanger.ts new file mode 100644 index 000000000..136b2dc66 --- /dev/null +++ b/packages/openbridge-webcomponents/src/automation/heat-exchanger/heat-exchanger.ts @@ -0,0 +1,62 @@ +import {TemplateResult, html} from 'lit'; +import '../../icons/icon-heatexhanger.js'; +import { + ObcAbstractSpecialtyTank, + SpecialtyTankSplitMode, +} from '../specialty-tank/abstract-specialty-tank.js'; +import {customElement} from '../../decorator.js'; + +/** + * ## Heat Exchanger + * + * Fixed-footprint instrument tile representing a heat exchanger. Shows a + * framed hot/cold medium area split diagonally into two corner-to-corner + * triangles (top-left hot, bottom-right cold, no divider), a centered + * heat-exchanger icon in a rounded frame, optional flame/snowflake corner + * glyphs, an optional badge row, and a tag readout below the frame. + * + * ### Features / Variants + * - `medium` toggles between the empty grey fill and the hot/cold colors. + * - `showMediumIcons` toggles the flame (top-left) and snowflake + * (bottom-right) corner glyphs. + * - `showTag` / `tag` control the identifier readout below the frame. + * - Enum-driven badges (`badgeControl`, `badgeInterlock`, + * `badgeCommandLocked`, `badgeAlert`) render in the top-right badge row; + * the `badges` slot overrides them. + * - Alert-frame overlay mirroring `obc-automation-tank` (`alert`, + * `alertFrameType`, `alertFrameThickness`, `alertFrameStatus`, + * `showAlertCategoryIcon`, `showAlertIcon`). + * + * ### Usage Guidelines + * Use for heat exchangers in automation system views. For heat pumps use + * `obc-heat-pump` (vertical split) and for hydraulic separators use + * `obc-hydraulic-separator` (horizontal split); the three tiles differ only + * in center icon and fill geometry. + * + * @slot badges - Custom badges, overriding the enum-driven defaults. + * @slot tag - Text or element replacing the `tag` property readout. + * @slot alert-icon - Custom icon for the alert frame. + * @slot alert-label - Label for the alert frame. + * @slot alert-timer - Timer for the alert frame. + * + * @ignition-base-width: 90px + * @ignition-base-height: 163px + * @ignition-center-horizontal + * @beta + */ +@customElement('obc-heat-exchanger') +export class ObcHeatExchanger extends ObcAbstractSpecialtyTank { + override get equipmentIcon(): TemplateResult { + return html``; + } + + override get splitMode(): SpecialtyTankSplitMode { + return SpecialtyTankSplitMode.diagonal; + } +} + +declare global { + interface HTMLElementTagNameMap { + 'obc-heat-exchanger': ObcHeatExchanger; + } +} diff --git a/packages/openbridge-webcomponents/src/automation/heat-pump/heat-pump.stories.ts b/packages/openbridge-webcomponents/src/automation/heat-pump/heat-pump.stories.ts new file mode 100644 index 000000000..fc27d4ebc --- /dev/null +++ b/packages/openbridge-webcomponents/src/automation/heat-pump/heat-pump.stories.ts @@ -0,0 +1,110 @@ +import type {Meta, StoryObj} from '@storybook/web-components-vite'; +import {ObcHeatPump} from './heat-pump.js'; +import './heat-pump.js'; +import {html} from 'lit'; +import { + ObcAlertFrameStatus, + ObcAlertFrameThickness, + ObcAlertFrameType, +} from '../../components/alert-frame/alert-frame.js'; +import { + AutomationButtonBadgeAlert, + AutomationButtonBadgeCommandLocked, + AutomationButtonBadgeControl, + AutomationButtonBadgeInterlock, +} from '../automation-button/abstract-automation-button.js'; + +type StoryArgs = ObcHeatPump; + +const meta: Meta = { + title: 'Automation/Tanks/Heat Pump', + tags: ['autodocs', '6.1'], + component: 'obc-heat-pump', + args: { + medium: false, + showMediumIcons: true, + showTag: true, + tag: '#0000', + badgeControl: AutomationButtonBadgeControl.None, + badgeInterlock: AutomationButtonBadgeInterlock.None, + badgeCommandLocked: AutomationButtonBadgeCommandLocked.None, + badgeAlert: AutomationButtonBadgeAlert.None, + alert: false, + alertFrameType: ObcAlertFrameType.SmallSideFlip, + alertFrameThickness: ObcAlertFrameThickness.Small, + alertFrameStatus: ObcAlertFrameStatus.Alarm, + showAlertCategoryIcon: true, + showAlertIcon: false, + }, + argTypes: { + badgeControl: { + options: Object.values(AutomationButtonBadgeControl), + control: {type: 'select'}, + }, + badgeInterlock: { + options: Object.values(AutomationButtonBadgeInterlock), + control: {type: 'select'}, + }, + badgeCommandLocked: { + options: Object.values(AutomationButtonBadgeCommandLocked), + control: {type: 'select'}, + }, + badgeAlert: { + options: Object.values(AutomationButtonBadgeAlert), + control: {type: 'select'}, + }, + alertFrameType: { + options: Object.values(ObcAlertFrameType), + control: {type: 'select'}, + }, + alertFrameThickness: { + options: Object.values(ObcAlertFrameThickness), + control: {type: 'select'}, + }, + alertFrameStatus: { + options: Object.values(ObcAlertFrameStatus), + control: {type: 'select'}, + }, + }, + render: (args) => html` + + `, +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const Default: Story = {}; + +export const WithMedium: Story = { + args: {medium: true}, +}; + +export const WithBadges: Story = { + args: { + medium: true, + badgeControl: AutomationButtonBadgeControl.Auto, + badgeInterlock: AutomationButtonBadgeInterlock.Interlock, + badgeCommandLocked: AutomationButtonBadgeCommandLocked.CommandLocked, + badgeAlert: AutomationButtonBadgeAlert.Alarm, + }, +}; + +export const WithAlert: Story = { + args: {medium: true, alert: true}, +}; diff --git a/packages/openbridge-webcomponents/src/automation/heat-pump/heat-pump.ts b/packages/openbridge-webcomponents/src/automation/heat-pump/heat-pump.ts new file mode 100644 index 000000000..d95d689ec --- /dev/null +++ b/packages/openbridge-webcomponents/src/automation/heat-pump/heat-pump.ts @@ -0,0 +1,62 @@ +import {TemplateResult, html} from 'lit'; +import '../../icons/icon-heatpump.js'; +import { + ObcAbstractSpecialtyTank, + SpecialtyTankSplitMode, +} from '../specialty-tank/abstract-specialty-tank.js'; +import {customElement} from '../../decorator.js'; + +/** + * ## Heat Pump + * + * Fixed-footprint instrument tile representing a heat pump. Shows a framed + * hot/cold medium area split vertically (left hot, right cold) with a + * centered divider bar, a centered heat-pump icon in a rounded frame, + * optional flame/snowflake corner glyphs, an optional badge row, and a tag + * readout below the frame. + * + * ### Features / Variants + * - `medium` toggles between the empty grey fill and the hot/cold colors. + * - `showMediumIcons` toggles the flame (top-left) and snowflake + * (bottom-right) corner glyphs. + * - `showTag` / `tag` control the identifier readout below the frame. + * - Enum-driven badges (`badgeControl`, `badgeInterlock`, + * `badgeCommandLocked`, `badgeAlert`) render in the top-right badge row; + * the `badges` slot overrides them. + * - Alert-frame overlay mirroring `obc-automation-tank` (`alert`, + * `alertFrameType`, `alertFrameThickness`, `alertFrameStatus`, + * `showAlertCategoryIcon`, `showAlertIcon`). + * + * ### Usage Guidelines + * Use for heat-pump devices in automation system views. For hydraulic + * separators use `obc-hydraulic-separator` (horizontal split) and for heat + * exchangers use `obc-heat-exchanger` (diagonal split); the three tiles + * differ only in center icon and fill geometry. + * + * @slot badges - Custom badges, overriding the enum-driven defaults. + * @slot tag - Text or element replacing the `tag` property readout. + * @slot alert-icon - Custom icon for the alert frame. + * @slot alert-label - Label for the alert frame. + * @slot alert-timer - Timer for the alert frame. + * + * @ignition-base-width: 90px + * @ignition-base-height: 163px + * @ignition-center-horizontal + * @beta + */ +@customElement('obc-heat-pump') +export class ObcHeatPump extends ObcAbstractSpecialtyTank { + override get equipmentIcon(): TemplateResult { + return html``; + } + + override get splitMode(): SpecialtyTankSplitMode { + return SpecialtyTankSplitMode.vertical; + } +} + +declare global { + interface HTMLElementTagNameMap { + 'obc-heat-pump': ObcHeatPump; + } +} diff --git a/packages/openbridge-webcomponents/src/automation/hydraulic-separator/hydraulic-separator.stories.ts b/packages/openbridge-webcomponents/src/automation/hydraulic-separator/hydraulic-separator.stories.ts new file mode 100644 index 000000000..fbe468ea8 --- /dev/null +++ b/packages/openbridge-webcomponents/src/automation/hydraulic-separator/hydraulic-separator.stories.ts @@ -0,0 +1,110 @@ +import type {Meta, StoryObj} from '@storybook/web-components-vite'; +import {ObcHydraulicSeparator} from './hydraulic-separator.js'; +import './hydraulic-separator.js'; +import {html} from 'lit'; +import { + ObcAlertFrameStatus, + ObcAlertFrameThickness, + ObcAlertFrameType, +} from '../../components/alert-frame/alert-frame.js'; +import { + AutomationButtonBadgeAlert, + AutomationButtonBadgeCommandLocked, + AutomationButtonBadgeControl, + AutomationButtonBadgeInterlock, +} from '../automation-button/abstract-automation-button.js'; + +type StoryArgs = ObcHydraulicSeparator; + +const meta: Meta = { + title: 'Automation/Tanks/Hydraulic Separator', + tags: ['autodocs', '6.1'], + component: 'obc-hydraulic-separator', + args: { + medium: false, + showMediumIcons: true, + showTag: true, + tag: '#0000', + badgeControl: AutomationButtonBadgeControl.None, + badgeInterlock: AutomationButtonBadgeInterlock.None, + badgeCommandLocked: AutomationButtonBadgeCommandLocked.None, + badgeAlert: AutomationButtonBadgeAlert.None, + alert: false, + alertFrameType: ObcAlertFrameType.SmallSideFlip, + alertFrameThickness: ObcAlertFrameThickness.Small, + alertFrameStatus: ObcAlertFrameStatus.Alarm, + showAlertCategoryIcon: true, + showAlertIcon: false, + }, + argTypes: { + badgeControl: { + options: Object.values(AutomationButtonBadgeControl), + control: {type: 'select'}, + }, + badgeInterlock: { + options: Object.values(AutomationButtonBadgeInterlock), + control: {type: 'select'}, + }, + badgeCommandLocked: { + options: Object.values(AutomationButtonBadgeCommandLocked), + control: {type: 'select'}, + }, + badgeAlert: { + options: Object.values(AutomationButtonBadgeAlert), + control: {type: 'select'}, + }, + alertFrameType: { + options: Object.values(ObcAlertFrameType), + control: {type: 'select'}, + }, + alertFrameThickness: { + options: Object.values(ObcAlertFrameThickness), + control: {type: 'select'}, + }, + alertFrameStatus: { + options: Object.values(ObcAlertFrameStatus), + control: {type: 'select'}, + }, + }, + render: (args) => html` + + `, +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const Default: Story = {}; + +export const WithMedium: Story = { + args: {medium: true}, +}; + +export const WithBadges: Story = { + args: { + medium: true, + badgeControl: AutomationButtonBadgeControl.Auto, + badgeInterlock: AutomationButtonBadgeInterlock.Interlock, + badgeCommandLocked: AutomationButtonBadgeCommandLocked.CommandLocked, + badgeAlert: AutomationButtonBadgeAlert.Alarm, + }, +}; + +export const WithAlert: Story = { + args: {medium: true, alert: true}, +}; diff --git a/packages/openbridge-webcomponents/src/automation/hydraulic-separator/hydraulic-separator.ts b/packages/openbridge-webcomponents/src/automation/hydraulic-separator/hydraulic-separator.ts new file mode 100644 index 000000000..f4383b177 --- /dev/null +++ b/packages/openbridge-webcomponents/src/automation/hydraulic-separator/hydraulic-separator.ts @@ -0,0 +1,64 @@ +import {TemplateResult, html} from 'lit'; +import '../../icons/icon-hydraulic-separator.js'; +import { + ObcAbstractSpecialtyTank, + SpecialtyTankSplitMode, +} from '../specialty-tank/abstract-specialty-tank.js'; +import {customElement} from '../../decorator.js'; + +/** + * ## Hydraulic Separator + * + * Fixed-footprint instrument tile representing a hydraulic separator. Shows + * a framed hot/cold medium area split horizontally (top hot, bottom cold) + * with a centered divider bar, a centered hydraulic-separator icon in a + * rounded frame, optional flame/snowflake corner glyphs, an optional badge + * row, and a tag readout below the frame. + * + * ### Features / Variants + * - `medium` toggles between the empty grey fill and the hot/cold colors. + * - `showMediumIcons` toggles the flame (top-left) and snowflake + * (bottom-right) corner glyphs. + * - `showTag` / `tag` control the identifier readout below the frame. + * - Enum-driven badges (`badgeControl`, `badgeInterlock`, + * `badgeCommandLocked`, `badgeAlert`) render in the top-right badge row; + * the `badges` slot overrides them. + * - Alert-frame overlay mirroring `obc-automation-tank` (`alert`, + * `alertFrameType`, `alertFrameThickness`, `alertFrameStatus`, + * `showAlertCategoryIcon`, `showAlertIcon`). + * + * ### Usage Guidelines + * Use for hydraulic separators in automation system views. For heat pumps + * use `obc-heat-pump` (vertical split) and for heat exchangers use + * `obc-heat-exchanger` (diagonal split); the three tiles differ only in + * center icon and fill geometry. + * + * @slot badges - Custom badges, overriding the enum-driven defaults. + * @slot tag - Text or element replacing the `tag` property readout. + * @slot alert-icon - Custom icon for the alert frame. + * @slot alert-label - Label for the alert frame. + * @slot alert-timer - Timer for the alert frame. + * + * @ignition-base-width: 90px + * @ignition-base-height: 163px + * @ignition-center-horizontal + * @beta + */ +@customElement('obc-hydraulic-separator') +export class ObcHydraulicSeparator extends ObcAbstractSpecialtyTank { + override get equipmentIcon(): TemplateResult { + return html``; + } + + override get splitMode(): SpecialtyTankSplitMode { + return SpecialtyTankSplitMode.horizontal; + } +} + +declare global { + interface HTMLElementTagNameMap { + 'obc-hydraulic-separator': ObcHydraulicSeparator; + } +} diff --git a/packages/openbridge-webcomponents/src/automation/specialty-tank/abstract-specialty-tank.ts b/packages/openbridge-webcomponents/src/automation/specialty-tank/abstract-specialty-tank.ts new file mode 100644 index 000000000..4aca2511d --- /dev/null +++ b/packages/openbridge-webcomponents/src/automation/specialty-tank/abstract-specialty-tank.ts @@ -0,0 +1,314 @@ +import {LitElement, TemplateResult, html, nothing, unsafeCSS} from 'lit'; +import {property, state} from 'lit/decorators.js'; +import {classMap} from 'lit/directives/class-map.js'; +import componentStyle from './specialty-tank.css?inline'; +import '../automation-badge/automation-badge.js'; +import {ObcAutomationBadgeType} from '../automation-badge/automation-badge.js'; +import { + AutomationButtonBadgeAlert, + AutomationButtonBadgeCommandLocked, + AutomationButtonBadgeControl, + AutomationButtonBadgeInterlock, +} from '../automation-button/abstract-automation-button.js'; +import '../../components/alert-frame/alert-frame.js'; +import { + ObcAlertFrameThickness, + ObcAlertFrameType, +} from '../../components/alert-frame/alert-frame.js'; +import {AlertType} from '../../types.js'; +import '../../icons/icon-heat-google.js'; +import '../../icons/icon-cold-google.js'; + +/** + * Hot/cold fill geometry of a specialty tank. Drives both the medium fill + * split and the divider through the `split-` CSS class on the tank + * container. + */ +export enum SpecialtyTankSplitMode { + /** Left half hot, right half cold, vertical divider bar. */ + vertical = 'vertical', + /** Top half hot, bottom half cold, horizontal divider bar. */ + horizontal = 'horizontal', + /** Two corner-to-corner triangles (top-left hot, bottom-right cold), no divider. */ + diagonal = 'diagonal', +} + +/** + * Abstract base for the specialty-tank tiles (`obc-heat-pump`, + * `obc-hydraulic-separator`, `obc-heat-exchanger`). Owns the shared frame, + * badge row, hot/cold medium fill, corner glyphs, centered equipment-icon + * frame, tag readout, and alert-frame overlay. Subclasses override two + * getters: `equipmentIcon` (the centered `obi-*` icon) and `splitMode` + * (the hot/cold fill geometry). + * + * Not a custom element — subclasses register themselves with + * `@customElement`. + * + * @ignore + */ +export class ObcAbstractSpecialtyTank extends LitElement { + /** Show the hot/cold medium colors instead of the empty grey fill. */ + @property({type: Boolean}) medium: boolean = false; + /** Show the flame (top-left) and snowflake (bottom-right) corner glyphs. */ + @property({type: Boolean, attribute: false}) showMediumIcons: boolean = true; + /** Show the tag readout below the tank frame. */ + @property({type: Boolean, attribute: false}) showTag: boolean = true; + @property({type: String}) tag: string = ''; + + @property({type: String}) badgeControl: AutomationButtonBadgeControl = + AutomationButtonBadgeControl.None; + /** Duty badge — reuses the interlock badge enum, whose `interlock` value renders the duty icon. */ + @property({type: String}) badgeInterlock: AutomationButtonBadgeInterlock = + AutomationButtonBadgeInterlock.None; + @property({type: String}) + badgeCommandLocked: AutomationButtonBadgeCommandLocked = + AutomationButtonBadgeCommandLocked.None; + @property({type: String}) badgeAlert: AutomationButtonBadgeAlert = + AutomationButtonBadgeAlert.None; + + @property({type: Boolean}) alert: boolean = false; + /** @availableWhen alert==true */ + @property({type: String}) alertFrameType: ObcAlertFrameType = + ObcAlertFrameType.SmallSideFlip; + /** @availableWhen alert==true */ + @property({type: String}) alertFrameThickness: ObcAlertFrameThickness = + ObcAlertFrameThickness.Small; + /** @availableWhen alert==true */ + @property({type: String}) alertFrameStatus: AlertType = AlertType.Alarm; + /** @availableWhen alert==true */ + @property({type: Boolean, attribute: false}) showAlertCategoryIcon: boolean = + true; + /** @availableWhen alert==true */ + @property({type: Boolean}) showAlertIcon: boolean = false; + + @state() private _hasBadges = false; + @state() private _hasTagSlot = false; + + get equipmentIcon(): TemplateResult { + throw new Error('Method "equipmentIcon" must be implemented in subclass'); + } + + get splitMode(): SpecialtyTankSplitMode { + throw new Error('Method "splitMode" must be implemented in subclass'); + } + + private _badgeControlType(): ObcAutomationBadgeType | null { + switch (this.badgeControl) { + case AutomationButtonBadgeControl.Local: + return ObcAutomationBadgeType.Local; + case AutomationButtonBadgeControl.LocalOnly: + return ObcAutomationBadgeType.LocalOnly; + case AutomationButtonBadgeControl.Manual: + return ObcAutomationBadgeType.Manual; + case AutomationButtonBadgeControl.ManualOnly: + return ObcAutomationBadgeType.ManualOnly; + case AutomationButtonBadgeControl.Auto: + return ObcAutomationBadgeType.Auto; + default: + return null; + } + } + + private _badgeInterlockType(): ObcAutomationBadgeType | null { + switch (this.badgeInterlock) { + case AutomationButtonBadgeInterlock.Interlock: + return ObcAutomationBadgeType.Interlock; + case AutomationButtonBadgeInterlock.InterlockInhibit: + return ObcAutomationBadgeType.InterlockInhibit; + default: + return null; + } + } + + private _badgeCommandLockedType(): ObcAutomationBadgeType | null { + if ( + this.badgeCommandLocked === + AutomationButtonBadgeCommandLocked.CommandLocked + ) { + return ObcAutomationBadgeType.CommandLocked; + } + return null; + } + + private _badgeAlertType(): ObcAutomationBadgeType | null { + switch (this.badgeAlert) { + case AutomationButtonBadgeAlert.Silence: + return ObcAutomationBadgeType.AlertSilenced; + case AutomationButtonBadgeAlert.Caution: + return ObcAutomationBadgeType.Caution; + case AutomationButtonBadgeAlert.Warning: + return ObcAutomationBadgeType.Warning; + case AutomationButtonBadgeAlert.Alarm: + return ObcAutomationBadgeType.Alarm; + case AutomationButtonBadgeAlert.LevelCritical: + return ObcAutomationBadgeType.LevelCritical; + case AutomationButtonBadgeAlert.LevelHigh: + return ObcAutomationBadgeType.LevelHigh; + case AutomationButtonBadgeAlert.LevelMedium: + return ObcAutomationBadgeType.LevelMedium; + case AutomationButtonBadgeAlert.LevelLow: + return ObcAutomationBadgeType.LevelLow; + case AutomationButtonBadgeAlert.LevelDiagnostic: + return ObcAutomationBadgeType.LevelDiagnostic; + default: + return null; + } + } + + private _onBadgesSlotChange(e: Event): void { + const slot = e.target as HTMLSlotElement; + this._hasBadges = slot + .assignedNodes({flatten: true}) + .some( + (n) => + n.nodeType === Node.ELEMENT_NODE || + (n.nodeType === Node.TEXT_NODE && !!n.textContent?.trim()) + ); + } + + private _onTagSlotChange(e: Event): void { + const slot = e.target as HTMLSlotElement; + this._hasTagSlot = slot + .assignedNodes({flatten: true}) + .some( + (n) => + n.nodeType === Node.ELEMENT_NODE || + (n.nodeType === Node.TEXT_NODE && !!n.textContent?.trim()) + ); + } + + override render() { + const controlBadge = this._badgeControlType(); + const dutyBadge = this._badgeInterlockType(); + const commandLockedBadge = this._badgeCommandLockedType(); + const alertBadge = this._badgeAlertType(); + const hasEnumBadges = + controlBadge !== null || + dutyBadge !== null || + commandLockedBadge !== null || + alertBadge !== null; + + const badgesHidden = !this._hasBadges && !hasEnumBadges; + const tagHidden = !this.showTag || (!this._hasTagSlot && !this.tag); + + const badgesCell = html` +
+ + ${controlBadge + ? html`` + : nothing} + ${dutyBadge + ? html`` + : nothing} + ${commandLockedBadge + ? html`` + : nothing} + ${alertBadge + ? html`` + : nothing} + +
+ `; + + const mediumFill = + this.splitMode === SpecialtyTankSplitMode.diagonal + ? html`
+ ` + : html`
+
+
`; + + const mediumIcons = this.showMediumIcons + ? html` + ` + : nothing; + + const tankContainerClasses = classMap({ + 'tank-container': true, + [`split-${this.splitMode}`]: true, + 'has-medium': this.medium, + }); + const tankContainer = html` +
+
+ ${mediumFill}${mediumIcons} +
${this.equipmentIcon}
+
+
+ `; + + const tagCell = html` +
+ ${this.tag} +
+ `; + + const alertFrameOverlay = this.alert + ? html` + + + + ` + : nothing; + + return html` + + `; + } + + static override styles = unsafeCSS(componentStyle); +} diff --git a/packages/openbridge-webcomponents/src/automation/specialty-tank/specialty-tank.css b/packages/openbridge-webcomponents/src/automation/specialty-tank/specialty-tank.css new file mode 100644 index 000000000..9b1dbca5e --- /dev/null +++ b/packages/openbridge-webcomponents/src/automation/specialty-tank/specialty-tank.css @@ -0,0 +1,251 @@ +* { + box-sizing: border-box; +} + +/* Fixed 90×163 tile mirroring the Figma structure: a 4px-padded state + * container (halo) holding a column of [badge row + tank frame] and the + * tag readout, separated by a 2px value-padding gap. The badge row + * collapses via the `hidden` attribute so the tank frame absorbs the + * freed space, matching the Figma variants where badges are hidden by + * default. */ +:host { + display: inline-block; + width: 90px; + height: 163px; +} + +/* The root