This commit is contained in:
Leon Liu 2025-01-01 09:51:34 +09:00
parent c91e9d1040
commit 28b3b9dc24
14 changed files with 251 additions and 32 deletions

BIN
bun.lockb

Binary file not shown.

View File

@ -1,13 +1,21 @@
<!DOCTYPE html>
<!doctype html>
<html lang="">
<head>
<meta charset="UTF-8">
<link rel="icon" href="/favicon.ico">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<script>
document.documentElement.classList.toggle(
'dark',
localStorage.theme === 'dark' ||
(!('theme' in localStorage) && window.matchMedia('(prefers-color-scheme: dark)').matches),
)
</script>
<link rel="stylesheet" href="https://rsms.me/inter/inter.css">
<title>Vite App</title>
</head>
<body>
<div id="app"></div>
<div class="bg-surface-0 dark:bg-surface-900 min-h-screen p-4 flex flex-col" id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>

View File

@ -13,11 +13,16 @@
"format": "prettier --write src/"
},
"dependencies": {
"fp-ts": "^2.16.9",
"pinia": "^2.3.0",
"primeicons": "^7.0.0",
"primevue": "^4.2.5",
"ts-pattern": "^5.6.0",
"uuid": "^11.0.3",
"vue": "^3.5.13"
},
"devDependencies": {
"@tailwindcss/typography": "^0.5.15",
"@tsconfig/node22": "^22.0.0",
"@types/node": "^22.10.2",
"@vitejs/plugin-vue": "^5.2.1",

View File

@ -1,12 +1,64 @@
<script setup lang="ts">
import DatePicker from 'primevue/datepicker'
import { ref } from 'vue'
import { Button, } 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 * 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'
const date = ref()
const darkMode = ref(document.documentElement.classList.contains('dark'))
const icon = computed(() => `pi pi-${darkMode.value ? "moon" : "sun"}`)
watch(darkMode, (dark) => {
document.documentElement.classList.toggle(
'dark',
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: {} },
{
type: 'group', id: uuid.v4(), name: "", enabled: true, rule: {}, filters: [
{ type: 'leaf', id: uuid.v4(), name: "", show: true, enabled: true, rule: {} }
]
}
]
const filters = ref(pipe(
localStorage.getItem("filters"),
O.fromNullable,
O.map((value): FilterConfig => JSON.parse(value)),
O.map(value => value.filters),
O.getOrElse(() => defaultFilters),
))
const selectedFilter = ref<Filter>()
</script>
<template>
<div class="flex items-center justify-center min-h-screen">
<DatePicker v-model="date" />
<div class="flex flex-row gap-2 items-center flex-grow-0">
<article class="prose dark:prose-invert">
<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">
<TreeNav :filters @nodeSelect="selectedFilter = $event.data" @nodeUnselect="selectedFilter = undefined" />
</div>
<div class="flex-1">
<FilterDetail v-if="selectedFilter" :filter="selectedFilter" />
<Info v-else />
</div>
</div>
</template>

3
src/base.css Normal file
View File

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

View File

@ -0,0 +1,19 @@
<script setup lang="ts">
import type { Filter } from '@/models';
import { ref, watchEffect } from 'vue'
import { ToggleButton, SelectButton, Card } from 'primevue'
const props = defineProps<{
filter: Filter
}>()
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" />
</template>

10
src/components/Info.vue Normal file
View File

@ -0,0 +1,10 @@
<script setup lang="ts">
import { ref } from 'vue'
</script>
<template>
<article class="prose dark:prose-invert">
<h2>How to use:</h2>
</article>
</template>

View File

@ -0,0 +1,24 @@
<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'
const props = defineProps<{
filters: Filter[]
}>()
const nodes = computed(() => pipe(
props.filters,
A.map(filterToTreeNode),
))
const selectedKey = ref()
</script>
<template>
<Tree :value="nodes" v-model:selectionKeys="selectedKey" selectionMode="single">
</Tree>
</template>

View File

@ -1,6 +1,8 @@
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'
@ -10,9 +12,7 @@ const app = createApp(App)
app.use(createPinia())
app.use(PrimeVue, {
theme: {
preset: 'none',
},
theme: 'none'
})
app.mount('#app')

62
src/models/index.ts Normal file
View File

@ -0,0 +1,62 @@
interface _Filter {
id: string;
name: string;
enabled: boolean;
rule: FilterRule;
}
export interface FilterConfig {
filters: Filter[]
}
export type FilterLeaf = _Filter & {
type: 'leaf';
show: boolean;
}
export type FilterGroup = _Filter & {
type: 'group';
filters: 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 = 'Normal' | 'Magic' | 'Rare' | 'Unique'; // Replace with actual type definition
export type Color = any; // 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 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];
// waystones
waystone_tier?: [Op, RangedNumber<1, 16>];
// 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];
}

26
src/services/filter.ts Normal file
View File

@ -0,0 +1,26 @@
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()
let icon = match(filter)
.with({ type: 'leaf' }, () => "file")
.with({ type: 'group' }, () => "folder")
.exhaustive()
return {
key: filter.id,
data: filter,
label: filter.id,
children,
icon: `pi pi-${icon}`
}
}
export function treeNodeToFilter(node: TreeNode): Filter {
return node.data;
}

View File

@ -53,22 +53,22 @@
* should be;
* :root[class="app-dark"] {
*/
@media (prefers-color-scheme: dark) {
:root {
--p-primary-color: var(--p-primary-400);
--p-primary-contrast-color: var(--p-surface-900);
--p-primary-hover-color: var(--p-primary-300);
--p-primary-active-color: var(--p-primary-200);
--p-content-border-color: var(--p-surface-700);
--p-content-hover-background: var(--p-surface-800);
--p-content-hover-color: var(--p-surface-0);
--p-highlight-background: color-mix(in srgb, var(--p-primary-400), transparent 84%);
--p-highlight-color: rgba(255, 255, 255, 0.87);
--p-highlight-focus-background: color-mix(in srgb, var(--p-primary-400), transparent 76%);
--p-highlight-focus-color: rgba(255, 255, 255, 0.87);
--p-text-color: var(--p-surface-0);
--p-text-hover-color: var(--p-surface-0);
--p-text-muted-color: var(--p-surface-400);
--p-text-hover-muted-color: var(--p-surface-300);
}
/* @media (prefers-color-scheme: dark) { */
:root[class='dark'] {
--p-primary-color: var(--p-primary-400);
--p-primary-contrast-color: var(--p-surface-900);
--p-primary-hover-color: var(--p-primary-300);
--p-primary-active-color: var(--p-primary-200);
--p-content-border-color: var(--p-surface-700);
--p-content-hover-background: var(--p-surface-800);
--p-content-hover-color: var(--p-surface-0);
--p-highlight-background: color-mix(in srgb, var(--p-primary-400), transparent 84%);
--p-highlight-color: rgba(255, 255, 255, 0.87);
--p-highlight-focus-background: color-mix(in srgb, var(--p-primary-400), transparent 76%);
--p-highlight-focus-color: rgba(255, 255, 255, 0.87);
--p-text-color: var(--p-surface-0);
--p-text-hover-color: var(--p-surface-0);
--p-text-muted-color: var(--p-surface-400);
--p-text-hover-muted-color: var(--p-surface-300);
}
/* } */

View File

@ -1,7 +1,17 @@
/** @type {import('tailwindcss').Config} */
import primeui from 'tailwindcss-primeui'
import typography from '@tailwindcss/typography'
import defaultTheme from 'tailwindcss/defaultTheme'
export default {
content: ['./index.html', './src/**/*.{vue,js,ts,jsx,tsx}'],
plugins: [primeui],
darkMode: 'selector',
theme: {
extend: {
fontFamily: {
sans: ['InterVariable', ...defaultTheme.fontFamily.sans],
},
},
},
plugins: [primeui, typography],
}