{"version":3,"file":"ReviewTimer.js","sources":["../src/modules/review-timer/jsonrpc/base.ts","../src/modules/review-timer/jsonrpc/review-timer.ts","../src/modules/review-timer/ep-websocket-provider.ts","../src/modules/review-timer/ep-review-timer.ts"],"sourcesContent":["import type { Client } from '@open-rpc/client-js'\r\nimport { WebSocketTransport } from '@open-rpc/client-js'\r\n\r\nexport interface ConnectedUserDTO {\r\n UserID: number;\r\n UserName: string;\r\n ConnectionID: string;\r\n DepartmentID: number;\r\n RoleID: number;\r\n}\r\n\r\nexport interface ChatRoomDTO {\r\n RoomID: string;\r\n RoomName: string;\r\n Members: ConnectedUserDTO[];\r\n}\r\n\r\nexport interface IEPermitsEntity {\r\n DateCreated: string | Date;\r\n CreatedByID: number;\r\n LastUpdateDate: string | Date;\r\n LastUpdateID: number;\r\n}\r\n\r\nexport const ParseDate = (date: string | Date) => typeof date === 'string' ? new Date(date) : date;\r\n\r\nexport function HasOpenConnection(client: Client): boolean {\r\n const transport = client.requestManager.getPrimaryTransport();\r\n return transport instanceof WebSocketTransport && transport.connection.readyState === WebSocket.OPEN;\r\n}\r\n\r\nexport async function Ping(client: Client): Promise {\r\n return await client.request({ method: 'Ping' })\r\n}\r\n\r\nexport async function GetAllRooms(client: Client): Promise {\r\n return await client.request({ method: 'GetAllRooms' })\r\n}\r\n\r\nexport async function GetAllUsers(client: Client): Promise {\r\n return await client.request({ method: 'GetAllUsers' })\r\n}\r\n\r\nexport async function WhoAmI(client: Client): Promise {\r\n return await client.request({ method: 'WhoAmI' })\r\n}\r\n","import type { Client } from '@open-rpc/client-js'\r\nimport { IEPermitsEntity } from './base';\r\n\r\nexport interface ReviewTimerDTO extends IEPermitsEntity {\r\n ReviewTimerID: number;\r\n ResubmitNumber?: number;\r\n PermitApplMasterID?: number;\r\n InspectionID?: number;\r\n ComplaintID?: number;\r\n ReviewerID: number;\r\n RelRoleDepartmentID: number;\r\n StartTime: string | Date;\r\n EndTime?: string | Date;\r\n ProjectNumber?: string;\r\n InspectionPermitNumber?: string;\r\n InspectionPermitCode?: string;\r\n ComplaintNumber?: string;\r\n DepartmentName?: string;\r\n RoleName?: string;\r\n}\r\n\r\nexport async function StartReviewTimer(client: Client,\r\n PermitApplMasterID: number | undefined = undefined,\r\n InspectionID: number | undefined = undefined,\r\n ComplaintID: number | undefined = undefined\r\n ): Promise {\r\n return await client.request({ method: 'StartReviewTimer', params: {\r\n PermitApplMasterID,\r\n InspectionID,\r\n ComplaintID,\r\n } })\r\n}\r\n\r\nexport async function StopReviewTimers(client: Client): Promise {\r\n return await client.request({ method: 'StopReviewTimers' })\r\n}\r\n\r\nexport async function GetOpenReviewTimers(client: Client): Promise {\r\n return await client.request({ method: 'GetOpenReviewTimers' })\r\n}\r\n","import { createContext, provide } from '@lit/context'\r\nimport { html, css } from 'lit'\r\nimport type { TemplateResult } from 'lit'\r\nimport { customElement, property } from 'lit/decorators.js'\r\nimport { useEffect } from 'haunted'\r\nimport { Client, RequestManager, WebSocketTransport } from '@open-rpc/client-js'\r\nimport { LitHauntedElement } from '../../mixins/lit-haunted-element';\r\nimport { IntervalController } from '../../controllers/interval-controller'\r\nimport { HasOpenConnection, Ping } from './jsonrpc'\r\n\r\nexport const jsonRpcClientContext = createContext(Symbol('ep-websocket-provider_client'))\r\nexport const websocketProviderContext = createContext | undefined>(Symbol('ep-websocket-provider_providerConnectionIssue'))\r\n\r\n@customElement('ep-websocket-provider')\r\nexport class EPWebSocketProvider extends LitHauntedElement {\r\n /**\r\n * This interval attempts to reconnect to the WebSocket every so often.\r\n * This is intended to prevent the connection from being closed by the load balancer.\r\n * Whether or not this interval is used or not depends on the `repeatHandshake` property.\r\n */\r\n private handshakeInterval = new IntervalController(this, () => this._handleInterval(), 90 * 1000); // 90 seconds\r\n /**\r\n * This interval attempts to keep the WebSocket connection \"chatty\" with a \"Ping\" message every so often.\r\n * This is intended to prevent the connection from being closed by the load balancer.\r\n * A ping message won't be sent unless the connection is detected to be \"open\" and the previous ping was successful.\r\n * The \"ping\" lifecycle is completely separate from the lifecycle of the connection itself.\r\n */\r\n private pingInterval = new IntervalController(this, () => this._handlePing(), 60 * 1000); // 60 seconds\r\n\r\n // Something sorta unique to identify instances\r\n private instance = Math.floor(Math.random() * 1000);\r\n\r\n @property({ attribute: 'service-url', reflect: true })\r\n serviceUrl: string = '';\r\n\r\n @property({ type: Boolean, attribute: 'repeat-handshake', reflect: true })\r\n repeatHandshake: boolean = false;\r\n\r\n @property({ attribute: false })\r\n lastPingFinished: boolean = true;\r\n\r\n @provide({ context: jsonRpcClientContext })\r\n @property({ attribute: false })\r\n client: Client | undefined = undefined;\r\n\r\n @provide({ context: websocketProviderContext })\r\n @property({ attribute: false })\r\n providerConnectionIssue: TemplateResult<1> | undefined = undefined;\r\n\r\n @property({ attribute: false })\r\n isAttemptingConnection: boolean = false;\r\n\r\n @property({ attribute: false })\r\n retryCount: number = 0;\r\n\r\n @property({ attribute: false })\r\n retryLimit: number = 5;\r\n\r\n @property({ attribute: false })\r\n retryDelayCountdown: number = 0;\r\n\r\n @property({ attribute: false })\r\n reconnectTimeoutID?: number;\r\n\r\n /**\r\n * The delay in seconds\r\n */\r\n get retryDelaySeconds(): number {\r\n const constant = 1;\r\n const base = 3;\r\n const power = this.retryCount;\r\n /**\r\n * 0 => 1 seconds\r\n * 1 => 3 seconds\r\n * 2 => 9 seconds\r\n * 3 => 27 seconds\r\n * 4 => 81 seconds\r\n * 5 => 243 seconds\r\n */\r\n const delay = constant * Math.pow(base, power);\r\n return delay;\r\n }\r\n\r\n // #region Render + Styles\r\n render() {\r\n // if the \"service url\" is changed, update our connection\r\n useEffect(() => this.reconnect(), [this.serviceUrl]);\r\n // Use \"repeat-handshake\" to control the interval\r\n useEffect(() => this.repeatHandshake ? this.handshakeInterval.startInterval() : this.handshakeInterval.stopInterval(), [this.repeatHandshake]);\r\n // Show a countdown until we refresh\r\n // useEffect(() => {\r\n // // Update message as we countdown\r\n // if (this.retryDelayCountdown > 0) {\r\n // this.providerConnectionIssue = html`Your connection to the server was dropped. Retrying in ${this.retryDelayCountdown} seconds...`;\r\n // }\r\n // // Decrement counter\r\n // setTimeout(() => {\r\n // this.retryDelayCountdown = Math.max(0, this.retryDelayCountdown - 1);\r\n // }, 1000)\r\n // }, [this.retryDelayCountdown])\r\n\r\n return html`\r\n \r\n `\r\n }\r\n\r\n override connectedCallback() {\r\n super.connectedCallback();\r\n console.debug(`[${this.tagName} #${this.instance}] Added to DOM at ${new Date().toLocaleTimeString()}.`);\r\n if (this.repeatHandshake) {\r\n this.handshakeInterval.startInterval();\r\n }\r\n this.pingInterval.startInterval();\r\n this.isAttemptingConnection = false;\r\n }\r\n\r\n override disconnectedCallback() {\r\n super.disconnectedCallback();\r\n console.debug(`[${this.tagName} #${this.instance}] Removed from DOM.`);\r\n this.handshakeInterval.stopInterval();\r\n this.pingInterval.stopInterval();\r\n this.isAttemptingConnection = true;\r\n this.client?.close();\r\n this.client = undefined;\r\n clearTimeout(this.reconnectTimeoutID);\r\n this.reconnectTimeoutID = undefined;\r\n }\r\n\r\n private reconnect() {\r\n //console.log('reconnect()')\r\n // Do nothing\r\n if (this.isAttemptingConnection) return;\r\n if (!window.navigator.onLine) return;\r\n if (this.serviceUrl === undefined || this.serviceUrl === '') return;\r\n // Verify service URL is a valid URL\r\n let service: URL | undefined = undefined;\r\n try {\r\n service = new URL(`${this.serviceUrl}`, document.location.toString());\r\n if (service.protocol === 'http:') service.protocol = 'ws:';\r\n if (service.protocol === 'https:') service.protocol = 'wss:';\r\n if (!service.protocol.startsWith('ws')) return;\r\n } catch {\r\n return;\r\n }\r\n //console.log('reconnect() - passed checks')\r\n\r\n // Lock out further attempts\r\n this.isAttemptingConnection = true;\r\n\r\n // Use this to guide whether or not we delay our re-connect attempt\r\n let previousConnectionFailed: boolean = false;\r\n\r\n // If a previous client exists\r\n if (this.client !== undefined) {\r\n // If the previous client does NOT have an open connection, perhaps it failed\r\n if (!HasOpenConnection(this.client)) {\r\n if (this.retryCount >= this.retryLimit) {\r\n this.providerConnectionIssue = html`Failed to repair connection ${this.retryLimit} times. Please try again later.`;\r\n // Prevent future attempts\r\n this.isAttemptingConnection = true;\r\n // Don't continue to connect\r\n return;\r\n }\r\n this.retryCount += 1;\r\n previousConnectionFailed = true;\r\n console.debug(`[${this.tagName} #${this.instance}] Previous connection failed, retry count: `, this.retryCount);\r\n // Get a `Date` object for when the retry should happen\r\n //const now = new Date();\r\n //console.info(now);\r\n const schedule = new Date();\r\n schedule.setSeconds(schedule.getSeconds() + this.retryDelaySeconds);\r\n // Update the label\r\n this.providerConnectionIssue = html`\r\n Your connection to the server was dropped.\r\n Retrying ...\r\n `;\r\n //this.retryDelayCountdown = this.retryDelaySeconds;\r\n }\r\n\r\n // Attempt to close the connection either way\r\n //console.log('close() called')\r\n this.client.close();\r\n }\r\n\r\n const delay = previousConnectionFailed ? this.retryDelaySeconds * 1000 : 0;\r\n console.debug(`[${this.tagName} #${this.instance}] ⏱ Waiting ${delay}ms until reconnecting...`);\r\n this.reconnectTimeoutID = window.setTimeout(() => {\r\n // Do stuff\r\n console.debug(`[${this.tagName} #${this.instance}] Trying connection...`)\r\n this.providerConnectionIssue = html`Connecting...`;\r\n // Establish the connection\r\n const transport = new WebSocketTransport(service!.href);\r\n const requestManager = new RequestManager([transport]);\r\n this.client = new Client(requestManager);\r\n // Once the client has completely connected, then allow future attempts\r\n Promise.race([ this.client.requestManager.connectPromise, new Promise((_, reject) => setTimeout(() => reject(\"Timeout exceeded\"), 5 * 1000))])\r\n .then(() => {\r\n this.isAttemptingConnection = false;\r\n this.providerConnectionIssue = undefined;\r\n const oldRetryCount = this.retryCount;\r\n // If the previous connection did NOT fail, that means that this was a routine handshake.\r\n // In that case, maybe the server is healthy again? Subtract our `retryCount` by 1 (not below 0).\r\n if (!previousConnectionFailed) {\r\n this.retryCount = Math.max(0, this.retryCount - 1);\r\n }\r\n const retryCountDidChange = oldRetryCount !== this.retryCount;\r\n console.debug(`[${this.tagName} #${this.instance}] Connected! ${retryCountDidChange ? `(retryCount reduced to ${this.retryCount})` : ``}`)\r\n })\r\n .catch((err) => {\r\n this.client?.close();\r\n this.isAttemptingConnection = false;\r\n this.providerConnectionIssue = undefined;\r\n console.warn(`[${this.tagName} #${this.instance}] Reconnect failed: `, err)\r\n });\r\n\r\n // Watch the connection for failures\r\n // To prevent manual calls to `.close()` from tripping this, we use the `isAttemptingConnection` lock.\r\n transport.connection.addEventListener('close', () => this.reconnect());\r\n\r\n // Unlock future attempts\r\n //this.isAttemptingConnection = false;\r\n }, delay);\r\n }\r\n\r\n private _handleInterval() {\r\n if (!window.navigator.onLine) {\r\n console.debug(`[${this.tagName} #${this.instance}] ⏭ Handshake skipped. Browser claims to not be connected to a network!`);\r\n return;\r\n }\r\n else if (!this.isAttemptingConnection) {\r\n console.debug(`[${this.tagName} #${this.instance}] 🤝 ${this.handshakeInterval.timeout / 1000} seconds have passed, time for a routine handshake.`);\r\n }\r\n this.reconnect();\r\n }\r\n\r\n private async _handlePing() {\r\n // If we're not connected to a network, don't try\r\n if (!window.navigator.onLine) return;\r\n // If we don't have a stable connection to the server yet, don't try\r\n if (this.isAttemptingConnection) return;\r\n // If we don't have an actual open connection to the server, don't try\r\n if (this.client === undefined || !HasOpenConnection(this.client)) return;\r\n console.debug(`[${this.tagName} #${this.instance}] 🏓 Connection deemed stable. Attempting a ping to try to keep connection open.`);\r\n try {\r\n // Prevent 2 pings from happening at the same time, if the server is slow or broken\r\n if (this.lastPingFinished) {\r\n this.lastPingFinished = false;\r\n await Ping(this.client);\r\n }\r\n }\r\n catch (err) {\r\n console.error(`[${this.tagName} #${this.instance}] Ping failed!`);\r\n }\r\n finally {\r\n this.lastPingFinished = true;\r\n }\r\n }\r\n\r\n static styles = css`\r\n :host {\r\n display: contents;\r\n }\r\n `\r\n // #endregion\r\n}\r\n\r\n// #endregion\r\n\r\ndeclare global {\r\n interface HTMLElementTagNameMap {\r\n 'ep-websocket-provider': EPWebSocketProvider\r\n }\r\n}\r\n","import { consume } from '@lit/context'\r\nimport { css, html } from 'lit'\r\nimport type { TemplateResult } from 'lit'\r\nimport { customElement, property } from 'lit/decorators.js'\r\nimport { ifDefined } from 'lit/directives/if-defined.js';\r\nimport { useEffect } from 'haunted'\r\nimport { LitHauntedElement } from '../../mixins/lit-haunted-element';\r\nimport { jsonRpcClientContext, websocketProviderContext } from './ep-websocket-provider';\r\nimport type { Client } from '@open-rpc/client-js'\r\nimport type { IJSONRPCNotification } from '@open-rpc/client-js/build/Request'\r\nimport { IntervalController } from '../../controllers/interval-controller';\r\nimport { GetOpenReviewTimers, HasOpenConnection, ParseDate, ReviewTimerDTO, StartReviewTimer, StopReviewTimers, WhoAmI } from './jsonrpc';\r\n\r\n@customElement('ep-review-timer')\r\nexport class EPReviewTimer extends LitHauntedElement {\r\n // Every 1 second, render the interface (used for counting timers)\r\n private clockInterval = new IntervalController(this, () => this.requestUpdate(), 1000);\r\n // Every 15 minutes, ask for new data (in case the database was updated by a job overnight)\r\n private staleInterval = new IntervalController(this, () => this._handlePeriodicStaleRefresh(), 15 * 60 * 1000); \r\n\r\n // #region Core Properties\r\n\r\n @property({ attribute: false })\r\n timerStartedAt?: Date;\r\n\r\n @property({ attribute: false })\r\n isLoading: boolean = true;\r\n\r\n @property({ attribute: false })\r\n innerConnectionIssue?: TemplateResult<1>;\r\n\r\n get currentConnectionIssue(): TemplateResult<1> | undefined {\r\n return this.providerConnectionIssue ?? this.innerConnectionIssue;\r\n }\r\n\r\n set currentConnectionIssue(value: TemplateResult<1> | undefined) {\r\n this.innerConnectionIssue = value;\r\n }\r\n\r\n @property({ attribute: false })\r\n openTimers: ReviewTimerDTO[] = []; // TODO: timer data\r\n\r\n get currentTimer(): ReviewTimerDTO | undefined {\r\n if (this.PermitApplMasterID !== undefined) {\r\n return this.openTimers.find(timer => timer.PermitApplMasterID === this.PermitApplMasterID);\r\n }\r\n else if (this.InspectionID !== undefined) {\r\n return this.openTimers.find(timer => timer.InspectionID === this.InspectionID);\r\n }\r\n else if (this.ComplaintID !== undefined) {\r\n return this.openTimers.find(timer => timer.ComplaintID === this.ComplaintID);\r\n }\r\n else {\r\n return undefined;\r\n }\r\n }\r\n\r\n get otherTimer(): ReviewTimerDTO | undefined {\r\n const currentTimer = this.currentTimer;\r\n return this.openTimers.find(timer => timer.ReviewTimerID !== currentTimer?.ReviewTimerID)\r\n }\r\n\r\n @property({ type: Number, attribute: 'permitapplmasterid'})\r\n PermitApplMasterID?: number;\r\n\r\n @property({ type: Number, attribute: 'inspectionid' })\r\n InspectionID?: number;\r\n\r\n @property({ type: Number, attribute: 'complaintid' })\r\n ComplaintID?: number;\r\n\r\n @property({ attribute: 'project-link' })\r\n projectLink?: string;\r\n\r\n @property({ attribute: 'inspection-link' })\r\n inspectionLink?: string;\r\n\r\n @property({ attribute: 'complaint-link' })\r\n complaintLink?: string;\r\n\r\n @property({ attribute: 'disabled-reason' })\r\n disabledReason?: string;\r\n\r\n @property({ type: Boolean, attribute: 'compact', reflect: true })\r\n isCompactMode: boolean = false;\r\n\r\n @property({ type: Boolean, attribute: 'disabled', reflect: true })\r\n isDisabled: boolean = false;\r\n\r\n // #endregion\r\n\r\n // #region Context Properties (Provided + Consumed)\r\n @consume({ context: jsonRpcClientContext, subscribe: true })\r\n @property({ attribute: false })\r\n client?: Client;\r\n\r\n @consume({ context: websocketProviderContext, subscribe: true })\r\n @property({ attribute: false })\r\n providerConnectionIssue?: TemplateResult<1>;\r\n // #endregion\r\n\r\n get formattedTimerTicker(): string {\r\n //console.log('formatting')\r\n if (this.timerStartedAt === undefined) return `--:--:--`;\r\n const now = new Date();\r\n const past = this.timerStartedAt ?? now;\r\n const secondsDiff = Math.abs(now.valueOf() - past.valueOf()) / 1000;\r\n let tally = secondsDiff;\r\n\r\n const hours = Math.floor(tally / (60 * 60));\r\n tally = tally % (60 * 60);\r\n const minutes = Math.floor(tally / 60);\r\n tally = tally % 60;\r\n const seconds = tally;\r\n\r\n const zeropad = (num: number) => Math.floor(num).toString().padStart(2, '0')\r\n \r\n return `${zeropad(hours)}:${zeropad(minutes)}:${zeropad(seconds)}`\r\n }\r\n\r\n render() {\r\n // Initialize the component if the WSS client is provided\r\n useEffect(async () => await this.setupClient(), [this.client]);\r\n\r\n useEffect(() => {\r\n if (this.currentConnectionIssue !== undefined) {\r\n this.timerStartedAt = undefined;\r\n }\r\n }, [this.currentConnectionIssue])\r\n\r\n // If any of those open timers are the one we should be seeing on this page, set the \"timerStartedAt\" value\r\n useEffect(() => {\r\n this.timerStartedAt = this.currentTimer !== undefined ? ParseDate(this.currentTimer.StartTime) : undefined;\r\n this._dispatchUpdateEvent();\r\n }, [this.openTimers, this.PermitApplMasterID, this.InspectionID, this.ComplaintID])\r\n\r\n // Automatically re-render the page every second, depending on if a timer is running or not\r\n useEffect(() => {\r\n if (this.timerStartedAt !== undefined) {\r\n this.clockInterval.startInterval();\r\n }\r\n else {\r\n this.clockInterval.stopInterval();\r\n }\r\n }, [this.timerStartedAt]);\r\n\r\n // maybe this will make fetching the \"otherTimer\" less expensive\r\n const otherTimer = this.otherTimer;\r\n const currentTimer = this.currentTimer;\r\n\r\n return html`\r\n
\r\n \r\n ${\r\n this.isLoading\r\n ? html``\r\n : ''\r\n }\r\n ${\r\n this.timerStartedAt === undefined\r\n ? (\r\n otherTimer === undefined\r\n ? 'Start Timer'\r\n : 'Start New Timer'\r\n )\r\n : 'Stop Timer'\r\n }\r\n \r\n \r\n ${\r\n this.currentConnectionIssue !== undefined\r\n ? html`${this.currentConnectionIssue}`\r\n : (\r\n this.timerStartedAt === undefined\r\n ? (\r\n otherTimer === undefined\r\n ? 'No timers in progress'\r\n : (\r\n /* Status Message: Project */\r\n otherTimer.PermitApplMasterID !== undefined && this.projectLink !== undefined\r\n ? (\r\n this.isCompactMode\r\n ? html`⏳ You have a Timer in Progress on Project #${otherTimer.ProjectNumber ?? '???'}`\r\n : html`Timer in Progress on Project #${otherTimer.ProjectNumber ?? '???'}`\r\n )\r\n /* Status Message: Inspection */\r\n : otherTimer?.InspectionID !== undefined\r\n ? (\r\n this.isCompactMode\r\n ? html`⏳ You have a Timer in Progress on ${otherTimer.InspectionPermitCode ?? '???'} Inspection for Permit #${otherTimer.InspectionPermitNumber ?? '???'}`\r\n : html`Timer in Progress on ${otherTimer.InspectionPermitCode ?? '???'} Inspection for Permit #${otherTimer.InspectionPermitNumber ?? '???'}`\r\n )\r\n /* Status Message: Complaint */\r\n : otherTimer?.ComplaintID !== undefined\r\n ? (\r\n this.isCompactMode\r\n ? html`⏳ You have a Timer in Progress on Complaint #${otherTimer.ComplaintNumber ?? '???'}`\r\n : html`Timer in Progress on Complaint #${otherTimer.ComplaintNumber ?? '???'}`\r\n )\r\n : html`Timer already in progress`\r\n )\r\n )\r\n : html`\r\n Timer started at ${new Intl.DateTimeFormat('en', { timeStyle: 'short' }).format(this.timerStartedAt)}\r\n ${\r\n currentTimer?.DepartmentName !== undefined\r\n ? ` under ${currentTimer.DepartmentName}`\r\n : ''\r\n }\r\n `\r\n )\r\n }\r\n \r\n \r\n ${this.formattedTimerTicker}\r\n \r\n
\r\n `\r\n }\r\n\r\n override connectedCallback() {\r\n super.connectedCallback();\r\n if (this.timerStartedAt !== undefined) {\r\n this.clockInterval.startInterval();\r\n }\r\n else {\r\n this.clockInterval.stopInterval();\r\n }\r\n this.staleInterval.startInterval();\r\n }\r\n\r\n override disconnectedCallback() {\r\n super.disconnectedCallback()\r\n this.clockInterval.stopInterval();\r\n this.staleInterval.stopInterval();\r\n }\r\n\r\n private async setupClient() {\r\n if (this.client !== undefined) {\r\n console.debug(`[${this.tagName}] waiting for client to connect...`)\r\n this.currentConnectionIssue = html`Establishing connection...`;\r\n try {\r\n // Attempts to connect, but specifies a 5000ms timeout\r\n await Promise.race([\r\n this.client.requestManager.connectPromise,\r\n new Promise((resolve, reject) => setTimeout(() => reject(\"Timeout exceeded\"), 30000)), \r\n ]);\r\n this.currentConnectionIssue = undefined;\r\n }\r\n catch (err) {\r\n console.error('[Review Timer] Client failed to connect', err);\r\n this.currentConnectionIssue = html`There was a problem initializing the Review Timer. Please try refreshing or changing your Department/Role. (1)`;\r\n return;\r\n }\r\n console.debug(`[${this.tagName}] client connected!`);\r\n\r\n // Make sure that our user has the correct session info available\r\n const userInfo = await WhoAmI(this.client);\r\n if (userInfo === null || userInfo.DepartmentID === -1 || userInfo.RoleID === -1) {\r\n console.error('[Review Timer] User info was missing: ', userInfo);\r\n this.currentConnectionIssue = html`There was a problem initializing the Review Timer. Please try refreshing or changing your Department/Role. (2)`;\r\n }\r\n\r\n // Check if the user has any timers already open\r\n this.openTimers = [...await GetOpenReviewTimers(this.client)]\r\n this.isLoading = false;\r\n\r\n // Setup notifications\r\n this.client.onNotification((data) => this._handleNotification(data));\r\n // Show a message when the connection is closed\r\n // (this.client.requestManager.getPrimaryTransport() as any).connection.addEventListener('close', () => {\r\n // this.currentConnectionIssue = 'Your connection to the server was closed. Please try refreshing.';\r\n // });\r\n // Show a message when an error happens\r\n this.client.onError(err => {\r\n console.error('[Review Timer] RPC Client Error: ', err);\r\n this.currentConnectionIssue = html`There was a problem with the Review Timer. Please try refreshing.`;\r\n })\r\n }\r\n }\r\n\r\n private async _handleClick(event: MouseEvent) {\r\n if (this.client !== undefined) {\r\n this.isLoading = true;\r\n if (this.timerStartedAt === undefined) {\r\n await StartReviewTimer(this.client, this.PermitApplMasterID, this.InspectionID, this.ComplaintID)\r\n }\r\n else {\r\n await StopReviewTimers(this.client)\r\n }\r\n /**\r\n * Commented out so we can rely on the Notification to determine when things aren't loading anymore\r\n * Pros:\r\n * - Don't flash \"isLoading\" twice\r\n * - WebSockets are fast, so this shouldn't be an issue\r\n * Cons:\r\n * - Assumes that the server will always send a notification (which should happen???)\r\n */\r\n //this.isLoading = false;\r\n }\r\n }\r\n\r\n private async _handleNotification(data: IJSONRPCNotification) {\r\n if (this.client !== undefined) {\r\n // If either notification happens, get the currently open timers. The UI responds based on what is present there.\r\n if (data.method === 'ReviewTimerStopped' || data.method === 'ReviewTimerStarted') {\r\n this.isLoading = true;\r\n this.openTimers = [...await GetOpenReviewTimers(this.client)]\r\n this.isLoading = false;\r\n }\r\n }\r\n }\r\n\r\n private async _handlePeriodicStaleRefresh() {\r\n if (this.client !== undefined && HasOpenConnection(this.client)) {\r\n console.debug(`[${this.tagName}] 🕸\\uFE0F Connection deemed stable. Attempting to refresh due to potentially stale data (${new Date().toLocaleTimeString()}).`);\r\n this.isLoading = true;\r\n this.openTimers = [...await GetOpenReviewTimers(this.client)]\r\n this.isLoading = false;\r\n }\r\n }\r\n\r\n private _dispatchUpdateEvent() {\r\n const event = new CustomEvent('timer-update', { \r\n bubbles: true,\r\n composed: true,\r\n })\r\n this.dispatchEvent(event);\r\n }\r\n\r\n static styles = css`\r\n :host {\r\n display: contents;\r\n --font-mono: ui-monospace, SFMono-Regular, SF Mono, Menlo, \"Cascadia Mono\", Consolas, Liberation Mono, monospace;\r\n --color-text: #000;\r\n --color-text-timer: #000;\r\n --color-container: blue;\r\n --color-button-fg: red;\r\n --color-button-bg: white;\r\n }\r\n\r\n [part=container][data-iscompactmode=false] {\r\n display: flex;\r\n flex-direction: row;\r\n align-items: center;\r\n gap: 12px;\r\n border-radius: 28px;\r\n padding: 24px;\r\n background-color: var(--color-container);\r\n transition: background-color 150ms;\r\n border: 1px solid rgba(0, 0, 0, 0.1);\r\n }\r\n [part=container][data-iscompactmode=true] {\r\n display: var(--display-compact, none);\r\n flex-direction: row;\r\n align-items: center;\r\n justify-content: center;\r\n gap: 12px;\r\n border-radius: 0px;\r\n padding: 10px;\r\n background-color: var(--color-container);\r\n transition: none;\r\n border: 1px solid rgba(0, 0, 0, 0.1);\r\n }\r\n\r\n [data-iscompactmode=true] [part=button], [data-iscompactmode=true] [part=timer], [data-iscompactmode=true] [part=gap] {\r\n display: none;\r\n }\r\n\r\n [data-iscompactmode=true] [part=subtitle] {\r\n font-size: 2.0em;\r\n }\r\n\r\n [data-iscompactmode=true] a {\r\n color: deepskyblue;\r\n }\r\n\r\n [part=subtitle] {\r\n font-size: 1.3em;\r\n }\r\n [data-isproblem=true] [part=subtitle] {\r\n --color-text: rgba(0, 0, 0, 0.5);\r\n }\r\n\r\n [part=timer] {\r\n font-size: 1.5em;\r\n margin-left: auto;\r\n white-space: nowrap;\r\n }\r\n\r\n [data-isloading=true], [data-isdisabled=true] {\r\n --color-text: rgba(0, 0, 0, 0.26);\r\n --color-text-timer: rgba(0, 0, 0, 0.26);\r\n --color-container: rgba(0, 0, 0, 0.12);\r\n /* --color-button-fg: rgba(255,255,255,0.9); */\r\n /* --color-button-bg: #026E00; */\r\n }\r\n\r\n /* [data-isdisabled=true] [part=button] {\r\n pointer-events: none;\r\n } */\r\n\r\n [data-isloading=false][data-istimernull=true] {\r\n /* base: rgb(0,255,0), source: theme picker at https://material-web.dev/ */\r\n --display-compact: none;\r\n --color-text: #026E00;\r\n --color-text-timer: #394B35;\r\n --color-container: #E5F2DB;\r\n --color-button-fg: rgba(255,255,255,0.95);\r\n --color-button-bg: #026E00;\r\n }\r\n\r\n [data-isloading=false][data-istimernull=true][data-iscurrenttimernull=false] {\r\n /* base: rgb(255,127,0), source: theme picker at https://material-web.dev/ */\r\n --display-compact: flex;\r\n --color-text: #924c00;\r\n --color-text-timer: #584235;\r\n --color-container: #ffeadd;\r\n --color-button-fg: rgba(255,255,255,0.95);\r\n --color-button-bg: #924c00;\r\n }\r\n\r\n [data-isloading=false][data-istimernull=true][data-iscurrenttimernull=false][data-iscompactmode=true] {\r\n /* base: rgb(255,127,0), source: theme picker at https://material-web.dev/ */\r\n --display-compact: flex;\r\n --color-container: #924c00;\r\n --color-text: rgba(255,255,255,0.95);\r\n }\r\n\r\n [data-isloading=false][data-istimernull=false] {\r\n /* base: rgb(255,0,0), source: theme picker at https://material-web.dev/ */\r\n --display-compact: none;\r\n --color-text: #a50100;\r\n --color-text-timer: #603E39;\r\n --color-container: #ffe9e6;\r\n --color-button-fg: rgba(255,255,255,0.95);\r\n --color-button-bg: #A50100;\r\n }\r\n\r\n \r\n `\r\n}\r\n\r\ndeclare global {\r\n interface HTMLElementTagNameMap {\r\n 'ep-review-timer': EPReviewTimer\r\n }\r\n}\r\n"],"names":["ParseDate","date","HasOpenConnection","client","transport","WebSocketTransport","Ping","WhoAmI","StartReviewTimer","PermitApplMasterID","InspectionID","ComplaintID","StopReviewTimers","GetOpenReviewTimers","jsonRpcClientContext","createContext","websocketProviderContext","EPWebSocketProvider","LitHauntedElement","IntervalController","power","useEffect","html","_a","service","previousConnectionFailed","schedule","delay","requestManager","RequestManager","Client","_","reject","oldRetryCount","retryCountDidChange","err","css","__decorateClass","property","provide","customElement","EPReviewTimer","value","timer","currentTimer","now","past","tally","hours","minutes","seconds","zeropad","num","otherTimer","ifDefined","resolve","userInfo","data","event","consume"],"mappings":"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"}