mirror of
https://github.com/Sendouc/sendou.ink.git
synced 2026-09-08 12:16:12 -05:00
Enable Biome rules 4
This commit is contained in:
@@ -34,7 +34,7 @@
|
||||
- state management is done via plain `useState` and React Context API
|
||||
- avoid using `useEffect`
|
||||
- all texts should be provided translations via the i18next library's `useTranslations` hook's `t` function
|
||||
- instead of `&&` operator for conditional rendering, use the ternary operator
|
||||
- instead of `&&` operator for conditional rendering, use the ternary operator; enforced by the `no-and-conditional-rendering` Biome plugin
|
||||
- for localized user-readable time strings use `<LocaleTime />`, `<LocaleTimeRange>` or `useFormatDistanceToNow`. If needed use `useDateTimeFormat` directly. NEVER use e.g. `toLocaleString` directly as it does not include users' language selection.
|
||||
|
||||
## Forms
|
||||
|
||||
@@ -103,7 +103,7 @@ export function BuildCard({
|
||||
>
|
||||
<div>
|
||||
<div className={styles.topRow}>
|
||||
{modes && modes.length > 0 && (
|
||||
{modes && modes.length > 0 ? (
|
||||
<div className={styles.modes}>
|
||||
{modes.map((mode) => (
|
||||
<Image
|
||||
@@ -117,7 +117,7 @@ export function BuildCard({
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
) : null}
|
||||
<h2 className={styles.title} data-testid="build-title">
|
||||
{title}
|
||||
</h2>
|
||||
@@ -159,11 +159,11 @@ export function BuildCard({
|
||||
{weapons.map((weapon) => (
|
||||
<RoundWeaponImage key={weapon.weaponSplId} weapon={weapon} />
|
||||
))}
|
||||
{weapons.length === 1 && (
|
||||
{weapons.length === 1 ? (
|
||||
<div className={styles.weaponText}>
|
||||
{t(`weapons:MAIN_${weapons[0].weaponSplId}` as any)}
|
||||
</div>
|
||||
)}
|
||||
) : null}
|
||||
</div>
|
||||
<div
|
||||
className={clsx(styles.gearAbilities, {
|
||||
@@ -219,7 +219,7 @@ export function BuildCard({
|
||||
{description}
|
||||
</SendouPopover>
|
||||
) : null}
|
||||
{canEdit && (
|
||||
{canEdit ? (
|
||||
<>
|
||||
<LinkButton
|
||||
shape="circle"
|
||||
@@ -248,7 +248,7 @@ export function BuildCard({
|
||||
/>
|
||||
</FormWithConfirm>
|
||||
</>
|
||||
)}
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -296,7 +296,7 @@ export default function Chart({
|
||||
},
|
||||
}}
|
||||
/>
|
||||
{tooltipData && (
|
||||
{tooltipData ? (
|
||||
<div
|
||||
className={styles.tooltip}
|
||||
style={{
|
||||
@@ -324,7 +324,7 @@ export default function Chart({
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ export function Label({
|
||||
return (
|
||||
<div className={clsx(styles.container, className, { "mb-0": !spaced })}>
|
||||
<label htmlFor={htmlFor} className={labelClassName}>
|
||||
{children} {required && <span className="text-error">*</span>}
|
||||
{children} {required ? <span className="text-error">*</span> : null}
|
||||
</label>
|
||||
{valueLimits ? (
|
||||
<div
|
||||
|
||||
@@ -82,15 +82,15 @@ export function MapPoolSelector({
|
||||
|
||||
return (
|
||||
<fieldset className={className}>
|
||||
{Boolean(title) && <legend>{title}</legend>}
|
||||
{Boolean(handleRemoval || allowBulkEdit) && (
|
||||
{title ? <legend>{title}</legend> : null}
|
||||
{handleRemoval || allowBulkEdit ? (
|
||||
<div className="stack horizontal sm justify-end">
|
||||
{handleRemoval && (
|
||||
{handleRemoval ? (
|
||||
<SendouButton variant="minimal" onClick={handleRemoval}>
|
||||
{t("actions.remove")}
|
||||
</SendouButton>
|
||||
)}
|
||||
{allowBulkEdit && (
|
||||
) : null}
|
||||
{allowBulkEdit ? (
|
||||
<SendouButton
|
||||
variant="minimal-destructive"
|
||||
isDisabled={mapPool.isEmpty()}
|
||||
@@ -98,16 +98,16 @@ export function MapPoolSelector({
|
||||
>
|
||||
{t("actions.clear")}
|
||||
</SendouButton>
|
||||
)}
|
||||
) : null}
|
||||
</div>
|
||||
)}
|
||||
) : null}
|
||||
<div className="stack md">
|
||||
{allowBulkEdit && (
|
||||
{allowBulkEdit ? (
|
||||
<MapPoolTemplateSelect
|
||||
value={template}
|
||||
handleChange={handleTemplateChange}
|
||||
/>
|
||||
)}
|
||||
) : null}
|
||||
{info}
|
||||
<MapPoolStages
|
||||
mapPool={mapPool}
|
||||
@@ -275,9 +275,8 @@ export function MapPoolStages({
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
{!isPresentational &&
|
||||
allowBulkEdit &&
|
||||
(mapPool.hasStage(stageId) ? (
|
||||
{!isPresentational && allowBulkEdit ? (
|
||||
mapPool.hasStage(stageId) ? (
|
||||
<SendouButton
|
||||
shape="circle"
|
||||
key="clear"
|
||||
@@ -297,7 +296,8 @@ export function MapPoolStages({
|
||||
aria-label={t("common:actions.selectAll")}
|
||||
size="small"
|
||||
/>
|
||||
))}
|
||||
)
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -358,7 +358,7 @@ function MapPoolTemplateSelect({
|
||||
</option>
|
||||
))}
|
||||
</optgroup>
|
||||
{recentEvents && recentEvents.length > 0 && (
|
||||
{recentEvents && recentEvents.length > 0 ? (
|
||||
<optgroup label={t("common:maps.template.yourRecentEvents")}>
|
||||
{recentEvents.map((event) => (
|
||||
<option key={event.id} value={`recent-event:${event.id}`}>
|
||||
@@ -366,7 +366,7 @@ function MapPoolTemplateSelect({
|
||||
</option>
|
||||
))}
|
||||
</optgroup>
|
||||
)}
|
||||
) : null}
|
||||
</select>
|
||||
</label>
|
||||
);
|
||||
|
||||
@@ -11,7 +11,7 @@ export function Section({
|
||||
}) {
|
||||
return (
|
||||
<section className={styles.section}>
|
||||
{title && <h2>{title}</h2>}
|
||||
{title ? <h2>{title}</h2> : null}
|
||||
<div className={className}>{children}</div>
|
||||
</section>
|
||||
);
|
||||
|
||||
@@ -59,10 +59,11 @@ export function SendouButton({
|
||||
}}
|
||||
className={buttonClassName({ className, variant, size, shape })}
|
||||
>
|
||||
{icon &&
|
||||
React.cloneElement(icon, {
|
||||
className: iconClassName(icon.props.className, children, size),
|
||||
})}
|
||||
{icon
|
||||
? React.cloneElement(icon, {
|
||||
className: iconClassName(icon.props.className, children, size),
|
||||
})
|
||||
: null}
|
||||
{children}
|
||||
</button>
|
||||
);
|
||||
@@ -112,10 +113,11 @@ export function LinkButton({
|
||||
data-testid={testId}
|
||||
aria-label={ariaLabel}
|
||||
>
|
||||
{icon &&
|
||||
React.cloneElement(icon, {
|
||||
className: iconClassName(icon.props.className, children, size),
|
||||
})}
|
||||
{icon
|
||||
? React.cloneElement(icon, {
|
||||
className: iconClassName(icon.props.className, children, size),
|
||||
})
|
||||
: null}
|
||||
{children}
|
||||
</a>
|
||||
);
|
||||
@@ -132,10 +134,11 @@ export function LinkButton({
|
||||
onClick={onClick}
|
||||
aria-label={ariaLabel}
|
||||
>
|
||||
{icon &&
|
||||
React.cloneElement(icon, {
|
||||
className: iconClassName(icon.props.className, children, size),
|
||||
})}
|
||||
{icon
|
||||
? React.cloneElement(icon, {
|
||||
className: iconClassName(icon.props.className, children, size),
|
||||
})
|
||||
: null}
|
||||
{children}
|
||||
</Link>
|
||||
);
|
||||
|
||||
@@ -116,7 +116,9 @@ export function NotificationContent({
|
||||
seen: Number(!stickyUnseenIds.has(notification.id)),
|
||||
}}
|
||||
/>
|
||||
{i !== notifications.length - 1 && <NotificationItemDivider />}
|
||||
{i !== notifications.length - 1 ? (
|
||||
<NotificationItemDivider />
|
||||
) : null}
|
||||
</React.Fragment>
|
||||
))}
|
||||
</NotificationsList>
|
||||
|
||||
@@ -167,7 +167,7 @@ function BuildAnalyzerPage() {
|
||||
};
|
||||
|
||||
const mainWeaponCategoryItems = [
|
||||
analyzed.stats.shotSpreadAir && (
|
||||
analyzed.stats.shotSpreadAir ? (
|
||||
<StatCard
|
||||
context={context}
|
||||
key="jumpShotSpread"
|
||||
@@ -175,8 +175,8 @@ function BuildAnalyzerPage() {
|
||||
title={t("analyzer:stat.jumpShotSpread")}
|
||||
suffix="°"
|
||||
/>
|
||||
),
|
||||
typeof analyzed.stats.shotSpreadGround === "number" && (
|
||||
) : null,
|
||||
typeof analyzed.stats.shotSpreadGround === "number" ? (
|
||||
<StatCard
|
||||
context={context}
|
||||
key="groundShotSpread"
|
||||
@@ -184,9 +184,9 @@ function BuildAnalyzerPage() {
|
||||
title={t("analyzer:stat.groundShotSpread")}
|
||||
suffix="°"
|
||||
/>
|
||||
),
|
||||
) : null,
|
||||
// Squeezer
|
||||
analyzed.stats.shotAutofireSpreadAir && (
|
||||
analyzed.stats.shotAutofireSpreadAir ? (
|
||||
<StatCard
|
||||
context={context}
|
||||
key="shotAutofireSpreadAir"
|
||||
@@ -194,8 +194,8 @@ function BuildAnalyzerPage() {
|
||||
title={t("analyzer:stat.shotAutofireSpreadAir")}
|
||||
suffix="°"
|
||||
/>
|
||||
),
|
||||
typeof analyzed.stats.shotAutofireSpreadGround === "number" && (
|
||||
) : null,
|
||||
typeof analyzed.stats.shotAutofireSpreadGround === "number" ? (
|
||||
<StatCard
|
||||
context={context}
|
||||
key="shotAutofireSpreadGround"
|
||||
@@ -203,7 +203,7 @@ function BuildAnalyzerPage() {
|
||||
title={t("analyzer:stat.shotAutofireSpreadGround")}
|
||||
suffix="°"
|
||||
/>
|
||||
),
|
||||
) : null,
|
||||
|
||||
...INK_CONSUME_TYPES.filter(
|
||||
(type) => analyzed.stats[`mainWeaponInkConsumptionPercentage_${type}`],
|
||||
@@ -216,7 +216,7 @@ function BuildAnalyzerPage() {
|
||||
suffix="%"
|
||||
/>
|
||||
)),
|
||||
typeof analyzed.stats.mainWeaponWhiteInkSeconds === "number" && (
|
||||
typeof analyzed.stats.mainWeaponWhiteInkSeconds === "number" ? (
|
||||
<StatCard
|
||||
context={context}
|
||||
key="whiteInkSeconds"
|
||||
@@ -224,8 +224,8 @@ function BuildAnalyzerPage() {
|
||||
title={t("analyzer:stat.whiteInk")}
|
||||
suffix={t("analyzer:suffix.seconds")}
|
||||
/>
|
||||
),
|
||||
typeof analyzed.weapon.brellaCanopyHp === "number" && (
|
||||
) : null,
|
||||
typeof analyzed.weapon.brellaCanopyHp === "number" ? (
|
||||
<StatCard
|
||||
context={context}
|
||||
key="brellaCanopyHp"
|
||||
@@ -233,8 +233,8 @@ function BuildAnalyzerPage() {
|
||||
title={t("analyzer:stat.canopyHp")}
|
||||
suffix={t("analyzer:suffix.hp")}
|
||||
/>
|
||||
),
|
||||
typeof analyzed.weapon.fullChargeSeconds === "number" && (
|
||||
) : null,
|
||||
typeof analyzed.weapon.fullChargeSeconds === "number" ? (
|
||||
<StatCard
|
||||
context={context}
|
||||
key="fullChargeSeconds"
|
||||
@@ -242,8 +242,8 @@ function BuildAnalyzerPage() {
|
||||
title={t("analyzer:stat.fullChargeSeconds")}
|
||||
suffix={t("analyzer:suffix.seconds")}
|
||||
/>
|
||||
),
|
||||
typeof analyzed.weapon.maxChargeHoldSeconds === "number" && (
|
||||
) : null,
|
||||
typeof analyzed.weapon.maxChargeHoldSeconds === "number" ? (
|
||||
<StatCard
|
||||
context={context}
|
||||
key="maxChargeHoldSeconds"
|
||||
@@ -251,7 +251,7 @@ function BuildAnalyzerPage() {
|
||||
title={t("analyzer:stat.maxChargeHoldSeconds")}
|
||||
suffix={t("analyzer:suffix.seconds")}
|
||||
/>
|
||||
),
|
||||
) : null,
|
||||
].filter(Boolean);
|
||||
|
||||
// a primary slot-only ability (e.g. Ninja Squid) or Ability Doubler alone leaves abilityPoints at 0
|
||||
@@ -313,47 +313,46 @@ function BuildAnalyzerPage() {
|
||||
{t("analyzer:compare")}
|
||||
</SendouTab>
|
||||
</SendouTabList>
|
||||
{[1, 2].map(
|
||||
(buildIndex) =>
|
||||
focusedBuild && (
|
||||
<SendouTabPanel
|
||||
id={`build-${buildIndex}`}
|
||||
key={`build-${buildIndex}`}
|
||||
>
|
||||
<AbilitiesSelector
|
||||
selectedAbilities={focusedBuild}
|
||||
onChange={(newBuild) => {
|
||||
const firstBuildIsEmpty = build
|
||||
{[1, 2].map((buildIndex) =>
|
||||
focusedBuild ? (
|
||||
<SendouTabPanel
|
||||
id={`build-${buildIndex}`}
|
||||
key={`build-${buildIndex}`}
|
||||
>
|
||||
<AbilitiesSelector
|
||||
selectedAbilities={focusedBuild}
|
||||
onChange={(newBuild) => {
|
||||
const firstBuildIsEmpty = build
|
||||
.flat()
|
||||
.every((ability) => ability === "UNKNOWN");
|
||||
|
||||
const buildWasEmptied =
|
||||
!firstBuildIsEmpty &&
|
||||
newBuild
|
||||
.flat()
|
||||
.every((ability) => ability === "UNKNOWN");
|
||||
|
||||
const buildWasEmptied =
|
||||
!firstBuildIsEmpty &&
|
||||
newBuild
|
||||
.flat()
|
||||
.every((ability) => ability === "UNKNOWN") &&
|
||||
focused === 1;
|
||||
|
||||
// otherwise build2 would be duplicated
|
||||
if (buildWasEmptied) {
|
||||
handleChange({
|
||||
newBuild: build2,
|
||||
newBuild2: newBuild,
|
||||
newFocused: 1,
|
||||
});
|
||||
return;
|
||||
}
|
||||
.every((ability) => ability === "UNKNOWN") &&
|
||||
focused === 1;
|
||||
|
||||
// otherwise build2 would be duplicated
|
||||
if (buildWasEmptied) {
|
||||
handleChange({
|
||||
[focused === 1 || firstBuildIsEmpty
|
||||
? "newBuild"
|
||||
: "newBuild2"]: newBuild,
|
||||
newFocused: firstBuildIsEmpty ? 1 : undefined,
|
||||
newBuild: build2,
|
||||
newBuild2: newBuild,
|
||||
newFocused: 1,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</SendouTabPanel>
|
||||
),
|
||||
return;
|
||||
}
|
||||
|
||||
handleChange({
|
||||
[focused === 1 || firstBuildIsEmpty
|
||||
? "newBuild"
|
||||
: "newBuild2"]: newBuild,
|
||||
newFocused: firstBuildIsEmpty ? 1 : undefined,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</SendouTabPanel>
|
||||
) : null,
|
||||
)}
|
||||
<SendouTabPanel id="build-compare">
|
||||
<APCompare
|
||||
@@ -382,16 +381,16 @@ function BuildAnalyzerPage() {
|
||||
}
|
||||
effects={allEffects}
|
||||
/>
|
||||
{showAbilityChunksRequired && (
|
||||
{showAbilityChunksRequired ? (
|
||||
<AbilityChunksRequired build={build} />
|
||||
)}
|
||||
) : null}
|
||||
</div>
|
||||
<div className={styles.patch}>
|
||||
{t("analyzer:patch")} {CURRENT_PATCH}
|
||||
</div>
|
||||
</div>
|
||||
<div className="stack md">
|
||||
{mainWeaponCategoryItems.length > 0 && (
|
||||
{mainWeaponCategoryItems.length > 0 ? (
|
||||
<StatCategory
|
||||
title={t("analyzer:stat.category.main")}
|
||||
summaryRightContent={
|
||||
@@ -410,7 +409,7 @@ function BuildAnalyzerPage() {
|
||||
>
|
||||
{mainWeaponCategoryItems}
|
||||
</StatCategory>
|
||||
)}
|
||||
) : null}
|
||||
|
||||
<StatCategory
|
||||
title={t("analyzer:stat.category.sub")}
|
||||
@@ -438,67 +437,67 @@ function BuildAnalyzerPage() {
|
||||
title={t("analyzer:stat.whiteInk")}
|
||||
suffix={t("analyzer:suffix.seconds")}
|
||||
/>
|
||||
{analyzed.stats.subVelocity && (
|
||||
{analyzed.stats.subVelocity ? (
|
||||
<StatCard
|
||||
context={context}
|
||||
stat={statKeyToTuple("subVelocity")}
|
||||
title={t("analyzer:stat.sub.velocity")}
|
||||
/>
|
||||
)}
|
||||
{analyzed.stats.subFirstPhaseDuration && (
|
||||
) : null}
|
||||
{analyzed.stats.subFirstPhaseDuration ? (
|
||||
<StatCard
|
||||
context={context}
|
||||
stat={statKeyToTuple("subFirstPhaseDuration")}
|
||||
title={t("analyzer:stat.sub.firstPhaseDuration")}
|
||||
suffix={t("analyzer:suffix.seconds")}
|
||||
/>
|
||||
)}
|
||||
{analyzed.stats.subSecondPhaseDuration && (
|
||||
) : null}
|
||||
{analyzed.stats.subSecondPhaseDuration ? (
|
||||
<StatCard
|
||||
context={context}
|
||||
stat={statKeyToTuple("subSecondPhaseDuration")}
|
||||
title={t("analyzer:stat.sub.secondPhaseDuration")}
|
||||
suffix={t("analyzer:suffix.seconds")}
|
||||
/>
|
||||
)}
|
||||
{analyzed.stats.subMarkingTimeInSeconds && (
|
||||
) : null}
|
||||
{analyzed.stats.subMarkingTimeInSeconds ? (
|
||||
<StatCard
|
||||
context={context}
|
||||
stat={statKeyToTuple("subMarkingTimeInSeconds")}
|
||||
title={t("analyzer:stat.sub.markingTimeInSeconds")}
|
||||
suffix={t("analyzer:suffix.seconds")}
|
||||
/>
|
||||
)}
|
||||
{analyzed.stats.subMarkingRadius && (
|
||||
) : null}
|
||||
{analyzed.stats.subMarkingRadius ? (
|
||||
<StatCard
|
||||
context={context}
|
||||
stat={statKeyToTuple("subMarkingRadius")}
|
||||
title={t("analyzer:stat.sub.markingRadius")}
|
||||
/>
|
||||
)}
|
||||
{analyzed.stats.subExplosionRadius && (
|
||||
) : null}
|
||||
{analyzed.stats.subExplosionRadius ? (
|
||||
<StatCard
|
||||
context={context}
|
||||
stat={statKeyToTuple("subExplosionRadius")}
|
||||
title={t("analyzer:stat.sub.explosionRadius")}
|
||||
/>
|
||||
)}
|
||||
{analyzed.stats.subHp && (
|
||||
) : null}
|
||||
{analyzed.stats.subHp ? (
|
||||
<StatCard
|
||||
context={context}
|
||||
stat={statKeyToTuple("subHp")}
|
||||
title={t("analyzer:stat.sub.hp")}
|
||||
suffix={t("analyzer:suffix.hp")}
|
||||
/>
|
||||
)}
|
||||
{analyzed.stats.subQsjBoost && (
|
||||
) : null}
|
||||
{analyzed.stats.subQsjBoost ? (
|
||||
<StatCard
|
||||
context={context}
|
||||
stat={statKeyToTuple("subQsjBoost")}
|
||||
title={t("analyzer:stat.sub.qsjBoost")}
|
||||
suffix={t("analyzer:abilityPoints.short")}
|
||||
/>
|
||||
)}
|
||||
) : null}
|
||||
</StatCategory>
|
||||
|
||||
<StatCategory
|
||||
@@ -567,7 +566,7 @@ function BuildAnalyzerPage() {
|
||||
/>
|
||||
))
|
||||
: null}
|
||||
{analyzed.stats.specialDurationInSeconds && (
|
||||
{analyzed.stats.specialDurationInSeconds ? (
|
||||
<StatCard
|
||||
context={context}
|
||||
stat={statKeyToTuple("specialDurationInSeconds")}
|
||||
@@ -585,8 +584,8 @@ function BuildAnalyzerPage() {
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
)}
|
||||
{analyzed.stats.specialDamageDistance && (
|
||||
) : null}
|
||||
{analyzed.stats.specialDamageDistance ? (
|
||||
<StatCard
|
||||
context={context}
|
||||
stat={statKeyToTuple("specialDamageDistance")}
|
||||
@@ -596,8 +595,8 @@ function BuildAnalyzerPage() {
|
||||
),
|
||||
})}
|
||||
/>
|
||||
)}
|
||||
{analyzed.stats.specialPaintRadius && (
|
||||
) : null}
|
||||
{analyzed.stats.specialPaintRadius ? (
|
||||
<StatCard
|
||||
context={context}
|
||||
stat={statKeyToTuple("specialPaintRadius")}
|
||||
@@ -607,8 +606,8 @@ function BuildAnalyzerPage() {
|
||||
),
|
||||
})}
|
||||
/>
|
||||
)}
|
||||
{analyzed.stats.specialFieldHp && (
|
||||
) : null}
|
||||
{analyzed.stats.specialFieldHp ? (
|
||||
<StatCard
|
||||
context={context}
|
||||
stat={statKeyToTuple("specialFieldHp")}
|
||||
@@ -619,8 +618,8 @@ function BuildAnalyzerPage() {
|
||||
})}
|
||||
suffix={t("analyzer:suffix.hp")}
|
||||
/>
|
||||
)}
|
||||
{analyzed.stats.specialDeviceHp && (
|
||||
) : null}
|
||||
{analyzed.stats.specialDeviceHp ? (
|
||||
<StatCard
|
||||
context={context}
|
||||
stat={statKeyToTuple("specialDeviceHp")}
|
||||
@@ -631,8 +630,8 @@ function BuildAnalyzerPage() {
|
||||
})}
|
||||
suffix={t("analyzer:suffix.hp")}
|
||||
/>
|
||||
)}
|
||||
{analyzed.stats.specialHookInkConsumptionPercentage && (
|
||||
) : null}
|
||||
{analyzed.stats.specialHookInkConsumptionPercentage ? (
|
||||
<StatCard
|
||||
context={context}
|
||||
stat={statKeyToTuple("specialHookInkConsumptionPercentage")}
|
||||
@@ -643,8 +642,8 @@ function BuildAnalyzerPage() {
|
||||
})}
|
||||
suffix="%"
|
||||
/>
|
||||
)}
|
||||
{analyzed.stats.specialInkConsumptionPerSecondPercentage && (
|
||||
) : null}
|
||||
{analyzed.stats.specialInkConsumptionPerSecondPercentage ? (
|
||||
<StatCard
|
||||
context={context}
|
||||
stat={statKeyToTuple(
|
||||
@@ -657,8 +656,8 @@ function BuildAnalyzerPage() {
|
||||
})}
|
||||
suffix="%"
|
||||
/>
|
||||
)}
|
||||
{analyzed.stats.specialReticleRadius && (
|
||||
) : null}
|
||||
{analyzed.stats.specialReticleRadius ? (
|
||||
<StatCard
|
||||
context={context}
|
||||
stat={statKeyToTuple("specialReticleRadius")}
|
||||
@@ -668,8 +667,8 @@ function BuildAnalyzerPage() {
|
||||
),
|
||||
})}
|
||||
/>
|
||||
)}
|
||||
{analyzed.stats.specialThrowDistance && (
|
||||
) : null}
|
||||
{analyzed.stats.specialThrowDistance ? (
|
||||
<StatCard
|
||||
context={context}
|
||||
stat={statKeyToTuple("specialThrowDistance")}
|
||||
@@ -679,8 +678,8 @@ function BuildAnalyzerPage() {
|
||||
),
|
||||
})}
|
||||
/>
|
||||
)}
|
||||
{analyzed.stats.specialMoveSpeed && (
|
||||
) : null}
|
||||
{analyzed.stats.specialMoveSpeed ? (
|
||||
<StatCard
|
||||
context={context}
|
||||
stat={statKeyToTuple("specialMoveSpeed")}
|
||||
@@ -690,8 +689,8 @@ function BuildAnalyzerPage() {
|
||||
),
|
||||
})}
|
||||
/>
|
||||
)}
|
||||
{analyzed.stats.specialAutoChargeRate && (
|
||||
) : null}
|
||||
{analyzed.stats.specialAutoChargeRate ? (
|
||||
<StatCard
|
||||
context={context}
|
||||
stat={statKeyToTuple("specialAutoChargeRate")}
|
||||
@@ -701,8 +700,8 @@ function BuildAnalyzerPage() {
|
||||
),
|
||||
})}
|
||||
/>
|
||||
)}
|
||||
{analyzed.stats.specialMaxRadius && (
|
||||
) : null}
|
||||
{analyzed.stats.specialMaxRadius ? (
|
||||
<StatCard
|
||||
context={context}
|
||||
stat={statKeyToTuple("specialMaxRadius")}
|
||||
@@ -713,8 +712,8 @@ function BuildAnalyzerPage() {
|
||||
})}
|
||||
popoverInfo={t("analyzer:stat.special.maxRadius.explanation")}
|
||||
/>
|
||||
)}
|
||||
{analyzed.stats.specialRadiusRangeMin && (
|
||||
) : null}
|
||||
{analyzed.stats.specialRadiusRangeMin ? (
|
||||
<StatCard
|
||||
context={context}
|
||||
stat={statKeyToTuple("specialRadiusRangeMin")}
|
||||
@@ -724,8 +723,8 @@ function BuildAnalyzerPage() {
|
||||
),
|
||||
})}
|
||||
/>
|
||||
)}
|
||||
{analyzed.stats.specialRadiusRangeMax && (
|
||||
) : null}
|
||||
{analyzed.stats.specialRadiusRangeMax ? (
|
||||
<StatCard
|
||||
context={context}
|
||||
stat={statKeyToTuple("specialRadiusRangeMax")}
|
||||
@@ -735,8 +734,8 @@ function BuildAnalyzerPage() {
|
||||
),
|
||||
})}
|
||||
/>
|
||||
)}
|
||||
{analyzed.stats.specialPowerUpDuration && (
|
||||
) : null}
|
||||
{analyzed.stats.specialPowerUpDuration ? (
|
||||
<StatCard
|
||||
context={context}
|
||||
stat={statKeyToTuple("specialPowerUpDuration")}
|
||||
@@ -747,7 +746,7 @@ function BuildAnalyzerPage() {
|
||||
})}
|
||||
suffix={t("analyzer:suffix.seconds")}
|
||||
/>
|
||||
)}
|
||||
) : null}
|
||||
</StatCategory>
|
||||
<StatCategory
|
||||
title={t("analyzer:stat.category.subDef")}
|
||||
@@ -787,7 +786,7 @@ function BuildAnalyzerPage() {
|
||||
/>
|
||||
</StatCategory>
|
||||
|
||||
{analyzed.stats.subWeaponDefenseDamages.length > 0 && (
|
||||
{analyzed.stats.subWeaponDefenseDamages.length > 0 ? (
|
||||
<StatCategory
|
||||
title={t("analyzer:stat.category.subWeaponDefenseDamages")}
|
||||
containerClassName={styles.tableContainer}
|
||||
@@ -814,9 +813,9 @@ function BuildAnalyzerPage() {
|
||||
multiShots={analyzed.weapon.multiShots}
|
||||
/>
|
||||
</StatCategory>
|
||||
)}
|
||||
) : null}
|
||||
|
||||
{analyzed.stats.damages.length > 0 && (
|
||||
{analyzed.stats.damages.length > 0 ? (
|
||||
<StatCategory
|
||||
title={t("analyzer:stat.category.damage")}
|
||||
containerClassName={styles.tableContainer}
|
||||
@@ -826,9 +825,9 @@ function BuildAnalyzerPage() {
|
||||
multiShots={analyzed.weapon.multiShots}
|
||||
/>
|
||||
</StatCategory>
|
||||
)}
|
||||
) : null}
|
||||
|
||||
{analyzed.stats.specialWeaponDamages.length > 0 && (
|
||||
{analyzed.stats.specialWeaponDamages.length > 0 ? (
|
||||
<StatCategory
|
||||
title={t("analyzer:stat.category.special.damage", {
|
||||
specialWeapon: t(
|
||||
@@ -839,9 +838,9 @@ function BuildAnalyzerPage() {
|
||||
>
|
||||
<DamageTable values={analyzed.stats.specialWeaponDamages} />
|
||||
</StatCategory>
|
||||
)}
|
||||
) : null}
|
||||
|
||||
{analyzed.stats.fullInkTankOptions.length > 0 && (
|
||||
{analyzed.stats.fullInkTankOptions.length > 0 ? (
|
||||
<StatCategory
|
||||
title={t("analyzer:stat.category.actionsPerInkTank")}
|
||||
containerClassName={styles.tableContainer}
|
||||
@@ -863,7 +862,7 @@ function BuildAnalyzerPage() {
|
||||
<PerInkTankGrid weaponSplId={mainWeaponId} />
|
||||
</div>
|
||||
</StatCategory>
|
||||
)}
|
||||
) : null}
|
||||
|
||||
<StatCategory
|
||||
title={t("analyzer:stat.category.movement")}
|
||||
@@ -890,34 +889,34 @@ function BuildAnalyzerPage() {
|
||||
stat={statKeyToTuple("runSpeed")}
|
||||
title={t("analyzer:stat.runSpeed")}
|
||||
/>
|
||||
{analyzed.stats.shootingRunSpeed && (
|
||||
{analyzed.stats.shootingRunSpeed ? (
|
||||
<StatCard
|
||||
context={context}
|
||||
stat={statKeyToTuple("shootingRunSpeed")}
|
||||
title={t("analyzer:stat.shootingRunSpeed")}
|
||||
/>
|
||||
)}
|
||||
{analyzed.stats.shootingRunSpeedCharging && (
|
||||
) : null}
|
||||
{analyzed.stats.shootingRunSpeedCharging ? (
|
||||
<StatCard
|
||||
context={context}
|
||||
stat={statKeyToTuple("shootingRunSpeedCharging")}
|
||||
title={t("analyzer:stat.shootingRunSpeedCharging")}
|
||||
/>
|
||||
)}
|
||||
{analyzed.stats.shootingRunSpeedFullCharge && (
|
||||
) : null}
|
||||
{analyzed.stats.shootingRunSpeedFullCharge ? (
|
||||
<StatCard
|
||||
context={context}
|
||||
stat={statKeyToTuple("shootingRunSpeedFullCharge")}
|
||||
title={t("analyzer:stat.shootingRunSpeedFullCharge")}
|
||||
/>
|
||||
)}
|
||||
{analyzed.stats.shootingRunSpeedSecondaryMode && (
|
||||
) : null}
|
||||
{analyzed.stats.shootingRunSpeedSecondaryMode ? (
|
||||
<StatCard
|
||||
context={context}
|
||||
stat={statKeyToTuple("shootingRunSpeedSecondaryMode")}
|
||||
title={t("analyzer:stat.shootingRunSpeedSecondaryMode")}
|
||||
/>
|
||||
)}
|
||||
) : null}
|
||||
<StatCard
|
||||
context={context}
|
||||
stat={statKeyToTuple("squidSurgeChargeFrames")}
|
||||
@@ -989,7 +988,7 @@ function BuildAnalyzerPage() {
|
||||
}
|
||||
/>
|
||||
</StatCategory>
|
||||
{objectShredderSelected && (
|
||||
{objectShredderSelected ? (
|
||||
<Link
|
||||
className={styles.noticeableLink}
|
||||
to={objectDamageCalculatorPage(mainWeaponId)}
|
||||
@@ -1002,7 +1001,7 @@ function BuildAnalyzerPage() {
|
||||
/>
|
||||
{t("analyzer:objCalcAd")}
|
||||
</Link>
|
||||
)}
|
||||
) : null}
|
||||
{user && focusedBuild && !buildIsEmpty(focusedBuild) ? (
|
||||
<Link
|
||||
className={styles.noticeableLink}
|
||||
@@ -1443,9 +1442,9 @@ function StatCategory({
|
||||
{summaryRightContent}
|
||||
</summary>
|
||||
<div className={containerClassName}>{children}</div>
|
||||
{textBelow && (
|
||||
{textBelow ? (
|
||||
<div className={styles.statCategoryExplanation}>{textBelow}</div>
|
||||
)}
|
||||
) : null}
|
||||
</details>
|
||||
);
|
||||
}
|
||||
@@ -1540,7 +1539,7 @@ function StatCard({
|
||||
<div className={styles.statCardTitleAndValueContainer}>
|
||||
<h2 className={styles.statCardTitle}>
|
||||
{title}{" "}
|
||||
{popoverInfo && (
|
||||
{popoverInfo ? (
|
||||
<SendouPopover
|
||||
trigger={
|
||||
<SendouButton className={styles.statCardPopoverTrigger}>
|
||||
@@ -1550,7 +1549,7 @@ function StatCard({
|
||||
>
|
||||
{popoverInfo}
|
||||
</SendouPopover>
|
||||
)}
|
||||
) : null}
|
||||
</h2>
|
||||
<div className={styles.statCardValues}>
|
||||
<div className={styles.statCardValue}>
|
||||
@@ -1684,9 +1683,9 @@ function DamageTable({
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{t("analyzer:damage.header.type")}</th>
|
||||
{showDistanceColumn && (
|
||||
{showDistanceColumn ? (
|
||||
<th>{t("analyzer:damage.header.distance")}</th>
|
||||
)}
|
||||
) : null}
|
||||
{damageIsSubWeaponDamage(firstRow) ? (
|
||||
<th>
|
||||
{comparisonValues
|
||||
@@ -1694,7 +1693,9 @@ function DamageTable({
|
||||
: t("analyzer:damage.header.baseDamage")}
|
||||
</th>
|
||||
) : null}
|
||||
{showDamageColumn && <th>{t("analyzer:damage.header.damage")}</th>}
|
||||
{showDamageColumn ? (
|
||||
<th>{t("analyzer:damage.header.damage")}</th>
|
||||
) : null}
|
||||
{showPopovers ? <th /> : null}
|
||||
</tr>
|
||||
</thead>
|
||||
@@ -1731,27 +1732,27 @@ function DamageTable({
|
||||
) : null}
|
||||
</div>
|
||||
</td>
|
||||
{showDistanceColumn && (
|
||||
{showDistanceColumn ? (
|
||||
<td>
|
||||
{typeof val.distance === "number"
|
||||
? val.distance
|
||||
: val.distance?.join("-")}
|
||||
</td>
|
||||
)}
|
||||
{damageIsSubWeaponDamage(val) && <td>{val.baseValue}</td>}
|
||||
{showDamageColumn && (
|
||||
) : null}
|
||||
{damageIsSubWeaponDamage(val) ? <td>{val.baseValue}</td> : null}
|
||||
{showDamageColumn ? (
|
||||
<td>
|
||||
{damage(val)}
|
||||
{comparisonVal ? `/${damage(comparisonVal)}` : null}{" "}
|
||||
{val.shotsToSplat && (
|
||||
{val.shotsToSplat ? (
|
||||
<span className={styles.shotsToSplat}>
|
||||
{t("analyzer:damage.toSplat", {
|
||||
count: val.shotsToSplat,
|
||||
})}
|
||||
</span>
|
||||
)}
|
||||
) : null}
|
||||
</td>
|
||||
)}
|
||||
) : null}
|
||||
{showPopovers ? (
|
||||
<td className={styles.popoverCell}>
|
||||
{renderPopover(val, (val as SubWeaponDamage).subWeaponId) ? (
|
||||
@@ -1858,11 +1859,11 @@ function ConsumptionTable({
|
||||
})}
|
||||
</tbody>
|
||||
</Table>
|
||||
{subWeaponId === TORPEDO_ID && (
|
||||
{subWeaponId === TORPEDO_ID ? (
|
||||
<div className={styles.consumptionTableExplanation}>
|
||||
{t("analyzer:torpedoExplanation")}
|
||||
</div>
|
||||
)}
|
||||
) : null}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -191,7 +191,7 @@ function Results() {
|
||||
|
||||
return (
|
||||
<Section title={t("calendar:results")} className={styles.resultsSection}>
|
||||
{data.event.participantCount && (
|
||||
{data.event.participantCount ? (
|
||||
<div className={styles.resultsParticipantCount}>
|
||||
{isTeamResults
|
||||
? t("calendar:participatedCount", {
|
||||
@@ -201,7 +201,7 @@ function Results() {
|
||||
count: data.event.participantCount,
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
) : null}
|
||||
<Table>
|
||||
<thead>
|
||||
<tr>
|
||||
@@ -276,9 +276,9 @@ function Description() {
|
||||
<Avatar user={data.event} size="xs" />
|
||||
{data.event.username}
|
||||
</div>
|
||||
{data.event.description && (
|
||||
{data.event.description ? (
|
||||
<div className="whitespace-pre-wrap">{data.event.description}</div>
|
||||
)}
|
||||
) : null}
|
||||
</div>
|
||||
</Section>
|
||||
);
|
||||
|
||||
@@ -92,7 +92,9 @@ function TeamLFGPost({
|
||||
<div className="stack horizontal items-center justify-between">
|
||||
<PostTeamLogoHeader team={post.team} />
|
||||
<div className="stack horizontal items-center sm">
|
||||
{isHydrated && <PostTimezonePill timezone={post.timezone} />}
|
||||
{isHydrated ? (
|
||||
<PostTimezonePill timezone={post.timezone} />
|
||||
) : null}
|
||||
{post.languages ? (
|
||||
<PostLanguagePill languages={post.languages} />
|
||||
) : null}
|
||||
@@ -273,12 +275,12 @@ function PostPills({
|
||||
invisible: !isHydrated,
|
||||
})}
|
||||
>
|
||||
{typeof timezone === "string" && isHydrated && (
|
||||
{typeof timezone === "string" && isHydrated ? (
|
||||
<PostTimezonePill timezone={timezone} />
|
||||
)}
|
||||
{!isHydrated && <PostTimezonePillPlaceholder />}
|
||||
) : null}
|
||||
{!isHydrated ? <PostTimezonePillPlaceholder /> : null}
|
||||
{languages ? <PostLanguagePill languages={languages} /> : null}
|
||||
{canEdit && <PostEditButton id={postId} />}
|
||||
{canEdit ? <PostEditButton id={postId} /> : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -128,7 +128,7 @@ function MapListCreator({ mapPool }: { mapPool: MapPool }) {
|
||||
<SendouButton onClick={handleCreateMaplist} isDisabled={disabled}>
|
||||
{t("common:maps.createMapList")}
|
||||
</SendouButton>
|
||||
{mapList && (
|
||||
{mapList ? (
|
||||
<>
|
||||
<ol className={styles.mapList}>
|
||||
{mapList.map(({ mode, stageId }, i) => (
|
||||
@@ -163,7 +163,7 @@ function MapListCreator({ mapPool }: { mapPool: MapPool }) {
|
||||
{t("common:actions.copyToClipboard")}
|
||||
</SendouButton>
|
||||
</>
|
||||
)}
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -61,9 +61,9 @@ export default function NotificationsPage() {
|
||||
seen: Number(!unseenIds.has(notification.id)),
|
||||
}}
|
||||
/>
|
||||
{i !== data.notifications.length - 1 && (
|
||||
{i !== data.notifications.length - 1 ? (
|
||||
<NotificationItemDivider />
|
||||
)}
|
||||
) : null}
|
||||
</React.Fragment>
|
||||
))}
|
||||
</NotificationsList>
|
||||
|
||||
@@ -321,7 +321,9 @@ function DamageReceiversGrid({
|
||||
</div>
|
||||
<div className="stack horizontal sm justify-center items-center">
|
||||
{t(`analyzer:damage.${damage.type}` as any)}
|
||||
{damage.objectShredder && <Ability ability="OS" size="TINY" />}
|
||||
{damage.objectShredder ? (
|
||||
<Ability ability="OS" size="TINY" />
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
@@ -332,8 +334,9 @@ function DamageReceiversGrid({
|
||||
<div>
|
||||
<Label htmlFor="ap">
|
||||
<div className={styles.apLabel}>
|
||||
{abilityPoints !== "0" &&
|
||||
damageReceiverAp[damageToReceiver.receiver]}
|
||||
{abilityPoints !== "0"
|
||||
? damageReceiverAp[damageToReceiver.receiver]
|
||||
: null}
|
||||
</div>
|
||||
</Label>
|
||||
<SendouPopover
|
||||
|
||||
@@ -356,7 +356,7 @@ function Groups() {
|
||||
<SendouTab id="groups" number={neutralGroups.length}>
|
||||
{t("q:looking.columns.groups")}
|
||||
</SendouTab>
|
||||
{isMobile && (
|
||||
{isMobile ? (
|
||||
<SendouTab
|
||||
id="received"
|
||||
number={groupsReceivedLikesFrom.length}
|
||||
@@ -367,12 +367,12 @@ function Groups() {
|
||||
: "q:looking.columns.invitations",
|
||||
)}
|
||||
</SendouTab>
|
||||
)}
|
||||
{isMobile && data.ownGroup && (
|
||||
) : null}
|
||||
{isMobile && data.ownGroup ? (
|
||||
<SendouTab id="own" number={data.ownGroup.members.length}>
|
||||
{t("q:looking.columns.myGroup")}
|
||||
</SendouTab>
|
||||
)}
|
||||
) : null}
|
||||
</SendouTabList>
|
||||
<SendouTabPanel id="groups">
|
||||
<div className="stack sm">
|
||||
|
||||
@@ -98,7 +98,7 @@ export function SwissBracket({
|
||||
return (
|
||||
<div className="stack xl">
|
||||
<div className="stack lg">
|
||||
{groups.length > 1 && (
|
||||
{groups.length > 1 ? (
|
||||
<div className="stack horizontal">
|
||||
{groups.map((g) => (
|
||||
<SendouButton
|
||||
@@ -118,7 +118,7 @@ export function SwissBracket({
|
||||
</SendouButton>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
) : null}
|
||||
<div className="stack lg">
|
||||
{rounds.map((round, roundI) => {
|
||||
const matches = bracket.data.match.filter(
|
||||
|
||||
@@ -293,9 +293,9 @@ function NewBadgeReceiversSelector({
|
||||
<div className="stack horizontal sm items-end">
|
||||
<ParticipationPill setResults={member.setResults} />
|
||||
</div>
|
||||
{i !== standingToReceive?.members.length - 1 && (
|
||||
{i !== standingToReceive?.members.length - 1 ? (
|
||||
<Divider className="mt-3" />
|
||||
)}
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
|
||||
@@ -424,12 +424,12 @@ function SeriesView({
|
||||
</div>
|
||||
</SendouTabPanel>
|
||||
<SendouTabPanel id="leaderboard">
|
||||
{hasLeaderboard && (
|
||||
{hasLeaderboard ? (
|
||||
<EventLeaderboard
|
||||
leaderboard={series.leaderboard!}
|
||||
ownEntry={series.ownEntry}
|
||||
/>
|
||||
)}
|
||||
) : null}
|
||||
</SendouTabPanel>
|
||||
</SendouTabs>
|
||||
</div>
|
||||
|
||||
@@ -37,7 +37,7 @@ export function UserResultsTable({
|
||||
<Table>
|
||||
<thead>
|
||||
<tr>
|
||||
{hasHighlightCheckboxes && <th />}
|
||||
{hasHighlightCheckboxes ? <th /> : null}
|
||||
<th id={placementHeaderId}>{t("results.placing")}</th>
|
||||
<th>{t("results.tournament")}</th>
|
||||
<th>{t("results.date")}</th>
|
||||
@@ -59,7 +59,7 @@ export function UserResultsTable({
|
||||
|
||||
return (
|
||||
<tr key={rowId}>
|
||||
{hasHighlightCheckboxes && (
|
||||
{hasHighlightCheckboxes ? (
|
||||
<td>
|
||||
<input
|
||||
value={result.teamId}
|
||||
@@ -73,7 +73,7 @@ export function UserResultsTable({
|
||||
defaultChecked={Boolean(result.isHighlight)}
|
||||
/>
|
||||
</td>
|
||||
)}
|
||||
) : null}
|
||||
<td className="pl-4 whitespace-nowrap" id={placementCellId}>
|
||||
<div className="stack horizontal xs items-end">
|
||||
<Placement placement={result.placement} />{" "}
|
||||
|
||||
@@ -259,7 +259,7 @@ function Document({
|
||||
<Fonts />
|
||||
</head>
|
||||
<body>
|
||||
{IS_E2E_TEST_RUN && <HydrationTestIndicator />}
|
||||
{IS_E2E_TEST_RUN ? <HydrationTestIndicator /> : null}
|
||||
<React.StrictMode>
|
||||
<SearchParamsProvider>
|
||||
<SendouToastRegion />
|
||||
|
||||
18
biome-plugins/no-and-conditional-rendering.grit
Normal file
18
biome-plugins/no-and-conditional-rendering.grit
Normal file
@@ -0,0 +1,18 @@
|
||||
language js
|
||||
|
||||
// `cond && <X />` renders the left value when it is falsy but not `null` /
|
||||
// `undefined` / `false`: a `0` count or an empty string ends up in the DOM as
|
||||
// text. The ternary form has no such edge, so it is the repo-wide convention
|
||||
// (AGENTS.md, "React"). Matches both `cond && <jsx/>` anywhere and any `&&`
|
||||
// standing directly as a JSX child, which covers helpers like
|
||||
// `{icon && React.cloneElement(icon, {...})}`.
|
||||
`$cond && $rendered` as $expression where {
|
||||
or {
|
||||
$rendered <: or {
|
||||
JsxTagExpression(),
|
||||
JsParenthesizedExpression(expression=JsxTagExpression())
|
||||
},
|
||||
$expression <: within JsxExpressionChild(expression=$expression)
|
||||
},
|
||||
register_diagnostic(span=$expression, message="Do not use `&&` for conditional rendering: a falsy left side that is not `null`/`undefined`/`false` (a `0` count, an empty string, `NaN`) renders itself. Use a ternary instead: `cond ? <X /> : null`.", severity="error")
|
||||
}
|
||||
@@ -221,6 +221,10 @@
|
||||
{
|
||||
"includes": ["**", "!app/utils/kysely.server.ts"],
|
||||
"plugins": ["./biome-plugins/no-kysely-sqlite-helpers.grit"]
|
||||
},
|
||||
{
|
||||
"includes": ["app/**"],
|
||||
"plugins": ["./biome-plugins/no-and-conditional-rendering.grit"]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user