diff --git a/.gitignore b/.gitignore index f6f096261..efab9d1f0 100644 --- a/.gitignore +++ b/.gitignore @@ -27,6 +27,7 @@ packages/openbridge-webcomponents-svelte/* **/.junie/ **/settings.local.json packages/openbridge-webcomponents/__vis__/darwin/ +packages/openbridge-webcomponents/__vis__/win32/ .idea/ **/.idea/ CLAUDE.md diff --git a/packages/openbridge-webcomponents/__vis__/linux/__baselines__/automation/heat-exchanger/heat-exchanger.stories.ts/default-auto.png b/packages/openbridge-webcomponents/__vis__/linux/__baselines__/automation/heat-exchanger/heat-exchanger.stories.ts/default-auto.png new file mode 100644 index 000000000..62500863c Binary files /dev/null and b/packages/openbridge-webcomponents/__vis__/linux/__baselines__/automation/heat-exchanger/heat-exchanger.stories.ts/default-auto.png differ diff --git a/packages/openbridge-webcomponents/__vis__/linux/__baselines__/automation/heat-exchanger/heat-exchanger.stories.ts/with-alert-auto.png b/packages/openbridge-webcomponents/__vis__/linux/__baselines__/automation/heat-exchanger/heat-exchanger.stories.ts/with-alert-auto.png new file mode 100644 index 000000000..520f7d858 Binary files /dev/null and b/packages/openbridge-webcomponents/__vis__/linux/__baselines__/automation/heat-exchanger/heat-exchanger.stories.ts/with-alert-auto.png differ diff --git a/packages/openbridge-webcomponents/__vis__/linux/__baselines__/automation/heat-exchanger/heat-exchanger.stories.ts/with-badges-auto.png b/packages/openbridge-webcomponents/__vis__/linux/__baselines__/automation/heat-exchanger/heat-exchanger.stories.ts/with-badges-auto.png new file mode 100644 index 000000000..0b854973a Binary files /dev/null and b/packages/openbridge-webcomponents/__vis__/linux/__baselines__/automation/heat-exchanger/heat-exchanger.stories.ts/with-badges-auto.png differ diff --git a/packages/openbridge-webcomponents/__vis__/linux/__baselines__/automation/heat-exchanger/heat-exchanger.stories.ts/with-medium-auto.png b/packages/openbridge-webcomponents/__vis__/linux/__baselines__/automation/heat-exchanger/heat-exchanger.stories.ts/with-medium-auto.png new file mode 100644 index 000000000..2c762d745 Binary files /dev/null and b/packages/openbridge-webcomponents/__vis__/linux/__baselines__/automation/heat-exchanger/heat-exchanger.stories.ts/with-medium-auto.png differ diff --git a/packages/openbridge-webcomponents/__vis__/linux/__baselines__/automation/heat-pump/heat-pump.stories.ts/default-auto.png b/packages/openbridge-webcomponents/__vis__/linux/__baselines__/automation/heat-pump/heat-pump.stories.ts/default-auto.png new file mode 100644 index 000000000..16232de17 Binary files /dev/null and b/packages/openbridge-webcomponents/__vis__/linux/__baselines__/automation/heat-pump/heat-pump.stories.ts/default-auto.png differ diff --git a/packages/openbridge-webcomponents/__vis__/linux/__baselines__/automation/heat-pump/heat-pump.stories.ts/with-alert-auto.png b/packages/openbridge-webcomponents/__vis__/linux/__baselines__/automation/heat-pump/heat-pump.stories.ts/with-alert-auto.png new file mode 100644 index 000000000..6a30cab9e Binary files /dev/null and b/packages/openbridge-webcomponents/__vis__/linux/__baselines__/automation/heat-pump/heat-pump.stories.ts/with-alert-auto.png differ diff --git a/packages/openbridge-webcomponents/__vis__/linux/__baselines__/automation/heat-pump/heat-pump.stories.ts/with-badges-auto.png b/packages/openbridge-webcomponents/__vis__/linux/__baselines__/automation/heat-pump/heat-pump.stories.ts/with-badges-auto.png new file mode 100644 index 000000000..57d663d00 Binary files /dev/null and b/packages/openbridge-webcomponents/__vis__/linux/__baselines__/automation/heat-pump/heat-pump.stories.ts/with-badges-auto.png differ diff --git a/packages/openbridge-webcomponents/__vis__/linux/__baselines__/automation/heat-pump/heat-pump.stories.ts/with-medium-auto.png b/packages/openbridge-webcomponents/__vis__/linux/__baselines__/automation/heat-pump/heat-pump.stories.ts/with-medium-auto.png new file mode 100644 index 000000000..d3f4b82ae Binary files /dev/null and b/packages/openbridge-webcomponents/__vis__/linux/__baselines__/automation/heat-pump/heat-pump.stories.ts/with-medium-auto.png differ diff --git a/packages/openbridge-webcomponents/__vis__/linux/__baselines__/automation/hydraulic-separator/hydraulic-separator.stories.ts/default-auto.png b/packages/openbridge-webcomponents/__vis__/linux/__baselines__/automation/hydraulic-separator/hydraulic-separator.stories.ts/default-auto.png new file mode 100644 index 000000000..83ab8535d Binary files /dev/null and b/packages/openbridge-webcomponents/__vis__/linux/__baselines__/automation/hydraulic-separator/hydraulic-separator.stories.ts/default-auto.png differ diff --git a/packages/openbridge-webcomponents/__vis__/linux/__baselines__/automation/hydraulic-separator/hydraulic-separator.stories.ts/with-alert-auto.png b/packages/openbridge-webcomponents/__vis__/linux/__baselines__/automation/hydraulic-separator/hydraulic-separator.stories.ts/with-alert-auto.png new file mode 100644 index 000000000..94803e00f Binary files /dev/null and b/packages/openbridge-webcomponents/__vis__/linux/__baselines__/automation/hydraulic-separator/hydraulic-separator.stories.ts/with-alert-auto.png differ diff --git a/packages/openbridge-webcomponents/__vis__/linux/__baselines__/automation/hydraulic-separator/hydraulic-separator.stories.ts/with-badges-auto.png b/packages/openbridge-webcomponents/__vis__/linux/__baselines__/automation/hydraulic-separator/hydraulic-separator.stories.ts/with-badges-auto.png new file mode 100644 index 000000000..5b0c74355 Binary files /dev/null and b/packages/openbridge-webcomponents/__vis__/linux/__baselines__/automation/hydraulic-separator/hydraulic-separator.stories.ts/with-badges-auto.png differ diff --git a/packages/openbridge-webcomponents/__vis__/linux/__baselines__/automation/hydraulic-separator/hydraulic-separator.stories.ts/with-medium-auto.png b/packages/openbridge-webcomponents/__vis__/linux/__baselines__/automation/hydraulic-separator/hydraulic-separator.stories.ts/with-medium-auto.png new file mode 100644 index 000000000..f73dcbde7 Binary files /dev/null and b/packages/openbridge-webcomponents/__vis__/linux/__baselines__/automation/hydraulic-separator/hydraulic-separator.stories.ts/with-medium-auto.png differ 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..2ba889697 --- /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.Silence, + }, +}; + +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..0d038ba3e --- /dev/null +++ b/packages/openbridge-webcomponents/src/automation/heat-exchanger/heat-exchanger.ts @@ -0,0 +1,66 @@ +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. + * + * @ignition-base-width: 90px + * @ignition-base-height: 163px + * @ignition-center-horizontal + * @beta + * + * @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. + */ +@customElement('obc-heat-exchanger') +export class ObcHeatExchanger extends ObcAbstractSpecialtyTank { + override get equipmentIcon(): TemplateResult { + return html``; + } + + override get splitMode(): SpecialtyTankSplitMode { + return SpecialtyTankSplitMode.diagonal; + } + + protected override get equipmentName(): string { + return 'Heat exchanger'; + } +} + +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..cd3dbf7c3 --- /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.Silence, + }, +}; + +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..133c7e60b --- /dev/null +++ b/packages/openbridge-webcomponents/src/automation/heat-pump/heat-pump.ts @@ -0,0 +1,66 @@ +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. + * + * @ignition-base-width: 90px + * @ignition-base-height: 163px + * @ignition-center-horizontal + * @beta + * + * @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. + */ +@customElement('obc-heat-pump') +export class ObcHeatPump extends ObcAbstractSpecialtyTank { + override get equipmentIcon(): TemplateResult { + return html``; + } + + override get splitMode(): SpecialtyTankSplitMode { + return SpecialtyTankSplitMode.vertical; + } + + protected override get equipmentName(): string { + return 'Heat pump'; + } +} + +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..1b1d90d44 --- /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.Silence, + }, +}; + +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..4cecdc69e --- /dev/null +++ b/packages/openbridge-webcomponents/src/automation/hydraulic-separator/hydraulic-separator.ts @@ -0,0 +1,68 @@ +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. + * + * @ignition-base-width: 90px + * @ignition-base-height: 163px + * @ignition-center-horizontal + * @beta + * + * @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. + */ +@customElement('obc-hydraulic-separator') +export class ObcHydraulicSeparator extends ObcAbstractSpecialtyTank { + override get equipmentIcon(): TemplateResult { + return html``; + } + + override get splitMode(): SpecialtyTankSplitMode { + return SpecialtyTankSplitMode.horizontal; + } + + protected override get equipmentName(): string { + return 'Hydraulic separator'; + } +} + +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..ddbb46ca1 --- /dev/null +++ b/packages/openbridge-webcomponents/src/automation/specialty-tank/abstract-specialty-tank.ts @@ -0,0 +1,348 @@ +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 Specialty Tank + * + * 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 provide three + * members: `equipmentIcon` (the centered `obi-*` icon), `splitMode` (the + * hot/cold fill geometry), and `equipmentName` (the accessible-name + * fallback used when `tag` is empty). + * + * ### 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. + * - Alert-frame overlay mirroring `obc-automation-tank`. + * + * ### Usage Guidelines + * Not a custom element — extend it and register the subclass with + * `@customElement`. The subclass carries the user-facing JSDoc consumed by + * Storybook autodocs. The override points throw at runtime instead of being + * declared `abstract` because the framework wrapper generators instantiate + * every manifest class through a concrete `Constructor` type — an + * abstract class breaks the generated React wrapper build (same reason + * `ObcAbstractAutomationButton` is concrete). + * + * @slot badges - Custom badges rendered in the top-right badge row, + * overriding the enum-driven defaults. The row collapses when both the + * slot and the badge enums are empty. + * @slot tag - Text or element replacing the `tag` property readout below + * the frame. Hidden when `showTag` is `false`. + * @slot alert-icon - Custom icon for the alert frame (alert only). + * @slot alert-label - Label for the alert frame (alert only). + * @slot alert-timer - Timer for the alert frame (alert only). + * + * @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; + /** Identifier text rendered in the tag readout (e.g. `#0000`). Also used as the accessible name. */ + @property({type: String}) tag: string = ''; + + /** Mode badge (auto / manual / local variants), leftmost in the badge row. */ + @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; + /** Command badge, third in the badge row. */ + @property({type: String}) + badgeCommandLocked: AutomationButtonBadgeCommandLocked = + AutomationButtonBadgeCommandLocked.None; + /** Alert badge, rightmost in the badge row. */ + @property({type: String}) badgeAlert: AutomationButtonBadgeAlert = + AutomationButtonBadgeAlert.None; + + /** Show an alert-frame overlay around the tile, mirroring `obc-automation-tank`. */ + @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'); + } + + /** Accessible-name fallback used when `tag` is empty. */ + protected get equipmentName(): string { + throw new Error('Method "equipmentName" 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 _slotHasContent(e: Event): boolean { + const slot = e.target as HTMLSlotElement; + return slot + .assignedNodes({flatten: true}) + .some( + (n) => + n.nodeType === Node.ELEMENT_NODE || + (n.nodeType === Node.TEXT_NODE && !!n.textContent?.trim()) + ); + } + + private _onBadgesSlotChange(e: Event): void { + this._hasBadges = this._slotHasContent(e); + } + + private _onTagSlotChange(e: Event): void { + this._hasTagSlot = this._slotHasContent(e); + } + + 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