diff --git a/config/i18n.json b/config/i18n.json index f63cbc6af1..319c498d2c 100644 --- a/config/i18n.json +++ b/config/i18n.json @@ -1273,6 +1273,7 @@ "Triumphs": { "HideCompleted": "Hide completed triumphs", "RevealRedacted": "Reveal redacted triumphs", + "SortRecords": "Sort triumphs by completion", "GildingTriumph": "Gilding Triumph" }, "Vendors": { diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 2d4e54f847..a1811ee081 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -1,6 +1,7 @@ ## Next * You can now search for Emotes and Ghost Projections on the Records page. +* Added button to sort triumphs by completion. * Greatly expanded the "Randomize Loadout" feature. You can now randomize a Loadout's subclass and its configuration, weapons, armor, cosmetics, and armor mods. * Randomize them individually through the three dots in a Loadout section. * Randomize the entire Loadout using the "Randomize" button at the bottom of the Loadout drawer. diff --git a/src/app/records/PresentationNode.tsx b/src/app/records/PresentationNode.tsx index 152a14747a..5e5aa00b77 100644 --- a/src/app/records/PresentationNode.tsx +++ b/src/app/records/PresentationNode.tsx @@ -36,6 +36,7 @@ export default function PresentationNode({ const defs = useD2Definitions()!; const completedRecordsHidden = useSelector(settingSelector('completedRecordsHidden')); const redactedRecordsRevealed = useSelector(settingSelector('redactedRecordsRevealed')); + const sortRecordProgression = useSelector(settingSelector('sortRecordProgression')); const presentationNodeHash = node.hash; const headerRef = useScrollNodeIntoView(path, presentationNodeHash); @@ -129,6 +130,7 @@ export default function PresentationNode({ ownedItemHashes={ownedItemHashes} completedRecordsHidden={completedRecordsHidden} redactedRecordsRevealed={redactedRecordsRevealed} + sortRecordProgression={sortRecordProgression} /> )} diff --git a/src/app/records/PresentationNodeLeaf.tsx b/src/app/records/PresentationNodeLeaf.tsx index a8b4b6c1be..6bb2239987 100644 --- a/src/app/records/PresentationNodeLeaf.tsx +++ b/src/app/records/PresentationNodeLeaf.tsx @@ -1,10 +1,17 @@ import { VendorItemDisplay } from 'app/vendors/VendorItemComponent'; +import { DestinyCollectibleState, DestinyRecordState } from 'bungie-api-ts/destiny2'; +import { sortBy } from 'lodash'; import Collectible from './Collectible'; import CollectiblesGrid from './CollectiblesGrid'; import Craftable from './Craftable'; import Metrics from './Metrics'; import { RecordGrid } from './Record'; -import { DimPresentationNodeLeaf } from './presentation-nodes'; +import { + DimCollectible, + DimMetric, + DimPresentationNodeLeaf, + DimRecord, +} from './presentation-nodes'; /** * Displays "leaf node" contents for presentation nodes (collectibles, triumphs, metrics) @@ -14,35 +21,41 @@ export default function PresentationNodeLeaf({ ownedItemHashes, completedRecordsHidden, redactedRecordsRevealed, + sortRecordProgression, }: { node: DimPresentationNodeLeaf; ownedItemHashes?: Set; completedRecordsHidden: boolean; redactedRecordsRevealed: boolean; + sortRecordProgression: boolean; }) { return ( <> {node.collectibles && node.collectibles.length > 0 && ( - {node.collectibles.map((collectible) => ( - - ))} + {(sortRecordProgression ? sortCollectibles(node.collectibles) : node.collectibles).map( + (collectible) => ( + + ) + )} )} {node.records && node.records.length > 0 && ( )} - {node.metrics && node.metrics.length > 0 && } + {node.metrics && node.metrics.length > 0 && ( + + )} {node.craftables && node.craftables.length > 0 && ( @@ -62,3 +75,53 @@ export default function PresentationNodeLeaf({ ); } + +function sortRecords(records: DimRecord[]): DimRecord[] { + return sortBy(records, (record) => { + // Triumph is already completed so move it to back of list. + if ( + record.recordComponent.state & DestinyRecordState.RecordRedeemed || + record.recordComponent.state & DestinyRecordState.CanEquipTitle || + !record.recordComponent.state + ) { + return -1; + } + + // check which key is used to track progress + let objectives; + if (record.recordComponent.intervalObjectives) { + objectives = record.recordComponent.intervalObjectives; + } else if (record.recordComponent.objectives) { + objectives = record.recordComponent.objectives; + } else { + // its a legacy triumph so it has no objectives and is not completed + return 0; + } + + // Sum up the progress + let totalProgress = 0; + for (const x of objectives) { + totalProgress += Math.min(1, x.progress! / x.completionValue); + } + return totalProgress / objectives.length; + }).reverse(); +} + +function sortCollectibles(collectibles: DimCollectible[]): DimCollectible[] { + return sortBy(collectibles, (collectible) => { + if (collectible.state & DestinyCollectibleState.NotAcquired) { + return -1; + } + return 0; + }); +} + +function sortMetrics(metrics: DimMetric[]): DimMetric[] { + return sortBy(metrics, (metric) => { + const objectives = metric.metricComponent.objectiveProgress; + if (objectives.complete) { + return -1; + } + return objectives.progress! / objectives.completionValue; + }).reverse(); +} diff --git a/src/app/records/PresentationNodeSearchResults.tsx b/src/app/records/PresentationNodeSearchResults.tsx index 8716d2e06d..16741fab21 100644 --- a/src/app/records/PresentationNodeSearchResults.tsx +++ b/src/app/records/PresentationNodeSearchResults.tsx @@ -18,7 +18,7 @@ export default function PresentationNodeSearchResults({ // TODO: make each node in path linkable const completedRecordsHidden = useSelector(settingSelector('completedRecordsHidden')); const redactedRecordsRevealed = useSelector(settingSelector('redactedRecordsRevealed')); - + const sortRecordProgression = useSelector(settingSelector('sortRecordProgression')); return (
{searchResults.map((sr) => ( @@ -46,6 +46,7 @@ export default function PresentationNodeSearchResults({ ownedItemHashes={ownedItemHashes} completedRecordsHidden={completedRecordsHidden} redactedRecordsRevealed={redactedRecordsRevealed} + sortRecordProgression={sortRecordProgression} /> ); })()} @@ -54,6 +55,7 @@ export default function PresentationNodeSearchResults({ ownedItemHashes={ownedItemHashes} completedRecordsHidden={completedRecordsHidden} redactedRecordsRevealed={redactedRecordsRevealed} + sortRecordProgression={sortRecordProgression} />
diff --git a/src/app/records/Records.tsx b/src/app/records/Records.tsx index 2c3f5ebc0c..149f572b60 100644 --- a/src/app/records/Records.tsx +++ b/src/app/records/Records.tsx @@ -49,6 +49,7 @@ export default function Records({ account }: Props) { const [completedRecordsHidden, setCompletedRecordsHidden] = useSetting('completedRecordsHidden'); const [redactedRecordsRevealed, setRedactedRecordsRevealed] = useSetting('redactedRecordsRevealed'); + const [sortRecordProgression, setSortRecordProgression] = useSetting('sortRecordProgression'); const defs = useD2Definitions(); @@ -107,7 +108,7 @@ export default function Records({ account }: Props) { const onToggleCompletedRecordsHidden = (checked: boolean) => setCompletedRecordsHidden(checked); const onToggleRedactedRecordsRevealed = (checked: boolean) => setRedactedRecordsRevealed(checked); - + const onToggleSortRecordProgression = (checked: boolean) => setSortRecordProgression(checked); return ( @@ -135,6 +136,13 @@ export default function Records({ account }: Props) { > {t('Triumphs.RevealRedacted')} + + {t('Triumphs.SortRecords')} + diff --git a/src/app/settings/initial-settings.ts b/src/app/settings/initial-settings.ts index 165a9a9df4..61ac607f26 100644 --- a/src/app/settings/initial-settings.ts +++ b/src/app/settings/initial-settings.ts @@ -8,6 +8,7 @@ export interface Settings extends DimApiSettings { language: DimLanguage; loIncludeVendorItems: boolean; theme: string; + sortRecordProgression: boolean; } export const initialSettingsState: Settings = { @@ -15,4 +16,5 @@ export const initialSettingsState: Settings = { loIncludeVendorItems: false, language: defaultLanguage(), theme: 'default', + sortRecordProgression: false, }; diff --git a/src/locale/en.json b/src/locale/en.json index 41846d2540..c1bee18217 100644 --- a/src/locale/en.json +++ b/src/locale/en.json @@ -1262,7 +1262,8 @@ "Triumphs": { "GildingTriumph": "Gilding Triumph", "HideCompleted": "Hide completed triumphs", - "RevealRedacted": "Reveal redacted triumphs" + "RevealRedacted": "Reveal redacted triumphs", + "SortRecords": "Sort triumphs by completion" }, "Vendors": { "Collections": "Collections",