This commit is contained in:
Leon Liu 2025-01-01 15:27:16 +09:00
parent 28b3b9dc24
commit c65dbcb8fc
9 changed files with 404 additions and 50 deletions

View File

@ -15,7 +15,7 @@
</head>
<body>
<div class="bg-surface-0 dark:bg-surface-900 min-h-screen p-4 flex flex-col" id="app"></div>
<div class="h-screen flex flex-col" id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>

View File

@ -1,5 +1,5 @@
<script setup lang="ts">
import { Button, } from 'primevue'
import { Button, Splitter, SplitterPanel } from 'primevue'
import TreeNav from './components/TreeNav.vue'
import FilterDetail from './components/FilterDetail.vue'
import Info from './components/Info.vue'
@ -52,13 +52,14 @@ const selectedFilter = ref<Filter>()
<h1>POE2 Loot Filter Config</h1>
</article><Button :icon @click="darkMode = !darkMode" severity="secondary" variant="outlined" rounded />
</div>
<div class="flex flex-1 flex-row">
<div class="flex-1">
<Splitter class="flex-1 min-h-0">
<SplitterPanel>
<TreeNav :filters @nodeSelect="selectedFilter = $event.data" @nodeUnselect="selectedFilter = undefined" />
</div>
<div class="flex-1">
</SplitterPanel>
<SplitterPanel class="flex flex-col">
<FilterDetail v-if="selectedFilter" :filter="selectedFilter" />
<Info v-else />
</div>
</div>
</SplitterPanel>
</Splitter>
</template>

View File

@ -1,3 +0,0 @@
html {
font-size: 14px;
}

View File

