├── .eslintignore ├── .eslintrc ├── .prettierrc ├── .DS_Store ├── public ├── _headers ├── _redirects ├── favicon.ico ├── favicon-16.png ├── favicon-32.png ├── favicon-64.png ├── android-196.png ├── apple-touch-180.png ├── notfound.html ├── nhg │ ├── NHaasGroteskDSPro-25Th.woff2 │ ├── NHaasGroteskDSPro-26ThIt.woff2 │ ├── NHaasGroteskDSPro-35XLt.woff2 │ ├── NHaasGroteskDSPro-45Lt.woff2 │ ├── NHaasGroteskDSPro-46LtIt.woff2 │ ├── NHaasGroteskDSPro-55Rg.woff2 │ ├── NHaasGroteskDSPro-56It.woff2 │ ├── NHaasGroteskDSPro-65Md.woff2 │ ├── NHaasGroteskDSPro-66MdIt.woff2 │ ├── NHaasGroteskDSPro-75Bd.woff2 │ ├── NHaasGroteskDSPro-76BdIt.woff2 │ ├── NHaasGroteskDSPro-95Blk.woff2 │ ├── NHaasGroteskTXPro-55Rg.woff2 │ ├── NHaasGroteskTXPro-56It.woff2 │ ├── NHaasGroteskTXPro-65Md.woff2 │ ├── NHaasGroteskTXPro-66MdIt.woff2 │ ├── NHaasGroteskTXPro-75Bd.woff2 │ ├── NHaasGroteskTXPro-76BdIt.woff2 │ ├── NHaasGroteskDSPro-15UltTh.woff2 │ ├── NHaasGroteskDSPro-36XLtIt.woff2 │ ├── NHaasGroteskDSPro-96BlkIt.woff2 │ ├── NHaasGroteskDSPro-16UltThIt.woff2 │ └── style.css └── mask-icon.svg ├── src ├── .DS_Store ├── assets │ └── xur_icon.png ├── views │ ├── ItemPage │ │ ├── heavy.png │ │ ├── primary.png │ │ ├── special.png │ │ ├── ammoTypes.js │ │ ├── selectors.js │ │ └── styles.styl │ ├── Inventory │ │ ├── solstice.jpg │ │ └── styles.styl │ ├── Loading │ │ ├── styles.styl │ │ └── index.js │ ├── Mods │ │ ├── detailedMod.styl │ │ ├── DetailedMod.js │ │ └── styles.styl │ ├── DataExplorerRedirect.js │ ├── App │ │ └── styles.styl │ ├── Triumphs │ │ ├── styles.styl │ │ └── index.js │ └── SolsticeOfHeroes │ │ └── styles.styl ├── components │ ├── ItemAttributes │ │ ├── heavy.png │ │ ├── primary.png │ │ ├── special.png │ │ ├── styles.styl │ │ └── index.js │ ├── Record │ │ ├── 037e-0000150c.png │ │ ├── 037e-00001685.png │ │ ├── 037e-00001686.png │ │ ├── index.js │ │ └── styles.styl │ ├── Item │ │ ├── masterwork-outline.png │ │ ├── selectors.js │ │ └── styles.styl │ ├── Footer │ │ ├── styles.styl │ │ └── index.js │ ├── MasterworkCatalyst │ │ ├── masterwork-hammer.png │ │ ├── styles.styl │ │ ├── index.js │ │ └── Debug.js │ ├── NewFilterBar │ │ ├── styles.styl │ │ └── index.js │ ├── LoginUpsell │ │ ├── styles.styl │ │ └── index.js │ ├── CTA │ │ ├── styles.styl │ │ └── index.js │ ├── BungieImage.js │ ├── LoginCTA │ │ └── index.js │ ├── DropdownMenu │ │ ├── styles.styl │ │ └── index.js │ ├── Dismissable │ │ ├── styles.styl │ │ └── index.js │ ├── StatTrack │ │ └── index.js │ ├── Search │ │ ├── index.js │ │ └── styles.styl │ ├── ItemPerks │ │ ├── styles.styl │ │ └── index.js │ ├── ExtraInfo │ │ ├── styles.styl │ │ └── index.js │ ├── DonateButton │ │ ├── index.js │ │ └── styles.styl │ ├── Modal.js │ ├── FancyImage │ │ └── index.js │ ├── Header │ │ ├── dropdownStyles.styl │ │ ├── LanguageDropdown.js │ │ ├── ProfileDropdown.js │ │ └── styles.styl │ ├── Section │ │ ├── styles.styl │ │ └── index.js │ ├── ChaliceRecipie │ │ ├── styles.styl │ │ └── index.js │ ├── ItemStats │ │ ├── styles.styl │ │ └── index.js │ ├── PresentationNode │ │ ├── styles.styl │ │ └── index.js │ ├── Objectives │ │ └── styles.styl │ ├── ItemBanner │ │ ├── styles.styl │ │ └── index.js │ ├── I18NDefinitionsString.js │ ├── ItemBannerNew │ │ ├── styles.styl │ │ └── index.js │ ├── ItemTooltip │ │ └── styles.styl │ ├── SectionList │ │ ├── styles.styl │ │ ├── index.js │ │ └── FilterDropdown.js │ ├── Icon.js │ ├── Popper │ │ └── index.js │ ├── PresentationNodeChildren │ │ ├── styles.styl │ │ └── index.js │ ├── ItemSet │ │ └── styles.styl │ └── ItemModal │ │ └── styles.styl ├── lib │ ├── log.js │ ├── utils.js │ ├── analytics.js │ ├── copyToClipboard.js │ ├── telemetry.js │ ├── DestinyAuthProvider.js │ ├── getItemExtraInfo.js │ ├── destinyUtils.js │ ├── i18n.js │ ├── destinyEnums.js │ └── getFromProfile.js ├── extraData │ ├── classOverrides.js │ ├── chalice.js │ └── catalystTriumphs.js ├── index.styl ├── setData │ ├── utils.js │ ├── __tests__ │ │ └── checkSyntax.js │ ├── collections.js │ ├── common │ │ ├── index.js │ │ ├── eververse.js │ │ ├── yearOneRaids.js │ │ ├── ironBanner.js │ │ └── trials.js │ ├── allItemsDeluxe.js │ ├── index.js │ ├── catalysts.js │ ├── eververseAndEvents.js │ └── allItems.js ├── setupEnv.js ├── store │ ├── utils.js │ ├── auth.js │ ├── definitions.js │ ├── debugProxifyDefs.js │ ├── index.js │ ├── profile.js │ └── preloadStore.js ├── makeSplitComponent.js ├── types.js ├── index.js ├── logo.svg └── ishar.svg ├── postcss.config.js ├── .env.local-sample ├── tests └── setData.js ├── .editorconfig ├── .flowconfig ├── .circleci └── config.yml ├── scripts ├── dlc1GhostHashes.js ├── getArmorTypePerks.js └── imageScreenshotSizes.js ├── LICENSE ├── .gitignore ├── package.json ├── README.md └── config-overrides.js /.eslintignore: -------------------------------------------------------------------------------- 1 | autotrack.build.js 2 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "react-app" 3 | } 4 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "parser": "flow", 4 | } -------------------------------------------------------------------------------- /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joshhunt/destinySets/HEAD/.DS_Store -------------------------------------------------------------------------------- /public/_headers: -------------------------------------------------------------------------------- 1 | /static/* 2 | Cache-Control: public,max-age=31536000 3 | -------------------------------------------------------------------------------- /public/_redirects: -------------------------------------------------------------------------------- 1 | /static/* /notfound.html 404 2 | /* /index.html 200 3 | -------------------------------------------------------------------------------- /src/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joshhunt/destinySets/HEAD/src/.DS_Store -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = () => ({ 2 | plugins: { autoprefixer: {} } 3 | }); 4 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joshhunt/destinySets/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /public/favicon-16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joshhunt/destinySets/HEAD/public/favicon-16.png -------------------------------------------------------------------------------- /public/favicon-32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joshhunt/destinySets/HEAD/public/favicon-32.png -------------------------------------------------------------------------------- /public/favicon-64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joshhunt/destinySets/HEAD/public/favicon-64.png -------------------------------------------------------------------------------- /public/android-196.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joshhunt/destinySets/HEAD/public/android-196.png -------------------------------------------------------------------------------- /src/assets/xur_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joshhunt/destinySets/HEAD/src/assets/xur_icon.png -------------------------------------------------------------------------------- /public/apple-touch-180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joshhunt/destinySets/HEAD/public/apple-touch-180.png -------------------------------------------------------------------------------- /src/views/ItemPage/heavy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joshhunt/destinySets/HEAD/src/views/ItemPage/heavy.png -------------------------------------------------------------------------------- /src/views/ItemPage/primary.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joshhunt/destinySets/HEAD/src/views/ItemPage/primary.png -------------------------------------------------------------------------------- /src/views/ItemPage/special.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joshhunt/destinySets/HEAD/src/views/ItemPage/special.png -------------------------------------------------------------------------------- /public/notfound.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |

404 Not Found

5 | 6 | 7 | -------------------------------------------------------------------------------- /src/views/Inventory/solstice.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joshhunt/destinySets/HEAD/src/views/Inventory/solstice.jpg -------------------------------------------------------------------------------- /public/nhg/NHaasGroteskDSPro-25Th.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joshhunt/destinySets/HEAD/public/nhg/NHaasGroteskDSPro-25Th.woff2 -------------------------------------------------------------------------------- /public/nhg/NHaasGroteskDSPro-26ThIt.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joshhunt/destinySets/HEAD/public/nhg/NHaasGroteskDSPro-26ThIt.woff2 -------------------------------------------------------------------------------- /public/nhg/NHaasGroteskDSPro-35XLt.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joshhunt/destinySets/HEAD/public/nhg/NHaasGroteskDSPro-35XLt.woff2 -------------------------------------------------------------------------------- /public/nhg/NHaasGroteskDSPro-45Lt.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joshhunt/destinySets/HEAD/public/nhg/NHaasGroteskDSPro-45Lt.woff2 -------------------------------------------------------------------------------- /public/nhg/NHaasGroteskDSPro-46LtIt.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joshhunt/destinySets/HEAD/public/nhg/NHaasGroteskDSPro-46LtIt.woff2 -------------------------------------------------------------------------------- /public/nhg/NHaasGroteskDSPro-55Rg.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joshhunt/destinySets/HEAD/public/nhg/NHaasGroteskDSPro-55Rg.woff2 -------------------------------------------------------------------------------- /public/nhg/NHaasGroteskDSPro-56It.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joshhunt/destinySets/HEAD/public/nhg/NHaasGroteskDSPro-56It.woff2 -------------------------------------------------------------------------------- /public/nhg/NHaasGroteskDSPro-65Md.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joshhunt/destinySets/HEAD/public/nhg/NHaasGroteskDSPro-65Md.woff2 -------------------------------------------------------------------------------- /public/nhg/NHaasGroteskDSPro-66MdIt.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joshhunt/destinySets/HEAD/public/nhg/NHaasGroteskDSPro-66MdIt.woff2 -------------------------------------------------------------------------------- /public/nhg/NHaasGroteskDSPro-75Bd.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joshhunt/destinySets/HEAD/public/nhg/NHaasGroteskDSPro-75Bd.woff2 -------------------------------------------------------------------------------- /public/nhg/NHaasGroteskDSPro-76BdIt.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joshhunt/destinySets/HEAD/public/nhg/NHaasGroteskDSPro-76BdIt.woff2 -------------------------------------------------------------------------------- /public/nhg/NHaasGroteskDSPro-95Blk.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joshhunt/destinySets/HEAD/public/nhg/NHaasGroteskDSPro-95Blk.woff2 -------------------------------------------------------------------------------- /public/nhg/NHaasGroteskTXPro-55Rg.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joshhunt/destinySets/HEAD/public/nhg/NHaasGroteskTXPro-55Rg.woff2 -------------------------------------------------------------------------------- /public/nhg/NHaasGroteskTXPro-56It.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joshhunt/destinySets/HEAD/public/nhg/NHaasGroteskTXPro-56It.woff2 -------------------------------------------------------------------------------- /public/nhg/NHaasGroteskTXPro-65Md.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joshhunt/destinySets/HEAD/public/nhg/NHaasGroteskTXPro-65Md.woff2 -------------------------------------------------------------------------------- /public/nhg/NHaasGroteskTXPro-66MdIt.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joshhunt/destinySets/HEAD/public/nhg/NHaasGroteskTXPro-66MdIt.woff2 -------------------------------------------------------------------------------- /public/nhg/NHaasGroteskTXPro-75Bd.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joshhunt/destinySets/HEAD/public/nhg/NHaasGroteskTXPro-75Bd.woff2 -------------------------------------------------------------------------------- /public/nhg/NHaasGroteskTXPro-76BdIt.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joshhunt/destinySets/HEAD/public/nhg/NHaasGroteskTXPro-76BdIt.woff2 -------------------------------------------------------------------------------- /src/components/ItemAttributes/heavy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joshhunt/destinySets/HEAD/src/components/ItemAttributes/heavy.png -------------------------------------------------------------------------------- /src/components/ItemAttributes/primary.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joshhunt/destinySets/HEAD/src/components/ItemAttributes/primary.png -------------------------------------------------------------------------------- /src/components/ItemAttributes/special.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joshhunt/destinySets/HEAD/src/components/ItemAttributes/special.png -------------------------------------------------------------------------------- /src/components/Record/037e-0000150c.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joshhunt/destinySets/HEAD/src/components/Record/037e-0000150c.png -------------------------------------------------------------------------------- /src/components/Record/037e-00001685.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joshhunt/destinySets/HEAD/src/components/Record/037e-00001685.png -------------------------------------------------------------------------------- /src/components/Record/037e-00001686.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joshhunt/destinySets/HEAD/src/components/Record/037e-00001686.png -------------------------------------------------------------------------------- /public/nhg/NHaasGroteskDSPro-15UltTh.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joshhunt/destinySets/HEAD/public/nhg/NHaasGroteskDSPro-15UltTh.woff2 -------------------------------------------------------------------------------- /public/nhg/NHaasGroteskDSPro-36XLtIt.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joshhunt/destinySets/HEAD/public/nhg/NHaasGroteskDSPro-36XLtIt.woff2 -------------------------------------------------------------------------------- /public/nhg/NHaasGroteskDSPro-96BlkIt.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joshhunt/destinySets/HEAD/public/nhg/NHaasGroteskDSPro-96BlkIt.woff2 -------------------------------------------------------------------------------- /src/components/Item/masterwork-outline.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joshhunt/destinySets/HEAD/src/components/Item/masterwork-outline.png -------------------------------------------------------------------------------- /.env.local-sample: -------------------------------------------------------------------------------- 1 | REACT_APP_API_KEY=... 2 | REACT_APP_BUNGIE_CLIENT_ID=... 3 | REACT_APP_BUNGIE_CLIENT_SECRET=... 4 | HTTPS=true 5 | PORT=4000 6 | -------------------------------------------------------------------------------- /public/nhg/NHaasGroteskDSPro-16UltThIt.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joshhunt/destinySets/HEAD/public/nhg/NHaasGroteskDSPro-16UltThIt.woff2 -------------------------------------------------------------------------------- /src/lib/log.js: -------------------------------------------------------------------------------- 1 | const debug = require('debug'); 2 | 3 | module.exports = function(logName) { 4 | return debug(`destinySets:${logName}`); 5 | }; 6 | -------------------------------------------------------------------------------- /src/components/Footer/styles.styl: -------------------------------------------------------------------------------- 1 | .footer 2 | font-size: 15px 3 | text-align: center 4 | padding: 10px 10px 25px 10px 5 | 6 | a 7 | color: inherit 8 | -------------------------------------------------------------------------------- /src/components/MasterworkCatalyst/masterwork-hammer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joshhunt/destinySets/HEAD/src/components/MasterworkCatalyst/masterwork-hammer.png -------------------------------------------------------------------------------- /tests/setData.js: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | 3 | import '../src/setData'; 4 | 5 | test(`all setData should be valid javascript`, t => { 6 | t.pass(); 7 | }); 8 | -------------------------------------------------------------------------------- /src/lib/utils.js: -------------------------------------------------------------------------------- 1 | import { get } from 'lodash'; 2 | 3 | export function getLower(obj, path, fallback = '') { 4 | return get(obj, path, fallback).toLowerCase(); 5 | } 6 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | charset = utf-8 6 | trim_trailing_whitespace = true 7 | insert_final_newline = true 8 | indent_style = space 9 | indent_size = 2 10 | -------------------------------------------------------------------------------- /src/extraData/classOverrides.js: -------------------------------------------------------------------------------- 1 | import { HUNTER, TITAN, WARLOCK } from 'app/lib/destinyEnums'; 2 | 3 | export default { 4 | 1907674137: WARLOCK, 5 | 1907674138: HUNTER, 6 | 1907674139: TITAN 7 | }; 8 | -------------------------------------------------------------------------------- /src/index.styl: -------------------------------------------------------------------------------- 1 | body 2 | margin: 0 3 | padding: 0 4 | font-family: sans-serif 5 | background: rgba(18/2, 22/2, 27/2, 1.0) 6 | color: white 7 | 8 | * 9 | font-family: 'Titillium Web', sans-serif 10 | box-sizing: border-box 11 | -------------------------------------------------------------------------------- /src/components/NewFilterBar/styles.styl: -------------------------------------------------------------------------------- 1 | .root 2 | display: flex 3 | flex-wrap: wrap 4 | margin: 0 -10px 5 | 6 | .filterItem 7 | margin: 0 10px 8 | display: flex 9 | align-items: center 10 | 11 | .checkbox 12 | margin-right: .5em 13 | -------------------------------------------------------------------------------- /.flowconfig: -------------------------------------------------------------------------------- 1 | [ignore] 2 | .*/node_modules/config-chain/test/broken.json 3 | .*/node_modules/protobufjs/src/bower.json 4 | .*/node_modules/npmconf/test/.* 5 | 6 | [include] 7 | 8 | [libs] 9 | 10 | [lints] 11 | 12 | [options] 13 | 14 | [strict] 15 | -------------------------------------------------------------------------------- /src/setData/utils.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import type { I18nDefinitionString } from '../types'; 3 | 4 | export const i18nDefinitionString = ( 5 | path: string, 6 | fallback: string 7 | ): I18nDefinitionString => { 8 | return { path, fallback }; 9 | }; 10 | -------------------------------------------------------------------------------- /src/setupEnv.js: -------------------------------------------------------------------------------- 1 | window.DESTINYSETS_ENV = 'prod'; 2 | if (window.location.href.includes('localhost')) { 3 | window.DESTINYSETS_ENV = 'dev'; 4 | } 5 | 6 | if (window.DESTINYSETS_ENV !== 'prod') { 7 | localStorage.debug = localStorage.debug || 'destinySets:*'; 8 | } 9 | -------------------------------------------------------------------------------- /src/components/LoginUpsell/styles.styl: -------------------------------------------------------------------------------- 1 | .root 2 | background: #2c3e50 3 | text-align: center 4 | padding: 25px 5 | font-size: 18px 6 | 7 | .heading 8 | font-weight: 400 9 | font-size: 22px 10 | margin: 0 0 10px 0 11 | 12 | .children 13 | margin-bottom: 20px 14 | -------------------------------------------------------------------------------- /src/components/CTA/styles.styl: -------------------------------------------------------------------------------- 1 | .cta 2 | color: black !important 3 | background: rgba(208, 170, 11, 1.0) 4 | padding: 10px 20px 5 | text-decoration: none 6 | transition: 150ms ease-in-out 7 | display: inline-block 8 | text-align: center 9 | 10 | &:hover 11 | background: #ffce1f -------------------------------------------------------------------------------- /src/lib/analytics.js: -------------------------------------------------------------------------------- 1 | const log = require('app/lib/log')('analytics'); 2 | 3 | export function trackEvent(...args) { 4 | try { 5 | log(`Track event ${args.join('|')}`); 6 | window.ga('send', 'event', ...args); 7 | } catch (err) { 8 | console.error('Unable to trackEvent', err); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/components/BungieImage.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export default function BungieImage({ src, ...props }) { 4 | if (!src) { 5 | return null; 6 | } 7 | const url = src.includes('//bungie.net/') ? src : `https://bungie.net/${src}`; 8 | return ; 9 | } 10 | -------------------------------------------------------------------------------- /src/lib/copyToClipboard.js: -------------------------------------------------------------------------------- 1 | export default function copy(str) { 2 | function listener(e) { 3 | e.clipboardData.setData('text/plain', str); 4 | e.preventDefault(); 5 | } 6 | document.addEventListener('copy', listener); 7 | document.execCommand('copy'); 8 | document.removeEventListener('copy', listener); 9 | } 10 | -------------------------------------------------------------------------------- /src/store/utils.js: -------------------------------------------------------------------------------- 1 | export function makeSimpleAction(type) { 2 | return () => ({ type }); 3 | } 4 | 5 | export function makePayloadAction(type) { 6 | return payload => ({ type, payload }); 7 | } 8 | 9 | export function toggle(state, field) { 10 | return { 11 | ...state, 12 | [field]: !state[field] 13 | }; 14 | } 15 | -------------------------------------------------------------------------------- /src/components/LoginCTA/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { authUrl } from 'app/lib/destinyAuth'; 3 | import CTA from '../CTA'; 4 | 5 | export default function LoginCTA({ children, ...props }) { 6 | return ( 7 | 8 | {children || 'Connect Bungie.net'} 9 | 10 | ); 11 | } 12 | -------------------------------------------------------------------------------- /src/components/CTA/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import classNames from 'classnames'; 4 | import styles from './styles.styl'; 5 | 6 | export default function CTA({ children, className, ...props }) { 7 | return ( 8 | 9 | {children} 10 | 11 | ); 12 | } 13 | -------------------------------------------------------------------------------- /src/setData/__tests__/checkSyntax.js: -------------------------------------------------------------------------------- 1 | const SET_FILES = [ 2 | 'yearOne', 3 | 'dlc1', 4 | 'dlc2', 5 | 'baseGame', 6 | 'allItems', 7 | 'allItemsDeluxe', 8 | 'strikeGear', 9 | 'common' 10 | ]; 11 | 12 | SET_FILES.forEach(setFile => { 13 | test(`${setFile} should be valid syntax`, () => { 14 | require(`../${setFile}`); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /src/components/DropdownMenu/styles.styl: -------------------------------------------------------------------------------- 1 | .root 2 | position: relative 3 | 4 | &.isOpen 5 | background: #12161c 6 | 7 | .inline 8 | content: 'inline' 9 | 10 | .content 11 | position: absolute 12 | top: 100% 13 | right: 0 14 | 15 | .inline & 16 | position: static 17 | width: 100% 18 | box-shadow: none 19 | background: rgba(white, .05) 20 | -------------------------------------------------------------------------------- /src/setData/collections.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import type { SetPage } from '../types'; 4 | 5 | export default ([ 6 | { 7 | name: 'Collections', 8 | sets: [ 9 | { 10 | name: 'Collections', 11 | id: 'COLLECTIONS_COLLECTIONS', 12 | big: true, 13 | query: 'special:tempCollections', 14 | sections: [] 15 | } 16 | ] 17 | } 18 | ]: SetPage); 19 | -------------------------------------------------------------------------------- /src/components/Dismissable/styles.styl: -------------------------------------------------------------------------------- 1 | .close 2 | background: none 3 | padding: 2px 4 | outline: none !important 5 | cursor: pointer 6 | appearance: none 7 | border: none 8 | font-size: 1em 9 | color: white 10 | line-height: 1 11 | position: absolute 12 | top: 0 13 | right: 0 14 | padding: 15px 15 | transition: 150ms ease-in-out background 16 | 17 | &:hover 18 | background: rgba(white, .25) 19 | -------------------------------------------------------------------------------- /src/views/Loading/styles.styl: -------------------------------------------------------------------------------- 1 | .root 2 | display: flex 3 | min-height: 100vh 4 | justify-content: center 5 | align-items: center 6 | margin: 0 7 | flex-direction: column 8 | 9 | .content 10 | font-size: 20px 11 | display: flex 12 | justify-content: center 13 | flex-direction: column 14 | align-items: center 15 | margin: 0 16 | flex: 1 1 auto 17 | width: 100% 18 | 19 | .topPart 20 | width: 100% 21 | -------------------------------------------------------------------------------- /src/components/StatTrack/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export default function StatTrack(props) { 4 | const { className, objective, def } = props; 5 | 6 | let value = 7 | ((objective || { progress: 0 }).progress || 0) / def.completionValue; 8 | value = +value.toFixed(2); 9 | 10 | return ( 11 |
12 | {value} {'//'} {def.progressDescription} 13 |
14 | ); 15 | } 16 | -------------------------------------------------------------------------------- /src/views/Mods/detailedMod.styl: -------------------------------------------------------------------------------- 1 | .root 2 | display: flex 3 | 4 | .accessory 5 | flex: 0 6 | 7 | .main 8 | flex: 1 9 | padding-left: 5px 10 | margin-top: 2px 11 | 12 | .description 13 | font-size: .9em 14 | opacity: .9 15 | 16 | .energyType 17 | height: 1em 18 | vertical-align: middle 19 | 20 | .arc 21 | color: #79ebf3 22 | 23 | .solar 24 | color: #f36f26 25 | 26 | .void 27 | color: #b082cb 28 | 29 | .stasis 30 | color: #4e87ff -------------------------------------------------------------------------------- /src/setData/common/index.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import type { ItemsList, SetSection } from '../../types'; 3 | 4 | export * from './ironBanner'; 5 | export * from './eververse'; 6 | export * from './trials'; 7 | export * from './factions'; 8 | export * from './yearOneRaids'; 9 | export * from './vendors'; 10 | export * from './gambitPrimeStuff'; 11 | 12 | export const section = (name: string, items: ItemsList): SetSection => ({ 13 | name, 14 | items 15 | }); 16 | -------------------------------------------------------------------------------- /src/views/Loading/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import Header from 'app/components/Header'; 4 | import styles from './styles.styl'; 5 | 6 | export default function Loading({ children }) { 7 | return ( 8 |
9 |
10 |
11 |
12 | 13 |
{children}
14 |
15 | ); 16 | } 17 | -------------------------------------------------------------------------------- /src/components/LoginUpsell/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import styles from './styles.styl'; 4 | import LoginCTA from '../LoginCTA'; 5 | import Dismissable from '../Dismissable'; 6 | 7 | export default function LoginUpsell({ children, ...props }) { 8 | return ( 9 | 10 | {children &&
{children}
} 11 | 12 | 13 |
14 | ); 15 | } 16 | -------------------------------------------------------------------------------- /src/components/Search/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import cx from 'classnames'; 3 | 4 | import Icon from 'app/components/Icon'; 5 | 6 | import s from './styles.styl'; 7 | 8 | export default function Search({ className, ...props }) { 9 | return ( 10 |
11 | 12 | 13 |
14 | ); 15 | } 16 | -------------------------------------------------------------------------------- /src/components/ItemPerks/styles.styl: -------------------------------------------------------------------------------- 1 | .root 2 | content: 'root' 3 | 4 | .perk 5 | display: flex 6 | align-items: center 7 | 8 | .perkImage 9 | height: 35px 10 | width: 35px 11 | object-fit: contain 12 | display: block 13 | margin-left: 10px 14 | 15 | .main 16 | flex: 1 17 | margin-left: 15px 18 | 19 | .name 20 | line-height: 1 21 | margin-bottom: 4px 22 | 23 | .description 24 | margin: 0 25 | opacity: .7 26 | font-size: 14px 27 | line-height: 1.2 28 | -------------------------------------------------------------------------------- /src/store/auth.js: -------------------------------------------------------------------------------- 1 | const INITIAL_STORE = {}; 2 | 3 | const SET_AUTH_STATUS = 'Auth - set status'; 4 | 5 | export default function authReducer(state = INITIAL_STORE, action) { 6 | switch (action.type) { 7 | case SET_AUTH_STATUS: 8 | return { 9 | ...state, 10 | ...action.payload 11 | }; 12 | 13 | default: 14 | return state; 15 | } 16 | } 17 | 18 | export function setAuthStatus(authStatus) { 19 | return { type: SET_AUTH_STATUS, payload: authStatus }; 20 | } 21 | -------------------------------------------------------------------------------- /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | jobs: 3 | build: 4 | docker: 5 | - image: circleci/node:8.9.4 6 | working_directory: ~/repo 7 | steps: 8 | - checkout 9 | - restore_cache: 10 | keys: 11 | - v1-dependencies-{{ checksum "package.json" }} 12 | - v1-dependencies- 13 | 14 | - run: yarn install 15 | - save_cache: 16 | paths: 17 | - node_modules 18 | key: v1-dependencies-{{ checksum "package.json" }} 19 | - run: yarn test 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /src/setData/allItemsDeluxe.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import type { SetPage } from '../types'; 4 | 5 | export default ([ 6 | { 7 | name: 'Everything', 8 | sets: [ 9 | { 10 | name: 'Weapons', 11 | id: "DELUXE_WEAPONS", 12 | big: true, 13 | query: 'is:weapon', 14 | sections: [] 15 | }, 16 | 17 | { 18 | name: 'Armor', 19 | id: "DELUXE_ARMOR", 20 | big: true, 21 | query: 'is:armor', 22 | sections: [] 23 | } 24 | ] 25 | } 26 | ]: SetPage); 27 | -------------------------------------------------------------------------------- /src/components/ExtraInfo/styles.styl: -------------------------------------------------------------------------------- 1 | .tick 2 | position: relative 3 | top: -1px 4 | display: inline-block 5 | line-height: 1 6 | text-align: center 7 | border-radius: 50% 8 | font-size: 10px 9 | padding: 3px 10 | width: 15px 11 | height: 15px 12 | color: white 13 | 14 | .greenTick 15 | composes: tick 16 | background: #44bd32; 17 | 18 | .blueTick 19 | composes: tick 20 | background: #3c94ff 21 | 22 | .orangeTick 23 | composes: tick 24 | background: #e67e22 25 | 26 | .greyTick 27 | composes: tick 28 | background: #7f8fa6 29 | -------------------------------------------------------------------------------- /src/components/DonateButton/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import cx from 'classnames'; 3 | 4 | import Icon from 'app/components/Icon'; 5 | 6 | import styles from './styles.styl'; 7 | 8 | export const DONATION_LINK = 'https://paypal.me/DestinySets'; 9 | 10 | export default function DonateButton({ className }) { 11 | return ( 12 | 18 | Support and donate 19 | 20 | ); 21 | } 22 | -------------------------------------------------------------------------------- /src/views/DataExplorerRedirect.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | 3 | import { saveDataExplorerVisited } from 'app/lib/ls'; 4 | 5 | export default class DataExplorerRedirect extends Component { 6 | componentDidMount() { 7 | saveDataExplorerVisited(true); 8 | 9 | let url = 'https://data.destinysets.com'; 10 | 11 | if (this.props.params.itemHash) { 12 | url = `${url}/i/InventoryItem:${this.props.params.itemHash}`; 13 | } 14 | 15 | window.location.href = url; 16 | } 17 | 18 | render() { 19 | return

