Compare commits

..

2 Commits

Author SHA1 Message Date
4ed7c8062a update 2025-01-02 09:28:40 +09:00
323d9e3339 update 2025-01-02 03:28:00 +09:00
13 changed files with 729 additions and 160 deletions

BIN
bun.lockb

Binary file not shown.

View File

@ -11,7 +11,7 @@
)
</script>
<link rel="stylesheet" href="https://rsms.me/inter/inter.css">
<title>Vite App</title>
<title>Path of Exile 2 Loot Filter Config</title>
</head>
<body>

View File

@ -13,6 +13,7 @@
"format": "prettier --write src/"
},
"dependencies": {
"file-saver": "^2.0.5",
"fp-ts": "^2.16.9",
"pinia": "^2.3.0",
"primeicons": "^7.0.0",
@ -24,6 +25,7 @@
"devDependencies": {
"@tailwindcss/typography": "^0.5.15",
"@tsconfig/node22": "^22.0.0",
"@types/file-saver": "^2.0.7",
"@types/node": "^22.10.2",
"@vitejs/plugin-vue": "^5.2.1",
"@vue/eslint-config-prettier": "^10.1.0",

View File

@ -1,21 +1,22 @@
<script setup lang="ts">
import { Button, Splitter, SplitterPanel } from 'primevue'
import { Button, Dialog, ConfirmPopup, Toast, Splitter, SplitterPanel, ScrollPanel, useToast } from 'primevue'
import TreeNav from './components/TreeNav.vue'
import FilterDetail from './components/FilterDetail.vue'
import Info from './components/Info.vue'
import { computed, ref, watch } from 'vue'
import type { TreeNode } from 'primevue/treenode'
import { computed, ref, toRaw, watch } from 'vue'
import * as O from 'fp-ts/Option'
import * as A from 'fp-ts/lib/Array';
import { pipe } from 'fp-ts/function'
import * as uuid from 'uuid';
import type { Filter, FilterConfig, FilterNode } from './models'
import { filterToTreeNode } from './services/filter'
import FileSaver from 'file-saver'
import { generateFilterText, type Filter, type FilterConfig, type FilterNode } from './models'
import { filterToTreeNode, treeNodeToFilter } from './services/filter'
const darkMode = ref(document.documentElement.classList.contains('dark'))
const icon = computed(() => `pi pi-${darkMode.value ? "moon" : "sun"}`)
const toast = useToast()
watch(darkMode, (dark) => {
document.documentElement.classList.toggle(
'dark',
@ -24,19 +25,11 @@ watch(darkMode, (dark) => {
localStorage.theme = dark ? 'dark' : 'light'
})
let defaultFilters: Filter[] = [
{ type: 'leaf', id: uuid.v4(), name: "", show: true, enabled: true, rule: {} },
{
type: 'group', id: uuid.v4(), name: "", enabled: true, rule: {}, filters: [
{ type: 'leaf', id: uuid.v4(), name: "", show: true, enabled: true, rule: {} }
]
}
]
let defaultFilters: Filter[] = []
const nodes = ref(pipe(
localStorage.getItem("filters"),
O.fromNullable,
O.map((value): FilterConfig => JSON.parse(value)),
O.map(value => value.filters),
O.map((value): Filter[] => JSON.parse(value)),
O.getOrElse(() => defaultFilters),
A.map(filterToTreeNode)
))
@ -53,17 +46,61 @@ function onDelete() {
selectedFilter.value = undefined
}
const previewText = ref()
function exportFilter() {
let text = generateFilterText(toRaw(nodes.value).map(treeNodeToFilter))
console.log(text)
previewText.value = text
exportDialogVisible.value = true;
}
function download() {
var blob = new Blob([previewText.value], { type: "text/plain;charset=utf-8" });
FileSaver.saveAs(blob, "poe2.filter")
}
const exportDialogVisible = ref(false);
function save() {
if (nodes.value) {
const filters = toRaw(nodes.value).map(treeNodeToFilter)
localStorage.setItem("filters", JSON.stringify(filters))
}
}
setInterval(save, 5000)
</script>
<template>
<ConfirmPopup />
<Toast />
<div class="flex flex-row gap-2 items-center flex-grow-0">
<article class="prose dark:prose-invert p-4">
<h1>POE2 Loot Filter Config</h1>
<h1>Path of Exile 2 Loot Filter Config</h1>
</article><Button :icon @click="darkMode = !darkMode" severity="secondary" variant="outlined" rounded />
<Button icon="pi pi-save" label="Save" severity="secondary" variant="outlined" rounded
@click="save(); toast.add({ severity: 'success', summary: 'Success', detail: 'Content Saved', life: 3000 });" />
<Button icon="pi pi-file-export" label="Export" severity="primary" @click="exportFilter" />
<Dialog v-model:visible="exportDialogVisible" modal header="Preview Filter Text">
<template #default>
<ScrollPanel class="h-[50vh]">
<pre>{{ previewText }}</pre>
</ScrollPanel>
</template>
<template #footer>
<Button type="button" label="Cancel" severity="secondary" @click="exportDialogVisible = false"></Button>
<Button type="button" icon="pi pi-download" label="Download" @click="download"></Button>
</template>
</Dialog>
<!-- <Button icon = "pi pi-save" severity="secondary" variant="outlined" rounded /> -->
</div>
<Splitter class="flex-1 min-h-0">
<SplitterPanel class="flex flex-col">
<TreeNav :nodes @nodeSelect="selectedFilter = $event" @nodeUnselect="selectedFilter = undefined" />
<TreeNav :nodes :selectedNode="selectedFilter" @nodeSelect="selectedFilter = $event"
@nodeUnselect="selectedFilter = undefined" />
</SplitterPanel>
<SplitterPanel class="flex flex-col">
<FilterDetail v-if="selectedFilter" :node="selectedFilter" @delete="onDelete" />

View File

@ -1,7 +1,37 @@
<script setup lang="ts">
import type { Filter, FilterNode } from '@/models';
import { toLines, type Filter, type FilterNode, type FilterRule } from '@/models';
import { computed, ref, watchEffect } from 'vue'
import { Button, ColorPicker, ScrollPanel, Tabs, Tab, TabPanels, TabList, TabPanel, ToggleButton, InputText, InputNumber, Select, ToggleSwitch, MultiSelect } from 'primevue'
import { useConfirm } from "primevue/useconfirm";
import { useToast } from "primevue/usetoast";
const confirm = useConfirm();
const toast = useToast();
const confirmDelete = (event: any) => {
const message = props.node.data.type === 'group' ? 'Do you want to delete this group and all rules under it?' : 'Do you want to delete this rule?'
confirm.require({
target: event.currentTarget,
message,
icon: 'pi pi-exclamation-triangle',
rejectProps: {
label: 'Cancel',
severity: 'secondary',
outlined: true
},
acceptProps: {
label: 'Delete',
severity: 'danger',
},
accept: () => {
props.onDelete()
toast.add({ severity: 'success', summary: 'Success', detail: 'Content Deleted', life: 3000 });
},
reject: () => {
}
});
};
const props = defineProps<{
node: FilterNode,
onDelete: () => void
@ -56,19 +86,63 @@ const BASE_TYPES = [
'Orb of Transmutation', 'Scroll of Wisdom', 'Portal Scroll'
];
// const search_class = (event) => {
// setTimeout(() => {
// if (!event.query.trim().length) {
// filteredCountries.value = [...countries.value];
// } else {
// filteredCountries.value = countries.value.filter((country) => {
// return country.name.toLowerCase().startsWith(event.query.toLowerCase());
// });
// }
// }, 250);
// }
const filter = computed(() => props.node.data)
const ruleWithLookup = computed(() => {
return calLookup(props.node)
})
type Lookup<T> = {
[K in keyof T]-?: 'none' | 'value' | 'inherit'
}
type RuleLookup = Lookup<FilterRule>;
function calLookup(node: FilterNode): [FilterRule, RuleLookup] {
let childResult = createLookup(node.data.rule)
if (!node.parent) {
return childResult
}
let parentResult = calLookup(node.parent)
return mergeLookup(childResult, parentResult)
}
function createLookup(rule: FilterRule): [FilterRule, RuleLookup] {
return [rule, {
class: rule.class == undefined ? "none" : "value",
base_type: rule.base_type == undefined ? "none" : "value",
area_level: rule.area_level == undefined ? "none" : "value",
drop_level: rule.drop_level == undefined ? "none" : "value",
item_level: rule.item_level == undefined ? "none" : "value",
rarity: rule.rarity == undefined ? "none" : "value",
sockets: rule.sockets == undefined ? "none" : "value",
quality: rule.quality == undefined ? "none" : "value",
stack_size: rule.stack_size == undefined ? "none" : "value",
waystone_tier: rule.waystone_tier == undefined ? "none" : "value",
set_font_size: rule.set_font_size == undefined ? "none" : "value",
set_text_color: rule.set_text_color == undefined ? "none" : "value",
set_border_color: rule.set_border_color == undefined ? "none" : "value",
set_background_color: rule.set_background_color == undefined ? "none" : "value",
play_alert_sound: rule.play_alert_sound == undefined ? "none" : "value",
play_effect: rule.play_effect == undefined ? "none" : "value",
minimap_icon: rule.minimap_icon == undefined ? "none" : "value"
}]
}
function mergeLookup([childValue, childLookup]: any, [parentValue, parentLookup]: any): [FilterRule, RuleLookup] {
const value: any = {}
const lookup: any = {}
for (const key in parentLookup) {
if (parentLookup[key] != 'none') {
lookup[key] = 'inherit'
value[key] = parentValue[key]
} else {
lookup[key] = childLookup[key]
value[key] = childValue[key]
}
}
return [value, lookup]
}
</script>
<template>
@ -82,8 +156,8 @@ const filter = computed(() => props.node.data)
<ToggleButton class="w-24 flex-shrink-0" onIcon="pi pi-eye" offIcon="pi pi-eye-slash" v-if="filter.type === 'leaf'"
v-model="filter.show" onLabel="Show" offLabel="Hide" />
<InputText class="w-full" type="text" :placeholder="filter.id" v-model="filter.name" />
<Button class="flex-shrink-0" icon="pi pi-trash" severity="danger" v-on:click="() => onDelete()" />
<InputText class="w-full" type="text" :placeholder="toLines(filter.rule).join(' ')" v-model="filter.name" />
<Button class="flex-shrink-0" icon="pi pi-trash" severity="danger" v-on:click="confirmDelete" />
</div>
<Tabs value="0" class="flex-1 min-h-0">
@ -106,12 +180,19 @@ const filter = computed(() => props.node.data)
Class
</td>
<td class="pr-4">
<ToggleSwitch class="align-middle" :model-value="filter.rule.class != undefined"
<ToggleSwitch v-if="ruleWithLookup[1].class != 'inherit'" class="align-middle"
:model-value="filter.rule.class != undefined"
@update:model-value="filter.rule.class = $event ? [] : undefined" />
<label v-else>Inherit</label>
</td>
<td class="h-11">
<MultiSelect v-if="filter.rule.class" v-model="filter.rule.class" display="chip" :options="CLASSES"
filter placeholder="Select Classes" class="w-full" :maxSelectedLabels=3 />
<MultiSelect v-if="ruleWithLookup[1].class != 'inherit' && filter.rule.class"
v-model="filter.rule.class" display="chip" :options="CLASSES" filter placeholder="Select Classes"
class="w-full" :maxSelectedLabels=3 fluid />
<MultiSelect disabled v-if="ruleWithLookup[1].class == 'inherit'"
:model-value="ruleWithLookup[0].class" display="chip" :options="CLASSES" filter
placeholder="Select Classes" class="w-full" :maxSelectedLabels=3 fluid />
</td>
</tr>
<tr>
@ -119,12 +200,18 @@ const filter = computed(() => props.node.data)
BaseType
</td>
<td class="pr-4">
<ToggleSwitch class="align-middle" :model-value="filter.rule.base_type != undefined"
<ToggleSwitch v-if="ruleWithLookup[1].base_type != 'inherit'" class="align-middle"
:model-value="filter.rule.base_type != undefined"
@update:model-value="filter.rule.base_type = $event ? [] : undefined" />
<label v-else>Inherit</label>
</td>
<td class="h-11">
<MultiSelect v-if="filter.rule.base_type" v-model="filter.rule.base_type" display="chip"
:options="BASE_TYPES" filter placeholder="Select Base Types" class="w-full" :maxSelectedLabels=3 />
<MultiSelect v-if="ruleWithLookup[1].base_type != 'inherit' && filter.rule.base_type"
v-model="filter.rule.base_type" display="chip" :options="BASE_TYPES" filter
placeholder="Select Base Types" class="w-full" :maxSelectedLabels=3 fluid />
<MultiSelect disabled v-if="ruleWithLookup[1].base_type == 'inherit'"
:model-value="ruleWithLookup[0].base_type" display="chip" :options="BASE_TYPES" filter
placeholder="Select Base Types" class="w-full" :maxSelectedLabels=3 fluid />
</td>
</tr>
<tr>
@ -132,12 +219,20 @@ const filter = computed(() => props.node.data)
Rarity
</td>
<td class="pr-4">
<ToggleSwitch class="align-middle" :model-value="filter.rule.rarity != undefined"
<ToggleSwitch v-if="ruleWithLookup[1].rarity != 'inherit'" class="align-middle"
:model-value="filter.rule.rarity != undefined"
@update:model-value="filter.rule.rarity = $event ? ['=', 'Normal'] : undefined" />
<label v-else>Inherit</label>
</td>
<td class="h-11 flex gap-2">
<Select v-if="filter.rule.rarity" v-model="filter.rule.rarity[0]" :options="OPERATORS" />
<Select v-if="filter.rule.rarity" v-model="filter.rule.rarity[1]" :options="RARITIES" />
<Select v-if="ruleWithLookup[1].rarity != 'inherit' && filter.rule.rarity"
v-model="filter.rule.rarity[0]" :options="OPERATORS" />
<Select v-if="ruleWithLookup[1].rarity != 'inherit' && filter.rule.rarity"
v-model="filter.rule.rarity[1]" :options="RARITIES" />
<Select disabled v-if="ruleWithLookup[1].rarity == 'inherit'"
:model-value="ruleWithLookup[0].rarity![0]" :options="OPERATORS" />
<Select disabled v-if="ruleWithLookup[1].rarity == 'inherit'"
:model-value="ruleWithLookup[0].rarity![1]" :options="RARITIES" />
</td>
</tr>
<tr>
@ -145,12 +240,20 @@ const filter = computed(() => props.node.data)
Sockets
</td>
<td class="pr-4">
<ToggleSwitch class="align-middle" :model-value="filter.rule.sockets != undefined"
<ToggleSwitch v-if="ruleWithLookup[1].sockets != 'inherit'" class="align-middle"
:model-value="filter.rule.sockets != undefined"
@update:model-value="filter.rule.sockets = $event ? ['>', 0] : undefined" />
<label v-else>Inherit</label>
</td>
<td class="h-11 flex gap-2">
<Select v-if="filter.rule.sockets" v-model="filter.rule.sockets[0]" :options="OPERATORS" />
<InputNumber class="w-16" fluid v-if="filter.rule.sockets" v-model="filter.rule.sockets[1]" />
<Select v-if="ruleWithLookup[1].sockets != 'inherit' && filter.rule.sockets"
v-model="filter.rule.sockets[0]" :options="OPERATORS" />
<InputNumber class="w-16" fluid v-if="ruleWithLookup[1].sockets != 'inherit' && filter.rule.sockets"
v-model="filter.rule.sockets[1]" />
<Select disabled v-if="ruleWithLookup[1].sockets == 'inherit'"
:model-value="ruleWithLookup[0].sockets![0]" :options="OPERATORS" />
<InputNumber disabled class="w-16" fluid v-if="ruleWithLookup[1].sockets == 'inherit'"
:model-value="ruleWithLookup[0].sockets![1]" />
</td>
</tr>
<tr>
@ -158,12 +261,20 @@ const filter = computed(() => props.node.data)
Quality
</td>
<td class="pr-4">
<ToggleSwitch class="align-middle" :model-value="filter.rule.quality != undefined"
<ToggleSwitch v-if="ruleWithLookup[1].quality != 'inherit'" class="align-middle"
:model-value="filter.rule.quality != undefined"
@update:model-value="filter.rule.quality = $event ? ['>', 0] : undefined" />
<label v-else>Inherit</label>
</td>
<td class="h-11 flex gap-2">
<Select v-if="filter.rule.quality" v-model="filter.rule.quality[0]" :options="OPERATORS" />
<InputNumber class="w-16" fluid v-if="filter.rule.quality" v-model="filter.rule.quality[1]" />
<Select v-if="ruleWithLookup[1].quality != 'inherit' && filter.rule.quality"
v-model="filter.rule.quality[0]" :options="OPERATORS" />
<InputNumber class="w-16" fluid v-if="ruleWithLookup[1].quality != 'inherit' && filter.rule.quality"
v-model="filter.rule.quality[1]" />
<Select disabled v-if="ruleWithLookup[1].quality == 'inherit'"
:model-value="ruleWithLookup[0].quality![0]" :options="OPERATORS" />
<InputNumber disabled class="w-16" fluid v-if="ruleWithLookup[1].quality == 'inherit'"
:model-value="ruleWithLookup[0].quality![1]" />
</td>
</tr>
<tr>
@ -171,12 +282,21 @@ const filter = computed(() => props.node.data)
StackSize
</td>
<td class="pr-4">
<ToggleSwitch class="align-middle" :model-value="filter.rule.stack_size != undefined"
<ToggleSwitch v-if="ruleWithLookup[1].stack_size != 'inherit'" class="align-middle"
:model-value="filter.rule.stack_size != undefined"
@update:model-value="filter.rule.stack_size = $event ? ['>', 0] : undefined" />
<label v-else>Inherit</label>
</td>
<td class="h-11 flex gap-2">
<Select v-if="filter.rule.stack_size" v-model="filter.rule.stack_size[0]" :options="OPERATORS" />
<InputNumber class="w-16" fluid v-if="filter.rule.stack_size" v-model="filter.rule.stack_size[1]" />
<Select v-if="ruleWithLookup[1].stack_size != 'inherit' && filter.rule.stack_size"
v-model="filter.rule.stack_size[0]" :options="OPERATORS" />
<InputNumber class="w-16" fluid
v-if="ruleWithLookup[1].stack_size != 'inherit' && filter.rule.stack_size"
v-model="filter.rule.stack_size[1]" />
<Select disabled v-if="ruleWithLookup[1].stack_size == 'inherit'"
:model-value="ruleWithLookup[0].stack_size![0]" :options="OPERATORS" />
<InputNumber disabled class="w-16" fluid v-if="ruleWithLookup[1].stack_size == 'inherit'"
:model-value="ruleWithLookup[0].stack_size![1]" />
</td>
</tr>
<tr>
@ -184,12 +304,21 @@ const filter = computed(() => props.node.data)
AreaLevel
</td>
<td class="pr-4">
<ToggleSwitch class="align-middle" :model-value="filter.rule.area_level != undefined"
<ToggleSwitch v-if="ruleWithLookup[1].area_level != 'inherit'" class="align-middle"
:model-value="filter.rule.area_level != undefined"
@update:model-value="filter.rule.area_level = $event ? ['>', 0] : undefined" />
<label v-else>Inherit</label>
</td>
<td class="h-11 flex gap-2">
<Select v-if="filter.rule.area_level" v-model="filter.rule.area_level[0]" :options="OPERATORS" />
<InputNumber class="w-16" fluid v-if="filter.rule.area_level" v-model="filter.rule.area_level[1]" />
<Select v-if="ruleWithLookup[1].area_level != 'inherit' && filter.rule.area_level"
v-model="filter.rule.area_level[0]" :options="OPERATORS" />
<InputNumber class="w-16" fluid
v-if="ruleWithLookup[1].area_level != 'inherit' && filter.rule.area_level"
v-model="filter.rule.area_level[1]" />
<Select disabled v-if="ruleWithLookup[1].area_level == 'inherit'"
:model-value="ruleWithLookup[0].area_level![0]" :options="OPERATORS" />
<InputNumber disabled class="w-16" fluid v-if="ruleWithLookup[1].area_level == 'inherit'"
:model-value="ruleWithLookup[0].area_level![1]" />
</td>
</tr>
<tr>
@ -197,12 +326,21 @@ const filter = computed(() => props.node.data)
DropLevel
</td>
<td class="pr-4">
<ToggleSwitch class="align-middle" :model-value="filter.rule.drop_level != undefined"
<ToggleSwitch v-if="ruleWithLookup[1].drop_level != 'inherit'" class="align-middle"
:model-value="filter.rule.drop_level != undefined"
@update:model-value="filter.rule.drop_level = $event ? ['>', 0] : undefined" />
<label v-else>Inherit</label>
</td>
<td class="h-11 flex gap-2">
<Select v-if="filter.rule.drop_level" v-model="filter.rule.drop_level[0]" :options="OPERATORS" />
<InputNumber class="w-16" fluid v-if="filter.rule.drop_level" v-model="filter.rule.drop_level[1]" />
<Select v-if="ruleWithLookup[1].drop_level != 'inherit' && filter.rule.drop_level"
v-model="filter.rule.drop_level[0]" :options="OPERATORS" />
<InputNumber class="w-16" fluid
v-if="ruleWithLookup[1].drop_level != 'inherit' && filter.rule.drop_level"
v-model="filter.rule.drop_level[1]" />
<Select disabled v-if="ruleWithLookup[1].drop_level == 'inherit'"
:model-value="ruleWithLookup[0].drop_level![0]" :options="OPERATORS" />
<InputNumber disabled class="w-16" fluid v-if="ruleWithLookup[1].drop_level == 'inherit'"
:model-value="ruleWithLookup[0].drop_level![1]" />
</td>
</tr>
<tr>
@ -210,12 +348,21 @@ const filter = computed(() => props.node.data)
ItemLevel
</td>
<td class="pr-4">
<ToggleSwitch class="align-middle" :model-value="filter.rule.item_level != undefined"
<ToggleSwitch v-if="ruleWithLookup[1].item_level != 'inherit'" class="align-middle"
:model-value="filter.rule.item_level != undefined"
@update:model-value="filter.rule.item_level = $event ? ['>', 0] : undefined" />
<label v-else>Inherit</label>
</td>
<td class="h-11 flex gap-2">
<Select v-if="filter.rule.item_level" v-model="filter.rule.item_level[0]" :options="OPERATORS" />
<InputNumber class="w-16" fluid v-if="filter.rule.item_level" v-model="filter.rule.item_level[1]" />
<Select v-if="ruleWithLookup[1].item_level != 'inherit' && filter.rule.item_level"
v-model="filter.rule.item_level[0]" :options="OPERATORS" />
<InputNumber class="w-16" fluid
v-if="ruleWithLookup[1].item_level != 'inherit' && filter.rule.item_level"
v-model="filter.rule.item_level[1]" />
<Select disabled v-if="ruleWithLookup[1].item_level == 'inherit'"
:model-value="ruleWithLookup[0].item_level![0]" :options="OPERATORS" />
<InputNumber disabled class="w-16" fluid v-if="ruleWithLookup[1].item_level == 'inherit'"
:model-value="ruleWithLookup[0].item_level![1]" />
</td>
</tr>
<tr>
@ -223,14 +370,21 @@ const filter = computed(() => props.node.data)
WaystoneTier
</td>
<td class="pr-4">
<ToggleSwitch class="align-middle" :model-value="filter.rule.waystone_tier != undefined"
<ToggleSwitch v-if="ruleWithLookup[1].waystone_tier != 'inherit'" class="align-middle"
:model-value="filter.rule.waystone_tier != undefined"
@update:model-value="filter.rule.waystone_tier = $event ? ['>', 0] : undefined" />
<label v-else>Inherit</label>
</td>
<td class="h-11 flex gap-2">
<Select v-if="filter.rule.waystone_tier" v-model="filter.rule.waystone_tier[0]"
:options="OPERATORS" />
<InputNumber class="w-16" fluid v-if="filter.rule.waystone_tier"
<Select v-if="ruleWithLookup[1].waystone_tier != 'inherit' && filter.rule.waystone_tier"
v-model="filter.rule.waystone_tier[0]" :options="OPERATORS" />
<InputNumber class="w-16" fluid
v-if="ruleWithLookup[1].waystone_tier != 'inherit' && filter.rule.waystone_tier"
v-model="filter.rule.waystone_tier[1]" />
<Select disabled v-if="ruleWithLookup[1].waystone_tier == 'inherit'"
:model-value="ruleWithLookup[0].waystone_tier![0]" :options="OPERATORS" />
<InputNumber disabled class="w-16" fluid v-if="ruleWithLookup[1].waystone_tier == 'inherit'"
:model-value="ruleWithLookup[0].waystone_tier![1]" />
</td>
</tr>
</tbody>
@ -250,12 +404,18 @@ const filter = computed(() => props.node.data)
SetFontSize
</td>
<td class="pr-4">
<ToggleSwitch class="align-middle" :model-value="filter.rule.set_font_size != undefined"
<ToggleSwitch v-if="ruleWithLookup[1].set_font_size != 'inherit'" class="align-middle"
:model-value="filter.rule.set_font_size != undefined"
@update:model-value="filter.rule.set_font_size = $event ? 40 : undefined" />
<label v-else>Inherit</label>
</td>
<td class="h-11">
<InputNumber class="w-16" fluid v-if="filter.rule.set_font_size"
<InputNumber class="w-16" fluid
v-if="ruleWithLookup[1].set_font_size != 'inherit' && filter.rule.set_font_size"
v-model="filter.rule.set_font_size" />
<InputNumber class="w-16" fluid disabled v-if="ruleWithLookup[1].set_font_size == 'inherit'"
:model-value="ruleWithLookup[0].set_font_size" />
</td>
</tr>
<tr>
@ -263,12 +423,18 @@ const filter = computed(() => props.node.data)
SetTextColor
</td>
<td class="pr-4">
<ToggleSwitch class="align-middle" :model-value="filter.rule.set_text_color != undefined"
<ToggleSwitch v-if="ruleWithLookup[1].set_text_color != 'inherit'" class="align-middle"
:model-value="filter.rule.set_text_color != undefined"
@update:model-value="filter.rule.set_text_color = $event ? { r: 100, g: 102, b: 241 } : undefined" />
<label v-else>Inherit</label>
</td>
<td class="h-11 flex gap-2">
<ColorPicker class="flex items-center" format="rgb" v-if="filter.rule.set_text_color"
<ColorPicker class="flex items-center" format="rgb"
v-if="ruleWithLookup[1].set_text_color != 'inherit' && filter.rule.set_text_color"
v-model="filter.rule.set_text_color" />
<ColorPicker class="flex items-center" format="rgb" disabled
v-if="ruleWithLookup[1].set_text_color == 'inherit'"
:model-value="ruleWithLookup[0].set_text_color" />
</td>
</tr>
<tr>
@ -276,12 +442,18 @@ const filter = computed(() => props.node.data)
SetBackgroundColor
</td>
<td class="pr-4">
<ToggleSwitch class="align-middle" :model-value="filter.rule.set_background_color != undefined"
<ToggleSwitch v-if="ruleWithLookup[1].set_background_color != 'inherit'" class="align-middle"
:model-value="filter.rule.set_background_color != undefined"
@update:model-value="filter.rule.set_background_color = $event ? { r: 100, g: 102, b: 241 } : undefined" />
<label v-else>Inherit</label>
</td>
<td class="h-11 flex gap-2">
<ColorPicker class="flex items-center" format="rgb" v-if="filter.rule.set_background_color"
<ColorPicker class="flex items-center" format="rgb"
v-if="ruleWithLookup[1].set_background_color != 'inherit' && filter.rule.set_background_color"
v-model="filter.rule.set_background_color" />
<ColorPicker class="flex items-center" format="rgb" disabled
v-if="ruleWithLookup[1].set_background_color == 'inherit'"
:model-value="ruleWithLookup[0].set_background_color" />
</td>
</tr>
<tr>
@ -289,12 +461,18 @@ const filter = computed(() => props.node.data)
SetBorderColor
</td>
<td class="pr-4">
<ToggleSwitch class="align-middle" :model-value="filter.rule.set_border_color != undefined"
<ToggleSwitch v-if="ruleWithLookup[1].set_border_color != 'inherit'" class="align-middle"
:model-value="filter.rule.set_border_color != undefined"
@update:model-value="filter.rule.set_border_color = $event ? { r: 100, g: 102, b: 241 } : undefined" />
<label v-else>Inherit</label>
</td>
<td class="h-11 flex gap-2">
<ColorPicker class="flex items-center" format="rgb" v-if="filter.rule.set_border_color"
<ColorPicker class="flex items-center" format="rgb"
v-if="ruleWithLookup[1].set_border_color != 'inherit' && filter.rule.set_border_color"
v-model="filter.rule.set_border_color" />
<ColorPicker class="flex items-center" format="rgb" disabled
v-if="ruleWithLookup[1].set_border_color == 'inherit'"
:model-value="ruleWithLookup[0].set_border_color" />
</td>
</tr>
<tr>
@ -302,14 +480,22 @@ const filter = computed(() => props.node.data)
PlayAlertSound
</td>
<td class="pr-4">
<ToggleSwitch class="align-middle" :model-value="filter.rule.play_alert_sound != undefined"
<ToggleSwitch v-if="ruleWithLookup[1].play_alert_sound != 'inherit'" class="align-middle"
:model-value="filter.rule.play_alert_sound != undefined"
@update:model-value="filter.rule.play_alert_sound = $event ? [2, 300] : undefined" />
<label v-else>Inherit</label>
</td>
<td class="h-11 flex gap-2">
<InputNumber class="w-16" fluid v-if="filter.rule.play_alert_sound"
<InputNumber class="w-16" fluid
v-if="ruleWithLookup[1].play_alert_sound != 'inherit' && filter.rule.play_alert_sound"
v-model="filter.rule.play_alert_sound[0]" />
<InputNumber class="w-16" fluid v-if="filter.rule.play_alert_sound"
<InputNumber class="w-16" fluid
v-if="ruleWithLookup[1].play_alert_sound != 'inherit' && filter.rule.play_alert_sound"
v-model="filter.rule.play_alert_sound[1]" />
<InputNumber class="w-16" fluid disabled v-if="ruleWithLookup[1].play_alert_sound == 'inherit'"
:model-value="ruleWithLookup[0].play_alert_sound![0]" />
<InputNumber class="w-16" fluid disabled v-if="ruleWithLookup[1].play_alert_sound == 'inherit'"
:model-value="ruleWithLookup[0].play_alert_sound![1]" />
</td>
</tr>
<tr>
@ -317,14 +503,25 @@ const filter = computed(() => props.node.data)
MinimapIcon
</td>
<td class="pr-4">
<ToggleSwitch class="align-middle" :model-value="filter.rule.minimap_icon != undefined"
<ToggleSwitch v-if="ruleWithLookup[1].minimap_icon != 'inherit'" class="align-middle"
:model-value="filter.rule.minimap_icon != undefined"
@update:model-value="filter.rule.minimap_icon = $event ? [2, 'White', 'Circle'] : undefined" />
<label v-else>Inherit</label>
</td>
<td class="h-11 flex gap-2">
<InputNumber class="w-16" fluid v-if="filter.rule.minimap_icon"
<InputNumber class="w-16" fluid
v-if="ruleWithLookup[1].minimap_icon != 'inherit' && filter.rule.minimap_icon"
v-model="filter.rule.minimap_icon[0]" />
<Select v-if="filter.rule.minimap_icon" v-model="filter.rule.minimap_icon[1]" :options="COLORS" />
<Select v-if="filter.rule.minimap_icon" v-model="filter.rule.minimap_icon[2]" :options="SHAPES" />
<Select v-if="ruleWithLookup[1].minimap_icon != 'inherit' && filter.rule.minimap_icon"
v-model="filter.rule.minimap_icon[1]" :options="COLORS" />
<Select v-if="ruleWithLookup[1].minimap_icon != 'inherit' && filter.rule.minimap_icon"
v-model="filter.rule.minimap_icon[2]" :options="SHAPES" />
<InputNumber class="w-16" fluid v-if="ruleWithLookup[1].minimap_icon == 'inherit'" disabled
:model-value="ruleWithLookup[0].minimap_icon![0]" />
<Select v-if="ruleWithLookup[1].minimap_icon == 'inherit'" disabled
:model-value="ruleWithLookup[0].minimap_icon![1]" :options="COLORS" />
<Select v-if="ruleWithLookup[1].minimap_icon == 'inherit'" disabled
:model-value="ruleWithLookup[0].minimap_icon![2]" :options="SHAPES" />
</td>
</tr>
<tr>
@ -332,11 +529,16 @@ const filter = computed(() => props.node.data)
PlayEffect
</td>
<td class="pr-4">
<ToggleSwitch class="align-middle" :model-value="filter.rule.play_effect != undefined"
<ToggleSwitch v-if="ruleWithLookup[1].play_effect != 'inherit'" class="align-middle"
:model-value="filter.rule.play_effect != undefined"
@update:model-value="filter.rule.play_effect = $event ? 'White' : undefined" />
<label v-else>Inherit</label>
</td>
<td class="h-11 flex gap-2">
<Select v-if="filter.rule.play_effect" v-model="filter.rule.play_effect" :options="COLORS" />
<Select v-if="ruleWithLookup[1].play_effect != 'inherit' && filter.rule.play_effect"
v-model="filter.rule.play_effect" :options="COLORS" />
<Select v-if="ruleWithLookup[1].play_effect == 'inherit'" disabled
:model-value="ruleWithLookup[0].play_effect" :options="COLORS" />
</td>
</tr>
</tbody>

View File

@ -1,12 +1,13 @@
<script setup lang="ts">
import { defaultGroup, defaultLeaf, type FilterNode } from '@/models';
import { defaultGroup, defaultLeaf, toLines, type FilterNode } from '@/models';
import { filterToTreeNode } from '@/services/filter';
import { Button, Menu } from 'primevue';
import { Button, Menu, Chip } from 'primevue';
import { ref } from 'vue'
const props = defineProps<{
node: FilterNode
}>()
const emit = defineEmits(['nodeSelect'])
const menu = ref();
const items = ref([
{
@ -17,6 +18,7 @@ const items = ref([
icon: 'pi pi-plus-circle',
command: () => {
props.node.children?.push(filterToTreeNode(defaultLeaf(), props.node) as FilterNode);
emit("nodeSelect", props.node.children![props.node.children!.length - 1], props.node)
}
},
{
@ -24,6 +26,7 @@ const items = ref([
icon: 'pi pi-folder-plus',
command: () => {
props.node.children?.push(filterToTreeNode(defaultGroup(), props.node) as FilterNode);
emit("nodeSelect", props.node.children![props.node.children!.length - 1], props.node)
}
}
]
@ -37,11 +40,18 @@ const toggle = (event: MouseEvent) => {
</script>
<template>
<div class="flex items-center w-full">
<label class="flex-1">{{ node.data.name || node.data.id }}</label>
<div v-if="node.data.type === 'group'">
<Button class="flex-shrink-0" icon="pi pi-plus" rounded variant="text" size="small" @click="toggle" />
<Menu ref="menu" :model="items" :popup="true" />
<div class="flex items-center w-full flex-1">
<div class="flex-1 flex truncate">
<Chip v-if="!node.data.name" class="text-xs py-1.5 px-2" v-for="line in toLines(node.data.rule)">
<span class="truncate max-w-40">{{ line }}</span>
</Chip>
<label v-else>{{ node.data.name }}</label>
</div>
<div class="flex-shrink-0 size-7 ml-4">
<div v-if="node.data.type === 'group'">
<Button class=" flex-shrink-0 !size-7" icon="pi pi-plus" rounded variant="text" size="small" @click="toggle" />
<Menu ref="menu" :model="items" :popup="true" />
</div>
</div>
</div>
</template>

45
src/components/Info.md Normal file
View File

@ -0,0 +1,45 @@
## How to use
```
To show this page: deselect the item on the left tree(by clicking it).
```
- Create POE loot filters with GUI.
- Generate the final loot filter files.
- Import them into the game.
You might want to use these references:
- [Official documentation on item filters](https://www.pathofexile.com/item-filter/about)
- [NeverSink PoE2litefilter](https://github.com/NeverSinkDev/NeverSink-PoE2litefilter)
### Features
- The tool is aware of available options to help you edit and prevent mistakes.
- Rules can be grouped into hierarchies, settings on groups will be forced on all descendants.
- The bottom rule has the highest priority. This is the opposite of the official POE file format, where the top rule has the highest priority.
- This is more intuitive, you can organize the rules, for example:
- Hide all normal weapons/armours.
- Show specific ones I'm interested in.
- It generates the official filter texts by reversing the configuration settings.
### Add new rules or groups
- Click the top + sign to add rules or groups.
- Click the + sign on a group to add into the group.
- **Group**: a group that can have partial settings of a rule.
- **End Rule**: a rule that has no descendants. Only this type has a Show or Hide setting.
### Reorder items
- Select an item in the tree view, then click the arrows to reorder items within its direct group.
### Editing
- Select an item on the tree, edit the details on the right panel.
- A default display name is generated, you can manually set the name in the top text input.
- Deleting a group will delete it **and** all the descendants.
- Rules that are turned off will not be included when generating the filter file.
- Turning off a group will exclude it **and** all the descendants.

View File

@ -1,10 +1,67 @@
<script setup lang="ts">
import { ref } from 'vue'
import { ScrollPanel } from 'primevue';
</script>
<template>
<article class="prose dark:prose-invert">
<h2>How to use:</h2>
</article>
<ScrollPanel class="flex-1 min-h-0">
<article class="prose dark:prose-invert px-5 pt-4">
<h2 id="how-to-use">How to use</h2>
<pre><code>To show this page: deselect <span class="hljs-keyword">the</span> <span class="hljs-built_in">item</span> <span class="hljs-keyword">on</span> <span class="hljs-keyword">the</span> left tree(<span class="hljs-keyword">by</span> clicking <span class="hljs-keyword">it</span>).
</code></pre>
<ul>
<li>Create POE loot filters with GUI.</li>
<li>Generate the final loot filter files.</li>
<li>Import them into the game.</li>
</ul>
<p>You might want to use these references:</p>
<ul>
<li><a href="https://www.pathofexile.com/item-filter/about">Official documentation on item filters</a></li>
<li><a href="https://github.com/NeverSinkDev/NeverSink-PoE2litefilter">NeverSink PoE2litefilter</a></li>
</ul>
<h3 id="features">Features</h3>
<ul>
<li>The tool is aware of available options to help you edit and prevent mistakes.</li>
<li>Rules can be grouped into hierarchies, settings on groups will be forced on all descendants.</li>
<li>The bottom rule has the highest priority. This is the opposite of the official POE file format, where the
top rule has the highest priority.<ul>
<li>This is more intuitive, you can organize the rules, for example:<ul>
<li>Hide all normal weapons/armours.</li>
<li>Show specific ones I&#39;m interested in.</li>
</ul>
</li>
<li>It generates the official filter texts by reversing the configuration settings.</li>
</ul>
</li>
</ul>
<h3 id="add-new-rules-or-groups">Add new rules or groups</h3>
<ul>
<li>
<p>Click the top + sign to add rules or groups.</p>
<ul>
<li>Click the + sign on a group to add into the group.</li>
</ul>
</li>
<li>
<p><strong>Group</strong>: a group that can have partial settings of a rule.</p>
</li>
<li><strong>End Rule</strong>: a rule that has no descendants. Only this type has a Show or Hide setting.</li>
</ul>
<h3 id="reorder-items">Reorder items</h3>
<ul>
<li>Select an item in the tree view, then click the arrows to reorder items within its direct group.</li>
</ul>
<h3 id="editing">Editing</h3>
<ul>
<li>Select an item on the tree, edit the details on the right panel.</li>
<li>A default display name is generated, you can manually set the name in the top text input.</li>
<li>Deleting a group will delete it <strong>and</strong> all the descendants.</li>
<li>Rules that are turned off will not be included when generating the filter file.<ul>
<li>Turning off a group will exclude it <strong>and</strong> all the descendants.</li>
</ul>
</li>
</ul>
</article>
</ScrollPanel>
</template>

View File

@ -1,14 +1,15 @@
<script setup lang="ts">
import { onUpdated, ref } from 'vue'
import { computed, onUpdated, ref, watch } from 'vue'
import { defaultGroup, defaultLeaf, type Filter, type FilterNode as FilterNodeType } from '../models';
import FilterNode from './FilterNode.vue';
import { Tree, Button, Menu, ScrollPanel } from 'primevue'
import { Tree, Button, ButtonGroup, Menu, ScrollPanel } from 'primevue'
import { match } from 'ts-pattern';
import FilterNode from './FilterNode.vue';
import type { TreeNode } from 'primevue/treenode';
import { filterToTreeNode } from '@/services/filter';
const props = defineProps<{
nodes: TreeNode[]
nodes: FilterNodeType[]
selectedNode?: FilterNodeType
}>()
const emit = defineEmits(['nodeSelect', 'nodeUnselect'])
const selectedKey = ref()
@ -35,6 +36,9 @@ const items = ref([
icon: 'pi pi-plus-circle',
command: () => {
props.nodes.push(filterToTreeNode(defaultLeaf()) as FilterNodeType);
let item = props.nodes[props.nodes.length - 1]
selectedKey.value = { [item.data.id]: true }
emit("nodeSelect", item)
}
},
{
@ -42,6 +46,9 @@ const items = ref([
icon: 'pi pi-folder-plus',
command: () => {
props.nodes.push(filterToTreeNode(defaultGroup()) as FilterNodeType);
let item = props.nodes[props.nodes.length - 1]
selectedKey.value = { [item.data.id]: true }
emit("nodeSelect", item)
}
}
]
@ -53,24 +60,119 @@ const toggle = (event: MouseEvent) => {
menu.value.toggle(event);
};
function moveToTop() {
if (!props.selectedNode || !selectedPosition.value) {
return
}
let index = selectedPosition.value[0]
let list: FilterNodeType[];
if (props.selectedNode.parent) {
list = props.selectedNode.parent.children!
} else {
list = props.nodes
}
list.splice(index, 1)
list.unshift(props.selectedNode)
}
function moveUp() {
if (!props.selectedNode || !selectedPosition.value || selectedPosition.value[0] <= 0) {
return
}
let index = selectedPosition.value[0]
let list: FilterNodeType[];
if (props.selectedNode.parent) {
list = props.selectedNode.parent.children!
} else {
list = props.nodes
}
list[index] = list[index - 1]
list[index - 1] = props.selectedNode
}
function moveDown() {
if (!props.selectedNode || !selectedPosition.value || selectedPosition.value[0] >= selectedPosition.value[1] - 1) {
return
}
let index = selectedPosition.value[0]
let list: FilterNodeType[];
if (props.selectedNode.parent) {
list = props.selectedNode.parent.children!
} else {
list = props.nodes
}
list[index] = list[index + 1]
list[index + 1] = props.selectedNode
}
function moveToBottom() {
if (!props.selectedNode || !selectedPosition.value || selectedPosition.value[0] >= selectedPosition.value[1] - 1) {
return
}
let index = selectedPosition.value[0]
let list: FilterNodeType[];
if (props.selectedNode.parent) {
list = props.selectedNode.parent.children!
} else {
list = props.nodes
}
list.splice(index, 1)
list.push(props.selectedNode)
}
const selectedPosition = computed(() => {
if (!props.selectedNode) {
return undefined
} else {
if (props.selectedNode.parent) {
return [props.selectedNode.parent.children!.findIndex(item => item === props.selectedNode), props.selectedNode.parent.children!.length]
} else {
return [props.nodes.findIndex(item => item === props.selectedNode), props.nodes.length];
}
}
})
function onNodeSelect(node: TreeNode, parent: TreeNode) {
selectedKey.value = { [node.data.id]: true }
expandedKeys.value = expandedKeys.value || {};
expandedKeys.value[parent.data.id] = true
emit('nodeSelect', node)
}
const expandedKeys
= ref()
</script>
<template>
<div class="flex flex-shrink-0 mr-6 ml-4 items-center">
<div class="flex flex-shrink-0 p-4 items-center">
<article class="prose dark:prose-invert flex-1">
<h2>List of Rules:</h2>
<h2>Rules:</h2>
</article>
<Button class="flex-shrink-0" icon="pi pi-plus" rounded variant="text" size="small" @click="toggle" />
<Menu ref="menu" :model="items" :popup="true" />
<ButtonGroup>
<Button class="flex-shrink-0" :disabled="selectedPosition == undefined || selectedPosition[0] <= 0"
v-tooltip.top="'Move to top'" severity="secondary" icon="pi pi-angle-double-up" size="small"
@click="moveToTop" />
<Button class="flex-shrink-0" :disabled="selectedPosition == undefined || selectedPosition[0] <= 0"
v-tooltip.top="'Move up'" severity="secondary" icon="pi pi-angle-up" size="small" @click="moveUp" />
<Button class="flex-shrink-0"
:disabled="selectedPosition == undefined || selectedPosition[0] >= selectedPosition[1] - 1"
v-tooltip.top="'Move down'" severity="secondary" icon="pi pi-angle-down" size="small" @click="moveDown" />
<Button class="flex-shrink-0"
:disabled="selectedPosition == undefined || selectedPosition[0] >= selectedPosition[1] - 1"
v-tooltip.top="'Move to bottom'" severity="secondary" icon="pi pi-angle-double-down" size="small"
@click="moveToBottom" />
<Button class="flex-shrink-0" icon="pi pi-plus" size="small" @click="toggle" />
<Menu ref="menu" :model="items" :popup="true" />
</ButtonGroup>
</div>
<ScrollPanel class="flex-1 min-h-0">
<Tree class="flex-1 pt-0" :value="nodes" v-model:selectionKeys="selectedKey" selectionMode="single"
@node-select="emit('nodeSelect', $event)" @node-unselect="emit('nodeUnselect', $event)">
<Tree v-model:expandedKeys="expandedKeys" class="flex-1 pt-0" :value="nodes" v-model:selectionKeys="selectedKey"
selectionMode="single" @node-select="
emit('nodeSelect', $event)" @node-unselect="emit('nodeUnselect', $event)">
<template #nodeicon="{ node }">
<span :class="nodeIcon(node.data)"></span>
</template>
<template #default="{ node }">
<FilterNode :node="asFilterNode(node)" />
<FilterNode :node="asFilterNode(node)" @node-select="onNodeSelect" />
</template>
</Tree>
</ScrollPanel>

View File

@ -7,11 +7,17 @@ import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'
import Tooltip from 'primevue/tooltip';
import ConfirmationService from 'primevue/confirmationservice';
import ToastService from 'primevue/toastservice';
const app = createApp(App)
app.use(createPinia())
app.use(PrimeVue, {
theme: 'none'
})
app.directive('tooltip', Tooltip)
app.use(ConfirmationService)
app.use(ToastService)
app.mount('#app')

View File

@ -1,31 +1,32 @@
import * as uuid from 'uuid';
import { show } from 'fp-ts'
import * as uuid from 'uuid'
interface _Filter {
id: string;
name: string;
enabled: boolean;
rule: FilterRule;
id: string
name: string
enabled: boolean
rule: FilterRule
}
export function defaultLeaf(): FilterLeaf {
return {
type: "leaf",
type: 'leaf',
id: uuid.v4(),
name: "",
name: '',
enabled: true,
rule: {},
show: true
show: true,
}
}
export function defaultGroup(): FilterGroup {
return {
type: "group",
type: 'group',
id: uuid.v4(),
name: "",
name: '',
enabled: true,
rule: {},
filters: []
filters: [],
}
}
@ -34,59 +35,59 @@ export interface FilterConfig {
}
export type FilterLeaf = _Filter & {
type: 'leaf';
show: boolean;
type: 'leaf'
show: boolean
}
export type FilterGroup = _Filter & {
type: 'group';
filters: Filter[]
type: 'group'
filters: Filter[]
}
export type Filter = FilterLeaf | FilterGroup;
export type Filter = FilterLeaf | FilterGroup
export type ItemClass = any; // Replace with actual type definition
export type ItemBaseType = any; // Replace with actual type definition
export type Op = any; // Replace with actual type definition
export type Level = RangedNumber<1, 100>; // Replace with actual type definition
export type ItemRarity = string; // Replace with actual type definition
export type GameColor = any; // Replace with actual type definition
export type MinimapIconShape = any; // Replace with actual type definition
export type ItemClass = any // Replace with actual type definition
export type ItemBaseType = any // Replace with actual type definition
export type Op = any // Replace with actual type definition
export type Level = RangedNumber<1, 100> // Replace with actual type definition
export type ItemRarity = string // Replace with actual type definition
export type GameColor = any // Replace with actual type definition
export type MinimapIconShape = any // Replace with actual type definition
export interface RangedNumber<T extends number, U extends number> {
value: number;
min: T;
max: U;
value: number
min: T
max: U
}
export interface Color {
r: number,
g: number,
r: number
g: number
b: number
}
export interface FilterRule {
class?: string[]; //
base_type?: string[]; //
area_level?: [string, number]; //
drop_level?: [string, number]; //
item_level?: [string, number]; //
rarity?: [string, string]; //
sockets?: [string, number]; //
quality?: [string, number]; //
stack_size?: [string, number]; //
class?: string[] //
base_type?: string[] //
area_level?: [string, number] //
drop_level?: [string, number] //
item_level?: [string, number] //
rarity?: [string, string] //
sockets?: [string, number] //
quality?: [string, number] //
stack_size?: [string, number] //
// waystones
waystone_tier?: [string, number]; //
// waystones
waystone_tier?: [string, number] //
// effects
set_font_size?: number; //
set_text_color?: Color;//
set_border_color?: Color;//
set_background_color?: Color;//
play_alert_sound?: [number, number];//
play_effect?: string;//
minimap_icon?: [number, string, string]; //
// effects
set_font_size?: number //
set_text_color?: Color //
set_border_color?: Color //
set_background_color?: Color //
play_alert_sound?: [number, number] //
play_effect?: string //
minimap_icon?: [number, string, string] //
}
export interface FilterNode {
@ -95,3 +96,105 @@ export interface FilterNode {
data: Filter
children?: FilterNode[]
}
export function toLines(rule: FilterRule): string[] {
let r: string[] = []
if (rule.class && rule.class.length > 0) {
r.push(`Class ${rule.class.map((c) => `"${c}"`).join(' ')}`)
}
if (rule.base_type && rule.base_type.length > 0) {
r.push(`BaseType ${rule.base_type.map((c) => `"${c}"`).join(' ')}`)
}
if (rule.rarity) {
r.push(`Rarity ${rule.rarity[0]} ${rule.rarity[1]}`)
}
if (rule.sockets) {
r.push(`Sockets ${rule.sockets[0]} ${rule.sockets[1]}`)
}
if (rule.quality) {
r.push(`Quality ${rule.quality[0]} ${rule.quality[1]}`)
}
if (rule.stack_size) {
r.push(`StackSize ${rule.stack_size[0]} ${rule.stack_size[1]}`)
}
if (rule.area_level) {
r.push(`AreaLevel ${rule.area_level[0]} ${rule.area_level[1]}`)
}
if (rule.drop_level) {
r.push(`DropLevel ${rule.drop_level[0]} ${rule.drop_level[1]}`)
}
if (rule.item_level) {
r.push(`ItemLevel ${rule.item_level[0]} ${rule.item_level[1]}`)
}
if (rule.waystone_tier) {
r.push(`WaystoneTier ${rule.waystone_tier[0]} ${rule.waystone_tier[1]}`)
}
if (rule.set_font_size) {
r.push(`SetFontSize ${rule.set_font_size}`)
}
if (rule.set_text_color) {
r.push(`SetTextColor ${formatColor(rule.set_text_color)}`)
}
if (rule.set_background_color) {
r.push(`SetBackgroundColor ${formatColor(rule.set_background_color)}`)
}
if (rule.set_border_color) {
r.push(`SetBorderColor ${formatColor(rule.set_border_color)}`)
}
if (rule.play_alert_sound) {
r.push(`PlayAlertSound ${rule.play_alert_sound[0]} ${rule.play_alert_sound[1]}`)
}
if (rule.minimap_icon) {
r.push(`MinimapIcon ${rule.minimap_icon[0]} ${rule.minimap_icon[1]} ${rule.minimap_icon[2]}`)
}
if (rule.play_effect) {
r.push(`PlayEffect ${rule.play_effect}`)
}
return r
}
function formatColor(color: Color) {
return `${color.r} ${color.g} ${color.b}`
}
export function generateFilterText(filters: Filter[]): string {
let flat = flatten(filters)
return flat
.map((f) => {
let lines = toLines(f.rule)
if (lines.length < 1) {
return undefined
}
lines.unshift(f.show ? 'Show' : 'Hide')
return lines.join('\n')
})
.filter((s) => s != undefined)
.reverse()
.join('\n\n')
}
function flatten(filters: Filter[]): Omit<FilterLeaf, 'id' | 'name' | 'type' | 'enabled'>[] {
return filters.flatMap((f) => {
if (!f.enabled) {
return []
}
if (f.type === 'group') {
let children = flatten(f.filters)
return children.map((c) => {
let rule = c.rule as any
Object.entries(f.rule).forEach(([k, v]) => {
if (v != undefined) {
rule[k] = v
}
})
return {
show: c.show,
rule,
}
})
} else {
return [{ show: f.show, rule: f.rule }]
}
})
}

View File

@ -1,19 +1,23 @@
import type { Filter } from '@/models'
import type { Filter, FilterNode } from '@/models'
import type { TreeNode } from 'primevue/treenode'
export function filterToTreeNode(filter: Filter, parent?: TreeNode): TreeNode {
let node: TreeNode = {
export function filterToTreeNode(filter: Filter, parent?: FilterNode): FilterNode {
let node = {
key: filter.id,
data: filter,
parent
}
if (filter.type === 'group' ) {
node.children = filter.filters.map(f => filterToTreeNode(f, node))
parent,
} as FilterNode
if (filter.type === 'group') {
node.children = filter.filters.map((f) => filterToTreeNode(f, node))
}
return node
}
export function treeNodeToFilter(node: TreeNode): Filter {
return node.data;
export function treeNodeToFilter(node: FilterNode): Filter {
let filter = node.data
if (filter.type === 'group') {
filter.filters = node.children!.map(treeNodeToFilter)
}
return filter
}

View File

@ -91,4 +91,5 @@ body {
.p-tree-node-label {
flex: 1;
min-width: 0;
}