;
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 |
--------------------------------------------------------------------------------
/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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
--------------------------------------------------------------------------------