Redirecting to data.destinysets.com...

; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/makeSplitComponent.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import Loading from 'app/views/Loading'; 3 | 4 | export default function makeSplitComponent(getComponent) { 5 | class AsyncComponent extends Component { 6 | state = { View: null }; 7 | componentDidMount() { 8 | getComponent().then(module => { 9 | this.setState({ View: module.default }); 10 | }); 11 | } 12 | 13 | render() { 14 | if (this.state.View) { 15 | return ; 16 | } 17 | 18 | return Loading...; 19 | } 20 | } 21 | 22 | return AsyncComponent; 23 | } 24 | -------------------------------------------------------------------------------- /src/views/ItemPage/ammoTypes.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import s from './styles.styl'; 4 | 5 | export const AMMO_TYPE = { 6 | 0: None, 7 | 1: ( 8 | 9 | {' '} 10 | Primary 11 | 12 | ), 13 | 2: ( 14 | 15 | {' '} 16 | Special 17 | 18 | ), 19 | 3: ( 20 | 21 | Heavy 22 | 23 | ), 24 | 4: Unknown 25 | }; 26 | -------------------------------------------------------------------------------- /src/components/Modal.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Modal from 'react-modal'; 3 | 4 | const MODAL_STYLES = { 5 | overlay: { 6 | backgroundColor: 'rgba(0, 0, 0, 0.75)', 7 | marginTop: 0, 8 | display: 'flex', 9 | alignItems: 'center', 10 | justifyContent: 'center', 11 | zIndex: 9999999999 12 | }, 13 | content: { 14 | position: 'static', 15 | background: 'none', 16 | border: 'none', 17 | maxHeight: '100vh' 18 | } 19 | }; 20 | 21 | export default function DSModal({ children, ...props }) { 22 | return ( 23 | 24 | {children} 25 | 26 | ); 27 | } 28 | -------------------------------------------------------------------------------- /src/components/DonateButton/styles.styl: -------------------------------------------------------------------------------- 1 | a.root 2 | background: linear-gradient(to right top, rgba(#52A6D9, 1), rgba(#92BF71, 1)) 3 | padding: 5px 20px 4 | border-radius: 30px 5 | display: inline-block 6 | margin: 10px 0 26px 0 7 | opacity: 1 8 | color: white 9 | text-decoration: none 10 | transition: all 150ms ease-in-out 11 | filter: brightness(100%) contrast(100%) saturate(100%) 12 | 13 | box-shadow: 0 0 0px rgba(0,0,0,0.5); 14 | font-size: 16px 15 | text-shadow: 0 1px 3px rgba(black, 0.25) 16 | 17 | &:hover 18 | filter: brightness(112%) contrast(105%) saturate(105%) 19 | box-shadow: 0 0 20px rgba(black, 0.5) 20 | 21 | .icon 22 | margin-right: 5px 23 | -------------------------------------------------------------------------------- /scripts/dlc1GhostHashes.js: -------------------------------------------------------------------------------- 1 | module.exports = [ 2 | 573576346, // Sagira's Shell 3 | 1558857470, // Star Map Shell 4 | 1558857466, // Electronica Shell 5 | 1558857471, // Cosmos Shell 6 | 1558857468, // Fast Lane Shell 7 | 1558857469, // Fire Victorious Shell 8 | 3681086675, 9 | 3681086674, 10 | 3681086673, 11 | 3681086672, 12 | 1271045323, 13 | 1271045322, 14 | 1271045319, 15 | 1271045318, 16 | 1271045317, 17 | 1271045316, 18 | 1271045315, 19 | 1271045314, 20 | 1271045313, 21 | 1271045312, 22 | 89965919, 23 | 89965918, 24 | 89965911, 25 | 89965910, 26 | 89965909, 27 | 89965908, 28 | 89965907, 29 | 89965906, 30 | 89965905, 31 | 89965904 32 | ]; 33 | -------------------------------------------------------------------------------- /src/views/App/styles.styl: -------------------------------------------------------------------------------- 1 | .root 2 | content: 'root' 3 | 4 | .bottomMessages 5 | position fixed 6 | bottom: 0 7 | left: 0 8 | right: 0 9 | z-index: 99999999999999 10 | 11 | .auth 12 | background: #090B0F 13 | 14 | .error 15 | position: relative 16 | padding: 20px 17 | text-align: center 18 | background: #e74c3c 19 | color: #ecf0f1 20 | 21 | .errorTitle 22 | margin: 0 0 10px 0 23 | font-size: 26px 24 | 25 | .errorText 26 | font-size: 18px 27 | margin: 0 28 | 29 | .manifestUpdate 30 | padding: 10px 31 | text-align: center 32 | background: #BDC3C7 33 | color: #090B0E 34 | box-shadow: 0 0 20px -5px rgba(black, 0.75) 35 | 36 | .icon 37 | margin-right: .25ch 38 | -------------------------------------------------------------------------------- /src/views/Triumphs/styles.styl: -------------------------------------------------------------------------------- 1 | $baseBg = darken(#1a1f28, 18%); 2 | // $baseBg = #15191E; 3 | $step = 25%; 4 | 5 | .root 6 | padding: 15px 7 | padding-bottom: 100px 8 | 9 | .scoreBox 10 | margin: 10px auto 11 | text-align: center 12 | text-transform: uppercase 13 | margin-bottom: 30px 14 | 15 | .scoreTitle 16 | font-size: 15px 17 | opacity: .5 18 | 19 | .scoreScore 20 | font-size: 45px 21 | font-weight: bold 22 | line-height: 1 23 | 24 | .crumb + .crumb 25 | // margin-left: 10px 26 | // padding-left: 10px 27 | // border-left: 1px solid rgba(white, .4) 28 | 29 | &:before 30 | content: ' > ' 31 | 32 | .crumbLink 33 | color: white 34 | text-transform: capitalize 35 | -------------------------------------------------------------------------------- /src/components/FancyImage/index.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | 3 | export default class FancyImage extends Component { 4 | state = { 5 | loaded: false 6 | }; 7 | 8 | onLoad = () => { 9 | this.setState({ loaded: true }); 10 | }; 11 | 12 | render() { 13 | const { className, ...props } = this.props; 14 | const styles = { 15 | opacity: 0, 16 | transition: 'opacity 300ms ease-in-out' 17 | }; 18 | 19 | if (this.state.loaded) { 20 | styles.opacity = 1; 21 | } 22 | 23 | return ( 24 | 31 | ); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/components/Search/styles.styl: -------------------------------------------------------------------------------- 1 | mobile() 2 | @media screen and (max-width: 679px) 3 | {block} 4 | 5 | .root 6 | display: flex 7 | align-items: center 8 | position: relative 9 | margin-left: 10px 10 | margin-right: 5px 11 | 12 | .input 13 | background: none 14 | border: none 15 | font-family: inherit 16 | font-size: inherit 17 | width: 70px 18 | padding-left: 20px 19 | outline: none 20 | color: white 21 | transition: all 250ms ease-in-out 22 | border-bottom: 1px rgba(white, 0) solid 23 | 24 | &:focus 25 | width: 200px 26 | border-bottom: 1px rgba(white, .2) solid 27 | 28 | +mobile() 29 | width: 20px 30 | 31 | .icon 32 | pointer-events: none 33 | position: absolute 34 | top: 14px 35 | left: 0 36 | font-size: 14px 37 | -------------------------------------------------------------------------------- /scripts/getArmorTypePerks.js: -------------------------------------------------------------------------------- 1 | const axios = require('axios'); 2 | 3 | const PERKS = [ 4 | // Warlock 5 | 1242428023, 6 | 1510638018, 7 | 1899914236, 8 | 9 | // Titan 10 | 1054437662, 11 | 3082437135, 12 | 4060674255, 13 | 14 | // Hunter 15 | 29391997, 16 | 640149492, 17 | 2883258157, 18 | ]; 19 | 20 | const perkMapping = {}; 21 | 22 | axios 23 | .get('https://destiny.plumbing/en/raw/DestinyInventoryItemDefinition.json') 24 | .then(({ data }) => { 25 | Object.values(data).forEach(item => { 26 | if ( 27 | item.perks && 28 | item.perks[0] && 29 | PERKS.includes(item.perks[0].perkHash) 30 | ) { 31 | perkMapping[item.hash] = item.perks[0].perkHash; 32 | } 33 | }); 34 | 35 | console.log(perkMapping); 36 | }); 37 | -------------------------------------------------------------------------------- /src/setData/index.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | export default { 4 | modsGenerated: require('./modsGenerated').default, 5 | yearOne: require('./yearOne').default, 6 | yearTwo: require('./yearTwo').default, 7 | yearThree: require('./yearThree').default, 8 | yearFour: require('./yearFour').default, 9 | yearFive: require('./yearFive').default, 10 | eververseAndEvents: require('./eververseAndEvents').default, 11 | dlc1: require('./dlc1').default, 12 | dlc2: require('./dlc2').default, 13 | baseGame: require('./baseGame').default, 14 | allItems: require('./allItems').default, 15 | allItemsDeluxe: require('./allItemsDeluxe').default, 16 | strikeGear: require('./strikeGear').default, 17 | catalysts: require('./catalysts').default, 18 | collections: require('./collections').default 19 | }; 20 | -------------------------------------------------------------------------------- /src/components/Header/dropdownStyles.styl: -------------------------------------------------------------------------------- 1 | $activeBg = rgba(white, .07) 2 | 3 | .root 4 | display: flex 5 | flex-wrap: wrap 6 | align-items: center 7 | padding: 9px 10px 8 | cursor: pointer 9 | 10 | .cachedRoot 11 | composes: root 12 | color: #88898C 13 | 14 | .main 15 | font-size: .95em 16 | flex: 1 17 | 18 | .small 19 | font-size: .8em 20 | margin-top: 6px 21 | 22 | .fakeButton 23 | margin-left 10px 24 | 25 | .dropdown 26 | background: #13161B 27 | box-shadow: 0 3px 3px rgba(black, 0.5); 28 | font-size: 14px 29 | min-width: 120px 30 | 31 | .dropdownWide 32 | composes: dropdown 33 | min-width: 170px 34 | 35 | .dropdownItem 36 | padding: 8px 15px 37 | cursor: pointer 38 | white-space: nowrap 39 | display: block 40 | 41 | &:hover 42 | background: $activeBg 43 | -------------------------------------------------------------------------------- /src/components/Section/styles.styl: -------------------------------------------------------------------------------- 1 | .root 2 | padding-bottom: 30px 3 | margin-top: -15px 4 | 5 | .root ~ .root 6 | margin-top: 0 7 | 8 | .title 9 | margin-top: 0 10 | margin-bottom: 0 11 | font-size: 22px 12 | font-weight: normal 13 | overflow: visible 14 | 15 | .riseTitle 16 | position: relative 17 | top: -34px 18 | height: 0 19 | 20 | .list 21 | display: flex 22 | flex-wrap: wrap 23 | justify-content: space-between 24 | margin: 0 -10px 25 | 26 | .set 27 | max-width: 50% 28 | min-width: 450px 29 | flex: 1 0 25% 30 | padding: 10px 31 | padding-bottom: 20px 32 | 33 | @media screen and (max-width: 910px) 34 | width: 100% 35 | max-width: initial 36 | min-width: 100% 37 | 38 | .setBig 39 | composes: set 40 | width: 100% 41 | max-width: initial 42 | flex-basis: 100% 43 | -------------------------------------------------------------------------------- /src/components/Dismissable/index.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | 3 | import Icon from 'app/components/Icon'; 4 | 5 | import s from './styles.styl'; 6 | 7 | export default class Dismissable extends Component { 8 | state = { 9 | active: true 10 | }; 11 | 12 | toggle = e => { 13 | if (this.props.onDismissed) { 14 | this.props.onDismissed(e); 15 | } 16 | 17 | this.setState(prev => ({ active: !prev.active })); 18 | }; 19 | 20 | render() { 21 | if (!this.state.active) { 22 | return null; 23 | } 24 | const { children, onDismissed, ...props } = this.props; 25 | 26 | return ( 27 |
28 | 31 | {children} 32 |
33 | ); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/components/ChaliceRecipie/styles.styl: -------------------------------------------------------------------------------- 1 | .root 2 | content: 'hello' 3 | margin-bottom: 10px 4 | font-size: 14px 5 | 6 | .recipie 7 | display: flex 8 | align-items: center 9 | margin-top: 5px 10 | 11 | .slot 12 | // display: flex 13 | // align-items: center 14 | font-size: 0 15 | padding: 2px 16 | background: rgba(white, .1) 17 | 18 | & + & 19 | margin-right: 5px 20 | 21 | .and 22 | display: inline-block 23 | font-size: 12px 24 | margin: 5px 25 | 26 | .icon 27 | width: 45px 28 | height: 45px 29 | display: inline-block 30 | vertical-align: middle 31 | 32 | .rune 33 | display: inline-block 34 | vertical-align: middle 35 | 36 | & + & 37 | &:before 38 | display: inline-block 39 | vertical-align: middle 40 | content: ' or ' 41 | line-height: 35px 42 | font-size: 12px 43 | margin: 5px 44 | -------------------------------------------------------------------------------- /src/components/ItemPerks/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import BungieImage from 'app/components/BungieImage'; 4 | 5 | import s from './styles.styl'; 6 | 7 | export default function ItemPerks({ perks, className }) { 8 | return ( 9 |
10 | {perks && 11 | perks.map(perk => ( 12 |
13 |
14 | 18 |
19 |
20 |
{perk.displayProperties.name}
21 |
22 | {perk.displayProperties.description} 23 |
24 |
25 |
26 | ))} 27 |
28 | ); 29 | } 30 | -------------------------------------------------------------------------------- /src/components/ItemStats/styles.styl: -------------------------------------------------------------------------------- 1 | .root 2 | display: table 3 | 4 | .item 5 | margin-bottom: 10px 6 | position: relative 7 | display: table-row 8 | 9 | .name 10 | text-align: right 11 | white-space: nowrap 12 | 13 | .name 14 | .valueCell 15 | font-size: .9em 16 | vertical-align: middle 17 | display: table-cell 18 | 19 | .valueCell 20 | width: 100% 21 | padding-left: 15px 22 | 23 | .bar 24 | position: relative 25 | height: 1.2em 26 | overflow: hidden 27 | background: #13161B 28 | border-radius: 2px 29 | overflow: hidden 30 | 31 | .numerical & 32 | background: none 33 | 34 | .barFill 35 | height: 100% 36 | background: rgb(80, 82, 93) 37 | border-radius: 0 2px 2px 0 38 | 39 | .numerical & 40 | display: none 41 | 42 | .value 43 | position: absolute 44 | top: 0 45 | bottom: 0 46 | left: 4px 47 | display: flex 48 | align-items: center 49 | line-height: 0 50 | font-size: .9em 51 | 52 | -------------------------------------------------------------------------------- /src/components/PresentationNode/styles.styl: -------------------------------------------------------------------------------- 1 | $trackHeight = 6px 2 | 3 | .root 4 | display: flex 5 | border: 1px solid rgba(white, .1) 6 | background: rgba(white, .03) 7 | padding: 10px 8 | align-items: center 9 | position: relative 10 | padding-bottom: $trackHeight + 10px 11 | color: white 12 | 13 | &, * 14 | text-decoration: none !important 15 | 16 | .count 17 | font-size: .9em 18 | opacity: .6 19 | 20 | .accessory 21 | content: 'accessory' 22 | margin-right: 10px 23 | 24 | .icon 25 | height: 35px 26 | width: auto 27 | object-fit: contain 28 | display: block 29 | 30 | .main 31 | flex: 1 32 | 33 | .name 34 | font-size: 16px 35 | 36 | .progress 37 | position: absolute 38 | bottom: 0 39 | left: 0 40 | right: 0 41 | background: rgba(white, .06) 42 | height: $trackHeight 43 | font-size: 10px 44 | 45 | .progressTrack 46 | position: absolute 47 | top: 0 48 | left: -1px 49 | bottom: -1px 50 | background: #404245 51 | -------------------------------------------------------------------------------- /src/types.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | export type I18nDefinitionString = {| 4 | path: string, 5 | fallback: string 6 | |}; 7 | 8 | export type ItemHash = number; 9 | 10 | export type ItemsList = Array; 11 | 12 | export type ItemType = 'exoticCatalysts'; 13 | 14 | export type SetSection = {| 15 | name: string, 16 | items?: ItemsList, 17 | query?: string, 18 | season?: number, 19 | bigItems?: boolean, 20 | itemType?: ItemType, 21 | itemGroups?: Array 22 | |}; 23 | 24 | export type DestinySet = {| 25 | name: string | I18nDefinitionString, 26 | id: string, 27 | description?: string | I18nDefinitionString, 28 | image?: string, 29 | small?: boolean, 30 | big?: boolean, 31 | noUi?: boolean, 32 | query?: string, 33 | sections: Array 34 | |}; 35 | 36 | export type SetPageSection = {| 37 | name: string, 38 | noUi?: boolean, 39 | sets: Array 40 | |}; 41 | 42 | export type SetPage = Array; 43 | -------------------------------------------------------------------------------- /src/components/NewFilterBar/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { 4 | HUNTER, 5 | TITAN, 6 | WARLOCK, 7 | FILTER_SHOW_COLLECTED, 8 | FILTER_SHOW_HIDDEN_SETS 9 | } from 'app/lib/destinyEnums'; 10 | 11 | import styles from './styles.styl'; 12 | 13 | const FILTER_NAMES = { 14 | [HUNTER]: 'Hunter', 15 | [TITAN]: 'Titan', 16 | [WARLOCK]: 'Warlock', 17 | [FILTER_SHOW_COLLECTED]: 'Collected items', 18 | [FILTER_SHOW_HIDDEN_SETS]: 'Hidden sets' 19 | }; 20 | 21 | export default function FilterBar({ filters, setFilterItem }) { 22 | return ( 23 |
24 | {Object.keys(filters).map(key => ( 25 | 34 | ))} 35 |
36 | ); 37 | } 38 | -------------------------------------------------------------------------------- /src/components/Objectives/styles.styl: -------------------------------------------------------------------------------- 1 | .objective 2 | position: relative 3 | z-index: 1 4 | background: rgba(white, .04) 5 | border: 1px solid rgba(white, .2) 6 | padding: 5px 8px 7 | 8 | .trackedStat & 9 | margin-bottom: 0 10 | 11 | & + & 12 | margin-top: 5px 13 | 14 | &.completedObjective 15 | opacity: .35 16 | 17 | .objectiveTrack 18 | position: absolute 19 | top: 0 20 | bottom: 0 21 | left: 0 22 | height: 100% 23 | width: 0 24 | z-index: 1 25 | background: rgba(white, .15) 26 | transition: width 250ms ease-in-out 27 | 28 | .trackedStat & 29 | display: none 30 | 31 | .objectiveText 32 | position: relative 33 | display: flex 34 | justify-content: space-between 35 | align-items: center 36 | line-height: 1.2 37 | z-index: 2 38 | 39 | & > :first-child 40 | padding-right: 30px 41 | 42 | & > :last-child 43 | font-weight: 600 44 | 45 | .completed 46 | opacity: .8 47 | font-size: .9em 48 | font-style: italic 49 | margin-bottom: $padding 50 | line-height: 1 51 | margin-top: 10px 52 | -------------------------------------------------------------------------------- /src/components/ItemBanner/styles.styl: -------------------------------------------------------------------------------- 1 | .root 2 | display: flex 3 | padding: 10px 15px 4 | align-items: center 5 | background-size: cover 6 | background-position: center left 7 | background-repeat: no-repeat 8 | 9 | .icon 10 | width: 3em 11 | height: 3em 12 | display: flex 13 | margin-right: 15px 14 | 15 | .body 16 | flex: 1 17 | 18 | .main 19 | font-size: 1em 20 | 21 | .main 22 | .sub 23 | display: flex 24 | justify-content: space-between 25 | 26 | .sub 27 | color: rgba(white, .75) 28 | font-size: .8em 29 | 30 | .close 31 | background: none 32 | padding: 2px 33 | outline: none !important 34 | cursor: pointer 35 | appearance: none 36 | border: none 37 | font-size: 1em 38 | color: white 39 | line-height: 1 40 | 41 | .exotic 42 | background: #ceae33 43 | 44 | .legendary 45 | background: #522f65 46 | 47 | .rare 48 | background: #5076a3 49 | 50 | .common 51 | background: #366f3c 52 | 53 | .basic 54 | background: #c3bcb4 55 | color: #1e242b 56 | 57 | .sub 58 | opacity: 1 59 | color: lighten(#1e242b, 15%) 60 | -------------------------------------------------------------------------------- /src/lib/telemetry.js: -------------------------------------------------------------------------------- 1 | export function getDebugProfile(path) {} 2 | 3 | export function saveDebugInfo(debugData, pathPrefix = 'debug') {} 4 | 5 | export function setUser() {} 6 | 7 | export function trackError(...args) { 8 | const { Raven } = window; 9 | 10 | if (!Raven) { 11 | return null; 12 | } 13 | 14 | Raven.captureException(...args); 15 | } 16 | 17 | export function setExtraUserContext(data) { 18 | const { Raven } = window; 19 | 20 | if (!Raven) { 21 | return null; 22 | } 23 | 24 | Raven.setExtraContext(data); 25 | } 26 | 27 | export function trackBreadcrumb(data) { 28 | const { Raven } = window; 29 | 30 | if (!Raven) { 31 | return null; 32 | } 33 | 34 | Raven.captureBreadcrumb(data); 35 | } 36 | 37 | export function errorPrompt(ev) { 38 | if (ev && ev.preventDefault) { 39 | ev.preventDefault(); 40 | } 41 | 42 | const { Raven } = window; 43 | 44 | if (!Raven) { 45 | window.alert( 46 | 'Unable to load error library. Maybe an adblocker interferred?' 47 | ); 48 | return null; 49 | } 50 | 51 | Raven.showReportDialog(); 52 | } 53 | -------------------------------------------------------------------------------- /src/components/Section/index.js: -------------------------------------------------------------------------------- 1 | import React, { Fragment, PureComponent } from 'react'; 2 | import cx from 'classnames'; 3 | 4 | import ItemSet from 'app/components/ItemSet'; 5 | 6 | import styles from './styles.styl'; 7 | 8 | export default class Section extends PureComponent { 9 | render() { 10 | const { 11 | name, 12 | noUi, 13 | slug, 14 | sets, 15 | setPopper, 16 | setModal, 17 | extendedItems 18 | } = this.props; 19 | 20 | return ( 21 |
22 |

{name}

