This commit is contained in:
Leon Liu 2025-01-03 13:06:53 +09:00
parent 20dc07b0e3
commit ed6bf05a59
7 changed files with 341 additions and 141 deletions

View File

@ -8,9 +8,13 @@
"permissions": [
"core:default",
"fs:default",
{
"identifier": "fs:scope",
"allow": [{ "path": "$DOCUMENT/My Games/Path of Exile 2" }, { "path": "$DOCUMENT/My Games/Path of Exile 2/**" }]
},
{
"identifier": "fs:allow-write-text-file",
"allow": [{ "path": "$DOCUMENT/My Games/Path of Exile 2/*" }]
"allow": [ { "path": "$DOCUMENT/My Games/Path of Exile 2/*" }]
}
]
}

View File

@ -1,16 +1,12 @@
<script setup lang="ts">
import { Button, Dialog, ConfirmPopup, Toast, Splitter, SplitterPanel, ScrollPanel, useToast } from 'primevue'
import { Button, InputText, ButtonGroup, Dialog, Listbox, 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, 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 FileSaver from 'file-saver'
import { generateFilterText, type Filter, type FilterConfig, type FilterNode } from './models'
import { filterToTreeNode, treeNodeToFilter } from './services/filter'
import { exists, BaseDirectory, writeTextFile } from '@tauri-apps/plugin-fs';
import { BaseDirectory, writeTextFile, lstat, readDir, readTextFile } from '@tauri-apps/plugin-fs';
const darkMode = ref(document.documentElement.classList.contains('dark'))
@ -26,14 +22,9 @@ watch(darkMode, (dark) => {
localStorage.theme = dark ? 'dark' : 'light'
})
let defaultFilters: Filter[] = []
const nodes = ref(pipe(
localStorage.getItem("filters"),
O.fromNullable,
O.map((value): Filter[] => JSON.parse(value)),
O.getOrElse(() => defaultFilters),
A.map(filterToTreeNode)
))
const filterName = ref()
const nodes = ref([] as FilterNode[])
const selectedFilter = ref<FilterNode>()
function onDelete() {
@ -47,31 +38,22 @@ 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, "Clearfell Default.filter")
}
const exportDialogVisible = ref(false);
const POE2_FOLDER = 'My Games/Path of Exile 2'
const PREFIX = 'Clearfell'
const CONFIG_SUFFIX = "config.json"
const FILTER_SUFFIX = 'filter'
const BASE_DIR = BaseDirectory.Document
async function save(toastOnComplete?: boolean) {
if (nodes.value) {
if (nodes.value && !!filterName.value) {
const filters = toRaw(nodes.value).map(treeNodeToFilter)
localStorage.setItem("filters", JSON.stringify(filters))
let text = generateFilterText(filters)
await writeTextFile('My Games/Path of Exile 2/Clearfell Default.filter', text, {
baseDir: BaseDirectory.Document,
});
await Promise.all([writeTextFile(`${POE2_FOLDER}/${PREFIX} ${filterName.value}.${CONFIG_SUFFIX}`, JSON.stringify({ filters }, null, 2), {
baseDir: BASE_DIR,
}), writeTextFile(`${POE2_FOLDER}/${PREFIX} ${filterName.value}.${FILTER_SUFFIX}`, text, {
baseDir: BASE_DIR,
})])
if (toastOnComplete) {
toast.add({ severity: 'success', summary: 'Success', detail: 'Content Saved', life: 3000 });
}
@ -80,34 +62,99 @@ async function save(toastOnComplete?: boolean) {
setInterval(save, 5000)
const CONFIG_PATTERN = `^${PREFIX} (.*)\\.config\\.json$`
const filterNames = ref([] as string[])
async function fetchFilterNames() {
const ls = await readDir(POE2_FOLDER, {
baseDir: BASE_DIR
})
filterNames.value = ls.map(f => {
if (f.isFile) {
let match = f.name.match(CONFIG_PATTERN)
if (match) {
return match[1]
}
}
return null
}).filter(f => f != null)
}
async function load() {
await fetchFilterNames()
loadDialogVisible.value = true
}
async function saveAs() {
await fetchFilterNames()
saveDialogVisible.value = true
}
const loadDialogVisible = ref(false)
const saveDialogVisible = ref(false)
async function loadFilter() {
const json = await readTextFile(`${POE2_FOLDER}/${PREFIX} ${loadName.value}.${CONFIG_SUFFIX}`, {
baseDir: BASE_DIR,
})
const filters: Filter[] = JSON.parse(json).filters
nodes.value = filters.map(f => filterToTreeNode(f))
filterName.value = loadName.value
loadDialogVisible.value = false
loadName.value = undefined
}
async function saveFilter() {
filterName.value = saveName.value
await save(true)
saveDialogVisible.value = false
saveName.value = undefined
}
const loadName = ref()
const saveName = ref()
const overwrite = computed(() => filterNames.value.includes(saveName.value))
</script>
<template>
<ConfirmPopup />
<Toast />
<Dialog v-model:visible="loadDialogVisible" modal header="Load Filter">
<Listbox v-model="loadName" :options="filterNames" />
<template #footer>
<Button label="Cancel" severity="secondary" @click="loadDialogVisible = false" autofocus />
<Button :disabled="!loadName" label="Load" severity="primary" @click="loadFilter" autofocus />
</template>
</Dialog>
<Dialog v-model:visible="saveDialogVisible" modal header="Save Filter As">
<div class="flex flex-col gap-2">
<label>Existing Filters:</label>
<Listbox v-model="saveName" :options="filterNames" />
</div>
<div class="flex flex-col gap-2 mt-4">
<label>Save Name:</label>
<InputText type="text" v-model="saveName" />
</div>
<template #footer>
<Button label="Cancel" severity="secondary" @click="saveDialogVisible = false" autofocus />
<Button :disabled="!saveName" :label="overwrite ? 'Overwrite' : 'Save'"
:severity="overwrite ? 'danger' : 'primary'" @click="saveFilter" autofocus />
</template>
</Dialog>
<div class="flex flex-row gap-2 items-center flex-grow-0">
<article class="prose dark:prose-invert p-4">
<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(true)" />
<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] w-[50vw]">
<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 /> -->
</article>
<ButtonGroup>
<Button :icon @click="darkMode = !darkMode" severity="secondary" variant="outlined" />
<Button icon="pi pi-file-import" label="Load" severity="secondary" variant="outlined" @click="load" />
<Button icon="pi pi-save" label="Save As" severity="secondary" variant="outlined" @click="saveAs" />
<Button :disabled="!filterName" icon="pi pi-save" label="Save" severity="primary" @click="save(true)" />
</ButtonGroup>
</div>
<Splitter class="flex-1 min-h-0">
<SplitterPanel class="flex flex-col">
<TreeNav :nodes :selectedNode="selectedFilter" @nodeSelect="selectedFilter = $event"
<TreeNav :filterName :nodes :selectedNode="selectedFilter" @nodeSelect="selectedFilter = $event"
@nodeUnselect="selectedFilter = undefined" />
</SplitterPanel>
<SplitterPanel class="flex flex-col">

View File

@ -1,10 +1,10 @@
<script setup lang="ts">
import { toLines, type Filter, type FilterNode, type FilterRule } from '@/models';
import { toDisplayLines, 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";
import { CLASSES, COLORS, BASE_TYPES, RARITIES, SHAPES, OPERATORS } from '@/models/settings';
import { CLASSES, COLORS, BASE_TYPES, RARITIES, SHAPES, OPERATORS, ARMOUR_TYPES } from '@/models/settings';
const confirm = useConfirm();
const toast = useToast();
@ -109,7 +109,7 @@ function mergeLookup([childValue, childLookup]: any, [parentValue, parentLookup]
<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="toLines(filter.rule).join(' ')" v-model="filter.name" />
<InputText class="w-full" type="text" :placeholder="toDisplayLines(filter).join(' ')" v-model="filter.name" />
<Button class="flex-shrink-0" icon="pi pi-trash" severity="danger" v-on:click="confirmDelete" />
</div>
@ -129,7 +129,7 @@ function mergeLookup([childValue, childLookup]: any, [parentValue, parentLookup]
</colgroup>
<tbody>
<tr>
<td class="pr-4">
<td class="pr-4 whitespace-nowrap">
Class
</td>
<td class="pr-4">
@ -140,16 +140,16 @@ function mergeLookup([childValue, childLookup]: any, [parentValue, parentLookup]
</td>
<td class="h-11">
<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 />
v-model="filter.rule.class" display="chip" :options="CLASSES.slice()" 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
:model-value="ruleWithLookup[0].class" display="chip" :options="CLASSES.slice()" filter
placeholder="Select Classes" class="w-full" :maxSelectedLabels=3 fluid />
</td>
</tr>
<tr>
<td class="pr-4">
<td class="pr-4 whitespace-nowrap">
BaseType
</td>
<td class="pr-4">
@ -160,15 +160,29 @@ function mergeLookup([childValue, childLookup]: any, [parentValue, parentLookup]
</td>
<td class="h-11">
<MultiSelect v-if="ruleWithLookup[1].base_type != 'inherit' && filter.rule.base_type"
v-model="filter.rule.base_type" display="chip" :options="BASE_TYPES" filter
v-model="filter.rule.base_type" display="chip" :options="BASE_TYPES.slice()" 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
:model-value="ruleWithLookup[0].base_type" display="chip" :options="BASE_TYPES.slice()" filter
placeholder="Select Base Types" class="w-full" :maxSelectedLabels=3 fluid />
</td>
</tr>
<tr>
<tr v-if="filter.type === 'leaf'">
<td class="pr-4 whitespace-nowrap">
ArmourType
</td>
<td class="pr-4">
<ToggleSwitch class="align-middle" :model-value="filter.leafRule.armour_type != undefined"
@update:model-value="filter.leafRule.armour_type = $event ? [] : undefined" />
</td>
<td class="h-11">
<MultiSelect v-if="filter.leafRule.armour_type" v-model="filter.leafRule.armour_type" display="chip"
:options="ARMOUR_TYPES.slice()" filter placeholder="Select Base Types" class="w-full"
:maxSelectedLabels=3 fluid />
</td>
</tr>
<tr>
<td class="pr-4 whitespace-nowrap">
Rarity
</td>
<td class="pr-4">
@ -179,17 +193,17 @@ function mergeLookup([childValue, childLookup]: any, [parentValue, parentLookup]
</td>
<td class="h-11 flex gap-2">
<Select v-if="ruleWithLookup[1].rarity != 'inherit' && filter.rule.rarity"
v-model="filter.rule.rarity[0]" :options="OPERATORS" />
v-model="filter.rule.rarity[0]" :options="OPERATORS.slice()" />
<Select v-if="ruleWithLookup[1].rarity != 'inherit' && filter.rule.rarity"
v-model="filter.rule.rarity[1]" :options="RARITIES" />
v-model="filter.rule.rarity[1]" :options="RARITIES.slice()" />
<Select disabled v-if="ruleWithLookup[1].rarity == 'inherit'"
:model-value="ruleWithLookup[0].rarity![0]" :options="OPERATORS" />
:model-value="ruleWithLookup[0].rarity![0]" :options="OPERATORS.slice()" />
<Select disabled v-if="ruleWithLookup[1].rarity == 'inherit'"
:model-value="ruleWithLookup[0].rarity![1]" :options="RARITIES" />
:model-value="ruleWithLookup[0].rarity![1]" :options="RARITIES.slice()" />
</td>
</tr>
<tr>
<td class="pr-4">
<td class="pr-4 whitespace-nowrap">
Sockets
</td>
<td class="pr-4">
@ -200,17 +214,17 @@ function mergeLookup([childValue, childLookup]: any, [parentValue, parentLookup]
</td>
<td class="h-11 flex gap-2">
<Select v-if="ruleWithLookup[1].sockets != 'inherit' && filter.rule.sockets"
v-model="filter.rule.sockets[0]" :options="OPERATORS" />
v-model="filter.rule.sockets[0]" :options="OPERATORS.slice()" />
<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" />
:model-value="ruleWithLookup[0].sockets![0]" :options="OPERATORS.slice()" />
<InputNumber disabled class="w-16" fluid v-if="ruleWithLookup[1].sockets == 'inherit'"
:model-value="ruleWithLookup[0].sockets![1]" />
</td>
</tr>
<tr>
<td class="pr-4">
<td class="pr-4 whitespace-nowrap">
Quality
</td>
<td class="pr-4">
@ -221,17 +235,17 @@ function mergeLookup([childValue, childLookup]: any, [parentValue, parentLookup]
</td>
<td class="h-11 flex gap-2">
<Select v-if="ruleWithLookup[1].quality != 'inherit' && filter.rule.quality"
v-model="filter.rule.quality[0]" :options="OPERATORS" />
v-model="filter.rule.quality[0]" :options="OPERATORS.slice()" />
<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" />
:model-value="ruleWithLookup[0].quality![0]" :options="OPERATORS.slice()" />
<InputNumber disabled class="w-16" fluid v-if="ruleWithLookup[1].quality == 'inherit'"
:model-value="ruleWithLookup[0].quality![1]" />
</td>
</tr>
<tr>
<td class="pr-4">
<td class="pr-4 whitespace-nowrap">
StackSize
</td>
<td class="pr-4">
@ -242,18 +256,18 @@ function mergeLookup([childValue, childLookup]: any, [parentValue, parentLookup]
</td>
<td class="h-11 flex gap-2">
<Select v-if="ruleWithLookup[1].stack_size != 'inherit' && filter.rule.stack_size"
v-model="filter.rule.stack_size[0]" :options="OPERATORS" />
v-model="filter.rule.stack_size[0]" :options="OPERATORS.slice()" />
<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" />
:model-value="ruleWithLookup[0].stack_size![0]" :options="OPERATORS.slice()" />
<InputNumber disabled class="w-16" fluid v-if="ruleWithLookup[1].stack_size == 'inherit'"
:model-value="ruleWithLookup[0].stack_size![1]" />
</td>
</tr>
<tr>
<td class="pr-4">
<td class="pr-4 whitespace-nowrap">
AreaLevel
</td>
<td class="pr-4">
@ -264,18 +278,18 @@ function mergeLookup([childValue, childLookup]: any, [parentValue, parentLookup]
</td>
<td class="h-11 flex gap-2">
<Select v-if="ruleWithLookup[1].area_level != 'inherit' && filter.rule.area_level"
v-model="filter.rule.area_level[0]" :options="OPERATORS" />
v-model="filter.rule.area_level[0]" :options="OPERATORS.slice()" />
<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" />
:model-value="ruleWithLookup[0].area_level![0]" :options="OPERATORS.slice()" />
<InputNumber disabled class="w-16" fluid v-if="ruleWithLookup[1].area_level == 'inherit'"
:model-value="ruleWithLookup[0].area_level![1]" />
</td>
</tr>
<tr>
<td class="pr-4">
<td class="pr-4 whitespace-nowrap">
DropLevel
</td>
<td class="pr-4">
@ -286,18 +300,18 @@ function mergeLookup([childValue, childLookup]: any, [parentValue, parentLookup]
</td>
<td class="h-11 flex gap-2">
<Select v-if="ruleWithLookup[1].drop_level != 'inherit' && filter.rule.drop_level"
v-model="filter.rule.drop_level[0]" :options="OPERATORS" />
v-model="filter.rule.drop_level[0]" :options="OPERATORS.slice()" />
<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" />
:model-value="ruleWithLookup[0].drop_level![0]" :options="OPERATORS.slice()" />
<InputNumber disabled class="w-16" fluid v-if="ruleWithLookup[1].drop_level == 'inherit'"
:model-value="ruleWithLookup[0].drop_level![1]" />
</td>
</tr>
<tr>
<td class="pr-4">
<td class="pr-4 whitespace-nowrap">
ItemLevel
</td>
<td class="pr-4">
@ -308,18 +322,33 @@ function mergeLookup([childValue, childLookup]: any, [parentValue, parentLookup]
</td>
<td class="h-11 flex gap-2">
<Select v-if="ruleWithLookup[1].item_level != 'inherit' && filter.rule.item_level"
v-model="filter.rule.item_level[0]" :options="OPERATORS" />
v-model="filter.rule.item_level[0]" :options="OPERATORS.slice()" />
<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" />
:model-value="ruleWithLookup[0].item_level![0]" :options="OPERATORS.slice()" />
<InputNumber disabled class="w-16" fluid v-if="ruleWithLookup[1].item_level == 'inherit'"
:model-value="ruleWithLookup[0].item_level![1]" />
</td>
</tr>
<tr>
<tr v-if="filter.type === 'leaf'">
<td class="pr-4 whitespace-nowrap">
(Area - Drop)Level
</td>
<td class="pr-4">
<ToggleSwitch class="align-middle" :model-value="filter.leafRule.area_minus_drop_level != undefined"
@update:model-value="filter.leafRule.area_minus_drop_level = $event ? ['>', 0] : undefined" />
</td>
<td class="h-11 flex gap-2">
<Select v-if="filter.leafRule.area_minus_drop_level"
v-model="filter.leafRule.area_minus_drop_level[0]" :options="OPERATORS.slice()" />
<InputNumber class="w-16" fluid v-if="filter.leafRule.area_minus_drop_level"
v-model="filter.leafRule.area_minus_drop_level[1]" />
</td>
</tr>
<tr>
<td class="pr-4 whitespace-nowrap">
WaystoneTier
</td>
<td class="pr-4">
@ -330,12 +359,12 @@ function mergeLookup([childValue, childLookup]: any, [parentValue, parentLookup]
</td>
<td class="h-11 flex gap-2">
<Select v-if="ruleWithLookup[1].waystone_tier != 'inherit' && filter.rule.waystone_tier"
v-model="filter.rule.waystone_tier[0]" :options="OPERATORS" />
v-model="filter.rule.waystone_tier[0]" :options="OPERATORS.slice()" />
<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" />
:model-value="ruleWithLookup[0].waystone_tier![0]" :options="OPERATORS.slice()" />
<InputNumber disabled class="w-16" fluid v-if="ruleWithLookup[1].waystone_tier == 'inherit'"
:model-value="ruleWithLookup[0].waystone_tier![1]" />
</td>
@ -353,7 +382,7 @@ function mergeLookup([childValue, childLookup]: any, [parentValue, parentLookup]
</colgroup>
<tbody>
<tr>
<td class="pr-4">
<td class="pr-4 whitespace-nowrap">
SetFontSize
</td>
<td class="pr-4">
@ -372,7 +401,7 @@ function mergeLookup([childValue, childLookup]: any, [parentValue, parentLookup]
</td>
</tr>
<tr>
<td class="pr-4">
<td class="pr-4 whitespace-nowrap">
SetTextColor
</td>
<td class="pr-4">
@ -391,7 +420,7 @@ function mergeLookup([childValue, childLookup]: any, [parentValue, parentLookup]
</td>
</tr>
<tr>
<td class="pr-4">
<td class="pr-4 whitespace-nowrap">
SetBackgroundColor
</td>
<td class="pr-4">
@ -410,7 +439,7 @@ function mergeLookup([childValue, childLookup]: any, [parentValue, parentLookup]
</td>
</tr>
<tr>
<td class="pr-4">
<td class="pr-4 whitespace-nowrap">
SetBorderColor
</td>
<td class="pr-4">
@ -429,7 +458,7 @@ function mergeLookup([childValue, childLookup]: any, [parentValue, parentLookup]
</td>
</tr>
<tr>
<td class="pr-4">
<td class="pr-4 whitespace-nowrap">
PlayAlertSound
</td>
<td class="pr-4">
@ -452,7 +481,7 @@ function mergeLookup([childValue, childLookup]: any, [parentValue, parentLookup]
</td>
</tr>
<tr>
<td class="pr-4">
<td class="pr-4 whitespace-nowrap">
MinimapIcon
</td>
<td class="pr-4">
@ -466,19 +495,19 @@ function mergeLookup([childValue, childLookup]: any, [parentValue, parentLookup]
v-if="ruleWithLookup[1].minimap_icon != 'inherit' && filter.rule.minimap_icon"
v-model="filter.rule.minimap_icon[0]" />
<Select v-if="ruleWithLookup[1].minimap_icon != 'inherit' && filter.rule.minimap_icon"
v-model="filter.rule.minimap_icon[1]" :options="COLORS" />
v-model="filter.rule.minimap_icon[1]" :options="COLORS.slice()" />
<Select v-if="ruleWithLookup[1].minimap_icon != 'inherit' && filter.rule.minimap_icon"
v-model="filter.rule.minimap_icon[2]" :options="SHAPES" />
v-model="filter.rule.minimap_icon[2]" :options="SHAPES.slice()" />
<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" />
:model-value="ruleWithLookup[0].minimap_icon![1]" :options="COLORS.slice()" />
<Select v-if="ruleWithLookup[1].minimap_icon == 'inherit'" disabled
:model-value="ruleWithLookup[0].minimap_icon![2]" :options="SHAPES" />
:model-value="ruleWithLookup[0].minimap_icon![2]" :options="SHAPES.slice()" />
</td>
</tr>
<tr>
<td class="pr-4">
<td class="pr-4 whitespace-nowrap">
PlayEffect
</td>
<td class="pr-4">
@ -489,9 +518,9 @@ function mergeLookup([childValue, childLookup]: any, [parentValue, parentLookup]
</td>
<td class="h-11 flex gap-2">
<Select v-if="ruleWithLookup[1].play_effect != 'inherit' && filter.rule.play_effect"
v-model="filter.rule.play_effect" :options="COLORS" />
v-model="filter.rule.play_effect" :options="COLORS.slice()" />
<Select v-if="ruleWithLookup[1].play_effect == 'inherit'" disabled
:model-value="ruleWithLookup[0].play_effect" :options="COLORS" />
:model-value="ruleWithLookup[0].play_effect" :options="COLORS.slice()" />
</td>
</tr>
</tbody>

View File

@ -1,5 +1,5 @@
<script setup lang="ts">
import { defaultGroup, defaultLeaf, toLines, type FilterNode } from '@/models';
import { defaultGroup, defaultLeaf, toDisplayLines, type FilterNode } from '@/models';
import { filterToTreeNode } from '@/services/filter';
import { Button, Menu, Chip } from 'primevue';
import { ref } from 'vue'
@ -42,7 +42,7 @@ const toggle = (event: MouseEvent) => {
<template>
<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)">
<Chip v-if="!node.data.name" class="text-xs py-1.5 px-2" v-for="line in toDisplayLines(node.data)">
<span class="truncate max-w-40">{{ line }}</span>
</Chip>
<label v-else>{{ node.data.name }}</label>

View File

@ -10,6 +10,7 @@ import { filterToTreeNode } from '@/services/filter';
const props = defineProps<{
nodes: FilterNodeType[]
selectedNode?: FilterNodeType
filterName?: string
}>()
const emit = defineEmits(['nodeSelect', 'nodeUnselect'])
const selectedKey = ref()
@ -144,7 +145,7 @@ const expandedKeys
<template>
<div class="flex flex-shrink-0 p-4 items-center">
<article class="prose dark:prose-invert flex-1">
<h2>Rules:</h2>
<h2>{{ filterName }}</h2>
</article>
<ButtonGroup>
<Button class="flex-shrink-0" :disabled="selectedPosition == undefined || selectedPosition[0] <= 0"

View File

@ -1,5 +1,13 @@
import { show } from 'fp-ts'
import * as uuid from 'uuid'
import type {
ARMOUR_TYPES,
BASE_TYPES,
CLASSES,
COLORS,
OPERATORS,
RARITIES,
SHAPES,
} from './settings'
interface _Filter {
id: string
@ -16,6 +24,7 @@ export function defaultLeaf(): FilterLeaf {
enabled: true,
rule: {},
show: true,
leafRule: {},
}
}
@ -37,6 +46,7 @@ export interface FilterConfig {
export type FilterLeaf = _Filter & {
type: 'leaf'
show: boolean
leafRule: LeafRule
}
export type FilterGroup = _Filter & {
@ -46,39 +56,37 @@ export type FilterGroup = _Filter & {
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 interface RangedNumber<T extends number, U extends number> {
value: number
min: T
max: U
}
export interface Color {
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] //
export type Class = (typeof CLASSES)[number]
export type BaseType = (typeof BASE_TYPES)[number]
export type ArmourType = (typeof ARMOUR_TYPES)[number]
export type Op = (typeof OPERATORS)[number]
export type Rarity = (typeof RARITIES)[number]
export type GameColor = (typeof COLORS)[number]
export type Shape = (typeof SHAPES)[number]
export interface LeafRule {
armour_type?: ArmourType[] // special addition
area_minus_drop_level?: [Op, number] // special addition
}
export interface FilterRule {
class?: Class[] //
base_type?: BaseType[] //
area_level?: [Op, number] //
drop_level?: [Op, number] //
item_level?: [Op, number] //
rarity?: [Op, Rarity] //
sockets?: [Op, number] //
quality?: [Op, number] //
stack_size?: [Op, number] //
// waystones
waystone_tier?: [string, number] //
waystone_tier?: [Op, number] //
// effects
set_font_size?: number //
@ -86,8 +94,8 @@ export interface FilterRule {
set_border_color?: Color //
set_background_color?: Color //
play_alert_sound?: [number, number] //
play_effect?: string //
minimap_icon?: [number, string, string] //
play_effect?: GameColor //
minimap_icon?: [number, GameColor, Shape] //
}
export interface FilterNode {
@ -97,7 +105,22 @@ export interface FilterNode {
children?: FilterNode[]
}
export function toLines(rule: FilterRule): string[] {
export function toDisplayLines(filter: Filter) {
let r: string[] = toLines(filter.rule)
if (filter.type === 'leaf') {
if (filter.leafRule.armour_type) {
r.push(`ArmourType ${filter.leafRule.armour_type.map((c) => `"${c}"`).join(' ')}`)
}
if (filter.leafRule.area_minus_drop_level) {
r.push(
`Area-DropLevel ${filter.leafRule.area_minus_drop_level[0]} ${filter.leafRule.area_minus_drop_level[1]}`,
)
}
}
return r
}
export function toLines(rule: FilterRuleRaw): string[] {
let r: string[] = []
if (rule.class && rule.class.length > 0) {
r.push(`Class ${rule.class.map((c) => `"${c}"`).join(' ')}`)
@ -105,6 +128,15 @@ export function toLines(rule: FilterRule): string[] {
if (rule.base_type && rule.base_type.length > 0) {
r.push(`BaseType ${rule.base_type.map((c) => `"${c}"`).join(' ')}`)
}
if (rule.base_armour) {
r.push(`BaseArmour ${rule.base_armour[0]} ${rule.base_armour[1]}`)
}
if (rule.base_evasion) {
r.push(`BaseEvasion ${rule.base_evasion[0]} ${rule.base_evasion[1]}`)
}
if (rule.base_energy_shield) {
r.push(`BaseEnergyShield ${rule.base_energy_shield[0]} ${rule.base_energy_shield[1]}`)
}
if (rule.rarity) {
r.push(`Rarity ${rule.rarity[0]} ${rule.rarity[1]}`)
}
@ -174,7 +206,7 @@ export function generateFilterText(filters: Filter[]): string {
.join('\n\n')
}
function flatten(filters: Filter[]): Omit<FilterLeaf, 'id' | 'name' | 'type' | 'enabled'>[] {
function flatten(filters: Filter[]): { show: boolean; rule: FilterRuleRaw }[] {
return filters.flatMap((f) => {
if (!f.enabled) {
return []
@ -194,7 +226,85 @@ function flatten(filters: Filter[]): Omit<FilterLeaf, 'id' | 'name' | 'type' | '
}
})
} else {
return [{ show: f.show, rule: f.rule }]
return expandLeaf(f).map((r) => ({ show: f.show, rule: r }))
}
})
}
type FilterRuleRaw = FilterRule & {
base_armour?: [Op, number]
base_evasion?: [Op, number]
base_energy_shield?: [Op, number]
}
function expandLeaf(leaf: FilterLeaf): FilterRuleRaw[] {
let result = [leaf.rule]
if (leaf.leafRule.area_minus_drop_level) {
result = []
for (let area_level = 1; area_level <= 100; area_level++) {
let rule = { ...leaf.rule }
let op: Op
switch (leaf.leafRule.area_minus_drop_level[0]) {
case '<':
op = '>'
break
case '<=':
op = '>='
break
case '>':
op = '<'
break
case '>=':
op = '<='
break
default:
op = leaf.leafRule.area_minus_drop_level[0]
}
rule.area_level = ['==', area_level]
rule.drop_level = [op, Math.max(0, area_level - leaf.leafRule.area_minus_drop_level[1])]
result.push(rule)
}
}
if (leaf.leafRule.armour_type) {
result = result.flatMap((r) => {
return leaf.leafRule.armour_type!.map((t) => {
let rule: FilterRuleRaw = { ...r }
switch (t) {
case 'Armour':
rule.base_armour = ['>', 0]
rule.base_evasion = ['<=', 0]
rule.base_energy_shield = ['<=', 0]
break
case 'Evasion':
rule.base_armour = ['<=', 0]
rule.base_evasion = ['>', 0]
rule.base_energy_shield = ['<=', 0]
break
case 'EnergyShield':
rule.base_armour = ['<=', 0]
rule.base_evasion = ['<=', 0]
rule.base_energy_shield = ['>', 0]
break
case 'Armour + Evasion':
rule.base_armour = ['>', 0]
rule.base_evasion = ['>', 0]
rule.base_energy_shield = ['<=', 0]
break
case 'Armour + EnergyShield':
rule.base_armour = ['>', 0]
rule.base_evasion = ['<=', 0]
rule.base_energy_shield = ['>', 0]
break
case 'Evasion + EnergyShield':
rule.base_armour = ['<=', 0]
rule.base_evasion = ['>', 0]
rule.base_energy_shield = ['>', 0]
break
}
return rule
})
})
}
return result
}

View File

@ -1,4 +1,4 @@
export const OPERATORS = ['=', '==', '!=', '<', '<=', '>', '>=']
export const OPERATORS = ['=', '==', '!=', '<', '<=', '>', '>='] as const
export const COLORS = [
'Red',
@ -12,7 +12,7 @@ export const COLORS = [
'Orange',
'Pink',
'Purple',
]
] as const
export const SHAPES = [
'Circle',
@ -27,9 +27,9 @@ export const SHAPES = [
'Kite',
'Pentagon',
'UpsideDownHouse',
]
] as const
export const RARITIES = ['Normal', 'Magic', 'Rare', 'Unique']
export const RARITIES = ['Normal', 'Magic', 'Rare', 'Unique'] as const
export const CLASSES = [
'Currency',
@ -62,7 +62,7 @@ export const CLASSES = [
'Helmet',
'Shield',
'Quiver',
]
] as const
export const BASE_TYPES = [
'Exalted Orb',
@ -85,4 +85,13 @@ export const BASE_TYPES = [
'Orb of Transmutation',
'Scroll of Wisdom',
'Portal Scroll',
]
] as const
export const ARMOUR_TYPES = [
'Armour',
'Evasion',
'EnergyShield',
'Armour + Evasion',
'Armour + EnergyShield',
'Evasion + EnergyShield',
] as const