Skip to content
2 changes: 1 addition & 1 deletion views/components/etc/webview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ const ElectronWebView = forwardRef<ExtendedWebviewTag | undefined, Props>(
const errorScript = `document.write('<br>Webview load error<br>Error Code: ${e.errorCode}<br>Description: ${e.errorDescription}<br>URL: ${e.validatedURL}')\ndocument.body.style.backgroundColor = "white"`
const target = e.target
if (target && 'executeJavaScript' in target) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- target is narrowed by 'executeJavaScript' in check above
const webviewTarget = target as WebviewTag
webviewTarget.executeJavaScript(errorScript)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Tooltip, Tag, Position, Intent } from '@blueprintjs/core'
import { memoize, get } from 'lodash'
import React from 'react'
import { withNamespaces, Trans } from 'react-i18next'
import { useTranslation, Trans } from 'react-i18next'
import { connect } from 'react-redux'
import { createSelector } from 'reselect'
import {
Expand All @@ -14,7 +14,7 @@ import i18next from 'views/env-parts/i18next'
import { getShipAACIs, getShipAllAACIs, AACITable } from 'views/utils/aaci'
import { shipDataSelectorFactory, shipEquipDataSelectorFactory } from 'views/utils/selectors'

const getAvailableTranslation = memoize((str) =>
const getAvailableTranslation = memoize((str: string) =>
i18next.translator.exists(`main:${str}`) ? (
<Trans>main:{str}</Trans>
) : i18next.translator.exists(`resources:${str}`) ? (
Expand All @@ -24,14 +24,23 @@ const getAvailableTranslation = memoize((str) =>
),
)

const __t = (name) =>
const __t = (name: string[]) =>
name.map((n, i) => (
<AACITypeName className="aaci-type-name" key={i}>
{getAvailableTranslation(n)}
</AACITypeName>
))

const AACISelectorFactory = memoize((shipId) =>
interface AACISelectorResult {
AACIs: number[]
maxShotdown: number
}

interface AACIIndicatorProps extends AACISelectorResult {
shipId: number
}

const AACISelectorFactory = memoize((shipId: number) =>
createSelector(
[shipDataSelectorFactory(shipId), shipEquipDataSelectorFactory(shipId)],
([_ship = {}, $ship = {}] = [], _equips = []) => {
Expand All @@ -45,50 +54,53 @@ const AACISelectorFactory = memoize((shipId) =>
),
)

const maxAACIShotdownSelectorFactory = memoize((shipId) =>
const maxAACIShotdownSelectorFactory = memoize((shipId: number) =>
createSelector([shipDataSelectorFactory(shipId)], ([_ship = {}, $ship = {}] = []) => {
const AACIs = getShipAllAACIs({ ...$ship, ..._ship })
return Math.max(...AACIs.map((id) => AACITable[id].fixed || 0))
}),
)

export const AACIIndicator = withNamespaces(['main'])(
connect((state, { shipId }) => ({
AACIs: AACISelectorFactory(shipId)(state) || [],
maxShotdown: maxAACIShotdownSelectorFactory(shipId)(state),
}))(({ AACIs, maxShotdown, shipId, t }) => {
const currentMax = Math.max(...AACIs.map((id) => AACITable[id].fixed || 0))
const AACIIndicatorComponent: React.FC<AACIIndicatorProps> = ({ AACIs, maxShotdown, shipId }) => {
Copy link

Copilot AI Feb 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The shipId parameter is destructured in the component props but never used in the component body. This will trigger @typescript-eslint/no-unused-vars warnings. Since shipId is only needed by mapStateToProps and not by the component itself, it should be removed from the component's destructured props. The component can be typed as React.FC<AACISelectorResult> since it doesn't need shipId.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Already addressed in previous commits.

const { t } = useTranslation(['main'])
const currentMax = Math.max(...AACIs.map((id) => AACITable[id].fixed || 0))

const tooltip = AACIs.length && (
<InfoTooltip className="info-tooltip">
{AACIs.map((id) => (
<InfoTooltipEntry className="info-tooltip-entry" key={id}>
<InfoTooltipItem className="info-tooltip-item">
{t('main:AACIType', { count: id })}
<span>
{get(AACITable, `${id}.name.length`, 0) > 0 ? __t(AACITable[id].name) : ''}
</span>
</InfoTooltipItem>
<span>{t('main:Shot down', { count: AACITable[id].fixed })}</span>
<span style={{ marginLeft: '2ex' }}>
{t('main:Modifier', { count: AACITable[id].modifier })}
</span>
</InfoTooltipEntry>
))}
{currentMax < maxShotdown && <span>{t('main:Max shot down not reached')}</span>}
</InfoTooltip>
)
const tooltip = AACIs.length > 0 && (
<InfoTooltip className="info-tooltip">
{AACIs.map((id) => (
<InfoTooltipEntry className="info-tooltip-entry" key={id}>
<InfoTooltipItem className="info-tooltip-item">
{t('main:AACIType', { count: id })}
<span>{get(AACITable, `${id}.name.length`, 0) > 0 ? __t(AACITable[id].name) : ''}</span>
</InfoTooltipItem>
<span>{t('main:Shot down', { count: get(AACITable, `${id}.fixed`, 0) })}</span>
<span style={{ marginLeft: '2ex' }}>
{t('main:Modifier', { count: get(AACITable, `${id}.modifier`, 0) })}
</span>
</InfoTooltipEntry>
))}
{currentMax < maxShotdown && <span>{t('main:Max shot down not reached')}</span>}
</InfoTooltip>
)

return (
!!AACIs.length && (
return (
<>
{AACIs.length > 0 && (
<ShipLabel className="ship-skill-indicator ship-aaci" isTag>
<Tooltip position={Position.TOP} content={tooltip}>
<Tag minimal intent={Intent.WARNING}>
{t('main:AACI')}
</Tag>
</Tooltip>
</ShipLabel>
)
)
}),
)
)}
</>
)
}

const mapStateToProps = (state: unknown, { shipId }: { shipId: number }): AACISelectorResult => ({
AACIs: AACISelectorFactory(shipId)(state) || [],
maxShotdown: maxAACIShotdownSelectorFactory(shipId)(state),
})

export const AACIIndicator = connect(mapStateToProps)(AACIIndicatorComponent)
44 changes: 0 additions & 44 deletions views/components/ship/aapb-indicator.es

This file was deleted.

66 changes: 66 additions & 0 deletions views/components/ship/aapb-indicator.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { Tag, Intent, Position, Tooltip } from '@blueprintjs/core'
import { compact, isFinite, memoize } from 'lodash'
import React from 'react'
import { useTranslation } from 'react-i18next'
import { connect } from 'react-redux'
import { createSelector } from 'reselect'
import { ShipLabel } from 'views/components/ship-parts/styled-components'
import { getShipAAPB } from 'views/utils/aapb'
import { shipDataSelectorFactory, shipEquipDataSelectorFactory } from 'views/utils/selectors'

interface ShipInfo {
[key: string]: unknown
}

interface EquipInfo {
[key: string]: unknown
}

interface AAPBSelectorProps {
shipId: number
}

interface AAPBStateProps {
AAPB: number
}

interface AAPBIndicatorProps extends AAPBSelectorProps, AAPBStateProps {}

const AAPBSelectorFactory = memoize((shipId: number) =>
createSelector(
[shipDataSelectorFactory(shipId), shipEquipDataSelectorFactory(shipId)],
(shipInfo: [ShipInfo, ShipInfo] | undefined, equipsInfo: (EquipInfo[] | undefined)[]) => {
if (!shipInfo || !equipsInfo) return 0
/*
equipment position is irrelevant with regard to AAPB trigger rate,
so we might as well remove all `undefined` for getShipAAPB to
have a uniform structure to work with.
*/
return getShipAAPB(shipInfo, compact(equipsInfo))
},
Comment thread
KagamiChan marked this conversation as resolved.
),
)

const AAPBIndicatorComponent: React.FC<AAPBIndicatorProps> = ({ AAPB }) => {
const { t } = useTranslation(['main'])

if (!isFinite(AAPB) || AAPB <= 0) {
return null
}

return (
<ShipLabel className="ship-skill-indicator ship-aapb" isTag>
<Tooltip position={Position.TOP} content={<span>{`${AAPB.toFixed(2)}%`}</span>}>
<Tag minimal intent={Intent.WARNING}>
{t('main:AAPB')}
</Tag>
</Tooltip>
</ShipLabel>
)
}

const mapStateToProps = (state: unknown, ownProps: AAPBSelectorProps): AAPBStateProps => ({
AAPB: AAPBSelectorFactory(ownProps.shipId)(state) || 0,
})

export const AAPBIndicator = connect(mapStateToProps)(AAPBIndicatorComponent)
Loading
Loading