23 |
24 | {sets.map((set, index) => ( 25 | 26 | 33 | 34 | ))} 35 |
36 |
37 | ); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Josh Hunt 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/store/definitions.js: -------------------------------------------------------------------------------- 1 | import { pickBy } from 'lodash'; 2 | import { makePayloadAction } from './utils'; 3 | 4 | export const SET_BULK_DEFINITIONS = 'Set bulk definitions'; 5 | export const DEFINITIONS_ERROR = 'Definitions error'; 6 | export const DEFINITIONS_STATUS = 'Definitions status'; 7 | 8 | export default function definitionsReducer(state = {}, { type, payload }) { 9 | switch (type) { 10 | case DEFINITIONS_ERROR: { 11 | return { 12 | ...state, 13 | error: true 14 | }; 15 | } 16 | 17 | case DEFINITIONS_STATUS: { 18 | return { 19 | ...state, 20 | status: payload.status 21 | }; 22 | } 23 | 24 | case SET_BULK_DEFINITIONS: { 25 | const filtered = pickBy(payload, defs => defs); 26 | 27 | return { 28 | ...state, 29 | ...filtered, 30 | error: false 31 | }; 32 | } 33 | 34 | default: 35 | return state; 36 | } 37 | } 38 | 39 | export const setBulkDefinitions = makePayloadAction(SET_BULK_DEFINITIONS); 40 | export const definitionsStatus = makePayloadAction(DEFINITIONS_STATUS); 41 | export const definitionsError = makePayloadAction(DEFINITIONS_ERROR); 42 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import 'app/lib/autotrack.build'; 2 | import 'indexeddbshim/dist/indexeddbshim.min'; 3 | 4 | import React from 'react'; 5 | import ReactDOM from 'react-dom'; 6 | import { AppContainer } from 'react-hot-loader'; 7 | import ReactModal from 'react-modal'; 8 | 9 | import './setupEnv'; 10 | import { trackEvent } from 'app/lib/analytics'; 11 | import AppRouter from './AppRouter'; 12 | import * as ls from 'app/lib/ls'; 13 | import 'app/lib/destinyAuth'; 14 | import './index.styl'; 15 | 16 | const isAuthRefreshiFrame = 17 | window.self !== window.top && window.parent.__HIDDEN_IFRAME_REFRESH_AUTH; 18 | 19 | const render = App => { 20 | ReactDOM.render( 21 | 22 | 23 | , 24 | document.getElementById('root') 25 | ); 26 | }; 27 | 28 | if (!isAuthRefreshiFrame) { 29 | ls.cleanUp(); 30 | 31 | render(AppRouter); 32 | ReactModal.setAppElement('#root'); 33 | 34 | ls.saveVisitCount(ls.getVisitCount() + 1); 35 | trackEvent('visit-count', ls.getVisitCount()); 36 | 37 | // Webpack Hot Module Replacement API 38 | if (module.hot) { 39 | module.hot.accept('./AppRouter', () => render(AppRouter)); 40 | } 41 | } else { 42 | console.log('Page is iFramed'); 43 | } 44 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.gitignore.io/api/node 3 | 4 | ### Node ### 5 | # Logs 6 | logs 7 | *.log 8 | npm-debug.log* 9 | 10 | # Runtime data 11 | pids 12 | *.pid 13 | *.seed 14 | *.pid.lock 15 | 16 | # Directory for instrumented libs generated by jscoverage/JSCover 17 | lib-cov 18 | 19 | # Coverage directory used by tools like istanbul 20 | coverage 21 | 22 | # nyc test coverage 23 | .nyc_output 24 | 25 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 26 | .grunt 27 | 28 | # node-waf configuration 29 | .lock-wscript 30 | 31 | # Compiled binary addons (http://nodejs.org/api/addons.html) 32 | build/Release 33 | 34 | # Dependency directories 35 | node_modules 36 | jspm_packages 37 | 38 | # Optional npm cache directory 39 | .npm 40 | 41 | # Optional eslint cache 42 | .eslintcache 43 | 44 | # Optional REPL history 45 | .node_repl_history 46 | 47 | # Output of 'npm pack' 48 | *.tgz 49 | 50 | # Yarn Integrity file 51 | .yarn-integrity 52 | 53 | 54 | # End of https://www.gitignore.io/api/node 55 | 56 | 57 | data 58 | dist 59 | working 60 | legacyData.json 61 | appConfig.js 62 | newset.json 63 | crowdin.yml 64 | deploy.sh 65 | firebaseCreds.json 66 | .env.* 67 | !.env.local-sample 68 | build 69 | -------------------------------------------------------------------------------- /src/components/Header/LanguageDropdown.js: -------------------------------------------------------------------------------- 1 | import React, { Fragment, Component } from 'react'; 2 | import DropdownMenu from 'app/components/DropdownMenu'; 3 | import Icon from 'app/components/Icon'; 4 | import { languages } from 'app/lib/i18n'; 5 | 6 | import styles from './dropdownStyles.styl'; 7 | 8 | export default class LanguageDropdown extends Component { 9 | renderContent = () => { 10 | return ( 11 | 12 | {languages.map(lang => ( 13 |
this.props.setLanguage(lang)} 17 | > 18 | {lang.name} 19 |
20 | ))} 21 |
22 | ); 23 | }; 24 | 25 | render() { 26 | return ( 27 | 33 |
{this.props.language.name}
34 | 35 |
36 | 37 |
38 |
39 | ); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/components/I18NDefinitionsString.js: -------------------------------------------------------------------------------- 1 | // import React from 'react'; 2 | import { get } from 'lodash'; 3 | import { connect } from 'react-redux'; 4 | 5 | import { getI18nString, saveI18nString } from 'app/lib/ls'; 6 | 7 | function I18NDefinitionsString({ msg }) { 8 | return msg || 'nothing'; 9 | } 10 | 11 | const JUNK_PREFIXES = [ 12 | 'Source: ', 13 | '入手方法: ', 14 | 'Fonte: ', 15 | 'Source : ', 16 | 'Quelle: ', 17 | 'Fuente: ', 18 | 'Источник: ', 19 | 'Źródło: ', 20 | '來源:', 21 | '출처: ' 22 | ]; 23 | const JUNK_REGEX = new RegExp(`^(${JUNK_PREFIXES.join('|')})`); 24 | 25 | const cleanup = msg => { 26 | return msg.replace(JUNK_REGEX, ''); 27 | }; 28 | 29 | const mapStateToProps = (state, props) => { 30 | if (!props.t) { 31 | return { msg: 'empty' }; 32 | } 33 | 34 | if (!props.t.path) { 35 | return { msg: props.t.fallback || props.t }; 36 | } 37 | 38 | const found = get(state.definitions, props.t.path); 39 | 40 | if (!found) { 41 | const fallback = getI18nString(props.t.path) || props.t.fallback; 42 | return { msg: cleanup(fallback) }; 43 | } else { 44 | saveI18nString(props.t.path, found); 45 | return { msg: cleanup(found) }; 46 | } 47 | }; 48 | 49 | export default connect(mapStateToProps)(I18NDefinitionsString); 50 | -------------------------------------------------------------------------------- /src/components/ItemBannerNew/styles.styl: -------------------------------------------------------------------------------- 1 | .root 2 | display: flex 3 | padding: 5px 10px 4 | align-items: center 5 | background-size: cover 6 | background-position: center left 7 | background-repeat: no-repeat 8 | 9 | .icon 10 | width: 3em 11 | height: 3em 12 | display: flex 13 | margin-right: 15px 14 | 15 | .body 16 | flex: 1 17 | 18 | .bodyEmblem 19 | composes: body 20 | padding-left: 50px 21 | 22 | .main 23 | font-size: 1em 24 | text-transform: uppercase 25 | 26 | .main 27 | .sub 28 | display: flex 29 | justify-content: space-between 30 | line-height: 1.3 31 | 32 | .itemName 33 | font-size: 1.3em 34 | font-weight: 600 35 | text-transform: uppercase 36 | 37 | .sub 38 | color: rgba(white, .75) 39 | 40 | .close 41 | background: none 42 | padding: 2px 43 | outline: none !important 44 | cursor: pointer 45 | appearance: none 46 | border: none 47 | font-size: 1em 48 | color: white 49 | line-height: 1 50 | 51 | .exotic 52 | background: #ceae33 53 | 54 | .legendary 55 | background: #522f65 56 | 57 | .rare 58 | background: #5076a3 59 | 60 | .common 61 | background: #366f3c 62 | 63 | .basic 64 | background: #c3bcb4 65 | color: #1e242b 66 | 67 | .sub 68 | opacity: 1 69 | color: lighten(#1e242b, 15%) 70 | -------------------------------------------------------------------------------- /src/logo.svg: -------------------------------------------------------------------------------- 1 | light -------------------------------------------------------------------------------- /src/components/ItemTooltip/styles.styl: -------------------------------------------------------------------------------- 1 | $padding = 10px 2 | 3 | .tooltip 4 | width: 320px 5 | font-size: 15px 6 | background: #20262d 7 | position: relative 8 | color: white 9 | box-shadow: 0 3px 6px rgba(black, 0.16), 0 3px 6px rgba(black, 0.23); 10 | 11 | &.small 12 | box-shadow: 0 2px 15px rgba(0, 0, 0, 0.5) 13 | 14 | .body 15 | overflow: auto 16 | padding: $padding 17 | padding-bottom: 0 18 | 19 | .small & 20 | font-size: 14px 21 | 22 | p 23 | margin: 0 0 $padding 0 24 | 25 | .header 26 | font-size: 15px 27 | 28 | .small & 29 | content: 'small' 30 | 31 | .description 32 | line-height: 1.3 33 | white-space: pre-line 34 | 35 | .small & 36 | display: none 37 | 38 | .body:hover & 39 | display: block 40 | 41 | .itemAttributes 42 | margin: 0 0 $padding 0 43 | 44 | .perks 45 | margin: 0 0 $padding 0 46 | 47 | .stats 48 | margin: 0 0 $padding 0 49 | 50 | .extraInfo 51 | opacity: .8 52 | font-size: .9em 53 | font-style: italic 54 | margin: 0 0 $padding 0 55 | 56 | .objectives 57 | font-size: 14px 58 | margin: 0 0 $padding 0 59 | 60 | .small & 61 | font-size: 13px 62 | 63 | .section 64 | & + & 65 | padding-top: $padding 66 | border-top: 1px solid rgba(white, .2) 67 | 68 | &:empty 69 | display: none 70 | 71 | &:empty + & 72 | border-top: none 73 | -------------------------------------------------------------------------------- /src/setData/catalysts.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import type { SetPage } from '../types'; 4 | 5 | export default ([ 6 | { 7 | name: 'Kinetic Weapons', 8 | sets: [ 9 | { 10 | name: 'Exotics', 11 | id: 'CATALYSTS', 12 | big: true, 13 | noUi: true, 14 | sections: [ 15 | { 16 | name: 'Kinetic weapons', 17 | itemType: 'exoticCatalysts', 18 | query: 'special:kineticCatalysts' 19 | } 20 | ] 21 | } 22 | ] 23 | }, 24 | { 25 | name: 'Energy Weapons', 26 | sets: [ 27 | { 28 | name: 'Exotics', 29 | id: 'CATALYSTS', 30 | big: true, 31 | noUi: true, 32 | sections: [ 33 | { 34 | name: 'Special weapons', 35 | itemType: 'exoticCatalysts', 36 | query: 'special:energyCatalysts' 37 | } 38 | ] 39 | } 40 | ] 41 | }, 42 | { 43 | name: 'Power Weapons', 44 | sets: [ 45 | { 46 | name: 'Exotics', 47 | id: 'CATALYSTS', 48 | big: true, 49 | noUi: true, 50 | sections: [ 51 | { 52 | name: 'Heavy weapons', 53 | itemType: 'exoticCatalysts', 54 | query: 'special:powerCatalysts' 55 | } 56 | ] 57 | } 58 | ] 59 | } 60 | ]: SetPage); 61 | -------------------------------------------------------------------------------- /src/store/debugProxifyDefs.js: -------------------------------------------------------------------------------- 1 | import { isObject, isArray, isNumber, isUndefined } from 'lodash'; 2 | 3 | const ITEM_DEF_KEYS = []; 4 | window.__ITEM_DEF_KEYS = ITEM_DEF_KEYS; 5 | const UNDEFINED_ITEM_DEF_KEYS = []; 6 | window.__UNDEFINED_ITEM_DEF_KEYS = UNDEFINED_ITEM_DEF_KEYS; 7 | const DEBUG_PROXIFY_ITEM_DEFS = false; 8 | 9 | function pushUniquely(arr, value) { 10 | if (!arr.includes(value)) { 11 | arr.push(value); 12 | } 13 | } 14 | 15 | export default function proxyifyDefs(defs, prevKeys = []) { 16 | if (!DEBUG_PROXIFY_ITEM_DEFS) { 17 | return defs; 18 | } 19 | 20 | const p = new Proxy(defs, { 21 | get: (obj, prop) => { 22 | const keys = [...prevKeys, prop]; 23 | const keysOfInterest = keys.slice(1).join('.'); 24 | 25 | pushUniquely(ITEM_DEF_KEYS, keysOfInterest); 26 | 27 | const value = obj[prop]; 28 | 29 | if (isUndefined(value)) { 30 | pushUniquely(UNDEFINED_ITEM_DEF_KEYS, keysOfInterest); 31 | return value; 32 | } else if (isArray(value)) { 33 | return value; 34 | } else if (isObject(value)) { 35 | const stuff = Object.keys(value).filter(k => !isNumber(k)); 36 | if (stuff.length) { 37 | return proxyifyDefs(value, keys); 38 | } 39 | 40 | return value; 41 | } 42 | 43 | return value; 44 | } 45 | }); 46 | 47 | return p; 48 | } 49 | -------------------------------------------------------------------------------- /src/ishar.svg: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | -------------------------------------------------------------------------------- /src/store/index.js: -------------------------------------------------------------------------------- 1 | import { createStore, combineReducers, applyMiddleware, compose } from 'redux'; 2 | import thunk from 'redux-thunk'; 3 | import { mapValues } from 'lodash'; 4 | 5 | import app from './reducer'; 6 | import definitions, { SET_BULK_DEFINITIONS } from './definitions'; 7 | import auth from './auth'; 8 | import profile from './profile'; 9 | 10 | import preloadStore from './preloadStore'; 11 | 12 | const rootReducer = combineReducers({ 13 | app, 14 | auth, 15 | profile, 16 | definitions 17 | }); 18 | 19 | function sanitiseDefintionsState(defintionsState) { 20 | if (!defintionsState) { 21 | return defintionsState; 22 | } 23 | return mapValues( 24 | defintionsState, 25 | definitions => 26 | `[${Object.keys(definitions || {}).length} definitions hidden]` 27 | ); 28 | } 29 | 30 | const composeEnhancers = 31 | typeof window === 'object' && window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ 32 | ? window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({ 33 | actionsBlacklist: [SET_BULK_DEFINITIONS], 34 | stateSanitizer: state => ({ 35 | ...state, 36 | definitions: sanitiseDefintionsState(state.definitions) 37 | }) 38 | }) 39 | : compose; 40 | 41 | const enhancer = composeEnhancers(applyMiddleware(thunk)); 42 | 43 | const store = preloadStore(createStore(rootReducer, enhancer)); 44 | window.__store = store; 45 | 46 | export default store; 47 | -------------------------------------------------------------------------------- /src/lib/DestinyAuthProvider.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import destinyAuth from 'app/lib/destinyAuth'; 3 | 4 | const log = require('app/lib/log')('authProvider'); 5 | 6 | export default function DestinyAuthProvider( 7 | WrappedComponent, 8 | onlyRenderWhenAuthed 9 | ) { 10 | return class DestinyAuthProviderWrapper extends Component { 11 | state = { 12 | isAuthenticated: false, 13 | authLoaded: false 14 | }; 15 | 16 | componentDidMount() { 17 | destinyAuth((err, { isAuthenticated, isFinal }) => { 18 | log('Auth state update', { err, isAuthenticated, isFinal }); 19 | 20 | if (err) { 21 | throw err; 22 | } 23 | 24 | this.setState({ isAuthenticated, authLoaded: isFinal }); 25 | }); 26 | } 27 | 28 | render() { 29 | if (onlyRenderWhenAuthed) { 30 | return this.state.isAuthenticated ? ( 31 | 36 | ) : ( 37 | Loading... 38 | ); 39 | } 40 | 41 | return ( 42 | 47 | ); 48 | } 49 | }; 50 | } 51 | -------------------------------------------------------------------------------- /src/components/DropdownMenu/index.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import cx from 'classnames'; 3 | import ClickOutside from 'react-click-outside'; 4 | 5 | import styles from './styles.styl'; 6 | 7 | export default class DropdownMenu extends Component { 8 | state = { 9 | visible: false 10 | }; 11 | 12 | toggleDropdown = () => { 13 | this.setState({ visible: !this.state.visible }); 14 | }; 15 | 16 | clickedOutside = () => { 17 | this.state.visible && this.setState({ visible: false }); 18 | }; 19 | 20 | onContentClick = ev => { 21 | this.props.stayOpen && ev.stopPropagation(); 22 | }; 23 | 24 | render() { 25 | const { 26 | inline, 27 | children, 28 | className, 29 | contentClassName, 30 | renderContent 31 | } = this.props; 32 | 33 | const { visible } = this.state; 34 | 35 | return ( 36 | 46 | {children} 47 | 48 | {visible && ( 49 |
53 | {renderContent()} 54 |
55 | )} 56 |
57 | ); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/components/ChaliceRecipie/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { connect } from 'react-redux'; 3 | 4 | import BunigeImage from 'app/components/BungieImage'; 5 | 6 | import s from './styles.styl'; 7 | 8 | const CHALICE_HASH = 1115550924; 9 | 10 | function Slot({ runes, itemDefs }) { 11 | return ( 12 |
13 | {runes.map(hash => { 14 | const item = itemDefs[hash]; 15 | return ( 16 | item && ( 17 |
18 | 22 |
23 | ) 24 | ); 25 | })} 26 |
27 | ); 28 | } 29 | 30 | function ChaliceRecipie({ itemDefs, recipie }) { 31 | const chalice = itemDefs[CHALICE_HASH]; 32 | const [slot1, slot2] = recipie; 33 | 34 | return ( 35 |
36 | Crafted in{' '} 37 | {chalice ? chalice.displayProperties.name : 'Chalice of Opulence'} with: 38 |
39 | 40 |
and
41 | 42 |
43 |
44 | ); 45 | } 46 | 47 | function mapStateToProps(state) { 48 | return { 49 | itemDefs: state.definitions.DestinyInventoryItemDefinition || {} 50 | }; 51 | } 52 | 53 | export default connect(mapStateToProps)(ChaliceRecipie); 54 | -------------------------------------------------------------------------------- /src/components/ItemStats/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import cx from 'classnames'; 3 | 4 | import { 5 | NUMERICAL_STATS, 6 | STAT_RECOVERY, 7 | STAT_RESILIENCE, 8 | STAT_MOBILITY 9 | } from 'app/lib/destinyEnums'; 10 | 11 | import styles from './styles.styl'; 12 | 13 | const MAX_VALUES = { 14 | [STAT_RECOVERY]: 3, 15 | [STAT_RESILIENCE]: 3, 16 | [STAT_MOBILITY]: 3 17 | }; 18 | 19 | function getMaxValue(statHash) { 20 | return MAX_VALUES[statHash] || 100; 21 | } 22 | 23 | export default function ItemStats({ stats, statDefs, className }) { 24 | return ( 25 |
26 | {stats.map(({ statHash, value }) => { 27 | const def = statDefs[statHash]; 28 | 29 | return ( 30 |
37 |
{def.displayProperties.name}
38 |
39 |
40 |
44 |
{value}
45 |
46 |
47 |
48 | ); 49 | })} 50 |
51 | ); 52 | } 53 | -------------------------------------------------------------------------------- /src/components/SectionList/styles.styl: -------------------------------------------------------------------------------- 1 | .root 2 | height: 42px 3 | 4 | .fixed 5 | position: fixed 6 | top: 60px 7 | display: flex 8 | z-index: 2 9 | top: 52px 10 | left: 0 11 | right: 0 12 | padding: 0 15px 13 | background: #090b0f 14 | box-shadow: 0 0 20px -5px rgba(black, 0.75) 15 | 16 | .list 17 | margin: 0 0 0 -15px 18 | padding: 0 0 0 5px 19 | white-space: nowrap 20 | overflow: auto 21 | -webkit-overflow-scrolling: touch 22 | flex: 1 23 | 24 | .item 25 | position: relative 26 | display: inline-block 27 | margin: 0 10px 28 | padding: 7px 0 8px 0 29 | font-size: 18px 30 | opacity: .5 31 | transition: 100ms ease-in-out opacity 32 | list-style: none 33 | cursor: pointer 34 | 35 | &:not(.itemActive):hover 36 | opacity: .75 37 | 38 | a, & 39 | color: inherit 40 | text-decoration: none 41 | 42 | &:after 43 | transition: 100ms ease-in-out height 44 | display: block 45 | content: '' 46 | position absolute 47 | bottom: 0 48 | right: 0 49 | left: 0 50 | height: 0 51 | background: rgba(white, .2) 52 | 53 | .itemActive 54 | opacity: 1 55 | 56 | &:after 57 | height: 3px 58 | 59 | .filter 60 | margin-right: -10px 61 | padding-left: 5px 62 | position: relative 63 | transition: opacity 150ms ease-in-out 64 | 65 | &:after 66 | content: '' 67 | position: absolute 68 | top: 8px 69 | bottom: 8px 70 | width:1px 71 | background: rgba(white, .25) 72 | left: 0 73 | 74 | .disabled 75 | opacity: .25 76 | pointer-events: none 77 | -------------------------------------------------------------------------------- /src/views/ItemPage/selectors.js: -------------------------------------------------------------------------------- 1 | import { createSelector } from 'reselect'; 2 | 3 | import { makeItemSelector, itemDefsSelector } from 'app/store/selectors'; 4 | 5 | export const makePerksSelector = () => 6 | createSelector(makeItemSelector(), itemDefsSelector, (item, itemDefs) => { 7 | if (!item) { 8 | return {}; 9 | } 10 | 11 | const socketCategory = 12 | item && 13 | item.sockets && 14 | item.sockets.socketCategories.find( 15 | ({ socketCategoryHash }) => (socketCategoryHash = 4241085061) 16 | ); 17 | 18 | const perks = 19 | socketCategory && 20 | socketCategory.socketIndexes 21 | .map(socketIndex => item.sockets.socketEntries[socketIndex]) 22 | .map(socket => { 23 | return { 24 | mainPerk: itemDefs[socket.singleInitialItemHash], 25 | altPerks: socket.reusablePlugItems 26 | .filter( 27 | ({ plugItemHash }) => 28 | socket.singleInitialItemHash !== plugItemHash 29 | ) 30 | .map(({ plugItemHash }) => ({ 31 | plugItem: itemDefs[plugItemHash] 32 | })), 33 | 34 | randomPerks: 35 | socket.randomizedPlugItems && 36 | socket.randomizedPlugItems.length > 0 && 37 | socket.randomizedPlugItems 38 | .map(randomRoll => itemDefs[randomRoll.plugItemHash]) 39 | .filter(Boolean) 40 | }; 41 | }); 42 | 43 | return { 44 | socketCategory, 45 | perks 46 | }; 47 | }); 48 | -------------------------------------------------------------------------------- /src/lib/getItemExtraInfo.js: -------------------------------------------------------------------------------- 1 | import { uniq } from 'lodash'; 2 | 3 | import { MASTERWORK_FLAG } from 'app/lib/destinyEnums'; 4 | 5 | const DEBUG_INCLUDE_ALL_SOURCES = window.location.href.includes( 6 | 'debugIncludeAllSources' 7 | ); 8 | 9 | export default function getItemExtraInfo(item, _itemInventoryEntry) { 10 | const itemInventoryEntry = _itemInventoryEntry || { 11 | instances: [{}], 12 | obtained: false 13 | }; 14 | 15 | const extraInfo = []; 16 | 17 | extraInfo.push( 18 | ...uniq( 19 | itemInventoryEntry.instances.map(getFriendlyItemLocation).filter(Boolean) 20 | ) 21 | ); 22 | 23 | return extraInfo; 24 | } 25 | 26 | export const DISMANTLED_MESSAGE = 'Dismantled, recorded in Google Drive'; 27 | const LOCATIONS = { 28 | characterEquipment: 'Equipped on Character', 29 | characterInventories: 'On Character', 30 | profileInventory: 'In Vault', 31 | destinySetsManual: 'Manually Marked as Collected', 32 | cloudInventory: DISMANTLED_MESSAGE 33 | // profilePlugSets: 'Unlocked', 34 | // vendorPlugStates: 'Unlocked' 35 | }; 36 | 37 | const masterworkLocations = [ 38 | 'characterEquipment', 39 | 'characterInventories', 40 | 'profileInventory' 41 | ]; 42 | 43 | export function getFriendlyItemLocation(instance) { 44 | let location = DEBUG_INCLUDE_ALL_SOURCES 45 | ? instance.location 46 | : LOCATIONS[instance.location]; 47 | 48 | if ( 49 | masterworkLocations.includes(instance.location) && 50 | instance.itemState & MASTERWORK_FLAG 51 | ) { 52 | location = `${location} & Masterworked`; 53 | } 54 | 55 | return location; 56 | } 57 | -------------------------------------------------------------------------------- /src/components/Icon.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import cx from 'classnames'; 3 | 4 | const MembershipType = { 5 | Xbox: 1, 6 | Playstation: 2, 7 | Steam: 3, 8 | BattleNet: 4, 9 | Stadia: 5 10 | }; 11 | 12 | const Icon = ({ 13 | name, 14 | icon, 15 | solid, 16 | regular, 17 | light, 18 | duotone, 19 | brand, 20 | spin, 21 | className, 22 | ...rest 23 | }) => { 24 | const prefix = 25 | { 26 | [solid ? 'true' : 'false']: 'fas', 27 | [regular ? 'true' : 'false']: 'far', 28 | [light ? 'true' : 'false']: 'fal', 29 | [duotone ? 'true' : 'false']: 'fad', 30 | [brand ? 'true' : 'false']: 'fab' 31 | }['true'] || 'far'; 32 | 33 | if (icon) { 34 | throw new Error('Icon prop specificed on Icon component!', { 35 | name, 36 | icon, 37 | solid, 38 | regular, 39 | light, 40 | duotone, 41 | brand 42 | }); 43 | } 44 | 45 | return ( 46 | 47 | 52 | 53 | ); 54 | }; 55 | 56 | export default Icon; 57 | 58 | export const PlatformIcon = ({ type, ...rest }) => { 59 | const iconMap = { 60 | [MembershipType.Xbox]: 'xbox', 61 | [MembershipType.Playstation]: 'playstation', 62 | [MembershipType.Steam]: 'steam', 63 | [MembershipType.BattleNet]: 'battle-net', 64 | [MembershipType.Stadia]: 'google' 65 | }; 66 | 67 | return ( 68 | 73 | ); 74 | }; 75 | -------------------------------------------------------------------------------- /src/components/SectionList/index.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import cx from 'classnames'; 3 | import { Link, scrollSpy } from 'react-scroll'; 4 | 5 | import Search from 'app/components/Search'; 6 | 7 | import FilterDropdown from './FilterDropdown'; 8 | import styles from './styles.styl'; 9 | 10 | export default class SectionList extends Component { 11 | componentDidMount() { 12 | setTimeout(() => scrollSpy.update(), 1); 13 | } 14 | 15 | render() { 16 | const { 17 | setData, 18 | filters, 19 | setFilterItem, 20 | searchValue, 21 | onSearchChange 22 | } = this.props; 23 | 24 | return ( 25 |
26 |
27 |
    28 | {setData.map(({ name, slug }) => ( 29 | 38 | {name} 39 | 40 | ))} 41 |
