This commit is contained in:
Leon Liu 2025-01-01 19:43:07 +09:00
parent c65dbcb8fc
commit a23e3c2e60
7 changed files with 175 additions and 45 deletions

View File

@ -9,7 +9,8 @@ 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 } from './models'
import type { Filter, FilterConfig, FilterNode } from './models'
import { filterToTreeNode } from './services/filter'
const darkMode = ref(document.documentElement.classList.contains('dark'))
@ -22,11 +23,6 @@ watch(darkMode, (dark) => {
)
localStorage.theme = dark ? 'dark' : 'light'
})
function onNodeSelect(node: TreeNode) {
}
function onNodeUnselect(node: TreeNode) {
}
let defaultFilters: Filter[] = [
{ type: 'leaf', id: uuid.v4(), name: "", show: true, enabled: true, rule: {} },
@ -36,28 +32,41 @@ let defaultFilters: Filter[] = [
]
}
]
const filters = ref(pipe(
const nodes = ref(pipe(
localStorage.getItem("filters"),
O.fromNullable,
O.map((value): FilterConfig => JSON.parse(value)),
O.map(value => value.filters),
O.getOrElse(() => defaultFilters),
A.map(filterToTreeNode)
))
const selectedFilter = ref<Filter>()
const selectedFilter = ref<FilterNode>()
function onDelete() {
let target = selectedFilter.value!
let array = nodes.value as FilterNode[]
if (target.parent) {
array = target.parent.children!
}
let index = array.findIndex(item => item === target)
array.splice(index, 1)
selectedFilter.value = undefined
}
</script>
<template>
<div class="flex flex-row gap-2 items-center flex-grow-0">
<article class="prose dark:prose-invert">
<article class="prose dark:prose-invert p-4">
<h1>POE2 Loot Filter Config</h1>
</article><Button :icon @click="darkMode = !darkMode" severity="secondary" variant="outlined" rounded />
</div>
<Splitter class="flex-1 min-h-0">
<SplitterPanel>
<TreeNav :filters @nodeSelect="selectedFilter = $event.data" @nodeUnselect="selectedFilter = undefined" />
<SplitterPanel class="flex flex-col">
<TreeNav :nodes @nodeSelect="selectedFilter = $event" @nodeUnselect="selectedFilter = undefined" />
</SplitterPanel>
<SplitterPanel class="flex flex-col">
<FilterDetail v-if="selectedFilter" :filter="selectedFilter" />
<FilterDetail v-if="selectedFilter" :node="selectedFilter" @delete="onDelete" />
<Info v-else />
</SplitterPanel>
</Splitter>

View File

@ -1,9 +1,10 @@
<script setup lang="ts">
import type { Filter } from '@/models';
import { ref, watchEffect } from 'vue'
import type { Filter, FilterNode } 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'
const props = defineProps<{
filter: Filter
node: FilterNode,
onDelete: () => void
}>()
const OPERATORS = ['=', '==', '!=', '<', '<=', '>', '>='];
@ -67,6 +68,7 @@ const BASE_TYPES = [
// }, 250);
// }
const filter = computed(() => props.node.data)
</script>
<template>
@ -81,7 +83,7 @@ const BASE_TYPES = [
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" />
<Button class="flex-shrink-0" icon="pi pi-trash" severity="danger" v-on:click="() => onDelete()" />
</div>
<Tabs value="0" class="flex-1 min-h-0">

View File

@ -0,0 +1,47 @@
<script setup lang="ts">
import { defaultGroup, defaultLeaf, type FilterNode } from '@/models';
import { filterToTreeNode } from '@/services/filter';
import { Button, Menu } from 'primevue';
import { ref } from 'vue'
const props = defineProps<{
node: FilterNode
}>()
const menu = ref();
const items = ref([
{
label: "Create new...",
items: [
{
label: 'End Rule',
icon: 'pi pi-plus-circle',
command: () => {
props.node.children?.push(filterToTreeNode(defaultLeaf(), props.node) as FilterNode);
}
},
{
label: 'Group',
icon: 'pi pi-folder-plus',
command: () => {
props.node.children?.push(filterToTreeNode(defaultGroup(), props.node) as FilterNode);
}
}
]
}
]);
const toggle = (event: MouseEvent) => {
event.stopPropagation();
menu.value.toggle(event);
};
</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>
</div>
</template>

View File

@ -1,22 +1,16 @@
<script setup lang="ts">
import { computed, ref } from 'vue'
import type { Filter, FilterConfig } from '../models';
import { filterToTreeNode } from '../services/filter';
import * as A from 'fp-ts/lib/Array';
import { pipe } from 'fp-ts/function'
import { Tree } from 'primevue'
import { onUpdated, ref } from 'vue'
import { defaultGroup, defaultLeaf, type Filter, type FilterNode as FilterNodeType } from '../models';
import FilterNode from './FilterNode.vue';
import { Tree, Button, Menu, ScrollPanel } from 'primevue'
import { match } from 'ts-pattern';
import type { TreeNode } from 'primevue/treenode';
import { filterToTreeNode } from '@/services/filter';
const props = defineProps<{
filters: Filter[]
nodes: TreeNode[]
}>()
const nodes = computed(() => {
return pipe(
props.filters,
A.map(filterToTreeNode),
)
})
const emit = defineEmits(['nodeSelect', 'nodeUnselect'])
const selectedKey = ref()
function nodeIcon(filter: Filter): string {
@ -26,12 +20,58 @@ function nodeIcon(filter: Filter): string {
.exhaustive()
return `p-tree-node-icon pi pi-fw pi-${icon}`;
}
function asFilterNode(a: TreeNode) {
return a as FilterNodeType
}
const menu = ref();
const items = ref([
{
label: "Create new...",
items: [
{
label: 'End Rule',
icon: 'pi pi-plus-circle',
command: () => {
props.nodes.push(filterToTreeNode(defaultLeaf()) as FilterNodeType);
}
},
{
label: 'Group',
icon: 'pi pi-folder-plus',
command: () => {
props.nodes.push(filterToTreeNode(defaultGroup()) as FilterNodeType);
}
}
]
}
]);
const toggle = (event: MouseEvent) => {
event.stopPropagation();
menu.value.toggle(event);
};
</script>
<template>
<Tree :value="nodes" v-model:selectionKeys="selectedKey" selectionMode="single">
<template #nodeicon="{ node }">
<span :class="nodeIcon(node.data)"></span>
</template>
</Tree>
<div class="flex flex-shrink-0 mr-6 ml-4 items-center">
<article class="prose dark:prose-invert flex-1">
<h2>List of Rules:</h2>
</article>
<Button class="flex-shrink-0" icon="pi pi-plus" rounded variant="text" size="small" @click="toggle" />
<Menu ref="menu" :model="items" :popup="true" />
</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)">
<template #nodeicon="{ node }">
<span :class="nodeIcon(node.data)"></span>
</template>
<template #default="{ node }">
<FilterNode :node="asFilterNode(node)" />
</template>
</Tree>
</ScrollPanel>
</template>

View File

@ -1,3 +1,4 @@
import * as uuid from 'uuid';
interface _Filter {
id: string;
@ -6,6 +7,28 @@ interface _Filter {
rule: FilterRule;
}
export function defaultLeaf(): FilterLeaf {
return {
type: "leaf",
id: uuid.v4(),
name: "",
enabled: true,
rule: {},
show: true
}
}
export function defaultGroup(): FilterGroup {
return {
type: "group",
id: uuid.v4(),
name: "",
enabled: true,
rule: {},
filters: []
}
}
export interface FilterConfig {
filters: Filter[]
}
@ -65,3 +88,10 @@ export interface FilterRule {
play_effect?: string;//
minimap_icon?: [number, string, string]; //
}
export interface FilterNode {
key: string
parent?: FilterNode
data: Filter
children?: FilterNode[]
}

View File

@ -1,19 +1,17 @@
import type { Filter } from '@/models'
import type { TreeNode } from 'primevue/treenode'
import { match } from 'ts-pattern'
export function filterToTreeNode(filter: Filter): TreeNode {
let children = match(filter)
.with({ type: 'leaf' }, (filter) => undefined)
.with({ type: 'group' }, (filter) => filter.filters.map(filterToTreeNode))
.exhaustive()
return {
export function filterToTreeNode(filter: Filter, parent?: TreeNode): TreeNode {
let node: TreeNode = {
key: filter.id,
data: filter,
label: filter.name || filter.id,
children,
parent
}
if (filter.type === 'group' ) {
node.children = filter.filters.map(f => filterToTreeNode(f, node))
}
return node
}
export function treeNodeToFilter(node: TreeNode): Filter {

View File

@ -88,3 +88,7 @@ body {
.p-colorpicker-panel {
z-index: 10 !important;
}
.p-tree-node-label {
flex: 1;
}