@ -1,19 +1,347 @@
<script setup lang="ts">
import type { Filter } from '@/models';
import { ref, watchEffect } from 'vue'
import { ToggleButton, SelectButton, Card } from 'primevue'
import { Button, ColorPicker, ScrollPanel, Tabs, Tab, TabPanels, TabList, TabPanel, ToggleButton, InputText, InputNumber, Select, ToggleSwitch, MultiSelect } from 'primevue'
const props = defineProps<{
filter: Filter
}>()
const OPERATORS = ['=', '==', '!=', '<', '<=', '>', '>='];
const COLORS = [
'Red',
'Green',
'Blue',
'Brown',
'White',
'Yellow',
'Cyan',
'Grey',
'Orange',
'Pink',
'Purple'
];
const SHAPES = [
'Circle',
'Diamond',
'Hexagon',
'Square',
'Star',
'Triangle',
'Cross',
'Moon',
'Raindrop',
'Kite',
'Pentagon',
'UpsideDownHouse'
];
const RARITIES = ['Normal', 'Magic', 'Rare', 'Unique'];
const CLASSES = [
'Currency', 'Stackable Currency', 'Jewel', 'Abyss Jewel', 'Divination Card',
'Gem', 'Flask', 'Map', 'Map Fragment', 'Fishing Rods', 'Amulet', 'Ring',
'Claw', 'Dagger', 'Wand', 'One Hand Sword', 'Thrusting One Hand Sword',
'One Hand Axe', 'One Hand Mace', 'Sceptre', 'Rune Dagger', 'Bow', 'Staff',
'Two Hand Sword', 'Two Hand Axe', 'Two Hand Mace', 'Warstaff', 'Body Armour',
'Boots', 'Gloves', 'Helmet', 'Shield', 'Quiver'
];
const BASE_TYPES = [
'Exalted Orb', 'Mirror of Kalandra', 'Eternal Orb', 'Divine Orb',
'Orb of Annulment', 'Chaos Orb', 'Vaal Orb', 'Regal Orb', 'Orb of Alchemy',
'Orb of Fusing', 'Blessed Orb', 'Cartographer\'s Chisel', 'Orb of Scouring',
'Jeweller\'s Orb', 'Chromatic Orb', 'Orb of Chance', 'Orb of Alteration',
'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);
// }
watchEffect(() => {
console.log(props.filter);
})
</script>
<template>
<article class="prose dark:prose-invert">
<h2>Filter detail:</h2>
</article>
<ToggleButton v-if="filter.type === 'leaf'" v-model="filter.show" onLabel="Show" offLabel="Hide" />
<div class="flex gap-4 items-center px-5 pt-4">
<div class="flex gap-2 items-center flex-shrink-0">
<label>Off</label>
<ToggleSwitch v-model="filter.enabled" />
<label>On</label>
</div>
<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" />
</div>
<Tabs value="0" class="flex-1 min-h-0">
<TabList>
<Tab value="0">Filters</Tab>
<Tab value="1">Effects</Tab>
</TabList>
<ScrollPanel class="flex-1 min-h-0">
<TabPanels>
<TabPanel value="0">
<table class="w-full align-middle">
<colgroup>
<col class="w-auto">
<col class="w-auto">
<col class="w-full">
</colgroup>
<tbody>
<tr>
<td class="pr-4">
Class
</td>
<td class="pr-4">
<ToggleSwitch class="align-middle" :model-value="filter.rule.class != undefined"
@update:model-value="filter.rule.class = $event ? [] : undefined" />
</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 />
</td>
</tr>
<tr>
<td class="pr-4">
BaseType
</td>
<td class="pr-4">
<ToggleSwitch class="align-middle" :model-value="filter.rule.base_type != undefined"
@update:model-value="filter.rule.base_type = $event ? [] : undefined" />
</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 />
</td>
</tr>
<tr>
<td class="pr-4">
Rarity
</td>
<td class="pr-4">
<ToggleSwitch class="align-middle" :model-value="filter.rule.rarity != undefined"
@update:model-value="filter.rule.rarity = $event ? ['=', 'Normal'] : undefined" />
</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" />
</td>
</tr>
<tr>
<td class="pr-4">
Sockets
</td>
<td class="pr-4">
<ToggleSwitch class="align-middle" :model-value="filter.rule.sockets != undefined"
@update:model-value="filter.rule.sockets = $event ? ['>', 0] : undefined" />
</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]" />
</td>
</tr>
<tr>
<td class="pr-4">
Quality
</td>
<td class="pr-4">
<ToggleSwitch class="align-middle" :model-value="filter.rule.quality != undefined"
@update:model-value="filter.rule.quality = $event ? ['>', 0] : undefined" />
</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]" />
</td>
</tr>
<tr>
<td class="pr-4">
StackSize
</td>
<td class="pr-4">
<ToggleSwitch class="align-middle" :model-value="filter.rule.stack_size != undefined"
@update:model-value="filter.rule.stack_size = $event ? ['>', 0] : undefined" />
</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]" />
</td>
</tr>
<tr>
<td class="pr-4">
AreaLevel
</td>
<td class="pr-4">
<ToggleSwitch class="align-middle" :model-value="filter.rule.area_level != undefined"
@update:model-value="filter.rule.area_level = $event ? ['>', 0] : undefined" />
</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]" />
</td>
</tr>
<tr>
<td class="pr-4">
DropLevel
</td>
<td class="pr-4">
<ToggleSwitch class="align-middle" :model-value="filter.rule.drop_level != undefined"
@update:model-value="filter.rule.drop_level = $event ? ['>', 0] : undefined" />
</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]" />
</td>
</tr>
<tr>
<td class="pr-4">
ItemLevel
</td>
<td class="pr-4">
<ToggleSwitch class="align-middle" :model-value="filter.rule.item_level != undefined"
@update:model-value="filter.rule.item_level = $event ? ['>', 0] : undefined" />
</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]" />
</td>
</tr>
<tr>
<td class="pr-4">
WaystoneTier
</td>
<td class="pr-4">
<ToggleSwitch class="align-middle" :model-value="filter.rule.waystone_tier != undefined"
@update:model-value="filter.rule.waystone_tier = $event ? ['>', 0] : undefined" />
</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"
v-model="filter.rule.waystone_tier[1]" />
</td>
</tr>
</tbody>
</table>
</TabPanel>
<TabPanel value="1">
<table class="w-full align-middle">
<colgroup>
<col class="w-auto">
<col class="w-auto">
<col class="w-full">
</colgroup>
<tbody>
<tr>
<td class="pr-4">
SetFontSize
</td>
<td class="pr-4">
<ToggleSwitch class="align-middle" :model-value="filter.rule.set_font_size != undefined"
@update:model-value="filter.rule.set_font_size = $event ? 40 : undefined" />
</td>
<td class="h-11">
<InputNumber class="w-16" fluid v-if="filter.rule.set_font_size"
v-model="filter.rule.set_font_size" />
</td>
</tr>
<tr>
<td class="pr-4">
SetTextColor
</td>
<td class="pr-4">
<ToggleSwitch 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" />
</td>
<td class="h-11 flex gap-2">
<ColorPicker class="flex items-center" format="rgb" v-if="filter.rule.set_text_color"
v-model="filter.rule.set_text_color" />
</td>
</tr>
<tr>
<td class="pr-4">
SetBackgroundColor
</td>
<td class="pr-4">
<ToggleSwitch 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" />
</td>
<td class="h-11 flex gap-2">
<ColorPicker class="flex items-center" format="rgb" v-if="filter.rule.set_background_color"
v-model="filter.rule.set_background_color" />
</td>
</tr>
<tr>
<td class="pr-4">
SetBorderColor
</td>
<td class="pr-4">
<ToggleSwitch 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" />
</td>
<td class="h-11 flex gap-2">
<ColorPicker class="flex items-center" format="rgb" v-if="filter.rule.set_border_color"
v-model="filter.rule.set_border_color" />
</td>
</tr>
<tr>
<td class="pr-4">
PlayAlertSound
</td>
<td class="pr-4">
<ToggleSwitch class="align-middle" :model-value="filter.rule.play_alert_sound != undefined"
@update:model-value="filter.rule.play_alert_sound = $event ? [2, 300] : undefined" />
</td>
<td class="h-11 flex gap-2">
<InputNumber class="w-16" fluid v-if="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"
v-model="filter.rule.play_alert_sound[1]" />
</td>
</tr>
<tr>
<td class="pr-4">
MinimapIcon
</td>
<td class="pr-4">
<ToggleSwitch class="align-middle" :model-value="filter.rule.minimap_icon != undefined"
@update:model-value="filter.rule.minimap_icon = $event ? [2, 'White', 'Circle'] : undefined" />
</td>
<td class="h-11 flex gap-2">
<InputNumber class="w-16" fluid v-if="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" />
</td>
</tr>
<tr>
<td class="pr-4">
PlayEffect
</td>
<td class="pr-4">
<ToggleSwitch class="align-middle" :model-value="filter.rule.play_effect != undefined"
@update:model-value="filter.rule.play_effect = $event ? 'White' : undefined" />
</td>
<td class="h-11 flex gap-2">
<Select v-if="filter.rule.play_effect" v-model="filter.rule.play_effect" :options="COLORS" />
</td>
</tr>
</tbody>
</table>
</TabPanel>
</TabPanels>
</ScrollPanel>
</Tabs>
</template>