42 | 43 | 44 | 45 |
2 && styles.disabled 49 | )} 50 | > 51 | 52 |
53 |
54 |
55 | ); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/setData/eververseAndEvents.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import type { SetPage } from '../types'; 3 | // import * as common from './common'; 4 | import * as eververseAndEvents from './common/eververseAndEvents'; 5 | 6 | export default ([ 7 | { 8 | name: 'Events', 9 | sets: [ 10 | eververseAndEvents.DAWNING_Y1, 11 | eververseAndEvents.CRIMSON_DAYS_Y1, 12 | eververseAndEvents.SOLSTICE_OF_HEROES, 13 | eververseAndEvents.FESTIVAL_OF_THE_LOST_Y2, 14 | eververseAndEvents.DAWNING_Y2, 15 | eververseAndEvents.CRIMSON_DAYS_Y2, 16 | eververseAndEvents.REVELRY_Y2, 17 | eververseAndEvents.MOMENTS_OF_TRIUMPH_Y2, 18 | eververseAndEvents.SOLSTICE_OF_HEROES_Y2, 19 | eververseAndEvents.FESTIVAL_OF_THE_LOST_Y3, 20 | eververseAndEvents.DAWNING_Y3, 21 | eververseAndEvents.CRIMSON_DAYS_Y3, 22 | eververseAndEvents.GUARDIAN_GAMES_Y3, 23 | eververseAndEvents.MOMENTS_OF_TRIUMPH_Y3, 24 | eververseAndEvents.SOLSTICE_OF_HEROES_Y3, 25 | eververseAndEvents.DAWNING_Y4, 26 | eververseAndEvents.GUARDIAN_GAMES_Y4, 27 | eververseAndEvents.SOLSTICE_OF_HEROES_Y4, 28 | eververseAndEvents.FESTIVAL_OF_THE_LOST_Y4, 29 | eververseAndEvents.BUNGIE_30TH_ANNIVERSARY_EVENT_Y4, 30 | eververseAndEvents.MOMENTS_OF_TRIUMPH_Y4, 31 | eververseAndEvents.GUARDIAN_GAMES_Y5, 32 | eververseAndEvents.SOLSTICE_Y5, 33 | eververseAndEvents.FESTIVAL_OF_THE_LOST_Y5, 34 | ] 35 | }, 36 | { 37 | name: 'Eververse', 38 | sets: [ 39 | eververseAndEvents.EVERVERSE_Y1, 40 | eververseAndEvents.EVERVERSE_Y2, 41 | eververseAndEvents.EVERVERSE_Y3, 42 | eververseAndEvents.EVERVERSE_Y4, 43 | eververseAndEvents.EVERVERSE_Y5, 44 | ] 45 | } 46 | ]: SetPage); 47 | -------------------------------------------------------------------------------- /src/components/Popper/index.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { createPortal } from 'react-dom'; 3 | import Popper from 'popper.js'; 4 | 5 | export default class PopperWrapper extends Component { 6 | state = { el: null }; 7 | 8 | componentDidMount() { 9 | const el = document.createElement('div'); 10 | document.body.appendChild(el); 11 | this.setState({ el }); 12 | } 13 | 14 | componentWillUnmount() { 15 | this.popper.destroy(); 16 | this.state.el && document.body.removeChild(this.state.el); 17 | } 18 | 19 | setPosition = () => { 20 | this.popper = new Popper(this.props.element, this.ref, { 21 | placement: 'right', 22 | modifiers: { 23 | applyStyle: { enabled: false }, 24 | preventOverflow: { enabled: true, boundariesElement: 'viewport' }, 25 | applyReactStyle: { 26 | enabled: true, 27 | fn: data => 28 | this.setState({ 29 | style: { ...data.styles, pointerEvents: 'none' }, 30 | arrowStyles: data.arrowStyles 31 | }), 32 | order: 900 33 | } 34 | } 35 | }); 36 | 37 | window.lastPopper = this.popper; 38 | 39 | window.requestAnimationFrame(() => { 40 | this.popper.update(); 41 | }); 42 | }; 43 | 44 | getRef = ref => { 45 | this.ref = ref; 46 | if (this.ref) { 47 | this.setPosition(); 48 | } 49 | }; 50 | 51 | render() { 52 | const { el, style, arrowStyles } = this.state; 53 | if (!el) { 54 | return null; 55 | } 56 | 57 | return createPortal( 58 |
59 | {this.props.children} 60 |
61 |
, 62 | this.state.el 63 | ); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/components/PresentationNodeChildren/styles.styl: -------------------------------------------------------------------------------- 1 | .root + .root 2 | margin-top: 30px 3 | 4 | .children 5 | display: grid 6 | grid-column-gap: 15px 7 | grid-row-gap: 15px 8 | 9 | .childPresentationNodes 10 | grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)) 11 | composes: children 12 | 13 | .childRecords 14 | composes: children 15 | grid-template-columns: repeat(auto-fill, minmax(350px, 1fr)) 16 | 17 | .headerWrap 18 | composes: childPresentationNodes 19 | 20 | .header 21 | color: inherit 22 | text-decoration: none 23 | margin-bottom: 15px 24 | 25 | .node 26 | .record 27 | color: inherit 28 | text-decoration: none 29 | 30 | .top 31 | margin-bottom: 30px 32 | 33 | h2 34 | margin: 0 35 | 36 | .nestedTop 37 | composes: top 38 | font-size: .8em 39 | margin-bottom: 8px 40 | 41 | .icon 42 | height: 40px 43 | 44 | .topLink 45 | color: inherit 46 | text-decoration: none 47 | display: flex 48 | align-items: center 49 | position: relative 50 | 51 | .iconWrapper 52 | margin-right: 15px 53 | 54 | .icon 55 | display: block 56 | height: 50px 57 | 58 | .iconCorners 59 | content: '' 60 | 61 | &:before 62 | &:after 63 | content: '' 64 | position: absolute 65 | display: block 66 | width: 8px 67 | height: 8px 68 | border: 1px solid rgba(white, .5) 69 | 70 | .iconCornersTop 71 | composes: iconCorners 72 | 73 | &:before 74 | top: 0 75 | left: 0 76 | border-style: solid none none solid 77 | 78 | &:after 79 | top: 0 80 | right: 0 81 | border-style: solid solid none none 82 | 83 | .iconCornersBottom 84 | composes: iconCorners 85 | 86 | &:before 87 | bottom: 0 88 | left: 0 89 | border-style: none none solid solid 90 | 91 | &:after 92 | bottom: 0 93 | right: 0 94 | border-style: none solid solid none 95 | 96 | -------------------------------------------------------------------------------- /src/components/ItemAttributes/styles.styl: -------------------------------------------------------------------------------- 1 | .root 2 | display: flex 3 | // padding: 15px 0 0 0 4 | 5 | ._power 6 | font-size: 30px 7 | line-height: 1 8 | display: flex 9 | align-items: center 10 | color: #C690DC 11 | font-weight: 600 12 | 13 | .elementalDamageIcon 14 | width: .95em 15 | height: .95em 16 | margin-right: .5ch 17 | 18 | .ammoIcon 19 | height: 22px 20 | vertical-align: top 21 | display: inline-block 22 | margin-right: 5px 23 | 24 | .kineticDamage 25 | composes: _power 26 | color: white 27 | 28 | .voidDamage 29 | composes: _power 30 | color: #b082cb 31 | 32 | .elementalDamageIcon 33 | background: #b082cb 34 | mask: url("https://www.bungie.net/common/destiny2_content/icons/DestinyDamageTypeDefinition_ceb2f6197dccf3958bb31cc783eb97a0.png") center center / cover; 35 | 36 | .arcDamage 37 | composes: _power 38 | color: #79ebf3 39 | 40 | .elementalDamageIcon 41 | background: #79ebf3 42 | mask: url("https://www.bungie.net/common/destiny2_content/icons/DestinyDamageTypeDefinition_092d066688b879c807c3b460afdd61e6.png") center center / cover; 43 | 44 | .solarDamage 45 | composes: _power 46 | color: #f36f26 47 | 48 | .elementalDamageIcon 49 | background: #f36f26 50 | mask: url("https://www.bungie.net/common/destiny2_content/icons/DestinyDamageTypeDefinition_2a1773e10968f2d088b97c22b22bba9e.png") center center / cover; 51 | 52 | .stasisDamage 53 | composes: _power 54 | color: #4e87ff 55 | 56 | .elementalDamageIcon 57 | background: #4e87ff 58 | mask: url("https://www.bungie.net/common/destiny2_content/icons/DestinyDamageTypeDefinition_530c4c3e7981dc2aefd24fd3293482bf.png") center center / cover; 59 | 60 | .ammoType 61 | display: flex 62 | align-items: center 63 | border-left: 1px solid #706A73 64 | padding-left: 10px 65 | margin-left: 10px 66 | text-transform: uppercase 67 | -------------------------------------------------------------------------------- /src/lib/destinyUtils.js: -------------------------------------------------------------------------------- 1 | import { EMBLEM, HUNTER, TITAN, WARLOCK, NO_CLASS } from 'app/lib/destinyEnums'; 2 | import { getLower } from 'app/lib/utils'; 3 | import CLASS_OVERRIDES from 'app/extraData/classOverrides'; 4 | 5 | // TODO: we can just use itemCategoryHashes for this now? 6 | export const isOrnament = item => 7 | item.inventory && 8 | item.inventory.stackUniqueLabel && 9 | item.plug && 10 | item.plug.plugCategoryIdentifier && 11 | item.plug.plugCategoryIdentifier.includes('skins'); 12 | 13 | export const flagEnum = (state, value) => !!(state & value); 14 | 15 | function classFromString(str) { 16 | const results = str.match(/hunter|titan|warlock/); 17 | if (!results) { 18 | return NO_CLASS; 19 | } 20 | 21 | switch (results[0]) { 22 | case 'hunter': 23 | return HUNTER; 24 | case 'warlock': 25 | return WARLOCK; 26 | case 'titan': 27 | return TITAN; 28 | default: 29 | return NO_CLASS; 30 | } 31 | } 32 | 33 | export const getItemClass = item => { 34 | if (!item) { 35 | return NO_CLASS; 36 | } 37 | 38 | if (CLASS_OVERRIDES.hasOwnProperty(item.hash)) { 39 | return CLASS_OVERRIDES[item.hash]; 40 | } 41 | 42 | const stackUniqueLabel = getLower(item, 'inventory.stackUniqueLabel'); 43 | const plugCategoryIdentifier = getLower(item, 'plug.plugCategoryIdentifier'); 44 | 45 | if (hasCategoryHash(item, EMBLEM) && stackUniqueLabel.length) { 46 | return classFromString(stackUniqueLabel); 47 | } 48 | 49 | // TODO: Ornaments might provide this better now 50 | if (item.classType === 3 && isOrnament(item)) { 51 | return classFromString(plugCategoryIdentifier); 52 | } 53 | 54 | return item.classType; 55 | }; 56 | 57 | export function hasCategoryHash(item, categoryHash) { 58 | return ( 59 | item.itemCategoryHashes && item.itemCategoryHashes.includes(categoryHash) 60 | ); 61 | } 62 | -------------------------------------------------------------------------------- /src/components/ItemAttributes/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import cx from 'classnames'; 3 | 4 | import s from './styles.styl'; 5 | 6 | const ELEMENTAL_DAMAGE_CLASS = { 7 | // 0: None, 8 | 1: s.kineticDamage, 9 | 2: s.arcDamage, 10 | 3: s.solarDamage, 11 | 4: s.voidDamage, 12 | // 5: Raid, 13 | 6: s.stasisDamage 14 | }; 15 | 16 | const AMMO_TYPE = { 17 | 0: None, 18 | 1: ( 19 | 20 | {' '} 21 | Primary 22 | 23 | ), 24 | 2: ( 25 | 26 | {' '} 27 | Special 28 | 29 | ), 30 | 3: ( 31 | 32 | Heavy 33 | 34 | ), 35 | 4: Unknown 36 | }; 37 | 38 | export default function ItemAttributes({ className, item }) { 39 | if (!item) { 40 | return null; 41 | } 42 | 43 | const ammoType = item.equippingBlock && item.equippingBlock.ammoType; 44 | const elementalDamageClass = 45 | item && item.damageTypes && item.damageTypes.length > 0 46 | ? ELEMENTAL_DAMAGE_CLASS[item.damageTypes[0]] 47 | : null; 48 | 49 | if (!(elementalDamageClass || ammoType)) { 50 | return null; 51 | } 52 | 53 | return ( 54 |
55 | {elementalDamageClass && ( 56 |
57 | {item.damageTypes[0] && item.damageTypes[0] !== 1 && ( 58 |
59 | )} 60 |
600
61 |
62 | )} 63 | 64 | {ammoType && ( 65 |
66 |
{AMMO_TYPE[ammoType]}
67 |
68 | )} 69 |
70 | ); 71 | } 72 | -------------------------------------------------------------------------------- /src/components/SectionList/FilterDropdown.js: -------------------------------------------------------------------------------- 1 | import React, { Fragment, Component } from 'react'; 2 | 3 | import DropdownMenu from 'app/components/DropdownMenu'; 4 | import Icon from 'app/components/Icon'; 5 | import { 6 | HUNTER, 7 | TITAN, 8 | WARLOCK, 9 | FILTER_SHOW_COLLECTED, 10 | FILTER_SHOW_HIDDEN_SETS, 11 | FILTER_SHOW_ORNAMENTS, 12 | FILTER_SHOW_WEAPONS 13 | } from 'app/lib/destinyEnums'; 14 | 15 | import styles from 'app/components/Header/dropdownStyles.styl'; 16 | 17 | const FILTER_NAMES = { 18 | [HUNTER]: 'Hunter', 19 | [TITAN]: 'Titan', 20 | [WARLOCK]: 'Warlock', 21 | [FILTER_SHOW_COLLECTED]: 'Collected items', 22 | [FILTER_SHOW_ORNAMENTS]: 'Ornaments', 23 | [FILTER_SHOW_WEAPONS]: 'Weapons', 24 | [FILTER_SHOW_HIDDEN_SETS]: 'Hidden sets' 25 | }; 26 | 27 | export default class FilterDropdown extends Component { 28 | renderContent = () => { 29 | const { filters, setFilterItem } = this.props; 30 | 31 | return ( 32 | 33 | {Object.keys(filters).map(key => ( 34 | 43 | ))} 44 | 45 | ); 46 | }; 47 | 48 | render() { 49 | return ( 50 | 57 |
Filters
58 | 59 |
60 | 61 |
62 |
63 | ); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/components/MasterworkCatalyst/styles.styl: -------------------------------------------------------------------------------- 1 | $green = #44bd32; 2 | $trackedGreen = #A9FF6A 3 | $borderWidth = 1px 4 | 5 | .root 6 | // max-width: 25% 7 | min-width: 290px 8 | max-width: 25% 9 | flex: 1 0 25% 10 | margin: 0 !important 11 | padding: 7px 12 | 13 | // @media screen and (max-width: 910px) 14 | // width: 100% 15 | // max-width: initial 16 | // min-width: 100% 17 | 18 | code 19 | font-family: monospace 20 | 21 | ul 22 | padding-left: 30px 23 | 24 | & + & 25 | border-left: none 26 | 27 | // &.completed 28 | // opacity: .5 29 | 30 | .inner 31 | display: flex 32 | flex-direction: column 33 | position: relative 34 | z-index: 1 35 | background: #15191E 36 | padding: 15px 37 | height: 100% 38 | 39 | .tick 40 | position: absolute 41 | top: 8px 42 | right: 8px 43 | background: $green 44 | width: 20px 45 | height: 20px 46 | font-size: 14px 47 | line-height: 1 48 | border-radius: 50px 49 | display: flex 50 | align-items: center 51 | justify-content: center 52 | 53 | .screenshot 54 | display: block 55 | width: 100% 56 | height: auto 57 | 58 | .objectives 59 | margin-top: 15px; 60 | 61 | .masterworkComplete 62 | display: inline-block 63 | height: 2em 64 | margin-right: .5ch 65 | vertical-align: middle 66 | 67 | & + span 68 | vertical-align: baseline 69 | 70 | .plugIcon 71 | height: 1.5em 72 | width: auto 73 | vertical-align: middle 74 | dispaly: inline-block 75 | margin-right: .75ch 76 | 77 | .itemLink 78 | color: inherit 79 | text-decoration: none 80 | 81 | .instance + .instance 82 | border-top: 1px solid rgba(white, .05) 83 | 84 | .red 85 | color: red 86 | 87 | .green 88 | color: green 89 | 90 | 91 | .faded 92 | opacity: .75em 93 | 94 | 95 | .completeMessage 96 | display: flex 97 | align-items: center 98 | flex-grow: 1 99 | justify-content: center 100 | margin-bottom: 0 101 | -------------------------------------------------------------------------------- /src/setData/common/eververse.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | export const EVERVERSE_S1_EMOTES = [ 4 | 1045929536, // Salty 5 | 1045929537, // Flip Out 6 | 95537029, // Spicy Ramen 7 | 566102034, // Six Shooter 8 | 265428940, // Funky Dance 9 | 1711459689, // Dancehall 10 | 2225838050, // Cranking Dance 11 | 955394464, // Odd Dance 12 | 683947069, // Taunt Dance 13 | 1471723144, // Confused 14 | 267290351, // Flowing Dance 15 | 930064526, // Bureaucratic Walk 16 | 2871095181, // Floss Dance 17 | 3342375124, // Shuffle Dance 18 | 3830186047, // Shoulder Dance 19 | 1449701838, // Dancy Dance 20 | 2294119165, // Play Dead 21 | 2541494210, // You're the Guardian 22 | 1539578239, // Sneaky 23 | 3958460049, // Don the Hat 24 | 1533012008, // Huddle Up 25 | 2916219759, // Gallop 26 | 924779391, // Sick 27 | 814933120, // Get Up 28 | 1444655707, // Sadness 29 | 1280248869 // Good Idea 30 | ]; 31 | 32 | export const EVERVERSE_S2_EMOTES = [ 33 | 4111030162, // Selfie 34 | 421712807, // Mic Drop 35 | 717948749, // Sweeping 36 | 2592651789, // Timely Dance 37 | 1546934407, // Trendy Dance 38 | 2733199859, // Odder Dance 39 | 799898425, // Overnight Dance 40 | 2381763476, // Disco Dance 41 | 3826786378, // Grooving Dance 42 | 3397849259, // Call Me 43 | 3969346718, // Can-Do Attitude 44 | 530968313, // Cool, Dude 45 | 964176440, // Humbug 46 | 3210179545 // Victory Taunt 47 | ]; 48 | 49 | export const EVERVERSE_S3_EMOTES = [ 50 | 3179188489, // Curtain Call 51 | 569940092, // Fireworks 52 | 3340490891, // Home Run 53 | 870041067, // Popcorn 54 | 2775819545, // Awaken the Warmind 55 | 1628538405, // Freaky Dance 56 | 159967058, // Hold On 57 | 2610757989, // Collaborative Dance 58 | 2041281101, // Celebratory Dance 59 | 285267633, // Synced Dance 60 | 3518794540, // Air Quotes 61 | 2284035163, // Applause 62 | 1073496778, // Opulent Clap 63 | 1008495915, // Shiver 64 | 1229016681, // Zeus-Like Physique 65 | 1348805779 // Impatience 66 | ]; 67 | -------------------------------------------------------------------------------- /public/mask-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "destinySets", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@destiny-plumbing/definitions": "0.2.2", 7 | "async": "^2.6.0", 8 | "autotrack": "^2.4.1", 9 | "axios": "^0.21.1", 10 | "babel-polyfill": "^6.26.0", 11 | "browser-locale": "^1.0.3", 12 | "classnames": "^2.2.5", 13 | "date-fns": "^2.0.0-alpha.11", 14 | "dexie": "^2.0.1", 15 | "dotenv": "^8.2.0", 16 | "flow-bin": "^0.72.0", 17 | "image-size": "^0.6.2", 18 | "immer": "^1.2.0", 19 | "indexeddbshim": "^3.8.0", 20 | "lodash": "^4.17.4", 21 | "npm": "^6.0.0", 22 | "popper.js": "^1.14.1", 23 | "prettier": "^1.18.2", 24 | "react": "^16.8.6", 25 | "react-click-outside": "tj/react-click-outside", 26 | "react-dom": "^16.8.6", 27 | "react-lazyload": "^2.3.0", 28 | "react-modal": "^3.1.13", 29 | "react-redux": "^5.0.7", 30 | "react-router": "3.2.0", 31 | "react-scripts": "1.0.17", 32 | "react-scroll": "^1.7.9", 33 | "react-tippy": "^1.2.3", 34 | "redux": "^3.7.2", 35 | "redux-thunk": "^2.3.0", 36 | "reselect": "^3.0.1", 37 | "temp-dir": "^2.0.0" 38 | }, 39 | "scripts": { 40 | "start": "react-app-rewired start", 41 | "build": "react-app-rewired build", 42 | "serve": "serve -s build", 43 | "test": "flow && react-app-rewired test --env=jsdom", 44 | "deploy": "yarn run build && yarn run upload", 45 | "upload": "env AWS_PROFILE=money aws s3 sync build/ s3://destinysets.com/ --region=ap-southeast-2 --acl public-read", 46 | "generate-autotrack": "autotrack -o public/autotrack.build.js -p clean-url-tracker,url-change-tracker", 47 | "eject": "react-scripts eject" 48 | }, 49 | "devDependencies": { 50 | "@sentry/cli": "^1.30.0", 51 | "autoprefixer": "^7.2.2", 52 | "babel-plugin-lodash": "^3.3.2", 53 | "imports-loader": "^0.8.0", 54 | "postcss-loader": "^2.0.9", 55 | "react-app-rewire-hot-loader": "^1.0.0", 56 | "react-app-rewired": "1.3.8", 57 | "react-hot-loader": "^4.0.0", 58 | "stylus": "^0.54.5", 59 | "stylus-loader": "3.0.2", 60 | "unused-files-webpack-plugin": "^3.4.0", 61 | "webpack-visualizer-plugin": "^0.1.11" 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/components/Item/selectors.js: -------------------------------------------------------------------------------- 1 | import { createSelector } from 'reselect'; 2 | 3 | import { 4 | itemDefsSelector, 5 | itemHashPropSelector, 6 | objectiveDefsSelector, 7 | objectiveInstancesSelector 8 | } from 'app/store/selectors'; 9 | 10 | export const makeItemDefSelector = () => { 11 | return createSelector( 12 | itemDefsSelector, 13 | itemHashPropSelector, 14 | (itemDefs, itemHash) => { 15 | return itemDefs ? itemDefs[itemHash] : null; 16 | } 17 | ); 18 | }; 19 | 20 | function calcObjectiveCompletion(objectiveInstances, item, objectiveDefs) { 21 | const objectives = item.objectives.objectiveHashes.map( 22 | hash => objectiveInstances[hash] 23 | ); 24 | 25 | if (!objectiveDefs) { 26 | return 0; 27 | } 28 | 29 | const eachCompletionIsWorth = 1 / objectives.length; 30 | 31 | const completion = objectives.reduce((acc, objective) => { 32 | if (!objective) { 33 | return acc; 34 | } 35 | 36 | const objectiveDef = objectiveDefs[objective.objectiveHash]; 37 | 38 | if (!objectiveDef) { 39 | return acc; 40 | } 41 | 42 | const completionValue = objectiveDef.completionValue; 43 | const percentCompleted = (objective.progress || 0) / completionValue; 44 | 45 | if (objective && objective.complete) { 46 | return acc + eachCompletionIsWorth; 47 | } 48 | 49 | return ( 50 | acc + 51 | Math.min(percentCompleted * eachCompletionIsWorth, eachCompletionIsWorth) 52 | ); 53 | }, 0); 54 | 55 | return completion; 56 | } 57 | 58 | export const makeItemObjectiveProgressSelector = () => { 59 | return createSelector( 60 | makeItemDefSelector(), 61 | objectiveDefsSelector, 62 | objectiveInstancesSelector, 63 | (itemDef, objectiveDefs, objectiveInstances) => { 64 | if (!itemDef || !objectiveDefs || !objectiveInstances) { 65 | return 0; 66 | } 67 | 68 | const objectiveHashes = 69 | itemDef.objectives && itemDef.objectives.objectiveHashes; 70 | 71 | if (!objectiveHashes) { 72 | return 0; 73 | } 74 | 75 | return calcObjectiveCompletion( 76 | objectiveInstances, 77 | itemDef, 78 | objectiveDefs 79 | ); 80 | } 81 | ); 82 | }; 83 | -------------------------------------------------------------------------------- /src/lib/i18n.js: -------------------------------------------------------------------------------- 1 | import { keyBy } from 'lodash'; 2 | import browserLocale from 'browser-locale'; 3 | 4 | export const ENGLISH = { 5 | code: 'en', 6 | name: 'English' 7 | }; 8 | 9 | export const FRENCH = { 10 | code: 'fr', 11 | name: 'French' 12 | }; 13 | 14 | export const GERMAN = { 15 | code: 'de', 16 | name: 'German' 17 | }; 18 | 19 | export const ITALIAN = { 20 | code: 'it', 21 | name: 'Italian' 22 | }; 23 | 24 | export const JAPANESE = { 25 | code: 'ja', 26 | name: 'Japanese' 27 | }; 28 | 29 | export const PORTUGUESE = { 30 | code: 'pt-br', 31 | name: 'Portuguese (Brazil)' 32 | }; 33 | 34 | export const SPANISH = { 35 | code: 'es', 36 | name: 'Spanish' 37 | }; 38 | 39 | export const SPANISH_LATIN = { 40 | code: 'es-mx', 41 | name: 'Spanish (Latin America)' 42 | }; 43 | 44 | export const RUSSIAN = { 45 | code: 'ru', 46 | name: 'Russian' 47 | }; 48 | 49 | export const POLISH = { 50 | code: 'pl', 51 | name: 'Polish' 52 | }; 53 | 54 | export const CHINESE_SIMP = { 55 | code: 'zh-chs', 56 | name: 'Chinese (Simplified)' 57 | } 58 | 59 | export const CHINESE_TRAD = { 60 | code: 'zh-cht', 61 | name: 'Chinese (Traditional)' 62 | }; 63 | 64 | export const KOREAN = { 65 | code: 'ko', 66 | name: 'Korean' 67 | } 68 | 69 | export const DEFAULT_LANG = ENGLISH; 70 | 71 | export const languages = [ 72 | ENGLISH, 73 | FRENCH, 74 | GERMAN, 75 | ITALIAN, 76 | JAPANESE, 77 | PORTUGUESE, 78 | SPANISH, 79 | SPANISH_LATIN, 80 | RUSSIAN, 81 | POLISH, 82 | CHINESE_SIMP, 83 | CHINESE_TRAD, 84 | KOREAN 85 | ]; 86 | 87 | export const languageByCode = keyBy(languages, lang => lang.code); 88 | 89 | export function getBrowserLocale() { 90 | return browserLocale().toLowerCase(); 91 | } 92 | 93 | export function getDefaultLanguage() { 94 | let shortLocale = DEFAULT_LANG.code; 95 | let fullLocale = DEFAULT_LANG.code; 96 | 97 | try { 98 | fullLocale = getBrowserLocale(); 99 | shortLocale = getBrowserLocale().split('-')[0]; 100 | } catch (e) {} 101 | 102 | return ( 103 | languageByCode[fullLocale] || languageByCode[shortLocale] || DEFAULT_LANG 104 | ); 105 | } 106 | -------------------------------------------------------------------------------- /src/store/profile.js: -------------------------------------------------------------------------------- 1 | import * as destiny from 'app/lib/destiny'; 2 | 3 | const INITIAL_STORE = {}; 4 | 5 | const SET_PROFILES = 'Set profiles'; 6 | const PROFILE_LOADING_START = 'Loading profile - start'; 7 | const PROFILE_ERROR = 'Profile error'; 8 | 9 | export default function profileReducer( 10 | state = INITIAL_STORE, 11 | { type, payload } 12 | ) { 13 | switch (type) { 14 | case SET_PROFILES: 15 | return { 16 | ...state, 17 | ...payload, 18 | error: undefined 19 | }; 20 | 21 | case PROFILE_LOADING_START: 22 | return { 23 | ...state, 24 | profileLoading: payload 25 | }; 26 | 27 | case PROFILE_ERROR: 28 | return { 29 | err: payload 30 | }; 31 | 32 | default: 33 | return state; 34 | } 35 | } 36 | 37 | export function setProfiles({ 38 | currentProfile, 39 | allProfiles, 40 | profileLoading, 41 | isCached 42 | }) { 43 | return { 44 | type: SET_PROFILES, 45 | payload: { 46 | profile: currentProfile, 47 | allProfiles, 48 | profileLoading, 49 | isCached 50 | } 51 | }; 52 | } 53 | 54 | export function switchProfile(newProfile) { 55 | return { 56 | type: SET_PROFILES, 57 | payload: { 58 | profile: newProfile 59 | } 60 | }; 61 | } 62 | 63 | export function setProfileLoading(payload = true) { 64 | return { type: PROFILE_LOADING_START, payload }; 65 | } 66 | 67 | function setError(payload) { 68 | return { type: PROFILE_ERROR, payload }; 69 | } 70 | 71 | export function fetchProfile() { 72 | return dispatch => { 73 | dispatch(setProfileLoading(true)); 74 | 75 | return destiny 76 | .getCurrentProfiles() 77 | .then(data => { 78 | const profile = destiny.getLastProfile(data); 79 | 80 | dispatch( 81 | setProfiles({ 82 | currentProfile: profile, 83 | allProfiles: data.profiles, 84 | isCached: false, 85 | profileLoading: false 86 | }) 87 | ); 88 | 89 | return profile; 90 | }) 91 | .catch(err => { 92 | console.error('Error fetching current profiles'); 93 | console.error(err); 94 | 95 | dispatch(setError(err)); 96 | }); 97 | }; 98 | } 99 | -------------------------------------------------------------------------------- /src/views/Mods/DetailedMod.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import cx from 'classnames'; 3 | import { connect } from 'react-redux'; 4 | 5 | import BungieImage from 'app/components/BungieImage'; 6 | import Item from 'app/components/Item'; 7 | import s from './detailedMod.styl'; 8 | 9 | export const STAT_TO_ENERGY = { 10 | 3578062600: 1198124803, // any 11 | 3779394102: 728351493, // arc 12 | 3344745325: 591714140, // solar 13 | 2399985800: 4069572561 // void 14 | }; 15 | 16 | const ELEMENT_CLASS_NAME = { 17 | // 1198124803: s.Any, 18 | 728351493: s.arc, 19 | 591714140: s.solar, 20 | 4069572561: s.void 21 | }; 22 | 23 | function DetailedMod({ 24 | itemHash, 25 | armourSlot, 26 | setPopper, 27 | onItemClick, 28 | itemDef, 29 | energyCost, 30 | energyDef 31 | }) { 32 | if (!itemDef) { 33 | return null; 34 | } 35 | 36 | return ( 37 |
onItemClick(itemHash)}> 38 |
39 | 40 |
41 | 42 |
43 |
{itemDef.displayProperties.name}
44 |
45 | {energyDef && ( 46 | 47 | {' '} 51 | {energyCost.value} {energyDef.displayProperties.name} energy 52 | 53 | )} 54 | , {armourSlot} 55 |
56 |
57 |
58 | ); 59 | } 60 | 61 | const mapStateToProps = (state, ownProps) => { 62 | const itemDefs = state.definitions.DestinyInventoryItemDefinition; 63 | const itemDef = itemDefs && itemDefs[ownProps.itemHash]; 64 | 65 | const energyCost = (itemDef.investmentStats || []).find( 66 | st => st.statTypeHash && st.hasOwnProperty('value') 67 | ); 68 | 69 | const energyDefs = state.definitions.DestinyEnergyTypeDefinition; 70 | const energyDef = 71 | energyDefs && 72 | energyCost && 73 | energyDefs[STAT_TO_ENERGY[energyCost.statTypeHash]]; 74 | 75 | return { 76 | itemDef, 77 | energyDef, 78 | energyCost 79 | }; 80 | }; 81 | 82 | export default connect(mapStateToProps)(DetailedMod); 83 | -------------------------------------------------------------------------------- /src/components/Footer/index.js: -------------------------------------------------------------------------------- 1 | import React, { Fragment } from 'react'; 2 | 3 | import { saveDataExplorerVisited } from 'app/lib/ls'; 4 | 5 | import styles from './styles.styl'; 6 | 7 | import DonateButton from 'app/components/DonateButton'; 8 | 9 | const rememberDev = () => saveDataExplorerVisited(true); 10 | 11 | export default function Footer({ item, children }) { 12 | return ( 13 |
14 | 15 | {children && ( 16 | 17 |
18 | {children} 19 |
20 |
21 | )} 22 |
23 | Developer, or curious about the data behind Destiny? Check out the{' '} 24 | 30 | Destiny Data Explorer 31 | 32 | . 33 |
34 |
35 | Made by{' '} 36 | 41 | Josh Hunt 42 | {' '} 43 | and{' '} 44 | 49 | Jakosaur 50 | {' '} 51 | for Destiny fans. 52 |
53 | Having issues or need help?{' '} 54 | 59 | File an issue on GitHub 60 | 61 | . 62 |
63 | Many thanks to{' '} 64 | 69 | JpDeathBlade 70 | {' '} 71 | and{' '} 72 | 77 | TodayInDestiny 78 | {' '} 79 | for the Eververse Bright Dust schedule. 80 |
81 |
82 | Destiny is a registered trademark of Bungie. Data and images sourced from 83 | Bungie. 84 |
85 | Copyright © World Class Development Ltd 2018 86 |
87 | ); 88 | } 89 | -------------------------------------------------------------------------------- /src/components/ItemSet/styles.styl: -------------------------------------------------------------------------------- 1 | $padding = 10px; 2 | 3 | .inner 4 | background: #15191E 5 | height: 100% 6 | padding-bottom: 5px // TODO: set this in a better way? On .section? 7 | 8 | .noUi & 9 | background: none 10 | 11 | .header 12 | position: relative 13 | background: #283039 14 | padding: $padding 15 | 16 | .noUi & 17 | background: none 18 | 19 | .hidden 20 | opacity: .5 21 | 22 | .headerImage 23 | width: "calc(100% + (%s * 2))" % $padding 24 | display: block 25 | margin: $padding * -1 26 | 27 | .headerImage + .headerText 28 | position: absolute 29 | bottom: 0 30 | left: 0 31 | right: 0 32 | background: linear-gradient(to top, rgba(black, 1), rgba(black, 0)) 33 | padding: $padding 34 | font-size: 18px 35 | 36 | .split 37 | display: flex 38 | 39 | .splitMain 40 | flex: 1 41 | 42 | 43 | .hiddenToggleText 44 | font-size: 14px 45 | 46 | .hiddenToggle 47 | background: none 48 | padding: 5px 49 | margin: -5px 50 | border: none 51 | outline: none !important 52 | color: inherit 53 | font-size: 15px 54 | opacity: .25 55 | transition: opacity 150ms ease-in-out 56 | cursor: pointer 57 | 58 | &:active 59 | opacity: .3 !important 60 | 61 | @media (hover: hover) 62 | .hiddenToggle 63 | opacity: 0 64 | 65 | .inner:hover .hiddenToggle 66 | opacity: .5 67 | 68 | &:hover 69 | opacity: .75 70 | 71 | .seasonLabel 72 | font-size: .75em 73 | background: #2B3037 74 | border-radius: 2px 75 | padding: 0px 6px 76 | font-weight: 400 77 | margin-left: .5em 78 | 79 | .itemListWrapper 80 | margin: 0 -5px -10px -10px 81 | 82 | .noUi & 83 | margin-left: 0 84 | margin-right: 0 85 | 86 | .itemList 87 | display: inline-flex 88 | flex-wrap: wrap 89 | margin: 0 5px 5px 5px 90 | 91 | .noUi & 92 | margin: 0 -7px 93 | 94 | .item 95 | margin-bottom: 10px !important 96 | margin-top: 0 !important 97 | 98 | .title 99 | .desc 100 | margin: 0 101 | 102 | .title 103 | margin-bottom: .25em 104 | color: #f7f8f8 105 | font-weight: 500 106 | 107 | &:only-child 108 | margin-bottom: 0 109 | 110 | .desc 111 | color: #b7bcbe 112 | 113 | .section 114 | padding: 0 $padding 115 | 116 | .noUi & 117 | padding: 0 118 | 119 | .sectionName 120 | margin-top: 10px 121 | margin-bottom: 2px 122 | font-weight: 400 123 | -------------------------------------------------------------------------------- /src/components/Record/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { connect } from 'react-redux'; 3 | import cx from 'classnames'; 4 | 5 | import { flagEnum } from 'app/lib/destinyUtils'; 6 | 7 | import { 8 | objectiveInstancesSelector, 9 | recordsSelector 10 | } from 'app/store/selectors'; 11 | 12 | import Objectives from 'app/components/Objectives'; 13 | 14 | import s from './styles.styl'; 15 | 16 | function Record({ 17 | className, 18 | record, 19 | recordState, 20 | recordInstance, 21 | objectiveInstances, 22 | objectiveDefs 23 | }) { 24 | if (!record) { 25 | return null; 26 | } 27 | 28 | const completed = 29 | recordState && 30 | (!recordState.objectiveNotCompleted || recordState.recordRedeemed); 31 | 32 | return ( 33 |
34 |
35 | 36 |
37 |
38 | {record.displayProperties.description} 39 |
40 | 41 | {recordInstance && recordInstance.objectives && ( 42 | 48 | )} 49 | 50 | {!recordState &&

Missing data

} 51 |
52 |
53 | ); 54 | } 55 | 56 | export const enumerateState = state => ({ 57 | none: flagEnum(state, 0), 58 | recordRedeemed: flagEnum(state, 1), 59 | rewardUnavailable: flagEnum(state, 2), 60 | objectiveNotCompleted: flagEnum(state, 4), 61 | obscured: flagEnum(state, 8), 62 | invisible: flagEnum(state, 16), 63 | entitlementUnowned: flagEnum(state, 32), 64 | canEquipTitle: flagEnum(state, 64) 65 | }); 66 | 67 | const mapStateToProps = (state, ownProps) => { 68 | const { DestinyRecordDefinition: recordDefs } = state.definitions; 69 | const records = recordsSelector(state); 70 | const record = records[ownProps.hash]; 71 | const recordState = record && record.enumeratedState; 72 | 73 | return { 74 | objectiveInstances: objectiveInstancesSelector(state), 75 | record: recordDefs && recordDefs[ownProps.hash], 76 | recordState, 77 | recordInstance: record, 78 | objectiveDefs: state.definitions.DestinyObjectiveDefinition 79 | }; 80 | }; 81 | 82 | export default connect(mapStateToProps)(Record); 83 | -------------------------------------------------------------------------------- /src/components/Record/styles.styl: -------------------------------------------------------------------------------- 1 | $green = #44bd32; 2 | $trackedGreen = #A9FF6A 3 | $borderWidth = 1px 4 | 5 | .root 6 | padding-top: 15px 7 | position: relative 8 | display: flex 9 | 10 | &:before 11 | &:after 12 | content: "" 13 | position: absolute 14 | top: $borderWidth * -1 15 | right: $borderWidth * -1 16 | opacity: 0 17 | pointer-events: none 18 | transition: 250ms ease-in-out 19 | 20 | &:before 21 | bottom: $borderWidth * -1 22 | width: $borderWidth 23 | background-image: linear-gradient($trackedGreen, transparent) 24 | 25 | &:after 26 | left: $borderWidth * -1 27 | height: $borderWidth 28 | background-image: linear-gradient(to right, transparent, $trackedGreen) 29 | 30 | &.tracked 31 | &:before 32 | &:after 33 | opacity: 1 34 | 35 | 36 | // .completed 37 | // border-color: rgba($green, .5) 38 | // background: rgba($green, .1) 39 | 40 | .accessory 41 | margin-right: 15px 42 | 43 | .icon 44 | width: 50px 45 | height: 50px 46 | object-fit: contain 47 | display: block 48 | 49 | .main 50 | flex: 1 51 | 52 | .name 53 | font-size: 16px 54 | display: flex 55 | 56 | .nameName 57 | flex: 1 58 | 59 | .points 60 | font-size: 14px 61 | 62 | .objectives 63 | font-size: 13px 64 | margin-top: 15px 65 | 66 | .description 67 | line-height: 1.2 68 | 69 | .small 70 | font-size: .8em 71 | opacity: .8 72 | 73 | .trackButton 74 | background: none 75 | border: none 76 | padding: 0 77 | position absolute 78 | top: 0 79 | right: 10px 80 | width: 15px 81 | height: 18px 82 | opacity: 0 83 | transition: 250ms ease-in-out 84 | cursor: pointer 85 | outline: none !important 86 | 87 | .tracked & 88 | .root:hover & 89 | opacity: 1 90 | 91 | .completed & 92 | right: 25px 93 | 94 | 95 | .trackButtonBg 96 | .trackButtonFg 97 | display: block 98 | position absolute 99 | top: 0 100 | left: 0 101 | 102 | .trackButtonBg 103 | width: 16px 104 | 105 | .trackButtonFg 106 | width: 12px 107 | left: 2px 108 | top: 1px 109 | 110 | .tracked & 111 | animation: spin 1.5s infinite linear 112 | 113 | .tick 114 | position: absolute 115 | top: -5px 116 | right: -5px 117 | background: $green 118 | width: 20px 119 | height: 20px 120 | font-size: 14px 121 | line-height: 1 122 | border-radius: 50px 123 | display: flex 124 | align-items: center 125 | justify-content: center 126 | 127 | @keyframes spin 128 | from 129 | transform: rotate(0deg) 130 | 131 | to 132 | transform: rotate(180deg) 133 | -------------------------------------------------------------------------------- /scripts/imageScreenshotSizes.js: -------------------------------------------------------------------------------- 1 | const axios = require('axios'); 2 | const async = require('async'); 3 | 4 | const url = require('url'); 5 | const https = require('https'); 6 | 7 | const sizeOf = require('image-size'); 8 | 9 | const LIMIT = 25; 10 | 11 | // const imgUrl = 'http://my-amazing-website.com/image.jpeg'; 12 | // const options = url.parse(imgUrl); 13 | 14 | function getImageDimensions(imageUrl, cb) { 15 | return new Promise((resolve, reject) => { 16 | const options = url.parse(imageUrl); 17 | 18 | https.get(options, function(response) { 19 | const chunks = []; 20 | 21 | response 22 | .on('data', function(chunk) { 23 | chunks.push(chunk); 24 | }) 25 | .on('end', function() { 26 | const buffer = Buffer.concat(chunks); 27 | const dimensions = sizeOf(buffer); 28 | const sizeKey = [dimensions.width, dimensions.height].join('x'); 29 | console.log(sizeKey, imageUrl); 30 | cb(null, dimensions); 31 | }) 32 | .on('error', cb); 33 | }); 34 | }); 35 | } 36 | 37 | const sizes = {}; 38 | 39 | axios 40 | .get('https://destiny.plumbing/en/raw/DestinyInventoryItemDefinition.json') 41 | .then(({ data }) => { 42 | async.eachLimit( 43 | Object.values(data), 44 | LIMIT, 45 | (item, cb) => { 46 | if (!item.screenshot) { 47 | return cb(); 48 | } 49 | 50 | const imageUrl = `https://www.bungie.net${item.screenshot}`; 51 | 52 | getImageDimensions(imageUrl, (err, dimensions) => { 53 | if (err) { 54 | return cb(err); 55 | } 56 | 57 | const sizeKey = [dimensions.width, dimensions.height].join('x'); 58 | 59 | if (!sizes[sizeKey]) { 60 | sizes[sizeKey] = []; 61 | } 62 | 63 | sizes[sizeKey].push(item); 64 | cb(); 65 | }); 66 | }, 67 | err => { 68 | if (err) { 69 | console.error('error with everything fucking up'); 70 | console.error(err); 71 | return; 72 | } 73 | 74 | console.log('\nAll done:'); 75 | 76 | Object.keys(sizes).forEach(sizeKey => { 77 | console.log(''); 78 | console.log(sizeKey); 79 | sizes[sizeKey].forEach(item => { 80 | console.log( 81 | ` - ${item.hash} // https://destinysets.com/data/${item.hash}` 82 | ); 83 | }); 84 | }); 85 | 86 | console.log('\n'); 87 | Object.keys(sizes).forEach(sizeKey => { 88 | const count = sizes[sizeKey].length; 89 | console.log(sizeKey, count); 90 | }); 91 | } 92 | ); 93 | }); 94 | -------------------------------------------------------------------------------- /src/components/ExtraInfo/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { connect } from 'react-redux'; 3 | 4 | import getItemExtraInfo, { DISMANTLED_MESSAGE } from 'app/lib/getItemExtraInfo'; 5 | 6 | import Icon from 'app/components/Icon'; 7 | 8 | import styles from './styles.styl'; 9 | 10 | const TICK_STYLE = { 11 | [DISMANTLED_MESSAGE]: styles.greyTick 12 | }; 13 | 14 | function ExtraInfo({ 15 | className, 16 | item, 17 | inventoryEntry, 18 | richVendorEntry, 19 | inCollection, 20 | vendorDefs 21 | }) { 22 | const extraInfo = getItemExtraInfo(item, inventoryEntry).map(location => { 23 | return ( 24 | 25 | 26 | 27 | {' '} 28 | {location} 29 | 30 | ); 31 | }); 32 | 33 | if (inCollection && inventoryEntry) { 34 | extraInfo.push( 35 | 36 | 37 | 38 | {' '} 39 | {inventoryEntry.obtained 40 | ? 'Unlocked in Collections' 41 | : 'Dismantled & Unlocked in Collections'} 42 | 43 | ); 44 | } 45 | 46 | if (!richVendorEntry && extraInfo.length === 0) { 47 | return null; 48 | } 49 | 50 | return ( 51 |
52 | {extraInfo.map((info, index) => ( 53 |
{info}
54 | ))} 55 | {richVendorEntry && 56 | richVendorEntry.map((ve, index) => { 57 | const vendor = vendorDefs[ve.vendorHash]; 58 | 59 | return ( 60 |
61 | 62 | 63 | {' '} 64 | Available from{' '} 65 | {vendor ? vendor.displayProperties.name : 'unknown vendor'} 66 | {ve.costs.length > 0 && ( 67 | 68 | {' for '} 69 | 70 | {ve.costs 71 | .map(cost => { 72 | return ( 73 | 74 | {cost.quantity}{' '} 75 | {cost.item && cost.item.displayProperties.name} 76 | 77 | ); 78 | }) 79 | .reduce((prev, curr) => [prev, ' and ', curr])} 80 | 81 | )} 82 |
83 | ); 84 | })} 85 |
86 | ); 87 | } 88 | 89 | export default connect(state => ({ 90 | vendorDefs: state.definitions.DestinyVendorDefinition 91 | }))(ExtraInfo); 92 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Destiny Sets 2 | 3 | ## Getting started 4 | 5 | Just want to update the set data? You can skip this party 6 | 7 | Prerequisites: 8 | 9 | * A fairly recent installation of Node - I use v8.9.3 10 | * Able to use Terminal/bash 11 | 12 | 1. Copy `.env.local-sample` to `.env.local` and fill in 13 | `REACT_APP_BUNGIE_CLIENT_ID` and `REACT_APP_API_KEY` with your Bungie.net 14 | OAuth client_id and API Key (see below for how to get these) 15 | 16 | 2. Install dependencies with `npm install` (or `yarn install` if you have Yarn) 17 | 18 | 3. Run the local dev server with `npm start` (or `yarn start` if you have 19 | Yarn) 20 | 21 | 4. You should see "Compiled successfully!", with instructions, and the site 22 | should open in your browser. 23 | 24 | * Note, as we're using HTTPS locally with a self-signed certificate, you'll 25 | get a security warning. You'll need to 'proceed anyway' to continue. 26 | 27 | ## How to contribute to the set data 28 | 29 | 1. Open the appropriate data file in `src/setData` 30 | 2. Look at the structure of the data - it reflects the layout of each page on 31 | the site. Ultimately, items on the pages are specified in the `items` array, 32 | by their item hash (ID). 33 | 3. Search for an item on the 34 | [Data Explorer page on DestinySets.com](https://destinysets.com/data), click 35 | the item and copy the `hash` - the number in the little box next to the 36 | item's name in the overlay. 37 | * e.g. The hash for "Contender's Shell" is `113124080`. 38 | 4. Paste the hash into the appropriate items array in the data file you're 39 | making the change to. Just make sure you keep the syntax valid, observing 40 | commas, etc. It's also nice to include the little `// item name` comment at 41 | the end of each line to make it easier for the next person 42 | 5. Save the file and Submit the pull request via Github 🎉 43 | 44 | If you're adding multiple items in one set, the Data Explorer has a special mode 45 | to make it easier. Click the "Explore the entire Destiny 2 database..." text at 46 | the top to enable "Collect Mode". Now, as you click an item it'll appear in the 47 | sidebar, automatically categorised, and copied to your clipboard in a format 48 | appropriate for the data files. 49 | 50 | ### Getting API Keys from bungie.net 51 | 52 | > TODO: write this better 53 | 54 | 1. New app at https://www.bungie.net/en/Application 55 | 2. oauth client type: public 56 | 3. redirect url: url the site is running at. for dev, this will probably be 57 | https://localhost:4000 58 | 4. check all scopes, except for "Administrate groups and clans..." 59 | 5. Origin Header: if it's just a key for local development, just enter `*` here 60 | -------------------------------------------------------------------------------- /src/components/Item/styles.styl: -------------------------------------------------------------------------------- 1 | $size = 44px; 2 | $green = #44bd32; 3 | 4 | ._root 5 | margin: 5px 6 | 7 | .root 8 | composes: _root 9 | display: flex 10 | 11 | .imageWrapper 12 | position: relative 13 | 14 | .root 15 | .fadeOut 16 | .image, 17 | .overlay 18 | opacity: .35 19 | 20 | &:hover 21 | .fadeOut 22 | .image, 23 | .overlay 24 | opacity: 1 25 | 26 | &.obtained 27 | &.dismantled 28 | &.checklisted 29 | .fadeOut 30 | .image, 31 | .overlay 32 | opacity: 1 33 | 34 | .image 35 | display: block 36 | height: $size 37 | width: auto 38 | min-width: $size 39 | background: rgba(#D2D2D2, 0.1) 40 | object-fit: cover 41 | 42 | .modStyle & 43 | height: 53px 44 | min-width: 53px 45 | background: none 46 | border-radius: 3px 47 | 48 | .overlay 49 | position: absolute 50 | top: 0 51 | left: 0 52 | width: 100% 53 | 54 | .overlay.overlay 55 | background: none !important 56 | 57 | .placeholder 58 | composes: _root image 59 | height: $size 60 | background: rgba(#D2D2D2, 0.1) 61 | 62 | .tick 63 | position: absolute 64 | top: -5px 65 | right: @top 66 | line-height: 1 67 | text-align: center 68 | background: $green 69 | border-radius: 50% 70 | font-size: 12px 71 | padding: 3px 72 | width: 18px 73 | height: 18px 74 | filter: drop-shadow(2px 4px 6px #15191D) 75 | 76 | .dismantled & 77 | background: #7f8fa6 78 | 79 | .checklisted & 80 | background: #3c94ff 81 | 82 | .purchasable 83 | composes: tick 84 | padding: 0 85 | background: none 86 | width: 20px 87 | height: 20px 88 | 89 | img 90 | width: 100% 91 | 92 | &.legendaryShards 93 | filter: drop-shadow(0px 0px 1px #15191d) drop-shadow(0px 0px 2px #15191d) drop-shadow(0px 0px 0px #15191d) drop-shadow(2px 4px 6px #15191d) 94 | 95 | &.noCurrency 96 | background: #e67e22 97 | 98 | .extended 99 | margin-left: 5px 100 | line-height: 1.2 101 | 102 | .itemType 103 | font-size: .85em 104 | opacity: .8 105 | margin-top: 2px 106 | 107 | .objectiveOverlay 108 | position: absolute 109 | bottom: 2px 110 | left: 2px 111 | right: 2px 112 | height: 4px 113 | background: rgba(black, .5) 114 | 115 | .objectiveTrack 116 | height: 100% 117 | background: $green 118 | 119 | .objectivesComplete 120 | opacity: .25 121 | 122 | .itemArc 123 | background: darken(#79ebf3, 20%) !important 124 | 125 | .itemSolar 126 | background: darken(#f36f26, 20%) !important 127 | 128 | .itemVoid 129 | background: darken(#b082cb, 20%) !important 130 | 131 | .itemStasis 132 | background: darken(#4e87ff, 20%) !important -------------------------------------------------------------------------------- /src/views/Mods/styles.styl: -------------------------------------------------------------------------------- 1 | $green = #44bd32; 2 | 3 | .splitHeading 4 | display: flex 5 | justify-content: space-between 6 | 7 | .gearCount 8 | opacity: .8 9 | 10 | .explainer 11 | font-size: 16px; 12 | font-family: 'nhg text' 13 | letter-spacing: .25px 14 | margin-bottom: 30px 15 | 16 | .page 17 | margin: 0 15px 18 | 19 | .set 20 | border-left: 3px solid #64686b 21 | padding: 0 15px 22 | margin-bottom: 50px 23 | 24 | .gearMain 25 | flex: 1 26 | 27 | .heading 28 | &, * 29 | font-family: 'nhg text' 30 | 31 | letter-spacing: 1px 32 | 33 | h1& 34 | font-size: 40px 35 | 36 | h2& 37 | font-size: 25px 38 | margin: 0 0 10px 0 39 | 40 | h3& 41 | font-size: 16px 42 | text-transform: uppercase 43 | border-bottom: 2px solid #64686b 44 | padding-bottom: 5px 45 | margin: 0 0 14px 0 46 | 47 | h4& 48 | font-size: 14px 49 | font-weight: normal 50 | margin: 0 0 8px 0 51 | color: #d8d9da 52 | 53 | .Arc 54 | border-bottom-color: #79ebf3 !important 55 | 56 | .Solar 57 | border-bottom-color: #f36f26 !important 58 | 59 | .Void 60 | border-bottom-color: #b082cb !important 61 | 62 | .Stasis 63 | border-bottom-color: #4e87ff !important 64 | 65 | .itemAny 66 | img 67 | background: black !important 68 | 69 | .energyIcon 70 | height: 1em 71 | margin-right: 2px 72 | 73 | .group 74 | & + & 75 | margin-top: 22px 76 | 77 | .setsList 78 | display: flex 79 | flex-direction: row 80 | margin: 0 -20px 81 | flex-wrap: wrap 82 | 83 | .setForElement 84 | flex: 1 85 | margin: 0 20px 0 20px 86 | min-width: 300px 87 | 88 | @media screen and (max-width: 1415px) 89 | margin-bottom: 40px 90 | 91 | .itemGroup 92 | margin: -5px 93 | font-size: 0 94 | 95 | .item 96 | display: inline-block 97 | 98 | 99 | .searchResultsList 100 | display: grid 101 | grid-template-columns: repeat( auto-fill, minmax(300px, 1fr) ); 102 | margin-bottom: 30px 103 | 104 | 105 | mobile() 106 | @media screen and (max-width: 679px) 107 | {block} 108 | 109 | .search 110 | display: flex 111 | align-items: center 112 | position: relative 113 | margin-bottom: 30px 114 | width: 300px 115 | 116 | .searchInput 117 | background: none 118 | border: none 119 | font-family: inherit 120 | font-size: inherit 121 | width: 100% 122 | padding-left: 20px 123 | outline: none 124 | color: white 125 | transition: all 250ms ease-in-out 126 | border-bottom: 1px rgba(white, .2) solid 127 | 128 | &:focus 129 | border-bottom: 1px rgba(white, .4) solid 130 | 131 | .searchIcon 132 | pointer-events: none 133 | position: absolute 134 | // top: 0 135 | left: 0 136 | font-size: 14px 137 | -------------------------------------------------------------------------------- /src/components/MasterworkCatalyst/index.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { connect } from 'react-redux'; 3 | import cx from 'classnames'; 4 | 5 | import BungieImage from 'app/components/BungieImage'; 6 | import Record from 'app/components/Record'; 7 | import ItemBanner from 'app/components/ItemBanner'; 8 | import Icon from 'app/components/Icon'; 9 | import catalystRecordsByItemHash from 'app/extraData/catalystTriumphs'; 10 | import { 11 | makeItemInstanceSelector, 12 | objectiveDefsSelector, 13 | recordsSelector 14 | } from 'app/store/selectors'; 15 | 16 | import { makeItemDefSelector } from 'app/components/Item/selectors'; 17 | 18 | import masterworkComplete from './masterwork-hammer.png'; 19 | 20 | import s from './styles.styl'; 21 | 22 | class MasterworkCatalyst extends Component { 23 | render() { 24 | const { className, itemDef, recordHash, recordState } = this.props; 25 | 26 | if (!itemDef) { 27 | return
; 28 | } 29 | 30 | const completed = 31 | recordState && 32 | (!recordState.objectiveNotCompleted || recordState.recordRedeemed); 33 | 34 | return ( 35 |
36 |
37 | {completed && ( 38 |
39 | 40 |
41 | )} 42 | 43 | 44 | 45 | 46 | {completed ? ( 47 |

48 | {' '} 53 | Masterwork Complete 54 |

55 | ) : ( 56 | 57 | )} 58 |
59 |
60 | ); 61 | } 62 | } 63 | 64 | function mapStateToProps() { 65 | const itemInstanceSelector = makeItemInstanceSelector(); 66 | const itemDefSelector = makeItemDefSelector(); 67 | 68 | return (state, ownProps) => { 69 | const recordHash = catalystRecordsByItemHash[ownProps.itemHash]; 70 | 71 | const records = recordsSelector(state); 72 | const record = records[recordHash]; 73 | const recordState = record && record.enumeratedState; 74 | 75 | return { 76 | itemDef: itemDefSelector(state, ownProps), 77 | instances: itemInstanceSelector(state, ownProps), 78 | objectiveDefs: objectiveDefsSelector(state), 79 | recordHash, 80 | recordState 81 | }; 82 | }; 83 | } 84 | 85 | export default connect(mapStateToProps)(MasterworkCatalyst); 86 | -------------------------------------------------------------------------------- /src/views/ItemPage/styles.styl: -------------------------------------------------------------------------------- 1 | $border-color = lighten(#12161B, 10%); 2 | 3 | .root 4 | padding: 15px 15px 5 | max-width: 1400px 6 | margin: auto 7 | 8 | .pageFlex 9 | display: flex 10 | justify-content: center 11 | flex-wrap: wrap 12 | margin: 0 -15px 13 | 14 | .itemMain 15 | .itemExtra 16 | .perksSection 17 | flex: 1 18 | width: 50% 19 | min-width: 300px 20 | margin: 0 15px 30px 15px 21 | 22 | .perksSection 23 | flex-basis: 100% 24 | 25 | .nameIconLockup 26 | display: flex 27 | align-items: flex-end 28 | margin: 0 15px 15px 15px 29 | width: 100% 30 | border-bottom: 1px solid $border-color 31 | padding-bottom: 10px 32 | 33 | .itemIcon 34 | height: 60px 35 | width: 60px 36 | display: block 37 | margin-right: 15px 38 | 39 | .itemName 40 | margin: 0 41 | font-size: 28px 42 | 43 | .screenshot 44 | width: 100% 45 | display: block 46 | 47 | .stats 48 | font-size: 17.8px 49 | 50 | .itemSource 51 | .description 52 | margin: 0 0 20px 0 53 | font-style: italic 54 | 55 | .itemSource 56 | opacity: .7 57 | font-style: normal 58 | 59 | .skeleton 60 | background: $border-color 61 | color: $border-color 62 | height: 1.2em 63 | width: 100% 64 | min-width: 150px 65 | margin-bottom: .2em 66 | 67 | .skeletonImage 68 | background: $border-color 69 | height: 60px 70 | width: 60px 71 | 72 | .itemAttributes 73 | margin-bottom: 20px 74 | 75 | .perks 76 | display: flex 77 | margin: 0 -10px 78 | max-width: 100% 79 | overflow: auto 80 | -webkit-overflow-scrolling: touch 81 | 82 | .socket 83 | flex: 1 0 0 84 | margin: 0 10px 85 | 86 | & + & 87 | border-left: 1px solid $border-color 88 | padding-left: 10px 89 | 90 | .perk 91 | display: flex 92 | align-items: center 93 | padding: 5px 94 | font-size: 14px 95 | 96 | .perkName 97 | white-space: nowrap 98 | overflow: hidden 99 | text-overflow: ellipsis 100 | 101 | .perkIcon 102 | height: 30px 103 | display: inline-block 104 | margin-right: 5px 105 | 106 | .altPerk 107 | composes: perk 108 | opacity: 0.8 109 | 110 | .altPerkEnabled 111 | composes: perk 112 | font-weight: bold 113 | border: 1px solid rgba(68,189,50,0.15); 114 | background: rgba(68,189,50,0.15); 115 | 116 | .randomPerk 117 | composes: perk 118 | opacity: 0.8 119 | font-size: 13px 120 | 121 | .perkIcon 122 | height: 25px 123 | 124 | .randomRollTitle 125 | margin-top: 10px 126 | font-weight: bold 127 | 128 | .tooltipPerkDescription 129 | .tooltipPerkName 130 | white-space: pre-line 131 | text-align: left 132 | display: inline-block 133 | 134 | .tooltipPerkName 135 | display: block 136 | font-weight: bold 137 | 138 | img 139 | height: 1em 140 | vertical-align: middle 141 | -------------------------------------------------------------------------------- /src/components/ItemBannerNew/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import cx from 'classnames'; 3 | 4 | import Icon from 'app/components/Icon'; 5 | 6 | import { 7 | EMBLEM, 8 | LEGENDARY, 9 | EXOTIC, 10 | UNCOMMON, 11 | RARE, 12 | COMMON, 13 | TITAN, 14 | HUNTER, 15 | WARLOCK 16 | } from 'app/lib/destinyEnums'; 17 | import { hasCategoryHash } from 'app/lib/destinyUtils'; 18 | 19 | import styles from './styles.styl'; 20 | 21 | const TIER_STYLE = { 22 | [EXOTIC]: styles.exotic, 23 | [LEGENDARY]: styles.legendary, 24 | [UNCOMMON]: styles.common, 25 | [RARE]: styles.rare, 26 | [COMMON]: styles.basic 27 | }; 28 | 29 | // TODO: not localised properly 30 | const CLASS_TYPE = { 31 | [TITAN]: 'Titan', 32 | [HUNTER]: 'Hunter', 33 | [WARLOCK]: 'Warlock' 34 | }; 35 | 36 | // TODO: not localised properly 37 | const WEAPON_SLOT = { 38 | 1498876634: 'Kinetic', 39 | 2465295065: 'Energy', 40 | 953998645: 'Power' 41 | }; 42 | 43 | export default function ItemBanner({ className, item, displayItem, onClose }) { 44 | const { 45 | inventory, 46 | classType, 47 | itemTypeName, 48 | itemTypeDisplayName, 49 | secondaryIcon, 50 | backgroundColor 51 | } = item; 52 | 53 | const { displayProperties } = displayItem || item; 54 | 55 | const tier = inventory.tierTypeHash; 56 | const isEmblem = hasCategoryHash(item, EMBLEM); 57 | const showEmblem = secondaryIcon && isEmblem; 58 | const weaponSlot = 59 | item.equippingBlock && 60 | WEAPON_SLOT[item.equippingBlock.equipmentSlotTypeHash]; 61 | 62 | const { red, green, blue } = backgroundColor || {}; 63 | 64 | return ( 65 |
73 |
74 |
75 |
{displayProperties.name}
76 | {onClose && ( 77 |
78 | 81 |
82 | )} 83 |
84 |
85 | {item.redacted ? ( 86 |
87 | Classified item 88 |
89 | ) : ( 90 |
91 | {' '} 92 | {CLASS_TYPE[classType]} {itemTypeName || itemTypeDisplayName}{' '} 93 | {weaponSlot && ` - ${weaponSlot}`} 94 |
95 | )} 96 | 97 |
{inventory.tierTypeName}
98 |
99 |
100 |
101 | ); 102 | } 103 | -------------------------------------------------------------------------------- /src/components/ItemModal/styles.styl: -------------------------------------------------------------------------------- 1 | // These two must be 119:24 for optimal emblem background 2 | $width = 450px 3 | $headerHeight = $width * (24/119) 4 | 5 | .root 6 | background: #20262d 7 | box-shadow: 0 2px 10px 0 rgba(black, 1) 8 | width: $width 9 | max-width: 100% 10 | padding: 0 15px 11 | overflow: auto 12 | border-radius: 4px 13 | position: relative 14 | 15 | .screenshot 16 | display: block 17 | width: calc(100% + 30px) 18 | object-fit: cover 19 | object-position: center 20 | margin: 0 -15px 21 | margin-bottom: 0 22 | transition: 200ms ease-in-out all 23 | 24 | .itemTop 25 | margin: 0 -15px 15px -15px 26 | padding: 10px 15px 27 | height: $headerHeight 28 | font-size: 20px 29 | 30 | .objectiveTitle 31 | font-size: 1.1em 32 | margin-bottom: 5px 33 | 34 | .objectives 35 | font-size: 14px 36 | 37 | .perks 38 | margin-bottom: 15px 39 | border-bottom: 1px solid rgba(white, .2) 40 | border-top: 1px solid rgba(white, .2) 41 | padding-bottom: 10px 42 | padding-top: 10px 43 | 44 | .close 45 | background: none 46 | padding: 15px 47 | outline: none !important 48 | cursor: pointer 49 | appearance: none 50 | border: none 51 | position: absolute 52 | top: 0 53 | right: 0 54 | font-size: 1em 55 | color: white 56 | line-height: 1 57 | 58 | .extraInfo 59 | opacity: .8 60 | font-size: .9em 61 | font-style: italic 62 | margin-bottom: 15px 63 | 64 | .viewItemLinks 65 | margin: 0 -8px 15px -8px 66 | padding: 0 67 | font-size: .9em 68 | 69 | .viewItemLinks li 70 | display: inline-block 71 | margin: 0 8px 72 | padding: 0 73 | list-style: none 74 | 75 | .root p a, 76 | .viewItemLinks a 77 | color: white !important 78 | 79 | .button 80 | background: lighten(#21262D, 10%) 81 | border: 1px solid @background 82 | color: #D4D6D7 83 | border-radius: 3px 84 | appearance: none 85 | font-size: 16px 86 | margin-top: 5px 87 | margin-bottom: 15px 88 | padding: 2px 10px 89 | outline: none !important 90 | transition: 150ms ease-in-out all 91 | cursor: pointer 92 | 93 | &:hover 94 | background: lighten(#21262D, 15%) 95 | border-color: @background 96 | 97 | &:active 98 | background: lighten(#21262D, 5%) 99 | border-color: @background 100 | 101 | & + & 102 | margin-left: 1em 103 | 104 | .mainButton 105 | composes: button 106 | border-color: #5d6573 107 | 108 | &:hover 109 | border-color: lighten(#5d6573, 15%) 110 | 111 | &:active 112 | border-color: lighten(#5d6573, 5%) 113 | 114 | .exotic 115 | background: #ceae33 116 | 117 | .legendary 118 | background: #522f65 119 | 120 | .rare 121 | background: #5076a3 122 | 123 | .common 124 | background: #366f3c 125 | 126 | .basic 127 | background: #c3bcb4 128 | color: #1e242b 129 | 130 | .ishtarLogo 131 | display: inline-block 132 | height: 1.2em 133 | margin-right: .25em 134 | vertical-align: middle 135 | 136 | & + a 137 | vertical-align: middle 138 | -------------------------------------------------------------------------------- /src/components/Header/ProfileDropdown.js: -------------------------------------------------------------------------------- 1 | import React, { Fragment, Component } from 'react'; 2 | import DropdownMenu from 'app/components/DropdownMenu'; 3 | import Icon, { PlatformIcon } from 'app/components/Icon'; 4 | 5 | import { PLATFORMS } from 'app/lib/destinyEnums'; 6 | 7 | import styles from './dropdownStyles.styl'; 8 | 9 | function Platform({ 10 | authExpired, 11 | profileLoading, 12 | profileCached, 13 | membershipType 14 | }) { 15 | if (profileLoading) { 16 | return ( 17 | 18 | Updating... 19 | 20 | ); 21 | } 22 | 23 | if (profileCached) { 24 | return ( 25 | 26 | Booting up... 27 | 28 | ); 29 | } 30 | 31 | if (authExpired) { 32 | return Login expired; 33 | } 34 | 35 | return ( 36 | 37 | {PLATFORMS[membershipType]} 38 | 39 | ); 40 | } 41 | 42 | export default class ProfileDropdown extends Component { 43 | renderContent = () => { 44 | return ( 45 | 46 | {this.props.allProfiles.map((profile, index) => ( 47 |
this.props.switchProfile(profile)} 51 | > 52 | {profile.profile.data.userInfo.displayName} 53 |
54 | 57 |
58 |
59 | ))} 60 | 61 |
62 | Log out 63 |
64 |
65 | ); 66 | }; 67 | 68 | render() { 69 | const { 70 | authExpired, 71 | currentProfile, 72 | profileCached, 73 | profileLoading 74 | } = this.props; 75 | 76 | const showGrey = authExpired || profileCached || profileLoading; 77 | 78 | return ( 79 | 84 |
85 |
{currentProfile.profile.data.userInfo.displayName}
86 | 87 |
88 | 96 |
97 |
98 | 99 |
100 | 101 |
102 |
103 | ); 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /src/lib/destinyEnums.js: -------------------------------------------------------------------------------- 1 | export const TITAN = 0; 2 | export const HUNTER = 1; 3 | export const WARLOCK = 2; 4 | export const NO_CLASS = 3; 5 | 6 | export const FILTER_SHOW_COLLECTED = 'showCollected'; 7 | export const FILTER_SHOW_HIDDEN_SETS = 'showHiddenSets'; 8 | export const FILTER_SHOW_ORNAMENTS = 'showOrnaments'; 9 | export const FILTER_SHOW_WEAPONS = 'showWeapons'; 10 | 11 | const testChecklists = false; 12 | 13 | const _CHECKLIST_PROFILE_COLLECTIONS = 3393554306; 14 | const CHECKLIST_SLEEPER_NODES = 365218222; 15 | export const CHECKLIST_PROFILE_COLLECTIONS = testChecklists 16 | ? CHECKLIST_SLEEPER_NODES 17 | : _CHECKLIST_PROFILE_COLLECTIONS; 18 | export const CHECKLIST_CHARACTER_COLLECTIONS = 3246761912; 19 | 20 | export const MASTERWORK_FLAG = 4; 21 | 22 | // itemCategoryHashes 23 | export const CLASS_ITEMS = 49; 24 | export const WEAPON = 1; 25 | export const EMBLEM = 19; 26 | export const ARMOR = 20; 27 | export const GHOST = 39; 28 | export const GHOST_PROJECTION = 1404791674; 29 | export const SHADER = 41; 30 | export const SHIP = 42; 31 | export const SPARROW = 43; 32 | export const EMOTES = 44; 33 | export const MODS1 = 56; 34 | export const MODS2 = 59; 35 | export const CLAN_BANNER = 58; 36 | export const DUMMIES = 3109687656; 37 | 38 | export const WEAPON_MODS_ORNAMENTS = 3124752623; 39 | export const ARMOR_MODS_ORNAMENTS = 1742617626; 40 | export const ARMOR_MODS_ORNAMENTS_HUNTER = 3683250363; 41 | export const ARMOR_MODS_ORNAMENTS_TITAN = 3229540061; 42 | export const ARMOR_MODS_ORNAMENTS_WARLOCK = 3684181176; 43 | 44 | export const HELMET = 45; 45 | export const ARMS = 46; 46 | export const CHEST = 47; 47 | export const LEGS = 48; 48 | export const CLASS_ITEM = 49; 49 | 50 | export const KINETIC_WEAPON = 2; 51 | export const ENERGY_WEAPON = 3; 52 | export const POWER_WEAPON = 4; 53 | 54 | export const LEGENDARY = 4008398120; 55 | export const EXOTIC = 2759499571; 56 | export const UNCOMMON = 2395677314; 57 | export const RARE = 2127292149; 58 | export const COMMON = 3340296461; 59 | 60 | export const XBOX = 1; 61 | export const PLAYSTATION = 2; 62 | export const PC_STEAM = 3; 63 | export const PC_BLIZZARD = 4; 64 | export const TIGERDEMON = 10; 65 | export const BUNGIENEXT = 254; 66 | 67 | export const KINETIC = 3373582085; 68 | export const VOID = 3454344768; 69 | export const SOLAR = 1847026933; 70 | export const ARC = 2303181850; 71 | 72 | export const STAT_RECOVERY = 1943323491; 73 | export const STAT_RESILIENCE = 392767087; 74 | export const STAT_MOBILITY = 2996146975; 75 | export const STAT_POWER = 1935470627; 76 | 77 | export const NUMERICAL_STATS = [4284893193, 3871231066, 2961396640]; 78 | export const STAT_BLACKLIST = [ 79 | 1480404414, // Attack 80 | 1935470627, // Power 81 | 3897883278, // Defense 82 | 1501155019 // Sparrow speed, always 0 83 | ]; 84 | 85 | export const PLATFORMS = { 86 | [XBOX]: 'Xbox', 87 | [PLAYSTATION]: 'PlayStation', 88 | [PC_STEAM]: 'PC (Steam)', 89 | [PC_BLIZZARD]: 'PC (Battle.net)', 90 | [TIGERDEMON]: 'TigerDemon', 91 | [BUNGIENEXT]: 'BungieNext' 92 | }; 93 | 94 | export const CLASSES = { 95 | [WARLOCK]: 'Warlock', 96 | [TITAN]: 'Titan', 97 | [HUNTER]: 'Hunter' 98 | }; 99 | -------------------------------------------------------------------------------- /src/views/SolsticeOfHeroes/styles.styl: -------------------------------------------------------------------------------- 1 | $green = #44bd32; 2 | 3 | 4 | .splitHeading 5 | display: flex 6 | justify-content: space-between 7 | 8 | .gearCount 9 | opacity: .8 10 | 11 | .explainer &, .loading 12 | font-size: 16px; 13 | font-family: 'nhg text' 14 | letter-spacing: .25px 15 | margin-bottom: 30px 16 | 17 | .page 18 | margin:0 30px 19 | 20 | .set 21 | border-left: 5px solid transparent 22 | padding: 0 15px 23 | 24 | &:nth-of-type(1) 25 | border-left-color: #4f76a4 26 | 27 | &:nth-of-type(2) 28 | border-left-color: #522f65 29 | 30 | &:nth-of-type(3) 31 | border-left-color: #522f65 32 | 33 | 34 | .gearMain 35 | flex: 1 36 | 37 | .heading 38 | &, * 39 | font-family: 'nhg text' 40 | letter-spacing: 1px 41 | 42 | h1& 43 | font-size: 50px 44 | 45 | h2& 46 | font-size: 35px 47 | margin-bottom: 15px 48 | 49 | h3& 50 | font-size: 14px 51 | text-transform: uppercase 52 | border-bottom: 1px solid #64686b 53 | padding-bottom: 5px 54 | 55 | .setsList 56 | display: flex 57 | flex-direction: row 58 | margin: 0 -20px 59 | 60 | @media screen and (max-width: 940px) 61 | display: block 62 | 63 | .setForClass 64 | flex: 1 65 | margin: 0 20px 66 | 67 | .gear 68 | position: relative 69 | display: flex 70 | margin-bottom: 30px 71 | line-height: 1 72 | opacity: .35 73 | transition: 200ms ease-in-out opacity 74 | 75 | &.obtained 76 | opacity: 1 77 | 78 | &.notLoggedIn 79 | opacity: .35 80 | 81 | .iconWell 82 | flex: 0 83 | margin-right: 10px 84 | 85 | .gearIcon 86 | width: 45px 87 | height: 45px 88 | 89 | .gearDescription 90 | margin: 5px 0 91 | line-height: 1.3 92 | 93 | > span 94 | font-size: .9em 95 | opacity: .75 96 | 97 | .classIcon 98 | width: 1.75em 99 | height: 1.75em 100 | // vertical-align: middle 101 | margin-right: 8px 102 | object-fit: cover 103 | 104 | &:global(.Warlock) 105 | height: 10px 106 | 107 | &:global(.Hunter) 108 | height: 13px 109 | 110 | &:global(.Titan) 111 | height: 13px 112 | 113 | .objectives 114 | margin-top: 10px 115 | font-size: 14px 116 | 117 | ._baseTick 118 | display: inline-block 119 | line-height: 1 120 | text-align: center 121 | background: $green 122 | border-radius: 50% 123 | font-size: 12px 124 | padding: 3px 125 | width: 18px 126 | height: 18px 127 | 128 | .inlineTick 129 | composes: _baseTick 130 | transform: scale(.8) 131 | margin-right: .5em 132 | 133 | .tick 134 | composes: _baseTick 135 | position: absolute 136 | top: -5px 137 | left: @top 138 | 139 | // .partiallyCompleted & 140 | // width: 16px 141 | // height: 16px 142 | // border: 2px solid $green 143 | // background: desaturate($green, 20%) 144 | 145 | // svg 146 | // position: relative 147 | // top: -3px 148 | // left: -3px 149 | 150 | .dismantled & 151 | background: #7f8fa6 152 | 153 | .checklisted & 154 | background: #3c94ff 155 | 156 | .toggleButton 157 | background: none 158 | border-radius: 2px 159 | border: 2px solid rgba(white, .7) 160 | appearance: none 161 | color: rgba(white, .9) 162 | font-size: 14px 163 | padding: 3px 10px 164 | -------------------------------------------------------------------------------- /src/setData/common/yearOneRaids.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | export const RAID_LEVIATHAN_WEAPONS = [ 4 | 2505533224, 5 | 3325744914, 6 | 3906942101, 7 | 1128225405, 8 | 3954531357, 9 | 3380742308, 10 | 3691881271, 11 | 1018072983 12 | ]; 13 | 14 | export const RAID_LEVIATHAN_ARMOR_HUNTER = [ 15 | 641933202, 16 | 1108389627, 17 | 2232730709, 18 | 3853397101, 19 | 1354679720 20 | ]; 21 | export const RAID_LEVIATHAN_ARMOR_TITAN = [ 22 | 2913992254, 23 | 2183861871, 24 | 3292127945, 25 | 2758465169, 26 | 311429764 27 | ]; 28 | export const RAID_LEVIATHAN_ARMOR_WARLOCK = [ 29 | 618662449, 30 | 3530284424, 31 | 1230192768, 32 | 336656482, 33 | 581908797 34 | ]; 35 | 36 | export const RAID_LEVIATHAN_ARMOR_PRESTIGE_HUNTER = [ 37 | 2013109093, 38 | 1960303676, 39 | 407863746, 40 | 30962014, 41 | 3984534843 42 | ]; 43 | export const RAID_LEVIATHAN_ARMOR_PRESTIGE_TITAN = [ 44 | 1413589587, 45 | 1879942842, 46 | 1876645652, 47 | 288406316, 48 | 574137193 49 | ]; 50 | export const RAID_LEVIATHAN_ARMOR_PRESTIGE_WARLOCK = [ 51 | 2700598110, 52 | 2676042151, 53 | 3592548939, 54 | 2193494689, 55 | 3763332442 56 | ]; 57 | 58 | // 59 | // RAID LAIR 1 - Eater of Worlds 60 | // 61 | export const RAID_EOW_WEAPONS = [ 62 | 3886263130, // I Am Alive 63 | 2707464805 // Zenith of Your Kind 64 | ]; 65 | 66 | export const RAID_EOW_ARMOR_HUNTER = [ 67 | 781488881, 68 | 3693007688, 69 | 4240859456, 70 | 75025442, 71 | 3168014845 72 | ]; 73 | export const RAID_EOW_ARMOR_TITAN = [ 74 | 3731175213, 75 | 88873628, 76 | 2938125956, 77 | 3386768934, 78 | 3681852889 79 | ]; 80 | export const RAID_EOW_ARMOR_WARLOCK = [ 81 | 161336786, 82 | 627690043, 83 | 1877424533, 84 | 3331120813, 85 | 574916072 86 | ]; 87 | 88 | export const RAID_EOW_ORNAMENTS_HUNTER = [ 89 | 3346055334, 90 | 2519280691, 91 | 3179514973, 92 | 2899660517, 93 | 1928662068 94 | ]; 95 | export const RAID_EOW_ORNAMENTS_TITAN = [ 96 | 3036703920, 97 | 3093970453, 98 | 2380365371, 99 | 1815512839, 100 | 3991114670 101 | ]; 102 | export const RAID_EOW_ORNAMENTS_WARLOCK = [ 103 | 1397648181, 104 | 1575703224, 105 | 3163954768, 106 | 4135965162, 107 | 2837735361 108 | ]; 109 | 110 | // 111 | // RAID LAIR 2 - Spire of Stars 112 | // 113 | export const RAID_SOS_WEAPONS = [2084611899, 4288031461]; 114 | 115 | export const RAID_SOS_ARMOR_HUNTER = [ 116 | 3518692432, 117 | 3316476193, 118 | 813277303, 119 | 91896851, 120 | 2475562438 121 | ]; 122 | export const RAID_SOS_ARMOR_TITAN = [ 123 | 1178920188, 124 | 2575374197, 125 | 2295412715, 126 | 4151496279, 127 | 1035112834 128 | ]; 129 | export const RAID_SOS_ARMOR_WARLOCK = [ 130 | 2305801487, 131 | 165966230, 132 | 4213777114, 133 | 1378348656, 134 | 3862230571 135 | ]; 136 | 137 | export const RAID_SOS_ORNAMENTS_HUNTER = [ 138 | 215292674, 139 | 3849801323, 140 | 385347493, 141 | 646591613, 142 | 3802263800 143 | ]; 144 | export const RAID_SOS_ORNAMENTS_TITAN = [ 145 | 3188290238, 146 | 2458159855, 147 | 17582153, 148 | 3032763153, 149 | 1331851268 150 | ]; 151 | export const RAID_SOS_ORNAMENTS_WARLOCK = [ 152 | 3909176373, 153 | 2543545988, 154 | 3439095932, 155 | 4091208110, 156 | 430065393 157 | ]; 158 | -------------------------------------------------------------------------------- /src/components/ItemBanner/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import cx from 'classnames'; 3 | 4 | import Icon from 'app/components/Icon'; 5 | 6 | import { 7 | EMBLEM, 8 | LEGENDARY, 9 | EXOTIC, 10 | UNCOMMON, 11 | RARE, 12 | COMMON, 13 | TITAN, 14 | HUNTER, 15 | WARLOCK 16 | } from 'app/lib/destinyEnums'; 17 | 18 | import styles from './styles.styl'; 19 | 20 | const TIER_STYLE = { 21 | [EXOTIC]: styles.exotic, 22 | [LEGENDARY]: styles.legendary, 23 | [UNCOMMON]: styles.common, 24 | [RARE]: styles.rare, 25 | [COMMON]: styles.basic 26 | }; 27 | 28 | const CLASS_TYPE = { 29 | [TITAN]: 'Titan', 30 | [HUNTER]: 'Hunter', 31 | [WARLOCK]: 'Warlock' 32 | }; 33 | 34 | // TODO: not localised properly 35 | const WEAPON_SLOT = { 36 | 1498876634: 'Kinetic', 37 | 2465295065: 'Energy', 38 | 953998645: 'Power' 39 | }; 40 | 41 | export default function ItemBanner({ className, item, displayItem, onClose }) { 42 | const { 43 | inventory, 44 | classType, 45 | itemTypeName, 46 | itemTypeDisplayName, 47 | itemCategoryHashes, 48 | secondaryIcon, 49 | backgroundColor 50 | } = item; 51 | 52 | const { displayProperties } = displayItem || item; 53 | 54 | const tier = inventory.tierTypeHash; 55 | const isEmblem = (itemCategoryHashes || []).includes(EMBLEM); 56 | const showEmblem = secondaryIcon && isEmblem; 57 | const icon = displayProperties.icon || '/img/misc/missing_icon_d2.png'; 58 | const weaponSlot = 59 | item.equippingBlock && 60 | WEAPON_SLOT[item.equippingBlock.equipmentSlotTypeHash]; 61 | 62 | const { red, green, blue } = backgroundColor || {}; 63 | 64 | return ( 65 |
73 | 79 | 80 |
81 |
82 |
83 | {(() => { 84 | if (item.hash === '1386601612' || item.hash === 1386601612) { 85 | debugger; 86 | } 87 | return displayProperties.name; 88 | })()} 89 |
90 | {onClose && ( 91 |
92 | 95 |
96 | )} 97 |
98 |
99 | {item.redacted ? ( 100 |
101 | Classified item 102 |
103 | ) : ( 104 |
105 | {' '} 106 | {CLASS_TYPE[classType]} {itemTypeName || itemTypeDisplayName}{' '} 107 | {weaponSlot && ` - ${weaponSlot}`} 108 |
109 | )} 110 |
{inventory.tierTypeName}
111 |
112 |
113 |
114 | ); 115 | } 116 | -------------------------------------------------------------------------------- /src/views/Inventory/styles.styl: -------------------------------------------------------------------------------- 1 | .root 2 | padding: 15px 3 | 4 | 5 | mobile() 6 | @media screen and (max-width: 679px) 7 | {block} 8 | 9 | .promo 10 | margin: auto 11 | font-size: 17px 12 | text-align: center 13 | max-width: 775px 14 | background: #15191E 15 | padding: 10px 16 | margin-bottom: 60px 17 | 18 | a 19 | color: white 20 | 21 | .maintenance 22 | composes: promo 23 | padding: 6px 24 | border: 2px solid #e74c3c 25 | 26 | .button 27 | color: black !important 28 | background: rgba(208, 170, 11, 1.0) 29 | padding: 10px 20px 30 | text-decoration: none 31 | transition: 150ms ease-in-out 32 | display: inline-block 33 | 34 | &:hover 35 | background: #ffce1f 36 | 37 | 38 | .trackedItems 39 | position: fixed 40 | right: 0 41 | bottom: 0 42 | padding-right: 16px 43 | padding-left: 16px 44 | max-height: calc(100vh - 52px) 45 | overflow: auto 46 | color: white 47 | z-index: 999 48 | 49 | +mobile() 50 | left: 0 51 | display: flex 52 | flex-direction: column 53 | align-items: center 54 | 55 | .trackedItem 56 | padding-bottom: 10px 57 | 58 | .info 59 | background: #B8B9BA 60 | padding: 8px 15px 61 | border-radius: 2px 62 | color: #090B0F 63 | margin-bottom: 50px 64 | 65 | .errorInfo 66 | composes: info 67 | background: #e74c3c 68 | color: white 69 | 70 | a 71 | color: white !important 72 | 73 | .hiddenSets 74 | text-align: center 75 | 76 | .unhideSetsButton 77 | background: none 78 | border: none 79 | text-decoration: underline 80 | color: inherit 81 | padding: 0 82 | font-size: inherit 83 | cursor: pointer 84 | outline: none !important 85 | transition: opacity 150ms ease-in-out 86 | 87 | &:active 88 | opacity: .6 89 | 90 | 91 | .solsticePromo 92 | &, * 93 | font-family: 'nhg text' 94 | 95 | margin-bottom: 50px + 25px 96 | text-shadow: 0 1px 1px 5px rgba(black, .75) 97 | margin-left: -15px 98 | margin-right: -15px 99 | position: relative 100 | 101 | .solsticeBg 102 | position: absolute 103 | top: 0 104 | left: 0 105 | bottom: 0 106 | width: 75%; 107 | background: url("./solstice.jpg") 108 | background-size: cover 109 | background-position: left 20% 110 | z-index: 0 111 | 112 | @media screen and (max-width: 900px) 113 | width: 100% 114 | 115 | .solsticePromoContent 116 | background: linear-gradient(to right, rgba(#090B0E, 0), rgba(#090B0E, 1) 40% ) 117 | // background: linear-gradient(to right, rgba(blue, 0), rgba(blue, .5) 50% ) 118 | padding: 50px 15px 119 | margin-left: 50% 120 | z-index: 1 121 | position relative 122 | 123 | @media screen and (max-width: 900px) 124 | margin: 0 125 | background: rgba(#090B0E, .75) 126 | text-align: center 127 | 128 | h1 129 | font-size: 40px 130 | margin-top: 0 131 | 132 | p 133 | font-size: 18px 134 | line-height: 1.5 135 | max-width: 800px 136 | margin-bottom: 20px 137 | 138 | .solsticeCta 139 | font-family: 'Titillium Web', sans-serif 140 | text-shadow: none 141 | margin: auto 142 | display: inline-block 143 | 144 | color: black !important 145 | background: rgba(208, 170, 11, 1.0) 146 | padding: 10px 20px 147 | text-decoration: none 148 | transition: 150ms ease-in-out 149 | display: inline-block 150 | text-align: center 151 | 152 | &:hover 153 | background: #ffce1f 154 | -------------------------------------------------------------------------------- /src/views/Triumphs/index.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { Link } from 'react-router'; 3 | import { connect } from 'react-redux'; 4 | import { get } from 'lodash'; 5 | 6 | import PresentationNodeChildren from 'app/components/PresentationNodeChildren'; 7 | 8 | import s from './styles.styl'; 9 | 10 | const ROOT_TRIUMPHS_NODE_HASH = 1024788583; 11 | const ROOT_SEALS_NODE_HASH = 1652422747; 12 | 13 | const OVERRIDE = { 14 | 1664035662: 3319885427 15 | }; 16 | 17 | function getBreadcrubLink(breadcrumbs, current) { 18 | const i = breadcrumbs.indexOf(current); 19 | const prev = i > -1 ? breadcrumbs.slice(0, i + 1) : []; 20 | return `/${prev.map(c => c.id).join('/')}`; 21 | } 22 | 23 | class Triumphs extends Component { 24 | render() { 25 | const { score, trackedRecords, breadcrumbs } = this.props; 26 | 27 | const viewCrumb = breadcrumbs[breadcrumbs.length - 1]; 28 | 29 | let view; 30 | 31 | if (viewCrumb.id === 'triumphs') { 32 | view = ( 33 |
34 | {trackedRecords.length ? ( 35 | 36 | ) : null} 37 | 38 | 43 | 44 | 48 |
49 | ); 50 | } else { 51 | view = ( 52 |
53 | bc.id).join('/')}`} 57 | /> 58 |
59 | ); 60 | } 61 | 62 | return ( 63 |
64 |
65 |
Total score
66 |
{score}
67 |
68 | 69 |

70 | {breadcrumbs.map(crumb => ( 71 | 72 | 76 | {crumb.node ? crumb.node.displayProperties.name : crumb.id} 77 | 78 | 79 | ))} 80 |

81 | 82 | {view} 83 |
84 | ); 85 | } 86 | } 87 | 88 | const mapStateToProps = (state, ownProps) => { 89 | const score = get(state, 'profile.profile.profileRecords.data.score'); 90 | 91 | const breadcrumbs = [ 92 | 'triumphs', 93 | ownProps.params.presentationNodeA, 94 | ownProps.params.presentationNodeB, 95 | ownProps.params.presentationNodeC 96 | ] 97 | .filter(Boolean) 98 | .map(hash => OVERRIDE[hash] || hash) 99 | .map(hash => { 100 | return { 101 | id: hash, 102 | node: 103 | state.definitions.DestinyPresentationNodeDefinition && 104 | state.definitions.DestinyPresentationNodeDefinition[hash] 105 | }; 106 | }); 107 | 108 | return { 109 | score, 110 | trackedRecords: state.app.trackedRecords, 111 | breadcrumbs 112 | }; 113 | }; 114 | 115 | export default connect(mapStateToProps)(Triumphs); 116 | -------------------------------------------------------------------------------- /config-overrides.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const { merge } = require('lodash'); 3 | const { getLoader, injectBabelPlugin } = require('react-app-rewired'); 4 | const rewireReactHotLoader = require('react-app-rewire-hot-loader'); 5 | const ExtractTextPlugin = require('extract-text-webpack-plugin'); 6 | const WebpackVisualizerPlugin = require('webpack-visualizer-plugin'); 7 | const { UnusedFilesWebpackPlugin } = require('unused-files-webpack-plugin'); 8 | 9 | module.exports = function override(config, env) { 10 | config = injectBabelPlugin('lodash', config); 11 | 12 | config.entry.unshift('babel-polyfill'); 13 | 14 | const babelLoader = getLoader(config.module.rules, rule => { 15 | return rule.loader && /[\/\\]babel-loader[\/\\]/.test(rule.loader); 16 | }); 17 | 18 | babelLoader.include = [ 19 | babelLoader.include, 20 | require.resolve('@destiny-plumbing/definitions') 21 | ]; 22 | 23 | const cssLoader = getLoader( 24 | config.module.rules, 25 | rule => String(rule.test) === String(/\.css$/) 26 | ); 27 | 28 | let stylusRules; 29 | 30 | if (false) { 31 | config.plugins.push( 32 | new UnusedFilesWebpackPlugin({ 33 | patterns: ['src/**/*.*'] 34 | }) 35 | ); 36 | } 37 | 38 | config.plugins.push( 39 | new WebpackVisualizerPlugin({ 40 | filename: './build/stats.html' 41 | }) 42 | ); 43 | 44 | if (env === 'development') { 45 | config = rewireReactHotLoader(config, env); 46 | 47 | stylusRules = { 48 | test: /\.styl$/, 49 | use: [ 50 | { loader: 'style-loader', options: { sourceMap: true } }, 51 | { 52 | loader: 'css-loader', 53 | options: { 54 | sourceMap: true, 55 | modules: true, 56 | importLoaders: 2, 57 | localIdentName: '[folder]--[local]--[hash:base64:2]' 58 | } 59 | }, 60 | { loader: 'postcss-loader', options: { sourceMap: true } }, 61 | { loader: 'stylus-loader', options: { sourceMap: true } } 62 | ] 63 | }; 64 | } else { 65 | const cssExtractTextLoader = cssLoader.loader[0]; 66 | if (!cssExtractTextLoader.loader.includes('extract-text-webpack-plugin')) { 67 | throw new Error('Unable to find extract-text loader for CSS, aborting'); 68 | } 69 | 70 | stylusRules = { 71 | test: /\.styl$/, 72 | use: ExtractTextPlugin.extract({ 73 | fallback: 'style-loader', 74 | use: [ 75 | { 76 | loader: 'css-loader', 77 | options: { 78 | sourceMap: true, 79 | modules: true, 80 | importLoaders: 2, 81 | localIdentName: '[folder]--[local]--[hash:base64:2]' 82 | } 83 | }, 84 | { loader: 'postcss-loader', options: { sourceMap: true } }, 85 | { loader: 'stylus-loader', options: { sourceMap: true } } 86 | ] 87 | }) 88 | }; 89 | } 90 | 91 | const oneOfRule = config.module.rules.find(rule => rule.oneOf !== undefined); 92 | if (oneOfRule) { 93 | oneOfRule.oneOf.unshift(stylusRules); 94 | } else { 95 | // Fallback to previous behaviour of adding to the end of the rules list. 96 | config.module.rules.push(stylusRules); 97 | } 98 | 99 | // throw 'Stopping'; 100 | 101 | return merge(config, { 102 | resolve: { 103 | alias: { 104 | app: path.resolve(__dirname, 'src') 105 | } 106 | } 107 | }); 108 | }; 109 | -------------------------------------------------------------------------------- /src/setData/common/ironBanner.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | export const IRONBANNER_S1_WEAPONS = [ 4 | 2014642399, // The Forward Path 5 | 4425887, // The Time-Worn Spire 6 | 3890960908, // The Guiding Sight 7 | 1189790632, // The Steady Hand 8 | 3424403076, // The Fool's Remedy 9 | 2961807684, // The Wizened Rebuke 10 | 2909905776, // The Hero's Burden 11 | 807192446 // The Day's Fury 12 | ]; 13 | 14 | export const IRONBANNER_S1_ARMOR_HUNTER = [ 15 | 1778619592, // Iron Truage Casque 16 | 1125953881, // Iron Truage Grips 17 | 3713593535, // Iron Truage Vest 18 | 4018139467, // Iron Truage Boots 19 | 665784782 // Mantle of Efrideet 20 | ]; 21 | 22 | export const IRONBANNER_S1_ARMOR_TITAN = [ 23 | 2203751966, // Iron Truage Helm 24 | 1473724751, // Iron Truage Gauntlets 25 | 3866772393, // Iron Truage Plate 26 | 2048225009, // Iron Truage Greaves 27 | 1352108388 // Radegast's Iron Sash 28 | ]; 29 | 30 | export const IRONBANNER_S1_ARMOR_WARLOCK = [ 31 | 307912519, // Iron Truage Hood 32 | 2549582030, // Iron Truage Gloves 33 | 830400994, // Iron Truage Vestments 34 | 337692872, // Iron Truage Legs 35 | 478854451 // Timur's Iron Bond 36 | ]; 37 | 38 | export const IRONBANNER_S2_WEAPONS = [ 39 | 1369487074, // Orimund's Anvil 40 | 3649055823, // Crimil's Dagger 41 | 622058944, // Jorum's Claw 42 | 3005104939, // Frostmire's Hex 43 | 94729174, // Gunnora's Axe 44 | 1870979911 // Orewing's Maul 45 | ]; 46 | 47 | export const IRONBANNER_S2_ORNAMENTS_HUNTER = [ 48 | 967086398, // Hunter Iron Pledge Ornament 49 | 2845443115, // Hunter Iron Pledge Ornament 50 | 4064021125, // Hunter Iron Pledge Ornament 51 | 2409736861, // Hunter Iron Pledge Ornament 52 | 1167898172 // Hunter Iron Pledge Ornament 53 | ]; 54 | 55 | export const IRONBANNER_S2_ORNAMENTS_TITAN = [ 56 | 3913452440, // Titan Iron Pledge Ornament 57 | 208020061, // Titan Iron Pledge Ornament 58 | 479059411, // Titan Iron Pledge Ornament 59 | 3922308175, // Titan Iron Pledge Ornament 60 | 2851718150 // Titan Iron Pledge Ornament 61 | ]; 62 | 63 | export const IRONBANNER_S2_ORNAMENTS_WARLOCK = [ 64 | 3388638931, // Warlock Iron Pledge Ornament 65 | 1355065950, // Warlock Iron Pledge Ornament 66 | 3045190354, // Warlock Iron Pledge Ornament 67 | 2984447248, // Warlock Iron Pledge Ornament 68 | 2455333439 // Warlock Iron Pledge Ornament 69 | ]; 70 | 71 | export const IRONBANNER_S3_WEAPONS = [ 72 | 982229638, // Allied Demand 73 | 3434507093, // Occluded Finality 74 | 4265183314, // Multimach CCX 75 | 701922966, // Finite Impactor 76 | 432716552, // Shining Sphere 77 | 1942069133 // Dark Decider 78 | ]; 79 | 80 | export const IRONBANNER_S3_ORNAMENTS_HUNTER = [ 81 | 2076777678, // Weyloran's Iron Mask 82 | 968545635, // Gheleon's Iron Grips 83 | 3329028839, // Perun's Iron Cuirass 84 | 3853099349, // Haakon's Iron Strides 85 | 2749869546 // Efrideet's Iron Cloak 86 | ]; 87 | 88 | export const IRONBANNER_S3_ORNAMENTS_TITAN = [ 89 | 2310582578, // Bretomart's Iron Helm 90 | 3380383295, // Radegast's Iron Gauntlets 91 | 4108794419, // Silimar's Iron Plate 92 | 2460797681, // Tormod's Iron Greaves 93 | 1101861022 // Finnala's Iron Mark 94 | ]; 95 | 96 | export const IRONBANNER_S3_ORNAMENTS_WARLOCK = [ 97 | 3905287389, // Ashraven's Iron Hood 98 | 2490313400, // Skorri's Iron Gloves 99 | 1485406574, // Colovance's Iron Robes 100 | 3044759626, // Timur's Iron Boots 101 | 316161003 // Nirwen's Iron Bond 102 | ]; 103 | -------------------------------------------------------------------------------- /src/setData/allItems.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import type { SetPage } from '../types'; 4 | 5 | export default ([ 6 | { 7 | name: 'Gear', 8 | sets: [ 9 | { 10 | name: 'Exotics', 11 | id: 'ALL_EXOTICS', 12 | big: true, 13 | query: 'is:exotic is:gear is:incollections', 14 | sections: [] 15 | }, 16 | { 17 | name: 'Weapons', 18 | id: 'ALL_WEAPONS', 19 | big: true, 20 | sections: [ 21 | { 22 | name: 'Kinetic', 23 | query: 'is:legendary is:weapon is:kinetic is:incollections' 24 | }, 25 | { 26 | name: 'Energy', 27 | query: 'is:legendary is:weapon is:energy is:incollections' 28 | }, 29 | { 30 | name: 'Power', 31 | query: 'is:legendary is:weapon is:power is:incollections' 32 | } 33 | ] 34 | }, 35 | { 36 | name: 'Armor', 37 | id: 'ALL_ARMOR', 38 | big: true, 39 | query: 'is:legendary is:armor is:incollections', 40 | sections: [] 41 | }, 42 | { 43 | name: 'Ghosts', 44 | id: 'ALL_GHOSTS', 45 | big: true, 46 | query: 'is:ghostly', // not in collections yet 47 | sections: [] 48 | } 49 | ] 50 | }, 51 | 52 | { 53 | name: 'Cosmetics', 54 | sets: [ 55 | { 56 | name: 'Emblems', 57 | id: 'ALL_EMBLEMS', 58 | big: true, 59 | sections: [ 60 | { 61 | name: 'Legendary', 62 | query: 'is:legendary is:emblem is:incollections' 63 | }, 64 | { 65 | name: 'Rare', 66 | query: 'is:rare is:emblem is:incollections' 67 | }, 68 | { 69 | name: 'Uncommon', 70 | query: 'is:uncommon is:emblem is:incollections' 71 | }, 72 | { 73 | name: 'Common', 74 | query: 'is:common is:emblem is:incollections' 75 | } 76 | ] 77 | }, 78 | 79 | { 80 | name: 'Shaders', 81 | id: 'ALL_SHADERS', 82 | big: true, 83 | sections: [ 84 | { 85 | name: 'Legendary', 86 | query: 'is:legendary is:shader is:incollections' 87 | }, 88 | { 89 | name: 'Rare', 90 | query: 'is:rare is:shader is:incollections' 91 | }, 92 | { 93 | name: 'Uncommon', 94 | query: 'is:uncommon is:shader is:incollections' 95 | } 96 | ] 97 | }, 98 | 99 | { 100 | name: 'Emotes', 101 | id: 'ALL_EMOTES', 102 | big: true, 103 | query: 'is:emote', // not in collections yet 104 | sections: [] 105 | }, 106 | 107 | { 108 | name: 'Ornaments', 109 | id: 'ALL_ORNAMENTS', 110 | big: true, 111 | query: 'is:ornament', // not all in collections yet 112 | sections: [] 113 | } 114 | ] 115 | }, 116 | 117 | { 118 | name: 'Vehicles', 119 | sets: [ 120 | { 121 | name: 'Sparrows', 122 | id: 'ALL_SPARROWS', 123 | query: 'is:sparrow is:incollections', 124 | sections: [] 125 | }, 126 | 127 | { 128 | name: 'Ships', 129 | id: 'ALL_SHIPS', 130 | query: 'is:ship is:incollections', 131 | sections: [] 132 | } 133 | ] 134 | } 135 | ]: SetPage); 136 | -------------------------------------------------------------------------------- /src/components/MasterworkCatalyst/Debug.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Link } from 'react-router'; 3 | 4 | import BungieImage from 'app/components/BungieImage'; 5 | 6 | import s from './styles.styl'; 7 | 8 | const ItemLink = ({ item, children }) => ( 9 | 10 | {children} 11 | 12 | ); 13 | 14 | const Bool = ({ bool, children }) => ( 15 | {children} 16 | ); 17 | 18 | const EmptyLi = () => ( 19 |
  • 20 | 21 | empty 22 | 23 |
  • 24 | ); 25 | 26 | function ReusablePlug({ plug, itemDefs, objectiveDefs }) { 27 | const reusablePlugItem = itemDefs[plug.plugItemHash]; 28 | 29 | if (!reusablePlugItem) { 30 | return ; 31 | } 32 | 33 | return ( 34 |
  • 35 | 36 | 40 | 41 | {reusablePlugItem.displayProperties.name} 42 | {' '} 43 | 44 | 45 | {plug.canInsert ? 'canInsert' : 'cantInsert'} 46 | ,{' '} 47 | 48 | {plug.enabled ? 'enabled' : 'disabled'} 49 | 50 | 51 | {plug.plugObjectives && ( 52 |
      53 | {plug.plugObjectives.map((objective, index3) => { 54 | const objectiveDef = objectiveDefs[objective.objectiveHash]; 55 | return ( 56 |
    • 57 | {objectiveDef.progressDescription} {objective.progress || 0} /{' '} 58 | {objectiveDef.completionValue} 59 |
    • 60 | ); 61 | })} 62 |
    63 | )} 64 |
    65 |
  • 66 | ); 67 | } 68 | 69 | function Socket({ socket, itemDefs, objectiveDefs }) { 70 | const plugItem = itemDefs[socket.plugHash]; 71 | 72 | if (!plugItem) { 73 | return ; 74 | } 75 | 76 | return ( 77 |
  • 78 |
    79 | 83 | 84 | {plugItem.displayProperties.name} 85 |
    86 | 87 | {socket.reusablePlugs && ( 88 |
      89 | {socket.reusablePlugs.map((plug, index) => ( 90 | 96 | ))} 97 |
    98 | )} 99 |
  • 100 | ); 101 | } 102 | 103 | export default function SocketDebug({ instances, itemDefs, objectiveDefs }) { 104 | return ( 105 |
    106 | {instances && 107 | instances.map(instance => { 108 | return ( 109 |
    110 |
      111 | {instance.$sockets.map((socket, index) => ( 112 | 118 | ))} 119 |
    120 |
    121 | ); 122 | })} 123 |
    124 | ); 125 | } 126 | -------------------------------------------------------------------------------- /src/extraData/chalice.js: -------------------------------------------------------------------------------- 1 | const PURPLE_BEAST = 2458424464; 2 | const PURPLE_JOY = 2458424465; 3 | const PURPLE_JUBILATION = 2458424467; 4 | 5 | const RED_GLUTTONY = 2458424469; 6 | const RED_CUNNING = 2458424466; 7 | const RED_AMBITION = 2458424468; 8 | 9 | const GREEN_PRIDE = 2458424473; 10 | const GREEN_WAR = 2458424471; 11 | const GREEN_DESIRE = 2458424470; 12 | 13 | const BLUE_EXCESS = 1023532672; 14 | const BLUE_WEALTH = 1023532673; 15 | const BLUE_PLEASURE = 2458424472; 16 | 17 | const ANY_RED = [RED_GLUTTONY, RED_CUNNING, RED_AMBITION]; 18 | const ANY_GREEN = [GREEN_PRIDE, GREEN_WAR, GREEN_DESIRE]; 19 | const ANY_PURPLE = [PURPLE_BEAST, PURPLE_JOY, PURPLE_JUBILATION]; 20 | const ANY_BLUE = [BLUE_EXCESS, BLUE_WEALTH, BLUE_PLEASURE]; 21 | 22 | const OPULENT_HELMENT = [[GREEN_WAR], ANY_BLUE]; 23 | const OPULENT_ARMS = [[RED_CUNNING], ANY_BLUE]; 24 | const OPULENT_CHEST = [[BLUE_PLEASURE], ANY_BLUE]; 25 | const OPULENT_LEGS = [[RED_GLUTTONY], ANY_BLUE]; 26 | const OPULENT_CLASSITEM = [[PURPLE_JOY], ANY_BLUE]; 27 | 28 | const EXODUS_HELMENT = [[GREEN_WAR], ANY_RED]; 29 | const EXODUS_ARMS = [[RED_CUNNING], ANY_RED]; 30 | const EXODUS_CHEST = [[BLUE_PLEASURE], ANY_RED]; 31 | const EXODUS_LEGS = [[RED_GLUTTONY], ANY_RED]; 32 | const EXODUS_CLASSITEM = [[PURPLE_JOY], ANY_RED]; 33 | 34 | export default { 35 | // AUSTRINGER 36 | 2429822977: [[GREEN_DESIRE], ANY_RED], 37 | 38 | // IMPERIAL DECREE 39 | 2919334548: [[BLUE_WEALTH], ANY_GREEN], 40 | 41 | // THE EPICUREAN 42 | 4124357815: [[BLUE_EXCESS], ANY_GREEN], 43 | 44 | // DRANG (BAROQUE) 45 | 79075821: [[GREEN_PRIDE], ANY_PURPLE], 46 | 47 | // BELOVED 48 | 4190932264: [[PURPLE_JUBILATION], ANY_RED], 49 | 50 | // CALUS MINI-TOOL 51 | 174192097: [[PURPLE_BEAST], ANY_PURPLE], 52 | 53 | // FIXED ODDS 54 | 1642384931: [[RED_AMBITION], ANY_BLUE], 55 | 56 | // EXODUS DOWN MASK 57 | 2172333833: EXODUS_HELMENT, 58 | 59 | // EXODUS DOWN GRIPS 60 | 3875829376: EXODUS_ARMS, 61 | 62 | // EXODUS DOWN VEST 63 | 126418248: EXODUS_CHEST, 64 | 65 | // EXODUS DOWN STRIDES 66 | 2953649850: EXODUS_LEGS, 67 | 68 | // EXODUS DOWN CLOAK 69 | 2252973221: EXODUS_CLASSITEM, 70 | 71 | // EXODUS DOWN HELM 72 | 582151075: EXODUS_HELMENT, 73 | 74 | // EXODUS DOWN GAUNTLETS 75 | 1678216306: EXODUS_ARMS, 76 | 77 | // EXODUS DOWN 78 | 1156448694: EXODUS_CHEST, 79 | 80 | // EXODUS DOWN GREAVES 81 | 2079454604: EXODUS_LEGS, 82 | 83 | // EXODUS DOWN MARK 84 | 527652447: EXODUS_CLASSITEM, 85 | 86 | // EXODUS DOWN HOOD 87 | 2731698402: EXODUS_HELMENT, 88 | 89 | // EXODUS DOWN GLOVES 90 | 2029766091: EXODUS_ARMS, 91 | 92 | // EXODUS DOWN ROBES 93 | 2218838661: EXODUS_CHEST, 94 | 95 | // EXODUS DOWN BOOTS 96 | 3545981149: EXODUS_LEGS, 97 | 98 | // EXODUS DOWN BOND 99 | 874856664: EXODUS_CLASSITEM, 100 | 101 | 906236408: OPULENT_HELMENT, // OPULENT STALKER MASK 102 | 1370039881: OPULENT_ARMS, // OPULENT STALKER GRIPS 103 | 3759327055: OPULENT_CHEST, // OPULENT STALKER VEST 104 | 1661981723: OPULENT_LEGS, // OPULENT STALKER STRIDES 105 | 1135872734: OPULENT_CLASSITEM, // OPULENT STALKER CLOAK 106 | 107 | 1420117606: OPULENT_HELMENT, // OPULENT DUELIST HELM 108 | 392500791: OPULENT_ARMS, // OPULENT DUELIST GAUNTLETS 109 | 2856582785: OPULENT_CHEST, // OPULENT DUELIST PLATE 110 | 1776578009: OPULENT_LEGS, // OPULENT DUELIST GREAVES 111 | 4105225180: OPULENT_CLASSITEM, // OPULENT DUELIST MARK 112 | 113 | 831222279: OPULENT_HELMENT, // OPULENT SCHOLAR HOOD 114 | 3072788622: OPULENT_ARMS, // OPULENT SCHOLAR GLOVES 115 | 2026757026: OPULENT_CHEST, // OPULENT SCHOLAR ROBES 116 | 1285460104: OPULENT_LEGS, // OPULENT SCHOLAR BOOTS 117 | 1250649843: OPULENT_CLASSITEM // OPULENT SCHOLAR BOND 118 | }; 119 | -------------------------------------------------------------------------------- /src/components/PresentationNodeChildren/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { connect } from 'react-redux'; 3 | 4 | import { Link } from 'react-router'; 5 | 6 | import PresentationNode from 'app/components/PresentationNode'; 7 | import Record from 'app/components/Record'; 8 | import BungieImage from 'app/components/BungieImage'; 9 | 10 | import s from './styles.styl'; 11 | 12 | function PresentationNodeChildren({ 13 | node, 14 | linkPrefix, 15 | isNested, 16 | showChildren 17 | }) { 18 | if (!node) { 19 | return null; 20 | } 21 | 22 | return ( 23 |
    24 |
    25 | 26 | {node.displayProperties.icon && ( 27 |
    28 | 32 |
    33 | )} 34 | 35 |

    {node.displayProperties.name}

    36 | 37 |
    38 | 39 | {node.children.presentationNodes.length > 0 && ( 40 |
    43 | {node.children.presentationNodes.map( 44 | ({ presentationNodeHash }) => 45 | showChildren ? ( 46 | 53 | ) : ( 54 | 59 | 60 | 61 | ) 62 | )} 63 |
    64 | )} 65 | 66 | {node.children.records.length > 0 && ( 67 |
    68 | {node.children.records.map(({ recordHash }) => ( 69 | 70 | ))} 71 |
    72 | )} 73 |
    74 | ); 75 | } 76 | 77 | export function fakePresentationNode(hash, recordHashes) { 78 | return { 79 | displayProperties: { 80 | name: 'Tracked triumphs' 81 | }, 82 | hash, 83 | children: { 84 | records: recordHashes.map(recordHash => ({ recordHash })), 85 | presentationNodes: [] 86 | } 87 | }; 88 | } 89 | 90 | const mapStateToProps = (state, ownProps) => { 91 | const { DestinyPresentationNodeDefinition: nodeDefs } = state.definitions; 92 | 93 | if (!nodeDefs) { 94 | return {}; 95 | } 96 | 97 | if (ownProps.hash === 'tracked') { 98 | return { node: fakePresentationNode('tracked', state.app.trackedRecords) }; 99 | } 100 | 101 | const node = nodeDefs[ownProps.hash]; 102 | 103 | const childrenHaveRecords = 104 | node.children.presentationNodes && 105 | node.children.presentationNodes.find(child => { 106 | const childNode = nodeDefs[child.presentationNodeHash]; 107 | return childNode.children.records.length > 0; 108 | }); 109 | 110 | return { 111 | node, 112 | childrenHaveRecords: !!childrenHaveRecords 113 | }; 114 | }; 115 | 116 | const ConnectedPresentationNodeChildren = connect(mapStateToProps)( 117 | PresentationNodeChildren 118 | ); 119 | 120 | export default ConnectedPresentationNodeChildren; 121 | -------------------------------------------------------------------------------- /src/lib/getFromProfile.js: -------------------------------------------------------------------------------- 1 | import { keyBy, isNumber } from 'lodash'; 2 | import fp from 'lodash/fp'; 3 | 4 | const ITEM_BLACKLIST = [ 5 | 4248210736, // Default shader 6 | 1608119540 // Default emblem 7 | ]; 8 | 9 | function itemMapper(item) { 10 | return item; 11 | } 12 | 13 | function fromCharacter(data) { 14 | return fp.flatMap(character => character.items.map(itemMapper), data); 15 | } 16 | 17 | function mapSockets(data, fn) { 18 | return fp.flow( 19 | fp.flatMap(({ sockets }) => fp.flatMap(socket => fn(socket), sockets)), 20 | fp.compact 21 | )(data); 22 | } 23 | 24 | function fromSockets(data) { 25 | return mapSockets(data, socket => 26 | fp.flatMap(plugItem => { 27 | return plugItem.canInsert ? plugItem.plugItemHash : null; 28 | }, socket.reusablePlugs) 29 | ); 30 | } 31 | 32 | function objectivesFromSockets(data) { 33 | const firstLevel = mapSockets(data, socket => socket.plugObjectives); 34 | const reusablePlugs = mapSockets(data, socket => 35 | fp.flatMap(plug => plug.plugObjectives, socket.reusablePlugs) 36 | ); 37 | 38 | return [...firstLevel, ...reusablePlugs]; 39 | } 40 | 41 | const fromProfilePlugSets = fp.flow( 42 | fp.flatMap(p => Object.values(p)), 43 | fp.flatMap(p => p), 44 | fp.filter(p => p.canInsert), 45 | fp.map(p => p.plugItemHash) 46 | ); 47 | 48 | function objectivesFromVendors(data) { 49 | return fp.flow( 50 | fp.flatMap(character => { 51 | return ( 52 | character && 53 | fp.flatMap(vendor => { 54 | return fp.flatMap(plugState => { 55 | return plugState.plugObjectives; 56 | }, vendor.plugStates.data); 57 | }, character.itemComponents) 58 | ); 59 | }), 60 | fp.compact 61 | )(data); 62 | } 63 | 64 | const socketsFromVendors = fp.flatMap(vendor => 65 | fromSockets(vendor.sockets.data) 66 | ); 67 | 68 | function fromVendorSockets(data) { 69 | return fp.flow( 70 | fp.flatMap( 71 | character => character && socketsFromVendors(character.itemComponents) 72 | ), 73 | fp.compact 74 | )(data); 75 | } 76 | 77 | function mergeItems(acc, [items, itemLocation]) { 78 | items.forEach(thing => { 79 | const itemHash = isNumber(thing) ? thing : thing.itemHash; 80 | 81 | if (ITEM_BLACKLIST.includes(itemHash)) { 82 | return acc; 83 | } 84 | 85 | if (!acc[itemHash]) { 86 | acc[itemHash] = { 87 | itemHash, 88 | obtained: true, 89 | instances: [] 90 | }; 91 | } 92 | 93 | acc[itemHash].instances.push({ 94 | location: itemLocation, 95 | itemState: thing.state 96 | }); 97 | }); 98 | 99 | return acc; 100 | } 101 | 102 | export function inventoryFromProfile(profile, vendorDefs) { 103 | const inventory = [ 104 | [fromCharacter(profile.characterEquipment.data), 'characterEquipment'], 105 | [fromCharacter(profile.characterInventories.data), 'characterInventories'], 106 | [profile.profileInventory.data.items.map(itemMapper), 'profileInventory'], 107 | [fromSockets(profile.itemComponents.sockets.data), 'itemSockets'], 108 | [fromVendorSockets(profile.$vendors.data), 'vendorSockets'], 109 | [fromProfilePlugSets(profile.profilePlugSets.data), 'profilePlugSets'], 110 | ].reduce(mergeItems, {}); 111 | 112 | window.__inventory = inventory; 113 | return inventory; 114 | } 115 | 116 | export function objectivesFromProfile(profile) { 117 | return keyBy( 118 | [ 119 | ...objectivesFromSockets(profile.itemComponents.sockets.data), 120 | ...fp.flatMap( 121 | obj => obj.objectives, 122 | profile.itemComponents.objectives.data 123 | ), 124 | ...objectivesFromVendors(profile.$vendors.data) 125 | ], 126 | 'objectiveHash' 127 | ); 128 | } 129 | -------------------------------------------------------------------------------- /src/extraData/catalystTriumphs.js: -------------------------------------------------------------------------------- 1 | export default { 2 | // [itemHash]: [recordHash] 3 | 4 | // Kinetic weapons 5 | 1345867570: 1385469960, // Sweet Business 6 | 2907129556: 2524364954, // Sturm 7 | 3628991659: 2383994221, // Vigilance Wing 8 | 2362471601: 209320411, // Rat King 9 | 1331482397: 1071947311, // MIDA Multi-Tool 10 | 3437746471: 1345348453, // Crimson 11 | 3844694310: 3368860448, // The Jade Rabbit 12 | 2286143274: 2856496392, // The Huckleberry 13 | 2856683562: 4137195476, // SUROS Regime 14 | 1541131350: 2761319400, // Cerberus+1 15 | 347366834: 836516980, // Ace of Spades 16 | 3211806999: 1233471745, // Izanagi's Burden 17 | 400096939: 591600693, // Outbreak Perfected 18 | 2816212794: 748675128, // Bad JuJu 19 | //: 2660706768 // Bastion 20 | 3512014804: 3663964046, // Lumina 21 | 2357297366: 2744473468, // Witherhoard 22 | 3856705927: 1107121513, // Hawkmoon 23 | 1594120904: 2708727662, // No Time to Explain 24 | 3654674561: 15917031, // Dead Man's Tale 25 | 603721696: 250211794, // Cryosthesia 77K 26 | 1853180924: 571025162, // Traveler's Chosen 27 | 1833195496: 478443982, // Ager's Scepter 28 | 2179048386: 3574136388, // Forerunner 29 | 2130065553: 3835718947, // Arbalest 30 | 46524085: 494981303, // Osteo Striga 31 | 1234150730: 3022631571, // Trespasser 32 | 417164956: 1060652297, // Jötunn 33 | 34 | // Energy weapons 35 | 4190156464: 4178028503, // Merciless 36 | 1345867571: 3802151748, // Coldheart 37 | 3549153978: 3968841949, // Fighting Lion 38 | 2907129557: 2940589008, // Sunshot 39 | 3628991658: 4147054663, // Graviton Lance 40 | 4255268456: 3746741161, // Skyburner's Oath 41 | 3141979347: 663317912, // Borealis 42 | 3089417789: 373671280, // Riskrunner 43 | 4124984448: 3784038415, // Hard Light 44 | 19024058: 1182982189, // Prometheus Lens 45 | 2208405142: 3518287681, // Telesto 46 | 3413074534: 1390549867, // Polaris Lance 47 | 814876685: 436556889, // Trinity Ghoul 48 | 3413860063: 3531533350, // Lord of Wolves 49 | 3524313097: 1107018752, // Eriana's Vow 50 | 4017959782: 3644944303, // Symmetry 51 | //: 3068761227 // Devil's Ruin 52 | 776191470: 1586771061, // Tommy's Matchbook 53 | 1665952087: 1940645774, // The Fourth Horseman 54 | 1363238943: 3567687058, // Ruinous Effigy 55 | 3460576091: 2135780490, // Duality 56 | 3260753130: 1833569807, // Ticuu's Divination 57 | 4289226715: 507778024, // Vex Mythoclast 58 | 3761898871: 2329638530, // Lorentz Driver 59 | 3588934839: 3628100770, // Le Monarque 60 | 2812324401: 1163649614, // Dead Messenger 61 | 62 | // Heavy weapons 63 | 3549153979: 3371896300, // The Prospector 64 | 1508896098: 2387948227, // The Wardcliff Coil 65 | 3580904581: 206322164, // Tractor Cannon 66 | 3580904580: 206322165, // Legend of Acrius 67 | 3141979346: 3589295049, // D.A.R.C.I. 68 | 3899270607: 299659704, // The Colony 69 | 1864563948: 3468043157, // Worldline Zero 70 | 4036115577: 1547246830, // Sleeper Simulant 71 | 1891561814: 173502661, // Whisper of the Worm 72 | 3766045777: 252263460, // Black Talon 73 | 2591746970: 3740374319, // Leviathan's Breath 74 | 2084878005: 1514331782, // Heir Apparent 75 | 2232171099: 2254190310, // Deathbringer 76 | 1363886209: 1855685192, // Gjallarhorn 77 | 1763584999: 263158944 // Grand Overture 78 | 79 | // 1364093401: 0, // The Last Word 80 | // 2694576561: 0, // Two-Tailed Fox 81 | // 2376481550: 0, // Anarchy 82 | // 3413860062: 0, // The Chaperone 83 | // 3973202132: 0, // Thorn 84 | // 2044500762: 0, // The Queenbreaker 85 | // 1852863732: 0, // Wavesplitter 86 | // 2130065553: 0, // Arbalest 87 | // 2069224589: 0, // One Thousand Voices 88 | // 3110698812: 0, // Tarrabah 89 | // 3325463374: 0, // Thunderlord 90 | // 1201830623: 0, // Truth 91 | // 417164956: 0, // Jötunn 92 | // 814876684: 0, // Wish-Ender 93 | // 204878059: 0, // Malfeasance 94 | }; 95 | -------------------------------------------------------------------------------- /src/setData/common/trials.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | export const TRIALS_S1_WEAPONS = [ 4 | 174804902, // The Long Walk 5 | 1909527966, // Prosecutor 6 | 2094938673, // Adjudicator 7 | 1187594590, // Relentless 8 | 1825472717, // The End 9 | 2850415209, // Judgment 10 | 1879212552, // A Sudden Death 11 | 3854037061 // A Swift Verdict 12 | ]; 13 | 14 | export const TRIALS_S1_ARMOR_HUNTER = [ 15 | 3075372781, // Flowing Cowl 16 | 4194072668, // Flowing Grips 17 | 508076356, // Flowing Vest 18 | 3155320806, // Flowing Boots 19 | 1717940633 // Cloak Judgment 20 | ]; 21 | 22 | export const TRIALS_S1_ARMOR_TITAN = [ 23 | 3976073347, // Crushing Helm 24 | 818644818, // Crushing Guard 25 | 2570653206, // Crushing Plate 26 | 1219883244, // Crushing Greaves 27 | 1900280383 // Mark Judgment 28 | ]; 29 | 30 | export const TRIALS_S1_ARMOR_WARLOCK = [ 31 | 2602907742, // Channeling Cowl 32 | 1406846351, // Channeling Wraps 33 | 1652467433, // Channeling Robes 34 | 2022923313, // Flowing Treads 35 | 2966633380 // Judgement's Wrap 36 | ]; 37 | 38 | export const TRIALS_S1_ARMOR_FLAWLESS_HUNTER = [ 39 | 400025382, // Floating Cowl 40 | 2641591727, // Floating Grips 41 | 548290755, // Floating Vest 42 | 854160041, // Floating Boots 43 | 238320914 // Cloak Relentless 44 | ]; 45 | 46 | export const TRIALS_S1_ARMOR_FLAWLESS_TITAN = [ 47 | 2391227800, // Annihilating Helm 48 | 1863012881, // Annihilating Guard 49 | 934145081, // Annihilating Plate 50 | 2221648235, // Annihilating Greaves 51 | 3624606676 // Mark Relentless 52 | ]; 53 | 54 | export const TRIALS_S1_ARMOR_FLAWLESS_WARLOCK = [ 55 | 2964441921, // Focusing Cowl 56 | 530515216, // Focusing Wraps 57 | 891933382, // Focusing Robes 58 | 4232174818, // Focusing Boots 59 | 1607431127 // Bond Relentless 60 | ]; 61 | 62 | export const TRIALS_S2_WEAPONS = [ 63 | 1503609584, // The Last Breath 64 | 2516360525, // Purpose 65 | 325519402, // Darkest Before 66 | 2071412133 // A Cold Sweat 67 | ]; 68 | 69 | export const TRIALS_S2_ORNAMENTS_HUNTER = [ 70 | 1698264996, // Trials Head Ornament 71 | 4155106369, // Trials Arms Ornament 72 | 3799317511, // Trials Chest Ornament 73 | 1254742579, // Trials Legs Ornament 74 | 1475681954 // Trials Class Ornament 75 | ]; 76 | 77 | export const TRIALS_S2_ORNAMENTS_FLAWLESS_HUNTER = [ 78 | 3000782857, // Trials Prestige Head Ornament 79 | 3907623580, // Trials Prestige Arms Ornament 80 | 1480689066, // Trials Prestige Chest Ornament 81 | 3491371766, // Trials Prestige Legs Ornament 82 | 85546655 // Trials Prestige Class Ornament 83 | ]; 84 | 85 | export const TRIALS_S2_ORNAMENTS_TITAN = [ 86 | 62776458, // Trials Head Ornament 87 | 2194111759, // Trials Arms Ornament 88 | 3647777257, // Trials Chest Ornament 89 | 3964883617, // Trials Legs Ornament 90 | 4275895304 // Trials Class Ornament 91 | ]; 92 | 93 | export const TRIALS_S2_ORNAMENTS_FLAWLESS_TITAN = [ 94 | 3042323431, // Trials Prestige Head Ornament 95 | 1309834898, // Trials Prestige Arms Ornament 96 | 3047754372, // Trials Prestige Chest Ornament 97 | 3675824124, // Trials Prestige Legs Ornament 98 | 1717767197 // Trials Prestige Class Ornament 99 | ]; 100 | 101 | export const TRIALS_S2_ORNAMENTS_WARLOCK = [ 102 | 948716319, // Trials Head Ornament 103 | 2984392562, // Trials Arms Ornament 104 | 607756182, // Trials Chest Ornament 105 | 2426456324, // Trials Legs Ornament 106 | 3828657371 // Trials Class Ornament 107 | ]; 108 | 109 | export const TRIALS_S2_ORNAMENTS_FLAWLESS_WARLOCK = [ 110 | 2338851618, // Trials Prestige Head Ornament 111 | 4105027183, // Trials Prestige Arms Ornament 112 | 2666197459, // Trials Prestige Chest Ornament 113 | 3026479209, // Trials Prestige Legs Ornament 114 | 2394332958 // Trials Prestige Class Ornament 115 | ]; 116 | 117 | export const TRIALS_S3_WEAPONS = [ 118 | 3950088638, // Motion to Suppress 119 | 3425561386, // Motion to Compel 120 | 1018777295 // Motion to Vacate 121 | ]; 122 | -------------------------------------------------------------------------------- /src/components/PresentationNode/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { connect } from 'react-redux'; 3 | import cx from 'classnames'; 4 | import { flatMapDeep, get } from 'lodash'; 5 | 6 | import BungieImage from 'app/components/BungieImage'; 7 | import { enumerateState } from 'app/components/Record'; 8 | import { fakePresentationNode } from 'app/components/PresentationNodeChildren'; 9 | 10 | import s from './styles.styl'; 11 | 12 | export function PresentationNode({ 13 | className, 14 | node, 15 | isHeader, 16 | totalChildRecords, 17 | childRecordsCompleted 18 | }) { 19 | if (!node) { 20 | return null; 21 | } 22 | 23 | return ( 24 |
    25 |
    26 |
    27 | 28 | {node.displayProperties && node.displayProperties.icon && ( 29 |
    30 | 31 |
    32 | )} 33 | 34 |
    35 |
    {node.displayProperties.name}
    36 |
    37 | {childRecordsCompleted} / {totalChildRecords} 38 |
    39 |
    40 | 41 |
    42 |
    51 |
    52 |
    53 | ); 54 | } 55 | 56 | function recursiveRecords(node, definitions) { 57 | if (!node || !node.children) { 58 | return []; 59 | } 60 | 61 | const fromChildren = flatMapDeep( 62 | node.children.presentationNodes, 63 | childNode => { 64 | const childPresentationNode = 65 | definitions.DestinyPresentationNodeDefinition[ 66 | childNode.presentationNodeHash 67 | ]; 68 | 69 | if ( 70 | childPresentationNode && 71 | childPresentationNode.children && 72 | childPresentationNode.children.records && 73 | childPresentationNode.children.records.length 74 | ) { 75 | return childPresentationNode.children.records 76 | .map( 77 | c => 78 | definitions.DestinyRecordDefinition && 79 | definitions.DestinyRecordDefinition[c.recordHash] 80 | ) 81 | .filter(Boolean); 82 | } 83 | 84 | return recursiveRecords(childPresentationNode, definitions); 85 | } 86 | ); 87 | 88 | const fromThis = node.children.records 89 | .map( 90 | c => 91 | definitions.DestinyRecordDefinition && 92 | definitions.DestinyRecordDefinition[c.recordHash] 93 | ) 94 | .filter(Boolean); 95 | 96 | return [...fromThis, ...fromChildren]; 97 | } 98 | 99 | const mapStateToProps = (state, ownProps) => { 100 | const { DestinyPresentationNodeDefinition: nodeDefs } = state.definitions; 101 | const node = 102 | ownProps.hash === 'tracked' 103 | ? fakePresentationNode('tracked', state.app.trackedRecords) 104 | : nodeDefs[ownProps.hash]; 105 | 106 | const childRecordDefs = recursiveRecords(node, state.definitions); 107 | const records = get(state, 'profile.profile.profileRecords.data.records'); 108 | 109 | const childRecordsCompleted = childRecordDefs.reduce((acc, recordDef) => { 110 | const record = records && records[recordDef.hash]; 111 | const recordState = record && enumerateState(record.state); 112 | const completed = recordState && !recordState.objectiveNotCompleted; 113 | return completed ? acc + 1 : acc; 114 | }, 0); 115 | 116 | return { 117 | node, 118 | totalChildRecords: childRecordDefs.length, 119 | childRecordsCompleted 120 | }; 121 | }; 122 | 123 | export default connect(mapStateToProps)(PresentationNode); 124 | -------------------------------------------------------------------------------- /public/nhg/style.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'nhg display'; 3 | src: url('NHaasGroteskDSPro-15UltTh.woff2') format('woff2'); 4 | font-weight: 100; 5 | font-style: normal; 6 | } 7 | 8 | @font-face { 9 | font-family: 'nhg display'; 10 | src: url('NHaasGroteskDSPro-16UltThIt.woff2') format('woff2'); 11 | font-weight: 200; 12 | font-style: italic; 13 | } 14 | 15 | @font-face { 16 | font-family: 'nhg display'; 17 | src: url('NHaasGroteskDSPro-25Th.woff2') format('woff2'); 18 | font-weight: 200; 19 | font-style: normal; 20 | } 21 | 22 | @font-face { 23 | font-family: 'nhg display'; 24 | src: url('NHaasGroteskDSPro-26ThIt.woff2') format('woff2'); 25 | font-weight: 200; 26 | font-style: italic; 27 | } 28 | 29 | /* @font-face { 30 | font-family: 'nhg display'; 31 | src: url('NHaasGroteskDSPro-36XLtIt.woff2') format('woff2'); 32 | font-weight: 300; 33 | font-style: italic; 34 | } 35 | 36 | @font-face { 37 | font-family: 'nhg display'; 38 | src: url('NHaasGroteskDSPro-35XLt.woff2') format('woff2'); 39 | font-weight: 300; 40 | font-style: normal; 41 | } */ 42 | 43 | @font-face { 44 | font-family: 'nhg display'; 45 | src: url('NHaasGroteskDSPro-45Lt.woff2') format('woff2'); 46 | font-weight: 300; 47 | font-style: normal; 48 | } 49 | 50 | @font-face { 51 | font-family: 'nhg display'; 52 | src: url('NHaasGroteskDSPro-46LtIt.woff2') format('woff2'); 53 | font-weight: 300; 54 | font-style: italic; 55 | } 56 | 57 | @font-face { 58 | font-family: 'nhg display'; 59 | src: url('NHaasGroteskDSPro-55Rg.woff2') format('woff2'); 60 | font-weight: normal; 61 | font-style: normal; 62 | } 63 | 64 | @font-face { 65 | font-family: 'nhg display'; 66 | src: url('NHaasGroteskDSPro-56It.woff2') format('woff2'); 67 | font-weight: normal; 68 | font-style: italic; 69 | } 70 | 71 | @font-face { 72 | font-family: 'nhg display'; 73 | src: url('NHaasGroteskDSPro-65Md.woff2') format('woff2'); 74 | font-weight: 500; 75 | font-style: normal; 76 | } 77 | 78 | @font-face { 79 | font-family: 'nhg display'; 80 | src: url('NHaasGroteskDSPro-66MdIt.woff2') format('woff2'); 81 | font-weight: 500; 82 | font-style: italic; 83 | } 84 | 85 | @font-face { 86 | font-family: 'nhg display'; 87 | src: url('NHaasGroteskDSPro-75Bd.woff2') format('woff2'); 88 | font-weight: bold; 89 | font-style: normal; 90 | } 91 | 92 | @font-face { 93 | font-family: 'nhg display'; 94 | src: url('NHaasGroteskDSPro-76BdIt.woff2') format('woff2'); 95 | font-weight: bold; 96 | font-style: italic; 97 | } 98 | 99 | @font-face { 100 | font-family: 'nhg display'; 101 | src: url('NHaasGroteskDSPro-95Blk.woff2') format('woff2'); 102 | font-weight: 900; 103 | font-style: normal; 104 | } 105 | @font-face { 106 | font-family: 'nhg display'; 107 | src: url('NHaasGroteskDSPro-96BlkIt.woff2') format('woff2'); 108 | font-weight: 900; 109 | font-style: italic; 110 | } 111 | 112 | @font-face { 113 | font-family: 'nhg text'; 114 | src: url('NHaasGroteskTXPro-55Rg.woff2') format('woff2'); 115 | font-weight: normal; 116 | font-style: normal; 117 | } 118 | 119 | @font-face { 120 | font-family: 'nhg text'; 121 | src: url('NHaasGroteskTXPro-56It.woff2') format('woff2'); 122 | font-weight: normal; 123 | font-style: italic; 124 | } 125 | 126 | @font-face { 127 | font-family: 'nhg text'; 128 | src: url('NHaasGroteskTXPro-65Md.woff2') format('woff2'); 129 | font-weight: 500; 130 | font-style: normal; 131 | } 132 | 133 | @font-face { 134 | font-family: 'nhg text'; 135 | src: url('NHaasGroteskTXPro-66MdIt.woff2') format('woff2'); 136 | font-weight: 500; 137 | font-style: italic; 138 | } 139 | 140 | @font-face { 141 | font-family: 'nhg text'; 142 | src: url('NHaasGroteskTXPro-75Bd.woff2') format('woff2'); 143 | font-weight: bold; 144 | font-style: normal; 145 | } 146 | 147 | @font-face { 148 | font-family: 'nhg text'; 149 | src: url('NHaasGroteskTXPro-76BdIt.woff2') format('woff2'); 150 | font-weight: bold; 151 | font-style: italic; 152 | } 153 | -------------------------------------------------------------------------------- /src/components/Header/styles.styl: -------------------------------------------------------------------------------- 1 | $baseColour = rgba(11, 13, 16, 1.0) 2 | $bgColour = lighten($baseColour, 4%) 3 | $filterColour = lighten($baseColour, 10%) 4 | $activeBg = rgba(white, .07) 5 | $padding = 15px 6 | $height = 52px 7 | 8 | .root 9 | height: $height 10 | 11 | .fixed 12 | z-index: 3 13 | background: $bgColour 14 | line-height: 1 15 | margin-bottom: $padding 16 | display: flex 17 | align-items: center 18 | padding: 0 $padding 19 | position: fixed 20 | top: 0 21 | left: 0 22 | right: 0 23 | box-shadow: 0 0 20px -5px rgba(black, 0.75) 24 | 25 | .siteName 26 | font-size: 22px 27 | display: flex 28 | align-items: center 29 | 30 | .logo 31 | width: 22px 32 | height: auto 33 | margin-right: 12px 34 | 35 | .links 36 | margin-left: $padding 37 | height: $height 38 | overflow: hidden 39 | display: flex 40 | flex-wrap: wrap 41 | 42 | .spacer 43 | flex: 1 44 | 45 | .link 46 | .dummyLink 47 | display: inline-block 48 | padding: 18px 10px 49 | 50 | .dummyLink 51 | padding-left: 0 52 | padding-right: 0 53 | 54 | .link 55 | color: inherit 56 | text-decoration: none 57 | white-space: nowrap 58 | transition: 150ms ease-in-out background 59 | 60 | &:hover 61 | background: $activeBg 62 | 63 | &.active 64 | position: relative 65 | 66 | &:after 67 | display: block 68 | content: '' 69 | position absolute 70 | bottom: 0 71 | right: 0 72 | left: 0 73 | height: 3px 74 | background: rgba(white, .2) 75 | 76 | 77 | .etc 78 | margin: 0 -8px 79 | display: flex 80 | align-items: center 81 | 82 | .headerLoginCta 83 | margin-left: 16px 84 | padding: 10px 32px; 85 | 86 | .socialLink 87 | display: inline-block 88 | margin: 0 8px 89 | opacity: .7 90 | transition: all 150ms ease-in-out 91 | color: white !important 92 | font-size: 22px 93 | 94 | &:hover 95 | opacity: 1 96 | 97 | .isOverflowing 98 | .socialLink 99 | display: none 100 | 101 | .toggleSidebar 102 | background: none 103 | margin-right: 5px 104 | margin-left: -10px 105 | padding: 10px 106 | border: none 107 | appearance: none 108 | color: white 109 | font-size: 20px 110 | cursor: pointer 111 | 112 | .sidebar 113 | position: fixed 114 | z-index: 1000 115 | top: 0 116 | bottom: 0 117 | width: 100% 118 | max-width: 300px 119 | background: $bgColour 120 | box-shadow: 0 0 20px -5px rgba(black, 0.75) 121 | transition: 350ms transform ease-in-out 122 | left: 0 123 | 124 | transform: translateX(-100%) // offscreen 125 | 126 | &:before 127 | pointer-events: none 128 | content: '' 129 | position: absolute 130 | z-index: -1 131 | top: 0 132 | bottom: 0 133 | background: rgba(#090B0E, 0) 134 | width: 1000% 135 | left: 100% 136 | transition: 350ms background ease-in-out 137 | 138 | .sidebarActive & 139 | transform: translateX(0) // onscreen 140 | pointer-events: initial 141 | 142 | &:before 143 | background: rgba(#090B0E, .7) 144 | 145 | .siteName 146 | margin-bottom: 15px 147 | 148 | .toggleSidebar 149 | margin: -12px 0 0 0 150 | 151 | .dummyLink 152 | display: none 153 | 154 | .socialLink 155 | display: inline-block !important 156 | 157 | .sidebarLoginCta 158 | margin: 12px auto 8px; 159 | display: block; 160 | 161 | .link 162 | display: block !important // override media query 163 | width: 100% 164 | padding: 10px 165 | padding-left: 15px 166 | margin-left: -5px 167 | line-height: 1 168 | 169 | &.active:after 170 | right: initial 171 | height: initial 172 | top: 0 173 | width: 3px 174 | 175 | .sidebarInner 176 | height: 100% 177 | overflow: scroll 178 | padding: 9px $padding 179 | 180 | .hr 181 | height: 1px 182 | width: 100% 183 | background: rgba(white, .2) 184 | margin: 10px 0 185 | 186 | .sidebarTop 187 | display: flex 188 | justify-content: space-between 189 | align-items: center 190 | 191 | .sidebarExtra 192 | text-align: center 193 | 194 | 195 | .link 196 | .dummyLink 197 | @media screen and (max-width: 500px) 198 | display: none 199 | -------------------------------------------------------------------------------- /src/store/preloadStore.js: -------------------------------------------------------------------------------- 1 | import * as ls from 'app/lib/ls'; 2 | import { 3 | setBulkFilters, 4 | setLanguage, 5 | trackOrnaments, 6 | trackRecords, 7 | setAppValue, 8 | setBulkHiddenItemSet 9 | } from 'app/store/reducer'; 10 | import { 11 | setBulkDefinitions, 12 | definitionsStatus, 13 | definitionsError 14 | } from 'app/store/definitions'; 15 | import { setProfiles } from 'app/store/profile'; 16 | import { getLastProfile } from 'app/lib/destiny'; 17 | import { trackError } from 'app/lib/telemetry'; 18 | import { staleThenRevalidate, _Dexie } from '@destiny-plumbing/definitions'; 19 | 20 | const log = require('app/lib/log')('authProvider'); 21 | 22 | // Clean up previous definitions 23 | _Dexie.delete('destinyManifest'); 24 | 25 | export const REQUIRED_DEFINITIONS = [ 26 | 'DestinyChecklistDefinition', 27 | 'DestinyObjectiveDefinition', 28 | 'DestinyStatDefinition', 29 | 'DestinyVendorDefinition', 30 | 'DestinyInventoryItemDefinition', 31 | 'DestinyClassDefinition', 32 | 'DestinySandboxPerkDefinition', 33 | 'DestinyEnergyTypeDefinition', 34 | 'DestinyCollectibleDefinition', 35 | 'DestinyPresentationNodeDefinition', 36 | 'DestinyRecordDefinition', 37 | 'DestinyActivityModeDefinition', 38 | 'DestinyPlaceDefinition', 39 | 'DestinyFactionDefinition' 40 | ]; 41 | 42 | function loadDefinitions(store, language) { 43 | try { 44 | staleThenRevalidate( 45 | process.env.REACT_APP_API_KEY, 46 | language.code, 47 | REQUIRED_DEFINITIONS, 48 | (err, result) => { 49 | if (err) { 50 | trackError(err); 51 | store.dispatch(definitionsError(err)); 52 | return; 53 | } 54 | 55 | if (result && result.loading) { 56 | store.dispatch(definitionsStatus({ status: 'downloading' })); 57 | } 58 | 59 | if (result && result.definitions) { 60 | store.dispatch(definitionsStatus({ status: null })); 61 | store.dispatch(setBulkDefinitions(result.definitions)); 62 | } 63 | } 64 | ); 65 | } catch (err) { 66 | trackError(err); 67 | store.dispatch(definitionsError(err)); 68 | } 69 | } 70 | 71 | export default function preloadStore(store) { 72 | const prevFilters = ls.getFilters(); 73 | if (prevFilters) { 74 | log('Dispatching previous filters', prevFilters); 75 | store.dispatch(setBulkFilters(prevFilters)); 76 | } 77 | 78 | const authData = ls.getAuth(); 79 | const profiles = ls.getProfiles(); 80 | const trackedItems = ls.getTrackedItems(); 81 | const trackedRecords = ls.getTrackedRecords(); 82 | const dataExplorerVisited = ls.getDataExplorerVisited(); 83 | 84 | log('debug', { authData, profiles }); 85 | 86 | if (trackedItems.length) { 87 | store.dispatch(trackOrnaments(trackedItems)); 88 | } 89 | 90 | if (trackedRecords.length) { 91 | store.dispatch(trackRecords(trackedRecords)); 92 | } 93 | 94 | if (dataExplorerVisited) { 95 | store.dispatch(setAppValue({ dataExplorerVisited })); 96 | } 97 | 98 | if (authData && profiles) { 99 | const profile = getLastProfile(profiles); 100 | log('Dispaying previous profile', profile); 101 | store.dispatch( 102 | setProfiles({ 103 | currentProfile: profile, 104 | allProfiles: profiles.profiles, 105 | isCached: true 106 | }) 107 | ); 108 | } 109 | 110 | const language = ls.getLanguage(); 111 | 112 | store.dispatch(setLanguage(language)); 113 | store.dispatch(setBulkHiddenItemSet(ls.getHiddenItemSets())); 114 | 115 | let prevState = store.getState(); 116 | 117 | store.subscribe(() => { 118 | const newState = store.getState(); 119 | window.__state = newState; 120 | 121 | if (newState.app.trackedItems !== prevState.app.trackedItems) { 122 | ls.saveTrackedItems(newState.app.trackedItems); 123 | } 124 | 125 | if (newState.app.trackedRecords !== prevState.app.trackedRecords) { 126 | ls.saveTrackedRecords(newState.app.trackedRecords); 127 | } 128 | 129 | if (newState.app.language !== prevState.app.language) { 130 | loadDefinitions(store, newState.app.language); 131 | } 132 | 133 | prevState = newState; 134 | }); 135 | 136 | loadDefinitions(store, language); 137 | 138 | return store; 139 | } 140 | --------------------------------------------------------------------------------