Skip to content

Commit

Permalink
Add "Any" option for conqueror dropdown select and fix trade search bug
Browse files Browse the repository at this point in the history
  • Loading branch information
ImHamba committed May 26, 2024
1 parent 8cce491 commit 1a90b9f
Show file tree
Hide file tree
Showing 8 changed files with 208 additions and 102 deletions.
24 changes: 16 additions & 8 deletions frontend/src/lib/components/SearchResult.svelte
Original file line number Diff line number Diff line change
@@ -1,27 +1,35 @@
<script lang="ts">
import type { SearchWithSeed } from '../skill_tree';
import { skillTree, translateStat, openTrade } from '../skill_tree';
import { openQueryTrade } from '$lib/utils/trade_utils';
import { constructSingleResultQuery, type SearchWithSeed } from '../skill_tree';
import { skillTree, translateStat } from '../skill_tree';
export let highlight: (newSeed: number, passives: number[]) => void;
export let set: SearchWithSeed;
export let jewel: number;
export let conqueror: string;
</script>
<div
class="my-2 border-white/50 border p-2 flex flex-col cursor-pointer"
on:click={() =>
const handleOnClick = () =>
highlight(
set.seed,
set.skills.map((s) => s.passive)
)}>
);
</script>

<div
class="my-2 border-white/50 border p-2 flex flex-col cursor-pointer"
on:click={handleOnClick}
on:keydown={handleOnClick}
role="button"
tabindex="0">
<div class="flex flex-row justify-between">
<!-- Padding -->
<button class="px-3 invisible">Trade</button>
<div class="font-bold text-orange-500 text-center">
Seed {set.seed} (weight {set.weight})
</div>
<button class="px-3 bg-blue-500/40 rounded" on:click={() => openTrade(jewel, conqueror, [set])}>Trade</button>
<button
class="px-3 bg-blue-500/40 rounded"
on:click={() => openQueryTrade(constructSingleResultQuery(jewel, conqueror, set))}>Trade</button>
</div>
{#each set.skills as skill}
<div class="mt-2">
Expand Down
27 changes: 27 additions & 0 deletions frontend/src/lib/components/TradeButton.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<script lang="ts">
import { openQueryTrade, type Query } from '$lib/utils/trade_utils';
export let queries: Query[];
export let showTradeLinks = false;
$: console.log(showTradeLinks);
$: hasMultipleQueries = queries.length > 1;
const handleOnClick = () => {
if (queries.length === 1) {
// if all filter fit into a single query, link straight to trade website
openQueryTrade(queries[0]);
} else if (hasMultipleQueries) {
// otherwise toggle display of batched trade links
showTradeLinks = !showTradeLinks;
}
};
</script>

<button
class="p-1 px-3 bg-blue-500/40 rounded disabled:bg-blue-900/40 mr-2"
on:click={handleOnClick}
disabled={!queries}>
{hasMultipleQueries ? (showTradeLinks ? 'Hide Trade Links' : 'Show Trade Links') : 'Trade'}
</button>
14 changes: 14 additions & 0 deletions frontend/src/lib/components/TradeLinks.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<script lang="ts">
import { openQueryTrade, type Query } from '$lib/utils/trade_utils';
// queries to display trade link buttons for
export let queries: Query[];
</script>

<div class="flex flex-wrap gap-2.5 my-2">
{#each queries as query, index}
<button class="p-1 px-3 bg-blue-500/40 rounded" on:click={() => openQueryTrade(query)}>
Trade Batch {index + 1}
</button>
{/each}
</div>
125 changes: 47 additions & 78 deletions frontend/src/lib/skill_tree.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import type { Translation, Node, SkillTreeData, Group, Sprite, TranslationFile } from './skill_tree_types';
import { data } from './types';
import { type Filter, type Query, filterGroupsToQuery, filtersToFilterGroup } from './utils/trade_utils';
import { chunkArray } from './utils/utils';

export let skillTree: SkillTreeData;

Expand Down Expand Up @@ -384,91 +386,58 @@ const tradeStatNames: { [key: number]: { [key: string]: string } } = {
}
};

export const constructQuery = (jewel: number, conqueror: string, result: SearchWithSeed[]) => {
const max_filter_length = 50;
const max_filters = 4;
const max_query_length = max_filter_length * max_filters;
const final_query = [];
const stat = {
type: 'count',
value: { min: 1 },
filters: [],
disabled: false
};
export const constructSingleResultQuery = (jewel: number, conqueror: string | null, result: SearchWithSeed): Query => {
const anyConqueror = conqueror === null;

// single seed case
if (result.length == 1) {
for (const conq of Object.keys(tradeStatNames[jewel])) {
stat.filters.push({
id: tradeStatNames[jewel][conq],
value: {
min: result[0].seed,
max: result[0].seed
},
disabled: conq != conqueror
});
}
const filters: Filter[] = Object.keys(tradeStatNames[jewel]).map((conq) => ({
id: tradeStatNames[jewel][conq],
value: {
min: result.seed,
max: result.seed
},
disabled: anyConqueror ? false : conq != conqueror
}));

final_query.push(stat);
// too many results case
} else if (result.length > max_query_length) {
for (let i = 0; i < max_filters; i++) {
final_query.push({
type: 'count',
value: { min: 1 },
filters: [],
disabled: i == 0 ? false : true
});
}
const filterGroup = filtersToFilterGroup(filters, false);
const query: Query = filterGroupsToQuery([filterGroup]);
return query;
};

for (const [i, r] of result.slice(0, max_query_length).entries()) {
const index = Math.floor(i / max_filter_length);
const constructSearchFilter = (jewel: number, conqueror: string | null, result: SearchWithSeed): Filter[] => {
// null conqueror indicates to search for any conqueror
const anyConqueror = conqueror === null;
const conquerors = anyConqueror ? Object.keys(tradeStatNames[jewel]) : [conqueror];

final_query[index].filters.push({
id: tradeStatNames[jewel][conqueror],
value: {
min: r.seed,
max: r.seed
}
});
return conquerors.map((conq) => ({
id: tradeStatNames[jewel][conq],
value: {
min: result.seed,
max: result.seed
}
} else {
for (const conq of Object.keys(tradeStatNames[jewel])) {
stat.disabled = conq != conqueror;

for (const r of result) {
stat.filters.push({
id: tradeStatNames[jewel][conq],
value: {
min: r.seed,
max: r.seed
}
});
}
}));
};

if (stat.filters.length > max_filter_length) {
stat.filters = stat.filters.slice(0, max_filter_length);
}
export const constructQueries = (jewel: number, conqueror: string | null, results: SearchWithSeed[]) => {
const max_filter_length = 50;
const max_filters = 4;
const max_query_length = max_filter_length * max_filters;

final_query.push(stat);
}
}
// convert all results into filters
const allFilters = results.flatMap((result) => constructSearchFilter(jewel, conqueror, result));

return {
query: {
status: {
option: 'online'
},
stats: final_query
},
sort: {
price: 'asc'
}
};
};
// group filters into groups of max_query_length, where each group is further grouped into chunks of max_filter_length
// this represents multiple trade links, where each trade link has multiple filter groups, and each filter group has multiple filters
const queryFilterGroups = chunkArray(allFilters, max_query_length).map((chunk) =>
chunkArray(chunk, max_filter_length)
);

// map filters groups to queries
const tradeQueries = queryFilterGroups.map((queryFilterGroup) => {
// for each query, map the chunks within it to filter groups
const tradeFilterGroups = queryFilterGroup.map((filters, index) => filtersToFilterGroup(filters, index !== 0));
const tradeQuery: Query = filterGroupsToQuery(tradeFilterGroups);
return tradeQuery;
});

export const openTrade = (jewel: number, conqueror: string, results: SearchWithSeed[]) => {
const url = new URL('https://www.pathofexile.com/trade/search/Necropolis');
url.searchParams.set('q', JSON.stringify(constructQuery(jewel, conqueror, results)));
window.open(url, '_blank');
return tradeQueries;
};
47 changes: 47 additions & 0 deletions frontend/src/lib/utils/trade_utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
export type Filter = {
id: string;
value: { min: number; max: number };
disabled?: boolean;
};
export type FilterGroup = {
type: string;
value: { min: number };
filters: Filter[];
disabled: boolean;
};
export type Query = {
query: {
status: {
option: string;
};
stats: FilterGroup[];
};
sort: {
price: string;
};
};

export const filtersToFilterGroup = (filters: Filter[], disabled: boolean): FilterGroup => ({
type: 'count',
value: { min: 1 },
filters: filters,
disabled: disabled
});

export const filterGroupsToQuery = (FilterGroups: FilterGroup[]): Query => ({
query: {
status: {
option: 'online'
},
stats: FilterGroups
},
sort: {
price: 'asc'
}
});

export const openQueryTrade = (query: Query) => {
const url = new URL('https://www.pathofexile.com/trade/search/Necropolis');
url.searchParams.set('q', JSON.stringify(query));
window.open(url, '_blank');
};
19 changes: 19 additions & 0 deletions frontend/src/lib/utils/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/**
* Break an array into chunks of the given size
*
* e.g. for chunk size 2:
*
* [1, 2, 3, 4, 5] => [[1, 2], [3, 4], [5]]
*/
export const chunkArray = <T>(inputArray: Array<T>, chunkSize: number): Array<T>[] =>
inputArray.reduce((resultArray, item, index) => {
const chunkIndex = Math.floor(index / chunkSize);

if (!resultArray[chunkIndex]) {
resultArray[chunkIndex] = []; // start a new chunk
}

resultArray[chunkIndex].push(item);

return resultArray;
}, [] as Array<T>[]);
5 changes: 3 additions & 2 deletions frontend/src/routes/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -117,8 +117,9 @@
{#if result.AlternatePassiveSkill}
<div class="mt-4">
<h3>Alternate Passive Skill</h3>
<span
>{result.AlternatePassiveSkill.Name} ({result.AlternatePassiveSkill.ID}) ({result.AlternatePassiveSkill})</span>
<span>
{result.AlternatePassiveSkill.Name} ({result.AlternatePassiveSkill.ID})
</span>
</div>

{#if result.StatRolls && Object.keys(result.StatRolls).length > 0}
Expand Down
Loading

0 comments on commit 1a90b9f

Please sign in to comment.