diff --git a/config/i18n.json b/config/i18n.json index 0a6d4d040a..c09b7e811c 100644 --- a/config/i18n.json +++ b/config/i18n.json @@ -616,6 +616,7 @@ "SelectModsCount": "{{selected}}/{{maxSelectable}}", "SelectModsCountActivityMods": "{{selected}}/{{maxSelectable}} Activity Mods", "SelectExotic": "Select exotic", + "SelectPerks": "Select perks", "SelectMods": "Select Mods", "SelectSetBonus": "Select Set Bonuses", "AddStack": "Add another copy of this mod", diff --git a/src/app/loadout-builder/LoadoutBuilder.tsx b/src/app/loadout-builder/LoadoutBuilder.tsx index 1c3dd3c99c..3940636592 100644 --- a/src/app/loadout-builder/LoadoutBuilder.tsx +++ b/src/app/loadout-builder/LoadoutBuilder.tsx @@ -131,6 +131,8 @@ export default memo(function LoadoutBuilder({ const loadoutParameters = loadout.parameters!; const lockedExoticHash = loadoutParameters.exoticArmorHash; + const exoticPerk1 = loadoutParameters.perks?.[0]; + const exoticPerk2 = loadoutParameters.perks?.[1]; const statConstraints = loadoutParameters.statConstraints!; const autoStatMods = Boolean(loadoutParameters.autoStatMods); const includeRuntimeStatBenefits = loadoutParameters.includeRuntimeStatBenefits ?? true; diff --git a/src/app/loadout-builder/filter/ExoticPicker.m.scss b/src/app/loadout-builder/filter/ExoticPicker.m.scss index eed3c25a6b..b1e6d93f3b 100644 --- a/src/app/loadout-builder/filter/ExoticPicker.m.scss +++ b/src/app/loadout-builder/filter/ExoticPicker.m.scss @@ -4,3 +4,20 @@ padding: 10px 10px 16px 10px; gap: 14px; } + +.footer { + composes: flexRow from '../../dim-ui/common.m.scss'; + align-items: center; +} + +.submitButton { + composes: dim-button from global; + padding-right: 16px; + padding-left: 16px; + margin-right: 8px; +} + +.selectedPerks { + --item-size: 32px; + gap: 10px; +} diff --git a/src/app/loadout-builder/filter/ExoticPicker.m.scss.d.ts b/src/app/loadout-builder/filter/ExoticPicker.m.scss.d.ts index a86ddf1ff2..cdc94a9b5e 100644 --- a/src/app/loadout-builder/filter/ExoticPicker.m.scss.d.ts +++ b/src/app/loadout-builder/filter/ExoticPicker.m.scss.d.ts @@ -2,6 +2,9 @@ // Please do not change this file! interface CssExports { 'container': string; + 'footer': string; + 'selectedPerks': string; + 'submitButton': string; } export const cssExports: CssExports; export default cssExports; diff --git a/src/app/loadout-builder/filter/ExoticPicker.tsx b/src/app/loadout-builder/filter/ExoticPicker.tsx index 1d73b79720..18275241ce 100644 --- a/src/app/loadout-builder/filter/ExoticPicker.tsx +++ b/src/app/loadout-builder/filter/ExoticPicker.tsx @@ -1,18 +1,25 @@ import { D2ManifestDefinitions } from 'app/destiny2/d2-definitions'; import { languageSelector } from 'app/dim-api/selectors'; +import { PressTip } from 'app/dim-ui/PressTip'; import Sheet from 'app/dim-ui/Sheet'; -import { TileGrid } from 'app/dim-ui/TileGrid'; +import { SheetHorizontalScrollContainer } from 'app/dim-ui/SheetHorizontalScrollContainer'; +import { TileGrid, TileGridTile } from 'app/dim-ui/TileGrid'; +import { useHotkey } from 'app/hotkeys/useHotkey'; import { DimLanguage } from 'app/i18n'; import { t } from 'app/i18next-t'; import { DimItem } from 'app/inventory/item-types'; +import { DefItemIcon } from 'app/inventory/ItemIcon'; import { allItemsSelector } from 'app/inventory/selectors'; +import { PlugDefTooltip } from 'app/item-popup/PlugTooltip'; import { isLoadoutBuilderItem } from 'app/loadout/loadout-item-utils'; import { useD2Definitions } from 'app/manifest/selectors'; import { SearchInput } from 'app/search/SearchInput'; import { startWordRegexp } from 'app/search/text-utils'; +import { useIsPhonePortrait } from 'app/shell/selectors'; import { uniqBy } from 'app/utils/collections'; import { compareBy, compareByIndex } from 'app/utils/comparators'; import { + getExtraIntrinsicPerkSockets, socketContainsIntrinsicPlug, socketContainsPlugWithCategory, } from 'app/utils/socket-utils'; @@ -243,3 +250,165 @@ export default function ExoticPicker({ ); } + +export function ExoticPerkPicker({ + lockedExoticHash, + onSelected, + onClose, +}: { + lockedExoticHash?: number; + onSelected: (selectedPerk1: number, selectedPerk2: number) => void; + onClose: () => void; +}) { + const defs = useD2Definitions()!; + const [selectedPerk1, setSelectedPerk1] = useState(0); + const [selectedPerk2, setSelectedPerk2] = useState(0); + + const allItems = useSelector(allItemsSelector).filter((item) => item.hash === lockedExoticHash); + + const row1Perks = new Map>(); + const row2Perks = new Map>(); + for (const item of allItems) { + const perks = getExtraIntrinsicPerkSockets(item); + if ( + perks && + perks.length === 2 && + perks[0].plugged?.plugDef.hash && + perks[1].plugged?.plugDef.hash + ) { + row1Perks.set( + perks[0].plugged.plugDef.hash, + row1Perks.get(perks[0].plugged.plugDef.hash) ?? new Set(), + ); + row1Perks.get(perks[0].plugged.plugDef.hash)!.add(perks[1].plugged.plugDef.hash); + row2Perks.set( + perks[1].plugged.plugDef.hash, + row2Perks.get(perks[1].plugged.plugDef.hash) ?? new Set(), + ); + row2Perks.get(perks[1].plugged.plugDef.hash)!.add(perks[0].plugged.plugDef.hash); + } + } + + const handlePerk1Click = (perkHash: number) => () => { + setSelectedPerk1((perk1) => { + if (perk1 === perkHash) { + return 0; + } + return perkHash; + }); + }; + const handlePerk2Click = (perkHash: number) => () => { + setSelectedPerk2((perk2) => { + if (perk2 === perkHash) { + return 0; + } + return perkHash; + }); + }; + + const footer = ({ onClose }: { onClose: () => void }) => ( +