View File

@ -5,20 +5,33 @@ import { filterToTreeNode } from '../services/filter';
import * as A from 'fp-ts/lib/Array';
import { pipe } from 'fp-ts/function'
import { Tree } from 'primevue'
import { match } from 'ts-pattern';
const props = defineProps<{
filters: Filter[]
}>()
const nodes = computed(() => pipe(
props.filters,
A.map(filterToTreeNode),
))
const nodes = computed(() => {
return pipe(
props.filters,
A.map(filterToTreeNode),
)
})
const selectedKey = ref()
function nodeIcon(filter: Filter): string {
const icon = match(filter)
.with({ type: 'leaf' }, (f) => f.show ? "eye" : "eye-slash")
.with({ type: 'group' }, () => "folder")
.exhaustive()
return `p-tree-node-icon pi pi-fw pi-${icon}`;
}
</script>
<template>
<Tree :value="nodes" v-model:selectionKeys="selectedKey" selectionMode="single">
<template #nodeicon="{ node }">
<span :class="nodeIcon(node.data)"></span>
</template>
</Tree>
</template>

View File

@ -2,7 +2,6 @@ import PrimeVue from 'primevue/config'
import './style.css'
import './assets/tailwind.css'
import 'primeicons/primeicons.css'
import './base.css'
import { createApp } from 'vue'
import { createPinia } from 'pinia'

View File

@ -26,8 +26,7 @@ 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 = 'Normal' | 'Magic' | 'Rare' | 'Unique'; // Replace with actual type definition
export type Color = any; // 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
@ -37,26 +36,32 @@ export interface RangedNumber<T extends number, U extends number> {
max: U;
}
export interface Color {
r: number,
g: number,
b: number
}
export interface FilterRule {
class?: ItemClass[];
base_type?: ItemBaseType[];
area_level?: [Op, Level];
drop_level?: [Op, Level];
item_level?: [Op, Level];
rarity?: [Op, ItemRarity];
sockets?: [Op, number];
quality?: [Op, number];
stack_size?: [Op, 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?: [Op, RangedNumber<1, 16>];
waystone_tier?: [string, number]; //
// effects
set_font_size?: RangedNumber<1, 45>;
set_text_color?: Color;
set_border_color?: Color;
set_background_color?: Color;
play_alert_sound?: [RangedNumber<1, 16>, RangedNumber<0, 300>];
play_effect?: GameColor;
minimap_icon?: [RangedNumber<0, 2>, GameColor, MinimapIconShape];
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]; //
}

View File

@ -8,16 +8,11 @@ export function filterToTreeNode(filter: Filter): TreeNode {
.with({ type: 'group' }, (filter) => filter.filters.map(filterToTreeNode))
.exhaustive()
let icon = match(filter)
.with({ type: 'leaf' }, () => "file")
.with({ type: 'group' }, () => "folder")
.exhaustive()
return {
key: filter.id,
data: filter,
label: filter.id,
label: filter.name || filter.id,
children,
icon: `pi pi-${icon}`
}
}

View File

@ -72,3 +72,19 @@
--p-text-hover-muted-color: var(--p-surface-300);
}
/* } */
body {
margin: 0px;
min-height: 100%;
overflow-x: hidden;
overflow-y: auto;
background-color: var(--p-primary-contrast-color);
font-weight: normal;
color: var(--p-text-color);
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.p-colorpicker-panel {
z-index: 10 !important;
}