mirror of
https://github.com/Sheldan/gw2-tools.git
synced 2026-04-29 14:15:17 +00:00
initial commit of functioning opening tracking
This commit is contained in:
0
gw2-tools-ui/src/App.css
Normal file
0
gw2-tools-ui/src/App.css
Normal file
55
gw2-tools-ui/src/App.js
Normal file
55
gw2-tools-ui/src/App.js
Normal file
@@ -0,0 +1,55 @@
|
||||
import './App.css';
|
||||
import './navbar.css';
|
||||
import {Link, Outlet, Route, Routes} from 'react-router-dom';
|
||||
import {AddOpening} from "./components/creation/AddOpening";
|
||||
import {ViewOpenings} from "./components/overview/ViewOpenings";
|
||||
import {ItemRatesOverview} from "./components/rates/ItemRatesOverview";
|
||||
|
||||
function Layout() {
|
||||
return <ul className="App-header">
|
||||
<nav className="navbar">
|
||||
<div className="container">
|
||||
<div className="nav-elements">
|
||||
<ul>
|
||||
<li>
|
||||
<Link to="/">Home</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link to="/viewOpenings">
|
||||
View openings
|
||||
</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link to="/addOpening">
|
||||
Add new openings
|
||||
</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link to="/dropRates">
|
||||
View drop rates
|
||||
</Link>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
<Outlet/>
|
||||
</ul>;
|
||||
}
|
||||
|
||||
function App() {
|
||||
return (
|
||||
<div>
|
||||
<Routes>
|
||||
<Route path="/" element={<Layout/>}>
|
||||
<Route path="addOpening" element={<AddOpening/>}/>
|
||||
<Route path="viewOpenings" element={<ViewOpenings/>}/>
|
||||
<Route path="dropRates" element={<ItemRatesOverview/>}/>
|
||||
</Route>
|
||||
</Routes>
|
||||
</div>
|
||||
|
||||
);
|
||||
}
|
||||
|
||||
export default App;
|
||||
2
gw2-tools-ui/src/App.test.js
Normal file
2
gw2-tools-ui/src/App.test.js
Normal file
@@ -0,0 +1,2 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import App from './App';
|
||||
17
gw2-tools-ui/src/components/common/ApiKeyInput.js
Normal file
17
gw2-tools-ui/src/components/common/ApiKeyInput.js
Normal file
@@ -0,0 +1,17 @@
|
||||
import {useSelector, useDispatch } from 'react-redux'
|
||||
import {setTo} from "../../redux/slices/apiKey";
|
||||
|
||||
export function ApiKeyInput() {
|
||||
const apiKey = useSelector(state => state.apiKey.value)
|
||||
const loadingState = useSelector(state => state.loadingState.value)
|
||||
const dispatch = useDispatch()
|
||||
return (
|
||||
<>
|
||||
<label>
|
||||
API Key: <input name="apiKey" value={apiKey} disabled={loadingState} onChange={(event) => {
|
||||
dispatch(setTo(event.target.value))
|
||||
}}/>
|
||||
</label>
|
||||
</>
|
||||
);
|
||||
}
|
||||
29
gw2-tools-ui/src/components/common/CurrencyDisplay.js
Normal file
29
gw2-tools-ui/src/components/common/CurrencyDisplay.js
Normal file
@@ -0,0 +1,29 @@
|
||||
import styles from "../creation/InventoryDifference.module.css";
|
||||
import {useSelector} from "react-redux";
|
||||
|
||||
export const CurrencyDisplay = ({currency}) => {
|
||||
const config = useSelector(state => state.config.value)
|
||||
// TODO render gold coins differently
|
||||
let classNames = `${styles.imageBottom} ${styles.numberDisplay} ${currency.changed > 0 ? styles.changedDisplayPositive : styles.changedDisplayNegative}`;
|
||||
|
||||
function displayItem() {
|
||||
if(config.showChangedOnly) {
|
||||
return currency.changed !== 0
|
||||
} else {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
let currencyDisplay = <div className={styles.imageContainer}>
|
||||
<img src={currency.iconUrl} title={currency.name} alt={currency.name} className={styles.currencyDisplay}/>
|
||||
<span
|
||||
className={`${styles.imageCentered} ${styles.numberDisplay}`}>{currency.amount > 1 ? `${currency.amount}x` : ''}</span>
|
||||
<span className={classNames}>{currency.changed !== 0 ? `${currency.changed}x` : ''}</span>
|
||||
|
||||
</div>;
|
||||
return (
|
||||
<>
|
||||
{displayItem() ? currencyDisplay : ''}
|
||||
</>
|
||||
)
|
||||
}
|
||||
38
gw2-tools-ui/src/components/common/InventoryItem.js
Normal file
38
gw2-tools-ui/src/components/common/InventoryItem.js
Normal file
@@ -0,0 +1,38 @@
|
||||
import styles from "../creation/InventoryDifference.module.css";
|
||||
import ItemDisplay from "./ItemDisplay.module.css";
|
||||
import {useSelector} from "react-redux";
|
||||
|
||||
export const InventoryItem = ({item}) => {
|
||||
const config = useSelector(state => state.config.value)
|
||||
let classNames = `${styles.imageBottom} ${styles.numberDisplay} ${item.changed > 0 ? styles.changedDisplayPositive : styles.changedDisplayNegative}`;
|
||||
|
||||
let rarityClasses = new Map()
|
||||
rarityClasses['LEGENDARY'] = ItemDisplay.legendary;
|
||||
rarityClasses['ASCENDED'] = ItemDisplay.ascended;
|
||||
rarityClasses['EXOTIC'] = ItemDisplay.exotic;
|
||||
rarityClasses['RARE'] = ItemDisplay.rare;
|
||||
rarityClasses['MASTERWORK'] = ItemDisplay.masterwork;
|
||||
rarityClasses['FINE'] = ItemDisplay.fine;
|
||||
rarityClasses['BASIC'] = ItemDisplay.basic;
|
||||
rarityClasses['JUNK'] = ItemDisplay.junk;
|
||||
|
||||
let itemDisplay = <div className={styles.imageContainer}>
|
||||
<img src={item.iconUrl} title={item.name} alt={item.name} className={`${styles.itemDisplay} ${rarityClasses[item.rarity]}`}/>
|
||||
<span
|
||||
className={`${styles.imageCentered} ${styles.numberDisplay}`}>{item.count !== 1 && item.count !== undefined ? `${item.count}x` : ''}</span>
|
||||
<span className={classNames}>{item.changed !== 0 && item.changed !== undefined ? `${item.changed}x` : ''}</span>
|
||||
</div>;
|
||||
|
||||
function displayItem() {
|
||||
if(config.showChangedOnly) {
|
||||
return item.changed !== 0
|
||||
} else {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return (
|
||||
<>
|
||||
{displayItem() ? itemDisplay : ''}
|
||||
</>
|
||||
)
|
||||
}
|
||||
31
gw2-tools-ui/src/components/common/ItemDisplay.module.css
Normal file
31
gw2-tools-ui/src/components/common/ItemDisplay.module.css
Normal file
@@ -0,0 +1,31 @@
|
||||
.junk {
|
||||
border: 3px solid #AAAAAA;
|
||||
}
|
||||
|
||||
.basic {
|
||||
border: 3px solid #0000;
|
||||
}
|
||||
|
||||
.fine {
|
||||
border: 3px solid #62A4DA;
|
||||
}
|
||||
|
||||
.masterwork {
|
||||
border: 3px solid #1a9306;
|
||||
}
|
||||
|
||||
.rare {
|
||||
border: 3px solid #fcd00b;
|
||||
}
|
||||
|
||||
.exotic {
|
||||
border: 3px solid #ffa405;
|
||||
}
|
||||
|
||||
.legendary {
|
||||
border: 3px solid #4C139D;
|
||||
}
|
||||
|
||||
.ascended {
|
||||
border: 3px solid #fb3e8d;
|
||||
}
|
||||
18
gw2-tools-ui/src/components/creation/AddOpening.js
Normal file
18
gw2-tools-ui/src/components/creation/AddOpening.js
Normal file
@@ -0,0 +1,18 @@
|
||||
import {ApiKeyInput} from "../common/ApiKeyInput";
|
||||
import {Settings} from "./Settings";
|
||||
import {OpeningSubmission} from "./OpeningSubmission";
|
||||
import {ItemDifference} from "./ItemDifference";
|
||||
import styles from "./AddOpening.module.css";
|
||||
|
||||
export function AddOpening() {
|
||||
return (
|
||||
<>
|
||||
<div className={styles.openingConfig}>
|
||||
<ApiKeyInput/>
|
||||
<Settings/>
|
||||
<OpeningSubmission/>
|
||||
<ItemDifference/>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
.openingConfig {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
flex-direction: column;
|
||||
}
|
||||
80
gw2-tools-ui/src/components/creation/Bank.js
Normal file
80
gw2-tools-ui/src/components/creation/Bank.js
Normal file
@@ -0,0 +1,80 @@
|
||||
import {useDispatch, useSelector} from 'react-redux'
|
||||
import {InventoryItem} from "../common/InventoryItem";
|
||||
import fetcher from "../../utils/fetcher";
|
||||
import {
|
||||
calculateBankDifferences,
|
||||
parseBank,
|
||||
} from "../../utils/inventoryUtils";
|
||||
import {useState} from "react";
|
||||
import {setAddedBankSlots, setBank, setRemovedBankSlots, updateChangedBankSlots} from "../../redux/slices/bank";
|
||||
import styles from "./InventoryDifference.module.css";
|
||||
|
||||
export function Bank() {
|
||||
const bank = useSelector(state => state.bank.value)
|
||||
const config = useSelector(state => state.config.value)
|
||||
const apiKey = useSelector(state => state.apiKey.value)
|
||||
const loadingState = useSelector(state => state.loadingState.value)
|
||||
|
||||
const [bankLoading, setBankLoading] = useState(false);
|
||||
|
||||
const dispatch = useDispatch()
|
||||
|
||||
let displayAdded = bank.addedSlots.length > 0
|
||||
let displayRemoved = bank.removedSlots.length > 0
|
||||
|
||||
const removedItems =
|
||||
<>
|
||||
{displayAdded ? <span className={styles.spanNewLine}>Removed items</span> : ''}
|
||||
{bank.removedSlots.map((item) => <InventoryItem item={item}/>)}
|
||||
</>
|
||||
|
||||
const addedItems =
|
||||
<>
|
||||
{displayRemoved ? <span className={styles.spanNewLine}>Added Items</span> : ''}
|
||||
{bank.addedSlots.map((item) => <InventoryItem item={item}/>)}
|
||||
</>
|
||||
|
||||
async function fetchBank() {
|
||||
const response = await fetcher("bank", {apiKey: apiKey})
|
||||
const bankResponse = await response.json()
|
||||
return parseBank(bankResponse);
|
||||
}
|
||||
|
||||
async function reloadBank() {
|
||||
setBankLoading(true)
|
||||
const bankSlots = await fetchBank();
|
||||
if(!config.locked) {
|
||||
const bankState = {
|
||||
slots: bankSlots,
|
||||
addedSlots: [],
|
||||
removedSlots: []
|
||||
}
|
||||
dispatch(setBank(bankState))
|
||||
} else {
|
||||
const [slotsToAdd, slotsToRemove, slotsToUpdate] = calculateBankDifferences(bank, bankSlots, config.mocking);
|
||||
dispatch(setRemovedBankSlots(slotsToRemove))
|
||||
dispatch(setAddedBankSlots(slotsToAdd))
|
||||
dispatch(updateChangedBankSlots(slotsToUpdate))
|
||||
}
|
||||
setBankLoading(false)
|
||||
}
|
||||
|
||||
let reloadButton = <button onClick={reloadBank} disabled={loadingState || bankLoading}>Update bank</button>;
|
||||
return (
|
||||
<>
|
||||
<div>
|
||||
<h2>Bank</h2>
|
||||
{bank.slots.length > 0 ? reloadButton : ''}
|
||||
</div>
|
||||
{bank.slots.map((item) =>
|
||||
<InventoryItem item={item}/>
|
||||
)}
|
||||
<>
|
||||
{config.locked ? addedItems : ''}
|
||||
</>
|
||||
<>
|
||||
{config.locked ? removedItems : ''}
|
||||
</>
|
||||
</>
|
||||
);
|
||||
}
|
||||
103
gw2-tools-ui/src/components/creation/CharacterInventory.js
Normal file
103
gw2-tools-ui/src/components/creation/CharacterInventory.js
Normal file
@@ -0,0 +1,103 @@
|
||||
import {useDispatch, useSelector} from "react-redux";
|
||||
import {InventoryItem} from "../common/InventoryItem";
|
||||
import {
|
||||
replaceCharacterItems,
|
||||
setAddedInventoryItemsForCharacter, setChangedInventoryItemsForCharacter,
|
||||
setRemovedInventoryItemsForCharacter
|
||||
} from "../../redux/slices/inventory";
|
||||
import fetcher from "../../utils/fetcher";
|
||||
import {
|
||||
calculateInventoryDifferencesForCharacter,
|
||||
calculateWalletDifference,
|
||||
parseInventory,
|
||||
parseWallet
|
||||
} from "../../utils/inventoryUtils";
|
||||
import {useState} from "react";
|
||||
import {setAddedWalletCurrency, setWallet, updateChangedWalletCurrencies} from "../../redux/slices/wallet";
|
||||
|
||||
export const CharacterInventory = ({character}) => {
|
||||
const loadingState = useSelector(state => state.loadingState.value)
|
||||
const apiKey = useSelector(state => state.apiKey.value)
|
||||
const wallet = useSelector(state => state.wallet.value)
|
||||
const config = useSelector(state => state.config.value)
|
||||
const [characterLoading, setCharacterLoading] = useState(false);
|
||||
const dispatch = useDispatch()
|
||||
|
||||
let displayAdded = character.addedItems.length
|
||||
|
||||
let addedItemsElement = <>
|
||||
{displayAdded ? <h2>Added items</h2> : ''}
|
||||
{character.addedItems.map((item) =>
|
||||
<InventoryItem item={item}/>
|
||||
)}
|
||||
</>;
|
||||
|
||||
async function reloadWallet() {
|
||||
const walletResponse = await fetcher("wallet", {apiKey: apiKey})
|
||||
const fullWallet = await walletResponse.json()
|
||||
const accountCurrencies = parseWallet(fullWallet);
|
||||
if(!config.locked) {
|
||||
const walletState = {
|
||||
currencies: accountCurrencies,
|
||||
addedCurrencies: []
|
||||
}
|
||||
dispatch(setWallet(walletState))
|
||||
} else {
|
||||
const [currenciesToUpdate, currenciesToAdd] = calculateWalletDifference(wallet, accountCurrencies, config.mocking);
|
||||
dispatch(setAddedWalletCurrency(currenciesToAdd))
|
||||
dispatch(updateChangedWalletCurrencies(currenciesToUpdate))
|
||||
}
|
||||
}
|
||||
|
||||
async function reloadCharacterInventory() {
|
||||
const response = await fetcher(`inventory/${character.name}`, {apiKey: apiKey})
|
||||
const characterInventoryObj = await response.json()
|
||||
if(!config.locked) {
|
||||
const inventory = parseInventory(characterInventoryObj);
|
||||
dispatch(replaceCharacterItems(inventory))
|
||||
} else {
|
||||
const parsedInventory = parseInventory(characterInventoryObj);
|
||||
const [
|
||||
itemsToAdd,
|
||||
itemsToRemove,
|
||||
itemsToUpdate
|
||||
] = calculateInventoryDifferencesForCharacter(character, parsedInventory, config.mocking);
|
||||
let itemsToAddPayload = {
|
||||
name: character.name,
|
||||
addedItems: itemsToAdd
|
||||
};
|
||||
let itemsToRemovePayload = {
|
||||
name: character.name,
|
||||
removedItems: itemsToRemove
|
||||
};
|
||||
let itemsToUpdatePayload = {
|
||||
name: character.name,
|
||||
items: itemsToUpdate
|
||||
};
|
||||
dispatch(setAddedInventoryItemsForCharacter(itemsToAddPayload))
|
||||
dispatch(setRemovedInventoryItemsForCharacter(itemsToRemovePayload))
|
||||
dispatch(setChangedInventoryItemsForCharacter(itemsToUpdatePayload))
|
||||
}
|
||||
}
|
||||
|
||||
async function updateCharacter() {
|
||||
setCharacterLoading(true)
|
||||
await reloadCharacterInventory();
|
||||
await reloadWallet();
|
||||
setCharacterLoading(false)
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div>
|
||||
<h1>{character.name}</h1>
|
||||
<button onClick={updateCharacter} disabled={loadingState || characterLoading}>Update character</button>
|
||||
</div>
|
||||
|
||||
{character.items.map((item) =>
|
||||
<InventoryItem item={item}/>
|
||||
)}
|
||||
{config.locked ? addedItemsElement : ''}
|
||||
</>
|
||||
)
|
||||
}
|
||||
30
gw2-tools-ui/src/components/creation/Inventories.js
Normal file
30
gw2-tools-ui/src/components/creation/Inventories.js
Normal file
@@ -0,0 +1,30 @@
|
||||
import {useSelector } from 'react-redux'
|
||||
import {CharacterInventory} from "./CharacterInventory";
|
||||
|
||||
export function Inventories() {
|
||||
const inventory = useSelector(state => state.inventory.value)
|
||||
const config = useSelector(state => state.config.value)
|
||||
|
||||
function shouldShowCharacter(characterInventory) {
|
||||
if(!config.showChangedOnly) { //
|
||||
return true;
|
||||
}
|
||||
// quick exits
|
||||
if(characterInventory.addedItems.length > 0) {
|
||||
return true;
|
||||
}
|
||||
if(characterInventory.removedItems.length > 0) {
|
||||
return true;
|
||||
}
|
||||
return characterInventory.items.filter(item => item.changed !== 0).length > 0;
|
||||
|
||||
}
|
||||
return (
|
||||
<>
|
||||
<h2>Inventories</h2>
|
||||
{inventory.map(characterInventory =>
|
||||
shouldShowCharacter(characterInventory) ? <CharacterInventory character={characterInventory} /> : ''
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
.itemDisplay {
|
||||
width: 33px;
|
||||
height: 33px;
|
||||
}
|
||||
|
||||
.currencyDisplay {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
}
|
||||
|
||||
.imageContainer {
|
||||
position: relative;
|
||||
text-align: center;
|
||||
display: inline-block;
|
||||
color: black;
|
||||
text-shadow: 2px 0 #fff, -2px 0 #fff, 0 2px #fff, 0 -2px #fff, 1px 1px #fff, -1px -1px #fff, 1px -1px #fff, -1px 1px #fff;
|
||||
}
|
||||
|
||||
.imageCentered {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
|
||||
.imageBottom {
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -100%);
|
||||
}
|
||||
|
||||
.numberDisplay {
|
||||
font-size: x-small;
|
||||
}
|
||||
|
||||
.changedDisplayNegative {
|
||||
color: #9C1A1C;
|
||||
}
|
||||
|
||||
.changedDisplayPositive {
|
||||
color: #3A7734;
|
||||
}
|
||||
|
||||
.spanNewLine {
|
||||
display: block;
|
||||
}
|
||||
|
||||
213
gw2-tools-ui/src/components/creation/ItemDifference.js
Normal file
213
gw2-tools-ui/src/components/creation/ItemDifference.js
Normal file
@@ -0,0 +1,213 @@
|
||||
import {useDispatch, useSelector} from "react-redux";
|
||||
import fetcher from "../../utils/fetcher";
|
||||
import {setLoading} from "../../redux/slices/loadingState";
|
||||
import {
|
||||
setAddedInventoryItemsForCharacter,
|
||||
setChangedInventoryItemsForCharacter,
|
||||
setInventoryForCharacter,
|
||||
setRemovedInventoryItemsForCharacter
|
||||
} from "../../redux/slices/inventory";
|
||||
import {setAddedWalletCurrency, setWallet, updateChangedWalletCurrencies} from "../../redux/slices/wallet";
|
||||
import {setAddedBankSlots, setBank, setRemovedBankSlots, updateChangedBankSlots} from "../../redux/slices/bank";
|
||||
import {
|
||||
calculateBankDifferences,
|
||||
calculateInventoryDifferencesForCharacter,
|
||||
calculateMaterialsDifference, calculateSharedInventoryDifferences,
|
||||
calculateWalletDifference,
|
||||
parseBank,
|
||||
parseInventory,
|
||||
parseMaterials, parseSharedInventory,
|
||||
parseWallet
|
||||
} from "../../utils/inventoryUtils";
|
||||
import {Bank} from "./Bank";
|
||||
import {Wallet} from "./Wallet";
|
||||
import {Inventories} from "./Inventories";
|
||||
import {
|
||||
setAddedMaterialSlots,
|
||||
setMaterials,
|
||||
setRemovedMaterialSlots,
|
||||
updateChangedMaterialSlots
|
||||
} from "../../redux/slices/materials";
|
||||
import {Materials} from "./Materials";
|
||||
import {SharedInventory} from "./SharedInventory";
|
||||
import {
|
||||
setAddedSharedInventorySlots,
|
||||
setRemovedSharedInventorySlots,
|
||||
setSharedInventory, updateChangedSharedInventorySlots
|
||||
} from "../../redux/slices/sharedInventory";
|
||||
|
||||
|
||||
export function ItemDifference() {
|
||||
const apiKey = useSelector(state => state.apiKey.value)
|
||||
const loadingState = useSelector(state => state.loadingState.value)
|
||||
const inventory = useSelector(state => state.inventory.value)
|
||||
const sharedInventory = useSelector(state => state.sharedInventory.value)
|
||||
const wallet = useSelector(state => state.wallet.value)
|
||||
const bank = useSelector(state => state.bank.value)
|
||||
const materials = useSelector(state => state.materials.value)
|
||||
const config = useSelector(state => state.config.value)
|
||||
|
||||
const dispatch = useDispatch()
|
||||
|
||||
async function fetchBank() {
|
||||
const response = await fetcher("bank", {apiKey: apiKey})
|
||||
const bank = await response.json()
|
||||
return parseBank(bank);
|
||||
}
|
||||
|
||||
async function updateBank() {
|
||||
const bankSlots = await fetchBank();
|
||||
if(!config.locked) {
|
||||
const bankState = {
|
||||
slots: bankSlots,
|
||||
addedSlots: [],
|
||||
removedSlots: []
|
||||
}
|
||||
dispatch(setBank(bankState))
|
||||
} else {
|
||||
const [slotsToAdd, slotsToRemove, slotsToUpdate] = calculateBankDifferences(bank, bankSlots, config.mocking);
|
||||
|
||||
dispatch(setRemovedBankSlots(slotsToRemove))
|
||||
dispatch(setAddedBankSlots(slotsToAdd))
|
||||
dispatch(updateChangedBankSlots(slotsToUpdate))
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchMaterials() {
|
||||
const response = await fetcher("materials", {apiKey: apiKey})
|
||||
const materials = await response.json()
|
||||
return parseMaterials(materials);
|
||||
}
|
||||
|
||||
async function updateMaterials() {
|
||||
const materialSlots = await fetchMaterials();
|
||||
if(!config.locked) {
|
||||
const materialsState = {
|
||||
slots: materialSlots,
|
||||
addedSlots: [],
|
||||
removedSlots: []
|
||||
}
|
||||
dispatch(setMaterials(materialsState))
|
||||
} else {
|
||||
const [slotsToAdd, slotsToRemove, slotsToUpdate] = calculateMaterialsDifference(materials, materialSlots, config.mocking);
|
||||
|
||||
dispatch(setRemovedMaterialSlots(slotsToRemove))
|
||||
dispatch(setAddedMaterialSlots(slotsToAdd))
|
||||
dispatch(updateChangedMaterialSlots(slotsToUpdate))
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchSharedInventory() {
|
||||
const response = await fetcher("sharedInventory", {apiKey: apiKey})
|
||||
const sharedInventoryResponse = await response.json()
|
||||
return parseSharedInventory(sharedInventoryResponse);
|
||||
}
|
||||
|
||||
async function updatedSharedInventory() {
|
||||
const sharedInventoryResponse = await fetchSharedInventory();
|
||||
if(!config.locked) {
|
||||
const sharedInventoryState = {
|
||||
slots: sharedInventoryResponse,
|
||||
addedSlots: [],
|
||||
removedSlots: []
|
||||
}
|
||||
dispatch(setSharedInventory(sharedInventoryState))
|
||||
} else {
|
||||
const [slotsToAdd, slotsToRemove, slotsToUpdate] = calculateSharedInventoryDifferences(sharedInventory, sharedInventoryResponse, config.mocking);
|
||||
|
||||
dispatch(setRemovedSharedInventorySlots(slotsToRemove))
|
||||
dispatch(setAddedSharedInventorySlots(slotsToAdd))
|
||||
dispatch(updateChangedSharedInventorySlots(slotsToUpdate))
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchInformation() {
|
||||
dispatch(setLoading(true))
|
||||
await updateWallet()
|
||||
await updateBank()
|
||||
await updateMaterials()
|
||||
await updatedSharedInventory()
|
||||
await updateInventoryCharacterSpecific()
|
||||
dispatch(setLoading(false))
|
||||
}
|
||||
|
||||
async function fetchWallet() {
|
||||
const response = await fetcher("wallet", {apiKey: apiKey})
|
||||
const fullWallet = await response.json()
|
||||
return parseWallet(fullWallet);
|
||||
}
|
||||
|
||||
async function updateWallet() {
|
||||
const accountCurrencies = await fetchWallet();
|
||||
if(!config.locked) {
|
||||
const walletState = {
|
||||
currencies: accountCurrencies,
|
||||
addedCurrencies: []
|
||||
}
|
||||
dispatch(setWallet(walletState))
|
||||
} else {
|
||||
const [currenciesToUpdate, currenciesToAdd] = calculateWalletDifference(wallet, accountCurrencies, config.mocking);
|
||||
dispatch(setAddedWalletCurrency(currenciesToAdd))
|
||||
dispatch(updateChangedWalletCurrencies(currenciesToUpdate))
|
||||
}
|
||||
}
|
||||
|
||||
async function updateInventoryCharacterSpecific() {
|
||||
const response = await fetcher("characters", {apiKey: apiKey})
|
||||
const characters = await response.json()
|
||||
if(!config.locked) {
|
||||
for (const charName of characters) {
|
||||
const parsedInventory = await fetchInventoryForCharacter(charName)
|
||||
dispatch(setInventoryForCharacter(parsedInventory))
|
||||
}
|
||||
} else {
|
||||
for (const charName of characters) {
|
||||
const parsedInventory = await fetchInventoryForCharacter(charName)
|
||||
const existingInventory = inventory.find((characterInventory => characterInventory.name === charName))
|
||||
const [
|
||||
itemsToAdd,
|
||||
itemsToRemove,
|
||||
itemsToUpdate
|
||||
] = calculateInventoryDifferencesForCharacter(existingInventory, parsedInventory, config.mocking);
|
||||
let itemsToAddPayload = {
|
||||
name: charName,
|
||||
addedItems: itemsToAdd
|
||||
};
|
||||
let itemsToRemovePayload = {
|
||||
name: charName,
|
||||
removedItems: itemsToRemove
|
||||
};
|
||||
let itemsToUpdatePayload = {
|
||||
name: charName,
|
||||
items: itemsToUpdate
|
||||
};
|
||||
dispatch(setAddedInventoryItemsForCharacter(itemsToAddPayload))
|
||||
dispatch(setRemovedInventoryItemsForCharacter(itemsToRemovePayload))
|
||||
dispatch(setChangedInventoryItemsForCharacter(itemsToUpdatePayload))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchInventoryForCharacter(characterName) {
|
||||
const response = await fetcher(`inventory/${characterName}/`, {apiKey: apiKey})
|
||||
const inventoryResponse = await response.json()
|
||||
return parseInventory(inventoryResponse);
|
||||
}
|
||||
|
||||
function noApiKeyProvided() {
|
||||
return apiKey === undefined || apiKey.length === 0
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div>
|
||||
<button onClick={fetchInformation} disabled={loadingState || noApiKeyProvided()}>Load information</button>
|
||||
<Wallet/>
|
||||
<Bank/>
|
||||
<Materials/>
|
||||
<SharedInventory/>
|
||||
<Inventories/>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
19
gw2-tools-ui/src/components/creation/LockStateInput.js
Normal file
19
gw2-tools-ui/src/components/creation/LockStateInput.js
Normal file
@@ -0,0 +1,19 @@
|
||||
import {useSelector, useDispatch } from 'react-redux'
|
||||
import {toggleLocked} from "../../redux/slices/config";
|
||||
|
||||
export function LockStateInput() {
|
||||
const loadingState = useSelector(state => state.loadingState.value)
|
||||
const config = useSelector(state => state.config.value)
|
||||
const dispatch = useDispatch()
|
||||
function toggleState() {
|
||||
dispatch(toggleLocked())
|
||||
}
|
||||
return (
|
||||
<>
|
||||
<label>
|
||||
Locked
|
||||
<input type={"checkbox"} checked={config.locked} disabled={loadingState} onChange={toggleState}/>
|
||||
</label>
|
||||
</>
|
||||
);
|
||||
}
|
||||
87
gw2-tools-ui/src/components/creation/Materials.js
Normal file
87
gw2-tools-ui/src/components/creation/Materials.js
Normal file
@@ -0,0 +1,87 @@
|
||||
import {useDispatch, useSelector} from 'react-redux'
|
||||
import {InventoryItem} from "../common/InventoryItem";
|
||||
import fetcher from "../../utils/fetcher";
|
||||
import {calculateMaterialsDifference, parseMaterials} from "../../utils/inventoryUtils";
|
||||
import {useState} from "react";
|
||||
import {
|
||||
setAddedMaterialSlots,
|
||||
setMaterials,
|
||||
setRemovedMaterialSlots,
|
||||
updateChangedMaterialSlots
|
||||
} from "../../redux/slices/materials";
|
||||
import styles from "./InventoryDifference.module.css";
|
||||
|
||||
export function Materials() {
|
||||
const materials = useSelector(state => state.materials.value)
|
||||
const config = useSelector(state => state.config.value)
|
||||
const apiKey = useSelector(state => state.apiKey.value)
|
||||
const loadingState = useSelector(state => state.loadingState.value)
|
||||
|
||||
const [materialsLoading, setMaterialsLoading] = useState(false);
|
||||
|
||||
const dispatch = useDispatch()
|
||||
|
||||
let displayAdded = materials.addedSlots.length
|
||||
let displayRemoved = materials.removedSlots.length
|
||||
|
||||
const removedItems =
|
||||
<>
|
||||
{displayAdded ? <span className={styles.spanNewLine}>Removed items</span> : ''}
|
||||
{materials.removedSlots.map((item) =>
|
||||
<InventoryItem item={item}/>
|
||||
)}
|
||||
</>
|
||||
|
||||
const addedItems =
|
||||
<>
|
||||
{displayRemoved ? <span className={styles.spanNewLine}>Added Items</span> : ''}
|
||||
{materials.addedSlots.map((item) =>
|
||||
<InventoryItem item={item}/>
|
||||
)}
|
||||
</>
|
||||
|
||||
async function fetchMaterials() {
|
||||
const response = await fetcher("materials", {apiKey: apiKey})
|
||||
const materials = await response.json()
|
||||
return parseMaterials(materials);
|
||||
}
|
||||
|
||||
async function reloadMaterials() {
|
||||
setMaterialsLoading(true)
|
||||
const materialSlots = await fetchMaterials();
|
||||
if(!config.locked) {
|
||||
const materialsState = {
|
||||
slots: materialSlots,
|
||||
addedSlots: [],
|
||||
removedSlots: []
|
||||
}
|
||||
dispatch(setMaterials(materialsState))
|
||||
} else {
|
||||
const [slotsToAdd, slotsToRemove, slotsToUpdate] = calculateMaterialsDifference(materials, materialSlots, config.mocking);
|
||||
|
||||
dispatch(setRemovedMaterialSlots(slotsToRemove))
|
||||
dispatch(setAddedMaterialSlots(slotsToAdd))
|
||||
dispatch(updateChangedMaterialSlots(slotsToUpdate))
|
||||
}
|
||||
setMaterialsLoading(false)
|
||||
}
|
||||
|
||||
let reloadButton = <button onClick={reloadMaterials} disabled={loadingState || materialsLoading}>Update materials</button>;
|
||||
return (
|
||||
<>
|
||||
<div>
|
||||
<h2>Materials</h2>
|
||||
{materials.slots.length > 0 ? reloadButton : ''}
|
||||
</div>
|
||||
{materials.slots.map((item) =>
|
||||
<InventoryItem item={item}/>
|
||||
)}
|
||||
<>
|
||||
{config.locked ? addedItems : ''}
|
||||
</>
|
||||
<>
|
||||
{config.locked ? removedItems : ''}
|
||||
</>
|
||||
</>
|
||||
);
|
||||
}
|
||||
137
gw2-tools-ui/src/components/creation/OpeningSubmission.js
Normal file
137
gw2-tools-ui/src/components/creation/OpeningSubmission.js
Normal file
@@ -0,0 +1,137 @@
|
||||
import {useSelector, useDispatch } from 'react-redux'
|
||||
import fetcher from "../../utils/fetcher";
|
||||
import {setLoading} from "../../redux/slices/loadingState";
|
||||
import {useState} from "react";
|
||||
import toast from 'react-simple-toasts';
|
||||
import 'react-simple-toasts/dist/theme/dark.css';
|
||||
|
||||
export function OpeningSubmission() {
|
||||
const apiKey = useSelector(state => state.apiKey.value)
|
||||
const inventory = useSelector(state => state.inventory.value)
|
||||
const sharedInventory = useSelector(state => state.sharedInventory.value)
|
||||
const wallet = useSelector(state => state.wallet.value)
|
||||
const bank = useSelector(state => state.bank.value)
|
||||
const materials = useSelector(state => state.materials.value)
|
||||
const loadingState = useSelector(state => state.loadingState.value)
|
||||
const dispatch = useDispatch()
|
||||
|
||||
const [openingDescription, setOpeningDescription] = useState("");
|
||||
|
||||
function createItem(item) {
|
||||
return {
|
||||
itemId: item.id,
|
||||
change: item.changed,
|
||||
itemType: "ITEM"
|
||||
};
|
||||
}
|
||||
|
||||
function createCurrency(currency) {
|
||||
return {
|
||||
itemId: currency.id,
|
||||
change: currency.changed,
|
||||
itemType: "CURRENCY"
|
||||
};
|
||||
}
|
||||
|
||||
function getChanges() {
|
||||
const changes = []
|
||||
const currencyChanges = []
|
||||
for (const charName in inventory) {
|
||||
const charInventory = inventory[charName]
|
||||
if (charInventory.addedItems) {
|
||||
charInventory.addedItems.forEach((item) => {
|
||||
changes.push(createItem(item))
|
||||
})
|
||||
}
|
||||
if (charInventory.items) {
|
||||
charInventory.items.forEach((item) => {
|
||||
if (item.changed !== 0) {
|
||||
changes.push(createItem(item))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
sharedInventory.slots.forEach((slot => {
|
||||
if (slot.changed !== 0) {
|
||||
changes.push(createItem(slot))
|
||||
}
|
||||
}))
|
||||
sharedInventory.addedSlots.forEach((slot => {
|
||||
changes.push(createItem(slot))
|
||||
}))
|
||||
wallet.currencies.forEach((slot => {
|
||||
if (slot.changed !== 0) {
|
||||
currencyChanges.push(createCurrency(slot))
|
||||
}
|
||||
}))
|
||||
wallet.addedCurrencies.forEach((slot => {
|
||||
currencyChanges.push(createCurrency(slot))
|
||||
}))
|
||||
bank.slots.forEach((slot => {
|
||||
if (slot.changed !== 0) {
|
||||
changes.push(createItem(slot))
|
||||
}
|
||||
}))
|
||||
bank.addedSlots.forEach((slot => {
|
||||
changes.push(createItem(slot))
|
||||
}))
|
||||
materials.slots.forEach((slot => {
|
||||
if (slot.changed !== 0) {
|
||||
changes.push(createItem(slot))
|
||||
}
|
||||
}))
|
||||
materials.addedSlots.forEach((slot => {
|
||||
changes.push(createItem(slot))
|
||||
}))
|
||||
|
||||
const changesMap = new Map()
|
||||
// this is done, so that changes over different area cancel one another out
|
||||
changes.forEach((change => {
|
||||
if(changesMap.has(change.itemId)) {
|
||||
changesMap.get(change.itemId).change += change.change;
|
||||
} else {
|
||||
changesMap.set(change.itemId, change)
|
||||
}
|
||||
}))
|
||||
const finalChangesList = []
|
||||
changesMap.forEach((value) => {
|
||||
if(value.change !== 0) {
|
||||
finalChangesList.push(value)
|
||||
}
|
||||
})
|
||||
currencyChanges.forEach((currencyChange) => {
|
||||
finalChangesList.push(currencyChange)
|
||||
})
|
||||
return finalChangesList;
|
||||
}
|
||||
|
||||
async function submitOpening() {
|
||||
dispatch(setLoading(true))
|
||||
const submissionBody = {}
|
||||
submissionBody.items = getChanges();
|
||||
submissionBody.description = openingDescription;
|
||||
await fetcher("openings", {apiKey: apiKey, method: "POST", body: JSON.stringify(submissionBody), headers: {"Content-Type": "application/json"}})
|
||||
setOpeningDescription("")
|
||||
toast('Opening has been submitted.')
|
||||
dispatch(setLoading(false))
|
||||
}
|
||||
|
||||
function hasChanges() {
|
||||
return getChanges().length > 0
|
||||
}
|
||||
|
||||
function updateDescription(newDescription) {
|
||||
setOpeningDescription(newDescription)
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div>
|
||||
<label>Description<textarea name="description" maxLength={1024} value={openingDescription} onChange={e => updateDescription(e.target.value)}/></label>
|
||||
<label>
|
||||
<button onClick={submitOpening} disabled={loadingState || !hasChanges()}>Submit opening</button>
|
||||
</label>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
32
gw2-tools-ui/src/components/creation/Settings.js
Normal file
32
gw2-tools-ui/src/components/creation/Settings.js
Normal file
@@ -0,0 +1,32 @@
|
||||
import {useSelector, useDispatch } from 'react-redux'
|
||||
import {toggleMocking, toggleShowChangedOnly} from "../../redux/slices/config";
|
||||
import {LockStateInput} from "./LockStateInput";
|
||||
|
||||
export function Settings() {
|
||||
const config = useSelector(state => state.config.value)
|
||||
const dispatch = useDispatch()
|
||||
|
||||
function handleMocking() {
|
||||
dispatch(toggleMocking());
|
||||
}
|
||||
|
||||
function handleToggleChangedOnly() {
|
||||
dispatch(toggleShowChangedOnly())
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div>
|
||||
<LockStateInput />
|
||||
<label>
|
||||
Mocking
|
||||
<input type={"checkbox"} checked={config.mocking} onChange={handleMocking}/>
|
||||
</label>
|
||||
<label>
|
||||
Display changed only
|
||||
<input type={"checkbox"} checked={config.showChangedOnly} onChange={handleToggleChangedOnly}/>
|
||||
</label>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
86
gw2-tools-ui/src/components/creation/SharedInventory.js
Normal file
86
gw2-tools-ui/src/components/creation/SharedInventory.js
Normal file
@@ -0,0 +1,86 @@
|
||||
import {useDispatch, useSelector} from 'react-redux'
|
||||
import {InventoryItem} from "../common/InventoryItem";
|
||||
import fetcher from "../../utils/fetcher";
|
||||
import {
|
||||
calculateSharedInventoryDifferences,
|
||||
parseSharedInventory
|
||||
} from "../../utils/inventoryUtils";
|
||||
import {useState} from "react";
|
||||
import styles from "./InventoryDifference.module.css";
|
||||
import {
|
||||
setAddedSharedInventorySlots,
|
||||
setRemovedSharedInventorySlots,
|
||||
setSharedInventory, updateChangedSharedInventorySlots
|
||||
} from "../../redux/slices/sharedInventory";
|
||||
|
||||
export function SharedInventory() {
|
||||
const sharedInventory = useSelector(state => state.sharedInventory.value)
|
||||
const config = useSelector(state => state.config.value)
|
||||
const apiKey = useSelector(state => state.apiKey.value)
|
||||
const loadingState = useSelector(state => state.loadingState.value)
|
||||
|
||||
const [sharedInventoryLoading, setSharedInventoryLoading] = useState(false);
|
||||
|
||||
const dispatch = useDispatch()
|
||||
|
||||
let displayAdded = sharedInventory.addedSlots.length > 0
|
||||
let displayRemoved = sharedInventory.removedSlots.length > 0
|
||||
|
||||
const removedItems =
|
||||
<>
|
||||
{displayAdded ? <span className={styles.spanNewLine}>Removed items</span> : ''}
|
||||
{sharedInventory.removedSlots.map((item) => <InventoryItem item={item}/>)}
|
||||
</>
|
||||
|
||||
const addedItems =
|
||||
<>
|
||||
{displayRemoved ? <span className={styles.spanNewLine}>Added Items</span> : ''}
|
||||
{sharedInventory.addedSlots.map((item) => <InventoryItem item={item}/>)}
|
||||
</>
|
||||
|
||||
async function fetchSharedInventory() {
|
||||
const response = await fetcher("sharedInventory", {apiKey: apiKey})
|
||||
const sharedInventoryResponse = await response.json()
|
||||
return parseSharedInventory(sharedInventoryResponse);
|
||||
}
|
||||
|
||||
async function reloadSharedInventory() {
|
||||
setSharedInventoryLoading(true)
|
||||
const sharedInventoryValue = await fetchSharedInventory();
|
||||
|
||||
if(!config.locked) {
|
||||
const sharedInventoryState = {
|
||||
slots: sharedInventoryValue,
|
||||
addedSlots: [],
|
||||
removedSlots: []
|
||||
}
|
||||
dispatch(setSharedInventory(sharedInventoryState))
|
||||
} else {
|
||||
const [slotsToAdd, slotsToRemove, slotsToUpdate] = calculateSharedInventoryDifferences(sharedInventory, sharedInventoryValue, config.mocking);
|
||||
dispatch(setRemovedSharedInventorySlots(slotsToRemove))
|
||||
dispatch(setAddedSharedInventorySlots(slotsToAdd))
|
||||
dispatch(updateChangedSharedInventorySlots(slotsToUpdate))
|
||||
}
|
||||
|
||||
setSharedInventoryLoading(false)
|
||||
}
|
||||
|
||||
let reloadButton = <button onClick={reloadSharedInventory} disabled={loadingState || sharedInventoryLoading}>Update shared inventory</button>;
|
||||
return (
|
||||
<>
|
||||
<div>
|
||||
<h2>Shared inventory</h2>
|
||||
{sharedInventory.slots.length > 0 ? reloadButton : ''}
|
||||
</div>
|
||||
{sharedInventory.slots.map((item) =>
|
||||
<InventoryItem item={item}/>
|
||||
)}
|
||||
<>
|
||||
{config.locked ? addedItems : ''}
|
||||
</>
|
||||
<>
|
||||
{config.locked ? removedItems : ''}
|
||||
</>
|
||||
</>
|
||||
);
|
||||
}
|
||||
13
gw2-tools-ui/src/components/creation/Wallet.js
Normal file
13
gw2-tools-ui/src/components/creation/Wallet.js
Normal file
@@ -0,0 +1,13 @@
|
||||
import {useSelector } from 'react-redux'
|
||||
import {CurrencyDisplay} from "../common/CurrencyDisplay";
|
||||
|
||||
export function Wallet() {
|
||||
const wallet = useSelector(state => state.wallet.value)
|
||||
return (
|
||||
<>
|
||||
<h2>Wallet</h2>
|
||||
{wallet.currencies.map(currency => <CurrencyDisplay currency={currency}/>)}
|
||||
{wallet.addedCurrencies.map(currency => <CurrencyDisplay currency={currency}/>)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
49
gw2-tools-ui/src/components/overview/OpeningDisplay.js
Normal file
49
gw2-tools-ui/src/components/overview/OpeningDisplay.js
Normal file
@@ -0,0 +1,49 @@
|
||||
import {InventoryItem} from "../common/InventoryItem";
|
||||
import styles from "./Openingdisplay.module.css";
|
||||
import {CurrencyDisplay} from "../common/CurrencyDisplay";
|
||||
import fetcher from "../../utils/fetcher";
|
||||
import {useDispatch, useSelector} from "react-redux";
|
||||
import {removeOpening} from "../../redux/slices/openings";
|
||||
import toast from "react-simple-toasts";
|
||||
|
||||
export function OpeningDisplay({opening, onlyMine}) {
|
||||
const apiKey = useSelector(state => state.apiKey.value)
|
||||
const dispatch = useDispatch()
|
||||
|
||||
let increasedItems = <>
|
||||
{opening.increasedItems.map((item) => <InventoryItem item={item}/>)}
|
||||
</>;
|
||||
let reducedItems = <>
|
||||
{opening.reducedItems.map((item) => <InventoryItem item={item}/>)}
|
||||
</>;
|
||||
let increasedCurrencies = <>
|
||||
{opening.increasedCurrencies.map((currency) => <CurrencyDisplay currency={currency}/>)}
|
||||
</>;
|
||||
let reducedCurrencies = <>
|
||||
{opening.reducedCurrencies.map((currency) => <CurrencyDisplay currency={currency}/>)}
|
||||
</>;
|
||||
|
||||
async function deleteOpeningClick() {
|
||||
await fetcher(`openings/${opening.id}`, {apiKey: apiKey, method: "DELETE"})
|
||||
dispatch(removeOpening({id: opening.id}))
|
||||
toast('Opening has been deleted.')
|
||||
}
|
||||
|
||||
let deleteOpeningButton = <button onClick={deleteOpeningClick}>Delete opening</button>;
|
||||
return (
|
||||
<>
|
||||
Description: {opening.description !== '' && opening.description !== undefined ? opening.description : 'No description'}
|
||||
: {opening.id} opened on: {opening.openingDate} {onlyMine && apiKey !== '' ? deleteOpeningButton : ''}
|
||||
<div className={styles.container}>
|
||||
<div className={styles.result}>
|
||||
{reducedItems}
|
||||
{reducedCurrencies}
|
||||
</div>
|
||||
<div className={styles.opened}>
|
||||
{increasedItems}
|
||||
{increasedCurrencies}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
101
gw2-tools-ui/src/components/overview/OpeningOverview.js
Normal file
101
gw2-tools-ui/src/components/overview/OpeningOverview.js
Normal file
@@ -0,0 +1,101 @@
|
||||
import {useDispatch, useSelector} from 'react-redux'
|
||||
import fetcher from "../../utils/fetcher";
|
||||
import {useState} from "react";
|
||||
import {OpeningDisplay} from "./OpeningDisplay";
|
||||
import {setOpenings} from "../../redux/slices/openings";
|
||||
|
||||
export function OpeningOverview() {
|
||||
const apiKey = useSelector(state => state.apiKey.value)
|
||||
const openings = useSelector(state => state.openings.value)
|
||||
const [onlyMine, setOnlyMine] = useState(true);
|
||||
const [openingsLoading, setOpeningsLoading] = useState(false);
|
||||
const dispatch = useDispatch()
|
||||
|
||||
async function fetchOpenings() {
|
||||
const onlyMineParameter = onlyMine ? '?showOwnOnly=true' : ''
|
||||
const response = await fetcher(`openings${onlyMineParameter}`, {apiKey: apiKey})
|
||||
const openingsObj = await response.json()
|
||||
return parseOpenings(openingsObj);
|
||||
}
|
||||
|
||||
function parseOpenings(openingsObj) {
|
||||
const openings = []
|
||||
openingsObj.openings.forEach((opening) => {
|
||||
const parsedOpening = {}
|
||||
const changedItems = []
|
||||
const increasedItems = []
|
||||
const reducedItems = []
|
||||
opening.itemChanges.forEach((changedItem) => {
|
||||
const newItem = {
|
||||
id: changedItem.id,
|
||||
changed: changedItem.count,
|
||||
name: changedItem.name,
|
||||
iconUrl: changedItem.iconUrl,
|
||||
rarity: changedItem.rarity
|
||||
}
|
||||
changedItems.push(newItem)
|
||||
if(newItem.changed > 0) {
|
||||
increasedItems.push(newItem)
|
||||
} else {
|
||||
reducedItems.push(newItem)
|
||||
}
|
||||
})
|
||||
const changedCurrencies = []
|
||||
const increasedCurrencies = []
|
||||
const reducedCurrencies = []
|
||||
opening.currencyChanges.forEach((changedCurrency) => {
|
||||
const newCurrency = {
|
||||
id: changedCurrency.id,
|
||||
name: changedCurrency.name,
|
||||
changed: changedCurrency.amount,
|
||||
iconUrl: changedCurrency.iconUrl
|
||||
}
|
||||
changedCurrencies.push(newCurrency)
|
||||
if(newCurrency.changed > 0) {
|
||||
increasedCurrencies.push(newCurrency)
|
||||
} else {
|
||||
reducedCurrencies.push(newCurrency)
|
||||
}
|
||||
})
|
||||
parsedOpening.items = changedItems;
|
||||
parsedOpening.currencies = changedCurrencies;
|
||||
parsedOpening.increasedItems = increasedItems;
|
||||
parsedOpening.increasedCurrencies = increasedCurrencies;
|
||||
parsedOpening.reducedCurrencies = reducedCurrencies;
|
||||
parsedOpening.reducedItems = reducedItems;
|
||||
parsedOpening.openingDate = opening.openingDate;
|
||||
parsedOpening.description = opening.description;
|
||||
parsedOpening.id = opening.openingId;
|
||||
openings.push(parsedOpening)
|
||||
})
|
||||
return openings;
|
||||
}
|
||||
|
||||
async function loadOpenings() {
|
||||
setOpeningsLoading(true)
|
||||
const openings = await fetchOpenings();
|
||||
dispatch(setOpenings(openings))
|
||||
setOpeningsLoading(false)
|
||||
}
|
||||
|
||||
function toggleShowMine() {
|
||||
setOnlyMine(!onlyMine)
|
||||
}
|
||||
|
||||
let mineFilter = <label>
|
||||
Display only mine
|
||||
<input type={"checkbox"} checked={onlyMine} onChange={toggleShowMine}/>
|
||||
</label>;
|
||||
return (
|
||||
<>
|
||||
{apiKey !== '' ? mineFilter : ''}
|
||||
<label>
|
||||
<button onClick={loadOpenings} disabled={openingsLoading}>Load openings</button>
|
||||
</label>
|
||||
<h2>Openings</h2>
|
||||
{openings.map(opening =>
|
||||
<OpeningDisplay opening={opening} onlyMine={onlyMine}/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
.container {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.result {
|
||||
width: 25%;
|
||||
background: lightblue;
|
||||
/* Just so it's visible */
|
||||
}
|
||||
.opened {
|
||||
flex: 1;
|
||||
/* Grow to rest of container */
|
||||
background: lightgreen;
|
||||
/* Just so it's visible */
|
||||
}
|
||||
9
gw2-tools-ui/src/components/overview/ViewOpenings.js
Normal file
9
gw2-tools-ui/src/components/overview/ViewOpenings.js
Normal file
@@ -0,0 +1,9 @@
|
||||
import {OpeningOverview} from "./OpeningOverview";
|
||||
|
||||
export function ViewOpenings() {
|
||||
return (
|
||||
<>
|
||||
<OpeningOverview/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
14
gw2-tools-ui/src/components/rates/DropRates.module.css
Normal file
14
gw2-tools-ui/src/components/rates/DropRates.module.css
Normal file
@@ -0,0 +1,14 @@
|
||||
.itemRateContainer {
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
}
|
||||
|
||||
.openedItem {
|
||||
flex: 1;
|
||||
background-color: palevioletred;
|
||||
}
|
||||
|
||||
.resultingItems {
|
||||
width: 90%;
|
||||
background-color: darkseagreen;
|
||||
}
|
||||
25
gw2-tools-ui/src/components/rates/ItemRateCurrencyDisplay.js
Normal file
25
gw2-tools-ui/src/components/rates/ItemRateCurrencyDisplay.js
Normal file
@@ -0,0 +1,25 @@
|
||||
import styles from "../creation/InventoryDifference.module.css";
|
||||
|
||||
export const ItemRateCurrencyDisplay = ({currency, showRelative}) => {
|
||||
|
||||
function getChangedValue() {
|
||||
if(showRelative) {
|
||||
return currency.per
|
||||
} else {
|
||||
return currency.amount
|
||||
}
|
||||
}
|
||||
|
||||
let currencyDisplay = <div className={styles.imageContainer}>
|
||||
<img src={currency.iconUrl} title={currency.name} alt={currency.name} className={styles.currencyDisplay}/>
|
||||
<span
|
||||
className={`${styles.imageCentered} ${styles.numberDisplay} ${styles.changedDisplayPositive}`}>{currency.per !== 0 && currency.per !== undefined
|
||||
&& currency.amount !== 0 && currency.amount !== undefined ? `${getChangedValue()}x` : ''}</span>
|
||||
</div>;
|
||||
|
||||
return (
|
||||
<>
|
||||
{currencyDisplay}
|
||||
</>
|
||||
)
|
||||
}
|
||||
28
gw2-tools-ui/src/components/rates/ItemRateDisplay.js
Normal file
28
gw2-tools-ui/src/components/rates/ItemRateDisplay.js
Normal file
@@ -0,0 +1,28 @@
|
||||
import {InventoryItem} from "../common/InventoryItem";
|
||||
import styles from "./DropRates.module.css";
|
||||
import {ItemRateItemDisplay} from "./ItemRateItemDisplay";
|
||||
import {ItemRateCurrencyDisplay} from "./ItemRateCurrencyDisplay";
|
||||
|
||||
export function ItemRateDisplay({item, showRelative}) {
|
||||
|
||||
let receivedItems = <>
|
||||
{item.receivedItems.map((receivedItem) => <ItemRateItemDisplay item={receivedItem} showRelative={showRelative}/>)}
|
||||
</>;
|
||||
|
||||
let receivedCurrencies = <>
|
||||
{item.receivedCurrencies.map((receivedCurrency) => <ItemRateCurrencyDisplay currency={receivedCurrency} showRelative={showRelative}/>)}
|
||||
</>;
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className={styles.itemRateContainer}>
|
||||
<div className={styles.openedItem}>
|
||||
<InventoryItem item={item.item}/>
|
||||
</div>
|
||||
<div className={styles.resultingItems}>
|
||||
{receivedItems} {receivedCurrencies}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
37
gw2-tools-ui/src/components/rates/ItemRateItemDisplay.js
Normal file
37
gw2-tools-ui/src/components/rates/ItemRateItemDisplay.js
Normal file
@@ -0,0 +1,37 @@
|
||||
import styles from "../creation/InventoryDifference.module.css";
|
||||
import ItemDisplay from "../common/ItemDisplay.module.css";
|
||||
|
||||
export const ItemRateItemDisplay = ({item, showRelative}) => {
|
||||
|
||||
let rarityClasses = new Map()
|
||||
rarityClasses['LEGENDARY'] = ItemDisplay.legendary;
|
||||
rarityClasses['ASCENDED'] = ItemDisplay.ascended;
|
||||
rarityClasses['EXOTIC'] = ItemDisplay.exotic;
|
||||
rarityClasses['RARE'] = ItemDisplay.rare;
|
||||
rarityClasses['MASTERWORK'] = ItemDisplay.masterwork;
|
||||
rarityClasses['FINE'] = ItemDisplay.fine;
|
||||
rarityClasses['BASIC'] = ItemDisplay.basic;
|
||||
rarityClasses['JUNK'] = ItemDisplay.junk;
|
||||
|
||||
function getChangedValue() {
|
||||
if(showRelative) {
|
||||
return item.per
|
||||
} else {
|
||||
return item.count
|
||||
}
|
||||
}
|
||||
|
||||
let itemDisplay = <div className={styles.imageContainer}>
|
||||
<img src={item.iconUrl} title={item.name} alt={item.name}
|
||||
className={`${styles.itemDisplay} ${rarityClasses[item.rarity]}`}/>
|
||||
<span
|
||||
className={`${styles.imageCentered} ${styles.numberDisplay} ${styles.changedDisplayPositive}`}>{item.per !== 0 && item.per !== undefined
|
||||
&& item.count !== 0 && item.count !== undefined ? `${getChangedValue()}x` : ''}</span>
|
||||
</div>;
|
||||
|
||||
return (
|
||||
<>
|
||||
{itemDisplay}
|
||||
</>
|
||||
)
|
||||
}
|
||||
95
gw2-tools-ui/src/components/rates/ItemRates.js
Normal file
95
gw2-tools-ui/src/components/rates/ItemRates.js
Normal file
@@ -0,0 +1,95 @@
|
||||
import fetcher from "../../utils/fetcher";
|
||||
import {useState} from "react";
|
||||
import {useDispatch, useSelector} from "react-redux";
|
||||
import {setItemRates} from "../../redux/slices/itemRates";
|
||||
import {ItemRateDisplay} from "./ItemRateDisplay";
|
||||
|
||||
export function ItemRates() {
|
||||
|
||||
const [itemRatesLoading, setItemRatesLoading] = useState(false);
|
||||
const [relativeRate, setRelativeRate] = useState(false);
|
||||
const itemRates = useSelector(state => state.itemRates.value)
|
||||
const apiKey = useSelector(state => state.apiKey.value)
|
||||
|
||||
const dispatch = useDispatch()
|
||||
|
||||
|
||||
function parseItemRates(itemRatesObj) {
|
||||
const itemRates = []
|
||||
itemRatesObj.itemRates.forEach((itemRate) => {
|
||||
const item = itemRate.item;
|
||||
const newItem = {
|
||||
id: item.id,
|
||||
changed: item.count,
|
||||
name: item.name,
|
||||
iconUrl: item.iconUrl,
|
||||
rarity: item.rarity
|
||||
}
|
||||
const receivedItems = []
|
||||
itemRate.receivedItems.forEach((receivedItem) => {
|
||||
const newReceivedItem = {
|
||||
id: receivedItem.id,
|
||||
count: receivedItem.count, // we use count for display
|
||||
name: receivedItem.name,
|
||||
iconUrl: receivedItem.iconUrl,
|
||||
rarity: receivedItem.rarity,
|
||||
per: +(receivedItem.count / Math.abs(item.count)).toFixed(2)
|
||||
}
|
||||
receivedItems.push(newReceivedItem)
|
||||
})
|
||||
const receivedCurrencies = []
|
||||
itemRate.receivedCurrencies.forEach((receivedCurrency) => {
|
||||
const newReceivedCurrency = {
|
||||
id: receivedCurrency.id,
|
||||
amount: receivedCurrency.amount, // we use count for display
|
||||
name: receivedCurrency.name,
|
||||
iconUrl: receivedCurrency.iconUrl,
|
||||
rarity: receivedCurrency.rarity,
|
||||
per: +(receivedCurrency.amount / Math.abs(item.count)).toFixed(2)
|
||||
}
|
||||
receivedCurrencies.push(newReceivedCurrency)
|
||||
})
|
||||
const itemRateObj = {
|
||||
item: newItem,
|
||||
receivedItems: receivedItems,
|
||||
receivedCurrencies: receivedCurrencies
|
||||
}
|
||||
itemRates.push(itemRateObj)
|
||||
})
|
||||
return itemRates;
|
||||
}
|
||||
|
||||
async function fetchItemRates() {
|
||||
const response = await fetcher(`itemRates`, {apiKey: apiKey})
|
||||
const itemRatesObj = await response.json()
|
||||
return parseItemRates(itemRatesObj);
|
||||
}
|
||||
|
||||
async function loadItemRates() {
|
||||
setItemRatesLoading(true)
|
||||
const fetchedItemRates = await fetchItemRates()
|
||||
dispatch(setItemRates(fetchedItemRates))
|
||||
setItemRatesLoading(false)
|
||||
}
|
||||
|
||||
|
||||
function toggleRelativeRate() {
|
||||
setRelativeRate(!relativeRate)
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<label>
|
||||
<button onClick={loadItemRates} disabled={itemRatesLoading}>Load item rates</button>
|
||||
<label>
|
||||
Show relative rate
|
||||
<input type={"checkbox"} checked={relativeRate} onChange={toggleRelativeRate}/>
|
||||
</label>
|
||||
</label>
|
||||
<h2>Item rates</h2>
|
||||
{itemRates.map(item =>
|
||||
<ItemRateDisplay item={item} showRelative={relativeRate}/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
9
gw2-tools-ui/src/components/rates/ItemRatesOverview.js
Normal file
9
gw2-tools-ui/src/components/rates/ItemRatesOverview.js
Normal file
@@ -0,0 +1,9 @@
|
||||
import {ItemRates} from "./ItemRates";
|
||||
|
||||
export function ItemRatesOverview() {
|
||||
return (
|
||||
<>
|
||||
<ItemRates/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
13
gw2-tools-ui/src/index.css
Normal file
13
gw2-tools-ui/src/index.css
Normal file
@@ -0,0 +1,13 @@
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
|
||||
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
|
||||
sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
code {
|
||||
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
|
||||
monospace;
|
||||
}
|
||||
26
gw2-tools-ui/src/index.js
Normal file
26
gw2-tools-ui/src/index.js
Normal file
@@ -0,0 +1,26 @@
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom/client';
|
||||
import './index.css';
|
||||
import App from './App';
|
||||
import { Provider } from 'react-redux'
|
||||
import { store } from './redux/store'
|
||||
import reportWebVitals from './reportWebVitals';
|
||||
import {BrowserRouter} from "react-router-dom";
|
||||
import { toastConfig } from 'react-simple-toasts';
|
||||
|
||||
toastConfig({ theme: 'dark' });
|
||||
const root = ReactDOM.createRoot(document.getElementById('root'));
|
||||
root.render(
|
||||
<React.StrictMode>
|
||||
<BrowserRouter basename={process.env.PUBLIC_URL}>
|
||||
<Provider store={store}>
|
||||
<App />
|
||||
</Provider>
|
||||
</BrowserRouter>
|
||||
</React.StrictMode>
|
||||
);
|
||||
|
||||
// If you want to start measuring performance in your app, pass a function
|
||||
// to log results (for example: reportWebVitals(console.log))
|
||||
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
|
||||
reportWebVitals();
|
||||
1
gw2-tools-ui/src/logo.svg
Normal file
1
gw2-tools-ui/src/logo.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 841.9 595.3"><g fill="#61DAFB"><path d="M666.3 296.5c0-32.5-40.7-63.3-103.1-82.4 14.4-63.6 8-114.2-20.2-130.4-6.5-3.8-14.1-5.6-22.4-5.6v22.3c4.6 0 8.3.9 11.4 2.6 13.6 7.8 19.5 37.5 14.9 75.7-1.1 9.4-2.9 19.3-5.1 29.4-19.6-4.8-41-8.5-63.5-10.9-13.5-18.5-27.5-35.3-41.6-50 32.6-30.3 63.2-46.9 84-46.9V78c-27.5 0-63.5 19.6-99.9 53.6-36.4-33.8-72.4-53.2-99.9-53.2v22.3c20.7 0 51.4 16.5 84 46.6-14 14.7-28 31.4-41.3 49.9-22.6 2.4-44 6.1-63.6 11-2.3-10-4-19.7-5.2-29-4.7-38.2 1.1-67.9 14.6-75.8 3-1.8 6.9-2.6 11.5-2.6V78.5c-8.4 0-16 1.8-22.6 5.6-28.1 16.2-34.4 66.7-19.9 130.1-62.2 19.2-102.7 49.9-102.7 82.3 0 32.5 40.7 63.3 103.1 82.4-14.4 63.6-8 114.2 20.2 130.4 6.5 3.8 14.1 5.6 22.5 5.6 27.5 0 63.5-19.6 99.9-53.6 36.4 33.8 72.4 53.2 99.9 53.2 8.4 0 16-1.8 22.6-5.6 28.1-16.2 34.4-66.7 19.9-130.1 62-19.1 102.5-49.9 102.5-82.3zm-130.2-66.7c-3.7 12.9-8.3 26.2-13.5 39.5-4.1-8-8.4-16-13.1-24-4.6-8-9.5-15.8-14.4-23.4 14.2 2.1 27.9 4.7 41 7.9zm-45.8 106.5c-7.8 13.5-15.8 26.3-24.1 38.2-14.9 1.3-30 2-45.2 2-15.1 0-30.2-.7-45-1.9-8.3-11.9-16.4-24.6-24.2-38-7.6-13.1-14.5-26.4-20.8-39.8 6.2-13.4 13.2-26.8 20.7-39.9 7.8-13.5 15.8-26.3 24.1-38.2 14.9-1.3 30-2 45.2-2 15.1 0 30.2.7 45 1.9 8.3 11.9 16.4 24.6 24.2 38 7.6 13.1 14.5 26.4 20.8 39.8-6.3 13.4-13.2 26.8-20.7 39.9zm32.3-13c5.4 13.4 10 26.8 13.8 39.8-13.1 3.2-26.9 5.9-41.2 8 4.9-7.7 9.8-15.6 14.4-23.7 4.6-8 8.9-16.1 13-24.1zM421.2 430c-9.3-9.6-18.6-20.3-27.8-32 9 .4 18.2.7 27.5.7 9.4 0 18.7-.2 27.8-.7-9 11.7-18.3 22.4-27.5 32zm-74.4-58.9c-14.2-2.1-27.9-4.7-41-7.9 3.7-12.9 8.3-26.2 13.5-39.5 4.1 8 8.4 16 13.1 24 4.7 8 9.5 15.8 14.4 23.4zM420.7 163c9.3 9.6 18.6 20.3 27.8 32-9-.4-18.2-.7-27.5-.7-9.4 0-18.7.2-27.8.7 9-11.7 18.3-22.4 27.5-32zm-74 58.9c-4.9 7.7-9.8 15.6-14.4 23.7-4.6 8-8.9 16-13 24-5.4-13.4-10-26.8-13.8-39.8 13.1-3.1 26.9-5.8 41.2-7.9zm-90.5 125.2c-35.4-15.1-58.3-34.9-58.3-50.6 0-15.7 22.9-35.6 58.3-50.6 8.6-3.7 18-7 27.7-10.1 5.7 19.6 13.2 40 22.5 60.9-9.2 20.8-16.6 41.1-22.2 60.6-9.9-3.1-19.3-6.5-28-10.2zM310 490c-13.6-7.8-19.5-37.5-14.9-75.7 1.1-9.4 2.9-19.3 5.1-29.4 19.6 4.8 41 8.5 63.5 10.9 13.5 18.5 27.5 35.3 41.6 50-32.6 30.3-63.2 46.9-84 46.9-4.5-.1-8.3-1-11.3-2.7zm237.2-76.2c4.7 38.2-1.1 67.9-14.6 75.8-3 1.8-6.9 2.6-11.5 2.6-20.7 0-51.4-16.5-84-46.6 14-14.7 28-31.4 41.3-49.9 22.6-2.4 44-6.1 63.6-11 2.3 10.1 4.1 19.8 5.2 29.1zm38.5-66.7c-8.6 3.7-18 7-27.7 10.1-5.7-19.6-13.2-40-22.5-60.9 9.2-20.8 16.6-41.1 22.2-60.6 9.9 3.1 19.3 6.5 28.1 10.2 35.4 15.1 58.3 34.9 58.3 50.6-.1 15.7-23 35.6-58.4 50.6zM320.8 78.4z"/><circle cx="420.9" cy="296.5" r="45.7"/><path d="M520.5 78.1z"/></g></svg>
|
||||
|
After Width: | Height: | Size: 2.6 KiB |
53
gw2-tools-ui/src/navbar.css
Normal file
53
gw2-tools-ui/src/navbar.css
Normal file
@@ -0,0 +1,53 @@
|
||||
/* taken from https://www.codevertiser.com/reactjs-responsive-navbar/ */
|
||||
|
||||
.container {
|
||||
max-width: 1100px;
|
||||
margin: 0 auto;
|
||||
padding: 0 15px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.navbar {
|
||||
height: 60px;
|
||||
background-color: #fef7e5;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.nav-elements {
|
||||
}
|
||||
|
||||
.nav-elements ul {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
list-style-type: none;
|
||||
}
|
||||
|
||||
.nav-elements ul li:not(:last-child) {
|
||||
margin-right: 60px;
|
||||
}
|
||||
|
||||
.nav-elements ul a {
|
||||
font-size: 16px;
|
||||
font-weight: 400;
|
||||
color: #2f234f;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.nav-elements ul a.active {
|
||||
color: #574c4c;
|
||||
font-weight: 500;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.nav-elements ul a.active::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: -4px;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 2px;
|
||||
background-color: #574c4c;
|
||||
}
|
||||
10
gw2-tools-ui/src/redux/apiKeyMiddleware.js
Normal file
10
gw2-tools-ui/src/redux/apiKeyMiddleware.js
Normal file
@@ -0,0 +1,10 @@
|
||||
import { createListenerMiddleware, isAnyOf } from "@reduxjs/toolkit";
|
||||
import {setTo} from "./slices/apiKey";
|
||||
|
||||
export const savingApiKeyMiddleware = createListenerMiddleware();
|
||||
savingApiKeyMiddleware.startListening({
|
||||
matcher: isAnyOf(setTo),
|
||||
effect: (action, listenerApi) => {
|
||||
localStorage.setItem("apiKey", JSON.stringify(listenerApi.getState().apiKey.value));
|
||||
}
|
||||
});
|
||||
17
gw2-tools-ui/src/redux/slices/apiKey.js
Normal file
17
gw2-tools-ui/src/redux/slices/apiKey.js
Normal file
@@ -0,0 +1,17 @@
|
||||
import { createSlice } from '@reduxjs/toolkit'
|
||||
|
||||
export const apiKeySlice = createSlice({
|
||||
name: 'apiKey',
|
||||
initialState: {
|
||||
value: ''
|
||||
},
|
||||
reducers: {
|
||||
setTo: (state, action) => {
|
||||
state.value = action.payload
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
export const { setTo } = apiKeySlice.actions
|
||||
|
||||
export default apiKeySlice.reducer
|
||||
30
gw2-tools-ui/src/redux/slices/bank.js
Normal file
30
gw2-tools-ui/src/redux/slices/bank.js
Normal file
@@ -0,0 +1,30 @@
|
||||
import { createSlice } from '@reduxjs/toolkit'
|
||||
|
||||
export const bankSlice = createSlice({
|
||||
name: 'bank',
|
||||
initialState: {
|
||||
value: {slots: [], addedSlots: [], removedSlots: []}
|
||||
},
|
||||
reducers: {
|
||||
setBank: (state, action) => {
|
||||
state.value = action.payload
|
||||
},
|
||||
setAddedBankSlots: (state, action) => {
|
||||
state.value.addedSlots = action.payload
|
||||
},
|
||||
updateChangedBankSlots: (state, action) => {
|
||||
let slotsToChange = action.payload;
|
||||
Object.keys(slotsToChange).forEach(slotId => {
|
||||
const existingSlot = state.value.slots.find(slot => slot.id === parseInt(slotId))
|
||||
existingSlot.changed = slotsToChange[slotId]
|
||||
})
|
||||
},
|
||||
setRemovedBankSlots: (state, action) => {
|
||||
state.value.removedSlots = action.payload
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
export const { setBank, setAddedBankSlots, updateChangedBankSlots, setRemovedBankSlots } = bankSlice.actions
|
||||
|
||||
export default bankSlice.reducer
|
||||
55
gw2-tools-ui/src/redux/slices/config.js
Normal file
55
gw2-tools-ui/src/redux/slices/config.js
Normal file
@@ -0,0 +1,55 @@
|
||||
import { createSlice } from '@reduxjs/toolkit'
|
||||
|
||||
export const configSlice = createSlice({
|
||||
name: 'config',
|
||||
initialState: {
|
||||
value: {
|
||||
showChangedOnly: false,
|
||||
mocking: false,
|
||||
locked: false
|
||||
}
|
||||
},
|
||||
reducers: {
|
||||
setShowChangedOnly: (state, action) => {
|
||||
state.value.showChangedOnly = action.payload
|
||||
},
|
||||
toggleShowChangedOnly: state => {
|
||||
state.value.showChangedOnly = !state.value.showChangedOnly
|
||||
},
|
||||
resetShowChangedOnly: state => {
|
||||
state.value.showChangedOnly = false
|
||||
},
|
||||
setMocking: (state, action) => {
|
||||
state.value.mocking = action.payload
|
||||
},
|
||||
toggleMocking: state => {
|
||||
state.value.mocking = !state.value.mocking
|
||||
},
|
||||
resetMocking: state => {
|
||||
state.value.mocking = false
|
||||
},
|
||||
setLocked: (state, action) => {
|
||||
state.value.locked = action.payload
|
||||
},
|
||||
toggleLocked: state => {
|
||||
state.value.locked = !state.value.locked
|
||||
},
|
||||
resetLocked: state => {
|
||||
state.value.locked = false
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
export const {
|
||||
setShowChangedOnly,
|
||||
toggleShowChangedOnly,
|
||||
resetShowChangedOnly,
|
||||
setMocking,
|
||||
toggleMocking,
|
||||
resetMocking,
|
||||
setLocked,
|
||||
toggleLocked,
|
||||
resetLocked
|
||||
} = configSlice.actions
|
||||
|
||||
export default configSlice.reducer
|
||||
101
gw2-tools-ui/src/redux/slices/inventory.js
Normal file
101
gw2-tools-ui/src/redux/slices/inventory.js
Normal file
@@ -0,0 +1,101 @@
|
||||
import { createSlice } from '@reduxjs/toolkit'
|
||||
|
||||
export const inventorySlice = createSlice({
|
||||
name: 'inventory',
|
||||
initialState: {
|
||||
value: []
|
||||
},
|
||||
reducers: {
|
||||
setInventory: (state, action) => {
|
||||
state.value = action.payload
|
||||
},
|
||||
setInventoryForCharacter: (state, action) => {
|
||||
const name = action.payload.name;
|
||||
const character = state.value.find(inventory => inventory.name === name)
|
||||
if(character !== undefined) {
|
||||
character.items = action.payload.items;
|
||||
character.addedItems = [];
|
||||
character.removedItems = [];
|
||||
} else {
|
||||
state.value.push(action.payload)
|
||||
}
|
||||
},
|
||||
replaceCharacterItems: (state, action) => {
|
||||
const name = action.payload.name;
|
||||
const character = state.value.find(inventory => inventory.name === name)
|
||||
character.items = action.payload.items;
|
||||
character.addedItems = [];
|
||||
character.removedItems = [];
|
||||
},
|
||||
clearCharacterItems: (state, action) => {
|
||||
const name = action.payload.name;
|
||||
const character = state.value.find(inventory => inventory.name === name)
|
||||
character.addedItems = [];
|
||||
character.removedItems = [];
|
||||
},
|
||||
setAddedInventoryItems: (state, action) => {
|
||||
let itemsToAdd = action.payload;
|
||||
for(const charName in itemsToAdd) {
|
||||
const itemsToAddForChar = itemsToAdd[charName];
|
||||
const existingCharInventory = state.value.find(inventory => inventory.name === charName)
|
||||
existingCharInventory.addedItems = itemsToAddForChar
|
||||
}
|
||||
},
|
||||
setRemovedInventoryItems: (state, action) => {
|
||||
let itemsToRemove = action.payload;
|
||||
for(const charName in itemsToRemove) {
|
||||
const itemsToRemoveForChar = itemsToRemove[charName];
|
||||
const existingCharInventory = state.value.find(inventory => inventory.name === charName)
|
||||
existingCharInventory.removedItems = itemsToRemoveForChar
|
||||
}
|
||||
},
|
||||
setChangedInventoryItems: (state, action) => {
|
||||
let itemsToChange = action.payload;
|
||||
for(const charName in itemsToChange) {
|
||||
const itemsToChangeForChar = itemsToChange[charName];
|
||||
const existingCharInventory = state.value.find(inventory => inventory.name === charName)
|
||||
existingCharInventory.items.forEach(item => {
|
||||
if(item.id in itemsToChangeForChar) {
|
||||
item.changed = itemsToChangeForChar[item.id]
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
setAddedInventoryItemsForCharacter: (state, action) => {
|
||||
let charName = action.payload.name;
|
||||
let itemsToAdd = action.payload.addedItems;
|
||||
const existingCharInventory = state.value.find(inventory => inventory.name === charName)
|
||||
existingCharInventory.addedItems = itemsToAdd
|
||||
},
|
||||
setRemovedInventoryItemsForCharacter: (state, action) => {
|
||||
let charName = action.payload.name;
|
||||
let itemsToRemove = action.payload.removedItems;
|
||||
const existingCharInventory = state.value.find(inventory => inventory.name === charName)
|
||||
existingCharInventory.removedItems = itemsToRemove
|
||||
},
|
||||
setChangedInventoryItemsForCharacter: (state, action) => {
|
||||
let charName = action.payload.name;
|
||||
let itemsToUpdate = action.payload.items;
|
||||
const existingCharInventory = state.value.find(inventory => inventory.name === charName)
|
||||
existingCharInventory.items.forEach(item => {
|
||||
if(item.id in itemsToUpdate) {
|
||||
item.changed = itemsToUpdate[item.id]
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
export const { setInventory,
|
||||
setInventoryForCharacter,
|
||||
replaceCharacterItems,
|
||||
clearCharacterItems ,
|
||||
setAddedInventoryItems,
|
||||
setRemovedInventoryItems,
|
||||
setChangedInventoryItems,
|
||||
setAddedInventoryItemsForCharacter,
|
||||
setRemovedInventoryItemsForCharacter,
|
||||
setChangedInventoryItemsForCharacter
|
||||
} = inventorySlice.actions
|
||||
|
||||
export default inventorySlice.reducer
|
||||
19
gw2-tools-ui/src/redux/slices/itemRates.js
Normal file
19
gw2-tools-ui/src/redux/slices/itemRates.js
Normal file
@@ -0,0 +1,19 @@
|
||||
import { createSlice } from '@reduxjs/toolkit'
|
||||
|
||||
export const itemRatesSlice = createSlice({
|
||||
name: 'itemRates',
|
||||
initialState: {
|
||||
value: []
|
||||
},
|
||||
reducers: {
|
||||
setItemRates: (state, action) => {
|
||||
state.value = action.payload
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
export const {
|
||||
setItemRates
|
||||
} = itemRatesSlice.actions
|
||||
|
||||
export default itemRatesSlice.reducer
|
||||
23
gw2-tools-ui/src/redux/slices/loadingState.js
Normal file
23
gw2-tools-ui/src/redux/slices/loadingState.js
Normal file
@@ -0,0 +1,23 @@
|
||||
import { createSlice } from '@reduxjs/toolkit'
|
||||
|
||||
export const loadingStateSlice = createSlice({
|
||||
name: 'loadingState',
|
||||
initialState: {
|
||||
value: false
|
||||
},
|
||||
reducers: {
|
||||
setLoading: (state, action) => {
|
||||
state.value = action.payload
|
||||
},
|
||||
toggleLoading: state => {
|
||||
state.value = !state.value
|
||||
},
|
||||
resetLoading: state => {
|
||||
state.value = false
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
export const { setLoading, toggleLoading, resetLoading } = loadingStateSlice.actions
|
||||
|
||||
export default loadingStateSlice.reducer
|
||||
30
gw2-tools-ui/src/redux/slices/materials.js
Normal file
30
gw2-tools-ui/src/redux/slices/materials.js
Normal file
@@ -0,0 +1,30 @@
|
||||
import { createSlice } from '@reduxjs/toolkit'
|
||||
|
||||
export const materialsSlice = createSlice({
|
||||
name: 'materials',
|
||||
initialState: {
|
||||
value: {slots: [], addedSlots: [], removedSlots: []}
|
||||
},
|
||||
reducers: {
|
||||
setMaterials: (state, action) => {
|
||||
state.value = action.payload
|
||||
},
|
||||
setAddedMaterialSlots: (state, action) => {
|
||||
state.value.addedSlots = action.payload
|
||||
},
|
||||
updateChangedMaterialSlots: (state, action) => {
|
||||
let slotsToChange = action.payload;
|
||||
Object.keys(slotsToChange).forEach(slotId => {
|
||||
const existingSlot = state.value.slots.find(slot => slot.id === parseInt(slotId))
|
||||
existingSlot.changed = slotsToChange[slotId]
|
||||
})
|
||||
},
|
||||
setRemovedMaterialSlots: (state, action) => {
|
||||
state.value.removedSlots = action.payload
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
export const { setMaterials, setRemovedMaterialSlots, setAddedMaterialSlots, updateChangedMaterialSlots, } = materialsSlice.actions
|
||||
|
||||
export default materialsSlice.reducer
|
||||
23
gw2-tools-ui/src/redux/slices/openings.js
Normal file
23
gw2-tools-ui/src/redux/slices/openings.js
Normal file
@@ -0,0 +1,23 @@
|
||||
import { createSlice } from '@reduxjs/toolkit'
|
||||
|
||||
export const openingsSlice = createSlice({
|
||||
name: 'openings',
|
||||
initialState: {
|
||||
value: []
|
||||
},
|
||||
reducers: {
|
||||
setOpenings: (state, action) => {
|
||||
state.value = action.payload
|
||||
},
|
||||
removeOpening: (state, action) => {
|
||||
const openingId = action.payload.id;
|
||||
state.value = state.value.filter(opening => opening.id !== openingId);
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
export const { setOpenings,
|
||||
removeOpening
|
||||
} = openingsSlice.actions
|
||||
|
||||
export default openingsSlice.reducer
|
||||
34
gw2-tools-ui/src/redux/slices/sharedInventory.js
Normal file
34
gw2-tools-ui/src/redux/slices/sharedInventory.js
Normal file
@@ -0,0 +1,34 @@
|
||||
import { createSlice } from '@reduxjs/toolkit'
|
||||
|
||||
export const sharedInventorySlice = createSlice({
|
||||
name: 'sharedInventory',
|
||||
initialState: {
|
||||
value: {slots: [], addedSlots: [], removedSlots: []}
|
||||
},
|
||||
reducers: {
|
||||
setSharedInventory: (state, action) => {
|
||||
state.value = action.payload
|
||||
},
|
||||
setAddedSharedInventorySlots: (state, action) => {
|
||||
state.value.addedSlots = action.payload
|
||||
},
|
||||
updateChangedSharedInventorySlots: (state, action) => {
|
||||
let slotsToChange = action.payload;
|
||||
Object.keys(slotsToChange).forEach(slotId => {
|
||||
const existingSlot = state.value.slots.find(slot => slot.id === parseInt(slotId))
|
||||
existingSlot.changed = slotsToChange[slotId]
|
||||
})
|
||||
},
|
||||
setRemovedSharedInventorySlots: (state, action) => {
|
||||
state.value.removedSlots = action.payload
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
export const {
|
||||
setSharedInventory,
|
||||
setAddedSharedInventorySlots,
|
||||
updateChangedSharedInventorySlots,
|
||||
setRemovedSharedInventorySlots } = sharedInventorySlice.actions
|
||||
|
||||
export default sharedInventorySlice.reducer
|
||||
27
gw2-tools-ui/src/redux/slices/wallet.js
Normal file
27
gw2-tools-ui/src/redux/slices/wallet.js
Normal file
@@ -0,0 +1,27 @@
|
||||
import { createSlice } from '@reduxjs/toolkit'
|
||||
|
||||
export const walletSlice = createSlice({
|
||||
name: 'wallet',
|
||||
initialState: {
|
||||
value: {currencies: [], addedCurrencies: []}
|
||||
},
|
||||
reducers: {
|
||||
setWallet: (state, action) => {
|
||||
state.value = action.payload
|
||||
},
|
||||
setAddedWalletCurrency: (state, action) => {
|
||||
state.value.addedCurrencies = action.payload
|
||||
},
|
||||
updateChangedWalletCurrencies: (state, action) => {
|
||||
let currenciesToChange = action.payload;
|
||||
Object.keys(currenciesToChange).forEach(currencyId => {
|
||||
const existingCurrency = state.value.currencies.find(currency => currency.id === parseInt(currencyId))
|
||||
existingCurrency.changed = currenciesToChange[currencyId]
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
export const { setWallet, setAddedWalletCurrency, updateChangedWalletCurrencies } = walletSlice.actions
|
||||
|
||||
export default walletSlice.reducer
|
||||
38
gw2-tools-ui/src/redux/store.js
Normal file
38
gw2-tools-ui/src/redux/store.js
Normal file
@@ -0,0 +1,38 @@
|
||||
import { configureStore } from '@reduxjs/toolkit'
|
||||
import apiKeyReducer from "./slices/apiKey";
|
||||
import inventoryReducer from "./slices/inventory";
|
||||
import loadingStateReducer from "./slices/loadingState";
|
||||
import walletReducer from "./slices/wallet";
|
||||
import bankReducer from "./slices/bank";
|
||||
import itemRatesReducer from "./slices/itemRates";
|
||||
import sharedInventoryReducer from "./slices/sharedInventory";
|
||||
import materialsReducer from "./slices/materials";
|
||||
import configReducer from "./slices/config";
|
||||
import openingsReducer from "./slices/openings";
|
||||
import {savingApiKeyMiddleware} from "./apiKeyMiddleware";
|
||||
|
||||
const apiKeyState = JSON.parse(localStorage.getItem("apiKey") || "null");
|
||||
|
||||
export const store = configureStore({
|
||||
preloadedState: {
|
||||
apiKey: {
|
||||
value: apiKeyState === null ? "" : apiKeyState
|
||||
}
|
||||
},
|
||||
reducer: {
|
||||
apiKey: apiKeyReducer,
|
||||
inventory: inventoryReducer,
|
||||
loadingState: loadingStateReducer,
|
||||
wallet: walletReducer,
|
||||
bank: bankReducer,
|
||||
materials: materialsReducer,
|
||||
config: configReducer,
|
||||
sharedInventory: sharedInventoryReducer,
|
||||
openings: openingsReducer,
|
||||
itemRates: itemRatesReducer
|
||||
},
|
||||
middleware: (getDefaultMiddleware) => [
|
||||
...getDefaultMiddleware(),
|
||||
savingApiKeyMiddleware.middleware
|
||||
]
|
||||
})
|
||||
13
gw2-tools-ui/src/reportWebVitals.js
Normal file
13
gw2-tools-ui/src/reportWebVitals.js
Normal file
@@ -0,0 +1,13 @@
|
||||
const reportWebVitals = onPerfEntry => {
|
||||
if (onPerfEntry && onPerfEntry instanceof Function) {
|
||||
import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
|
||||
getCLS(onPerfEntry);
|
||||
getFID(onPerfEntry);
|
||||
getFCP(onPerfEntry);
|
||||
getLCP(onPerfEntry);
|
||||
getTTFB(onPerfEntry);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
export default reportWebVitals;
|
||||
5
gw2-tools-ui/src/setupTests.js
Normal file
5
gw2-tools-ui/src/setupTests.js
Normal file
@@ -0,0 +1,5 @@
|
||||
// jest-dom adds custom jest matchers for asserting on DOM nodes.
|
||||
// allows you to do things like:
|
||||
// expect(element).toHaveTextContent(/react/i)
|
||||
// learn more: https://github.com/testing-library/jest-dom
|
||||
import '@testing-library/jest-dom';
|
||||
14
gw2-tools-ui/src/utils/fetcher.js
Normal file
14
gw2-tools-ui/src/utils/fetcher.js
Normal file
@@ -0,0 +1,14 @@
|
||||
function updateOptions(options, apiKey) {
|
||||
const update = { ...options };
|
||||
if (apiKey) {
|
||||
update.headers = {
|
||||
...update.headers,
|
||||
"gw2-api-key": `${apiKey}`,
|
||||
};
|
||||
}
|
||||
return update;
|
||||
}
|
||||
|
||||
export default function fetcher(url, options) {
|
||||
return fetch(url, updateOptions(options, options.apiKey));
|
||||
}
|
||||
307
gw2-tools-ui/src/utils/inventoryUtils.js
Normal file
307
gw2-tools-ui/src/utils/inventoryUtils.js
Normal file
@@ -0,0 +1,307 @@
|
||||
export function parseInventory(inventory) {
|
||||
let charName = inventory.name;
|
||||
let charInventory = {}
|
||||
charInventory.name = charName
|
||||
charInventory.items = []
|
||||
charInventory.addedItems = []
|
||||
charInventory.removedItems = []
|
||||
const usedItems = new Map()
|
||||
for (const bag of inventory.bags) {
|
||||
for (const item of bag.items) {
|
||||
if(!(item.id in usedItems)) {
|
||||
let itemToAdd = {
|
||||
id: item.id,
|
||||
name: item.name,
|
||||
iconUrl: item.iconUrl,
|
||||
rarity: item.rarity,
|
||||
count: item.count,
|
||||
changed: 0
|
||||
};
|
||||
charInventory.items.push(itemToAdd)
|
||||
usedItems[item.id] = itemToAdd
|
||||
} else {
|
||||
usedItems[item.id].count += item.count
|
||||
}
|
||||
}
|
||||
}
|
||||
return charInventory;
|
||||
}
|
||||
|
||||
export function parseWallet(fullWallet) {
|
||||
const currencies = fullWallet.currencies;
|
||||
const accountCurrencies = []
|
||||
for (const currency of currencies) {
|
||||
accountCurrencies.push({
|
||||
id: currency.id,
|
||||
name: currency.name,
|
||||
amount: currency.amount,
|
||||
iconUrl: currency.iconUrl,
|
||||
order: currency.order,
|
||||
changed: 0
|
||||
})
|
||||
}
|
||||
return accountCurrencies;
|
||||
}
|
||||
|
||||
export function parseBank(bank) {
|
||||
const slots = bank.slots;
|
||||
const bankSlots = []
|
||||
const usedItems = new Map()
|
||||
for (const slot of slots) {
|
||||
if(!(slot.id in usedItems)) {
|
||||
let itemToAdd = {
|
||||
id: slot.id,
|
||||
name: slot.name,
|
||||
count: slot.count,
|
||||
rarity: slot.rarity,
|
||||
iconUrl: slot.iconUrl,
|
||||
changed: 0
|
||||
};
|
||||
bankSlots.push(itemToAdd)
|
||||
usedItems[slot.id] = itemToAdd;
|
||||
} else {
|
||||
usedItems[slot.id].count += slot.count
|
||||
}
|
||||
}
|
||||
return bankSlots;
|
||||
}
|
||||
|
||||
export function parseSharedInventory(sharedInventory) {
|
||||
const slots = sharedInventory.slots;
|
||||
const sharedInventorySlots = []
|
||||
const usedItems = new Map()
|
||||
for (const slot of slots) {
|
||||
if(!(slot.id in usedItems)) {
|
||||
let itemToAdd = {
|
||||
id: slot.id,
|
||||
name: slot.name,
|
||||
count: slot.count,
|
||||
rarity: slot.rarity,
|
||||
iconUrl: slot.iconUrl,
|
||||
changed: 0
|
||||
};
|
||||
sharedInventorySlots.push(itemToAdd)
|
||||
usedItems[slot.id] = itemToAdd;
|
||||
} else {
|
||||
usedItems[slot.id].count += slot.count
|
||||
}
|
||||
}
|
||||
return sharedInventorySlots;
|
||||
}
|
||||
|
||||
export function parseMaterials(materials) {
|
||||
const slots = materials.slots;
|
||||
const materialSlots = []
|
||||
for (const slot of slots) {
|
||||
materialSlots.push({
|
||||
id: slot.id,
|
||||
name: slot.name,
|
||||
count: slot.count,
|
||||
rarity: slot.rarity,
|
||||
iconUrl: slot.iconUrl,
|
||||
changed: 0
|
||||
})
|
||||
}
|
||||
return materialSlots;
|
||||
}
|
||||
|
||||
|
||||
export function calculateBankDifferences(existingBank, incomingBankSlots, mocking) {
|
||||
const bankMap = new Map()
|
||||
existingBank.slots.forEach(slot => {
|
||||
bankMap[slot.id] = slot
|
||||
})
|
||||
const slotsToAdd = []
|
||||
const slotsAfterUpdate = new Map();
|
||||
const slotsToRemove = []
|
||||
const slotsToUpdate = new Map()
|
||||
incomingBankSlots.forEach(slot => {
|
||||
slotsAfterUpdate[slot.id] = slot;
|
||||
let mockValue = ~~(Math.random() * 10) === 0 && mocking;
|
||||
if (!(slot.id in bankMap) || mockValue) {
|
||||
slotsToAdd.push({
|
||||
id: slot.id,
|
||||
name: slot.name,
|
||||
iconUrl: slot.iconUrl,
|
||||
rarity: slot.rarity,
|
||||
count: 0,
|
||||
changed: slot.count
|
||||
})
|
||||
} else {
|
||||
const existingItem = bankMap[slot.id];
|
||||
const mockingValue = mocking ? ~~(Math.random() * 25 - 12) : 0
|
||||
slotsToUpdate[slot.id] = slot.count - existingItem.count + mockingValue;
|
||||
}
|
||||
})
|
||||
existingBank.slots.forEach(slot => {
|
||||
let mockValue = ~~(Math.random() * 20) === 0 && mocking;
|
||||
if (!(slot.id in slotsAfterUpdate) || mockValue) {
|
||||
slotsToRemove.push({
|
||||
id: slot.id,
|
||||
name: slot.name,
|
||||
iconUrl: slot.iconUrl,
|
||||
rarity: slot.rarity,
|
||||
count: 0,
|
||||
changed: -slot.count
|
||||
})
|
||||
}
|
||||
})
|
||||
return [slotsToAdd, slotsToRemove, slotsToUpdate];
|
||||
}
|
||||
|
||||
export function calculateSharedInventoryDifferences(existingSharedInventory, incomingSharedInventorySlots, mocking) {
|
||||
const sharedInventoryMap = new Map()
|
||||
existingSharedInventory.slots.forEach(slot => {
|
||||
sharedInventoryMap[slot.id] = slot
|
||||
})
|
||||
const slotsToAdd = []
|
||||
const slotsAfterUpdate = new Map();
|
||||
const slotsToRemove = []
|
||||
const slotsToUpdate = new Map()
|
||||
incomingSharedInventorySlots.forEach(slot => {
|
||||
slotsAfterUpdate[slot.id] = slot;
|
||||
let mockValue = ~~(Math.random() * 10) === 0 && mocking;
|
||||
if (!(slot.id in sharedInventoryMap) || mockValue) {
|
||||
slotsToAdd.push({
|
||||
id: slot.id,
|
||||
name: slot.name,
|
||||
iconUrl: slot.iconUrl,
|
||||
rarity: slot.rarity,
|
||||
count: 0,
|
||||
changed: slot.count
|
||||
})
|
||||
} else {
|
||||
const existingItem = sharedInventoryMap[slot.id];
|
||||
const mockingValue = mocking ? ~~(Math.random() * 25 - 12) : 0
|
||||
slotsToUpdate[slot.id] = slot.count - existingItem.count + mockingValue;
|
||||
}
|
||||
})
|
||||
existingSharedInventory.slots.forEach(slot => {
|
||||
let mockValue = ~~(Math.random() * 20) === 0 && mocking;
|
||||
if (!(slot.id in slotsAfterUpdate) || mockValue) {
|
||||
slotsToRemove.push({
|
||||
id: slot.id,
|
||||
name: slot.name,
|
||||
iconUrl: slot.iconUrl,
|
||||
rarity: slot.rarity,
|
||||
count: 0,
|
||||
changed: -slot.count
|
||||
})
|
||||
}
|
||||
})
|
||||
return [slotsToAdd, slotsToRemove, slotsToUpdate];
|
||||
}
|
||||
|
||||
export function calculateMaterialsDifference(materials, materialSlots, mocking) {
|
||||
const materialMap = new Map()
|
||||
materials.slots.forEach(slot => {
|
||||
materialMap[slot.id] = slot
|
||||
})
|
||||
const slotsToAdd = []
|
||||
const slotsAfterUpdate = new Map()
|
||||
const slotsToRemove = []
|
||||
const slotsToUpdate = new Map()
|
||||
materialSlots.forEach(slot => {
|
||||
slotsAfterUpdate[slot.id] = slot;
|
||||
let mockValue = ~~(Math.random() * 10) === 0 && mocking;
|
||||
if (!(slot.id in materialMap) || mockValue) {
|
||||
slotsToAdd.push({
|
||||
id: slot.id,
|
||||
name: slot.name,
|
||||
iconUrl: slot.iconUrl,
|
||||
rarity: slot.rarity,
|
||||
count: 0,
|
||||
changed: slot.count
|
||||
})
|
||||
} else {
|
||||
const existingItem = materialMap[slot.id];
|
||||
const mockingValue = mocking ? ~~(Math.random() * 25 - 12) : 0
|
||||
slotsToUpdate[slot.id] = slot.count - existingItem.count + mockingValue;
|
||||
}
|
||||
})
|
||||
materials.slots.forEach(slot => {
|
||||
let mockValue = ~~(Math.random() * 20) === 0 && mocking;
|
||||
if (!(slot.id in slotsAfterUpdate) || mockValue) {
|
||||
slotsToRemove.push({
|
||||
id: slot.id,
|
||||
name: slot.name,
|
||||
iconUrl: slot.iconUrl,
|
||||
rarity: slot.rarity,
|
||||
count: 0,
|
||||
changed: -slot.count
|
||||
})
|
||||
}
|
||||
})
|
||||
return [slotsToAdd, slotsToRemove, slotsToUpdate];
|
||||
}
|
||||
|
||||
export function calculateWalletDifference(wallet, accountCurrencies, mocking) {
|
||||
const walletMap = new Map()
|
||||
const currenciesToUpdate = new Map()
|
||||
wallet.currencies.forEach(currency => {
|
||||
walletMap[currency.id] = currency
|
||||
})
|
||||
const currenciesToAdd = []
|
||||
accountCurrencies.forEach(currency => {
|
||||
let mockValue = ~~(Math.random() * 10) === 0 && mocking;
|
||||
if (!(currency.id in walletMap) || mockValue) {
|
||||
currenciesToAdd.push({
|
||||
id: currency.id,
|
||||
name: currency.name,
|
||||
iconUrl: currency.iconUrl,
|
||||
amount: 0,
|
||||
changed: currency.amount
|
||||
})
|
||||
} else {
|
||||
const existingItem = walletMap[currency.id];
|
||||
const mockingValue = mocking ? ~~(Math.random() * 25 - 12) : 0
|
||||
currenciesToUpdate[currency.id] = currency.amount - existingItem.amount + mockingValue;
|
||||
}
|
||||
})
|
||||
return [currenciesToUpdate, currenciesToAdd];
|
||||
}
|
||||
|
||||
export function calculateInventoryDifferencesForCharacter(existingInventory, incomingCharacterInventory, mocking) {
|
||||
const itemMap = new Map()
|
||||
existingInventory.items.forEach(item => {
|
||||
itemMap[item.id] = item
|
||||
})
|
||||
const itemsAfterUpdate = new Map()
|
||||
const itemsToUpdateForCharacter = {}
|
||||
const itemsToAdd = []
|
||||
const itemsToRemove = []
|
||||
incomingCharacterInventory.items.forEach(item => {
|
||||
itemsAfterUpdate[item.id] = item
|
||||
let mockValue = ~~(Math.random() * 10) === 0 && mocking;
|
||||
if (!(item.id in itemMap) || mockValue) {
|
||||
itemsToAdd.push({
|
||||
id: item.id,
|
||||
name: item.name,
|
||||
iconUrl: item.iconUrl,
|
||||
rarity: item.rarity,
|
||||
count: 0,
|
||||
changed: item.count
|
||||
})
|
||||
} else { // completely removed items are not marked with - or completely removed
|
||||
const existingItem = itemMap[item.id];
|
||||
const mockingValue = mocking ? ~~(Math.random() * 25 - 12) : 0
|
||||
itemsToUpdateForCharacter[item.id] = item.count - existingItem.count + mockingValue;
|
||||
}
|
||||
})
|
||||
for (const itemIdBefore in itemMap) {
|
||||
let mockValue = ~~(Math.random() * 20) === 0 && mocking;
|
||||
if (!(itemIdBefore in itemsAfterUpdate) || mockValue) {
|
||||
const itemBefore = itemMap[itemIdBefore]
|
||||
itemsToRemove.push({
|
||||
id: itemBefore.id,
|
||||
name: itemBefore.name,
|
||||
iconUrl: itemBefore.iconUrl,
|
||||
rarity: itemBefore.rarity,
|
||||
count: 0,
|
||||
changed: -itemBefore.count
|
||||
})
|
||||
itemsToUpdateForCharacter[itemIdBefore] = -itemMap[itemIdBefore].count
|
||||
}
|
||||
}
|
||||
return [itemsToAdd, itemsToRemove, itemsToUpdateForCharacter]
|
||||
}
|
||||
Reference in New Issue
Block a user