update
This commit is contained in:
parent
323d9e3339
commit
4ed7c8062a
@ -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",
|
||||
|
||||
68
src/App.vue
68
src/App.vue
@ -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,13 +46,56 @@ 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>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">
|
||||
|
||||
@ -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
|
||||
@ -57,6 +87,62 @@ const BASE_TYPES = [
|
||||
];
|
||||
|
||||
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>
|
||||
@ -70,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">
|
||||
@ -94,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>
|
||||
@ -107,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>
|
||||
@ -120,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>
|
||||
@ -133,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>
|
||||
@ -146,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>
|
||||
@ -159,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>
|
||||
@ -172,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>
|
||||
@ -185,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>
|
||||
@ -198,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>
|
||||
@ -211,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>
|
||||
@ -238,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>
|
||||
@ -251,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>
|
||||
@ -264,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>
|
||||
@ -277,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>
|
||||
@ -290,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>
|
||||
@ -305,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>
|
||||
@ -320,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>
|
||||
|
||||
@ -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
45
src/components/Info.md
Normal 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.
|
||||
@ -1,10 +1,67 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { ScrollPanel } from 'primevue';
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<article class="prose dark:prose-invert px-5 pt-4">
|
||||
<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'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>
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, onUpdated, ref } from 'vue'
|
||||
import { computed, onUpdated, ref, watch } from 'vue'
|
||||
import { defaultGroup, defaultLeaf, type Filter, type FilterNode as FilterNodeType } from '../models';
|
||||
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';
|
||||
@ -36,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)
|
||||
}
|
||||
},
|
||||
{
|
||||
@ -43,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)
|
||||
}
|
||||
}
|
||||
]
|
||||
@ -117,50 +123,56 @@ const selectedPosition = computed(() => {
|
||||
return undefined
|
||||
} else {
|
||||
if (props.selectedNode.parent) {
|
||||
let r = [props.selectedNode.parent.children!.findIndex(item => item === props.selectedNode), props.selectedNode.parent.children!.length]
|
||||
console.dir(r)
|
||||
return r
|
||||
return [props.selectedNode.parent.children!.findIndex(item => item === props.selectedNode), props.selectedNode.parent.children!.length]
|
||||
} else {
|
||||
let r = [props.nodes.findIndex(item => item === props.selectedNode), props.nodes.length];
|
||||
console.dir(r)
|
||||
return r
|
||||
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" :disabled="selectedPosition == undefined || selectedPosition[0] <= 0"
|
||||
v-tooltip.top="'Move to top'" severity="secondary" variant="text" rounded 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" variant="text" rounded 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" variant="text" rounded 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" variant="text" rounded icon="pi pi-angle-double-down"
|
||||
size="small" @click="moveToBottom" />
|
||||
<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" rounded variant="text" size="small" @click="toggle" />
|
||||
<Menu ref="menu" :model="items" :popup="true" />
|
||||
<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>
|
||||
|
||||
@ -8,6 +8,8 @@ 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)
|
||||
|
||||
@ -15,5 +17,7 @@ app.use(createPinia())
|
||||
app.use(PrimeVue, {
|
||||
theme: 'none'
|
||||
})
|
||||
app.directive('tooltip', Tooltip);
|
||||
app.directive('tooltip', Tooltip)
|
||||
app.use(ConfirmationService)
|
||||
app.use(ToastService)
|
||||
app.mount('#app')
|
||||
|
||||
@ -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 }]
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@ -1,19 +1,23 @@
|
||||
import type { Filter, FilterNode } from '@/models'
|
||||
import type { TreeNode } from 'primevue/treenode'
|
||||
|
||||
export function filterToTreeNode(filter: Filter, parent?: TreeNode): FilterNode {
|
||||
export function filterToTreeNode(filter: Filter, parent?: FilterNode): FilterNode {
|
||||
let node = {
|
||||
key: filter.id,
|
||||
data: filter,
|
||||
parent
|
||||
parent,
|
||||
} as FilterNode
|
||||
if (filter.type === 'group' ) {
|
||||
node.children = filter.filters.map(f => filterToTreeNode(f, node))
|
||||
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
|
||||
}
|
||||
|
||||
@ -91,4 +91,5 @@ body {
|
||||
|
||||
.p-tree-node-label {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user