rework noita objectives + deathcounter
This commit is contained in:
+158
-74
@@ -1,11 +1,22 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, onMounted } from "vue";
|
||||
import dayjs from "dayjs";
|
||||
import {
|
||||
createColumnHelper,
|
||||
useVueTable,
|
||||
getCoreRowModel,
|
||||
getFilteredRowModel,
|
||||
getSortedRowModel,
|
||||
type ColumnFiltersState,
|
||||
type SortingState,
|
||||
} from "@tanstack/vue-table";
|
||||
|
||||
interface Objective {
|
||||
objectiv_id: string;
|
||||
count: number;
|
||||
points_per_objectiv?: number;
|
||||
total_points?: number;
|
||||
first_seen_at: string;
|
||||
seed: string;
|
||||
points_per_objectiv: number;
|
||||
total_points: number;
|
||||
}
|
||||
|
||||
const userInfo = ref({
|
||||
@@ -21,61 +32,91 @@ const isUploading = ref(false);
|
||||
const isDragover = ref(false);
|
||||
const objectives = ref<Objective[]>([]);
|
||||
const objectiveSearchQuery = ref("");
|
||||
const objectiveSortBy = ref<"id" | "count" | "points_per" | "total_points">("id");
|
||||
const objectiveSortDesc = ref(false);
|
||||
const isLoadingLeaderboard = ref(false);
|
||||
const leaderboard = ref<any[]>([]);
|
||||
const isLeaderboardModalOpen = ref(false);
|
||||
|
||||
const filteredObjectives = computed(() => {
|
||||
const query = objectiveSearchQuery.value.toLowerCase();
|
||||
const columnHelper = createColumnHelper<Objective>();
|
||||
const sorting = ref<SortingState>([]);
|
||||
const columnFilters = ref<ColumnFiltersState>([]);
|
||||
|
||||
let filtered = objectives.value;
|
||||
|
||||
if (query) {
|
||||
filtered = filtered.filter(
|
||||
(obj) =>
|
||||
obj.objectiv_id.toLowerCase().includes(query) ||
|
||||
obj.count.toString().includes(query)
|
||||
);
|
||||
}
|
||||
|
||||
const sorted = [...filtered].sort((a, b) => {
|
||||
let aValue: number | string;
|
||||
let bValue: number | string;
|
||||
|
||||
switch (objectiveSortBy.value) {
|
||||
case "points_per":
|
||||
aValue = a.points_per_objectiv || 0;
|
||||
bValue = b.points_per_objectiv || 0;
|
||||
break;
|
||||
case "total_points":
|
||||
aValue = a.total_points || 0;
|
||||
bValue = b.total_points || 0;
|
||||
break;
|
||||
case "id":
|
||||
default:
|
||||
aValue = a.objectiv_id.toLowerCase();
|
||||
bValue = b.objectiv_id.toLowerCase();
|
||||
}
|
||||
|
||||
if (aValue < bValue) return objectiveSortDesc.value ? 1 : -1;
|
||||
if (aValue > bValue) return objectiveSortDesc.value ? -1 : 1;
|
||||
return 0;
|
||||
});
|
||||
|
||||
return sorted;
|
||||
});
|
||||
|
||||
const toggleObjectiveSort = (column: "id" | "points_per" | "total_points") => {
|
||||
if (objectiveSortBy.value === column) {
|
||||
objectiveSortDesc.value = !objectiveSortDesc.value;
|
||||
} else {
|
||||
objectiveSortBy.value = column;
|
||||
objectiveSortDesc.value = false;
|
||||
}
|
||||
const formatDate = (dateString: string) => {
|
||||
const date = dayjs(dateString);
|
||||
return date.format("MMM DD, YYYY HH:mm");
|
||||
};
|
||||
|
||||
const getDateTooltip = (dateString: string) => {
|
||||
const date = dayjs(dateString);
|
||||
return date.format("dddd, MMMM D, YYYY [at] h:mm A");
|
||||
};
|
||||
|
||||
const columns = [
|
||||
columnHelper.accessor("objectiv_id", {
|
||||
header: "Objective ID",
|
||||
cell: (info) => info.getValue(),
|
||||
}),
|
||||
columnHelper.accessor("total_points", {
|
||||
header: "Total Points",
|
||||
cell: (info) => info.getValue() || 0,
|
||||
}),
|
||||
columnHelper.accessor("first_seen_at", {
|
||||
header: "First seen",
|
||||
cell: (info) => formatDate(info.getValue()),
|
||||
sortingFn: (rowA, rowB) => {
|
||||
const dateA = dayjs(rowA.original.first_seen_at);
|
||||
const dateB = dayjs(rowB.original.first_seen_at);
|
||||
return dateA.isBefore(dateB) ? -1 : dateA.isAfter(dateB) ? 1 : 0;
|
||||
},
|
||||
}),
|
||||
columnHelper.accessor("seed", {
|
||||
header: "Seed",
|
||||
cell: (info) => info.getValue(),
|
||||
}),
|
||||
];
|
||||
|
||||
const table = computed(() =>
|
||||
useVueTable({
|
||||
get data() {
|
||||
return objectives.value;
|
||||
},
|
||||
columns,
|
||||
state: {
|
||||
get sorting() {
|
||||
return sorting.value;
|
||||
},
|
||||
get columnFilters() {
|
||||
return columnFilters.value;
|
||||
},
|
||||
},
|
||||
onSortingChange: (updater) => {
|
||||
sorting.value =
|
||||
typeof updater === "function" ? updater(sorting.value) : updater;
|
||||
},
|
||||
onColumnFiltersChange: (updater) => {
|
||||
columnFilters.value =
|
||||
typeof updater === "function" ? updater(columnFilters.value) : updater;
|
||||
},
|
||||
getCoreRowModel: getCoreRowModel(),
|
||||
getFilteredRowModel: getFilteredRowModel(),
|
||||
getSortedRowModel: getSortedRowModel(),
|
||||
filterFns: {
|
||||
fuzzy: (row, columnId, value) => {
|
||||
const itemData = row.getValue(columnId);
|
||||
const searchValue = value.toLowerCase();
|
||||
if (columnId === "first_seen_at") {
|
||||
const dateStr = itemData as string;
|
||||
const formatted = dayjs(dateStr).format("MMM DD, YYYY HH:mm");
|
||||
return formatted.toLowerCase().includes(searchValue);
|
||||
}
|
||||
return String(itemData).toLowerCase().includes(searchValue);
|
||||
},
|
||||
},
|
||||
globalFilterFn: "fuzzy",
|
||||
})
|
||||
);
|
||||
|
||||
const filteredObjectives = computed(() => table.value.getRowModel().rows);
|
||||
|
||||
const handleFileUpload = (event: Event) => {
|
||||
const input = event.target as HTMLInputElement;
|
||||
if (input.files) {
|
||||
@@ -387,8 +428,18 @@ onMounted(() => {
|
||||
|
||||
<div v-if="objectives.length > 0" class="space-y-4">
|
||||
<!-- Search Input -->
|
||||
<input v-model="objectiveSearchQuery" type="text" placeholder="Search objectives..."
|
||||
class="input input-bordered w-full" />
|
||||
<input
|
||||
:value="columnFilters.find((f) => f.id === 'objectiv_id')?.value ?? ''"
|
||||
@input="
|
||||
(e) => {
|
||||
const target = e.target as HTMLInputElement;
|
||||
table.getColumn('objectiv_id')?.setFilterValue(target.value);
|
||||
}
|
||||
"
|
||||
type="text"
|
||||
placeholder="Search objectives..."
|
||||
class="input input-bordered w-full"
|
||||
/>
|
||||
|
||||
<!-- Results Summary -->
|
||||
<div class="text-sm text-base-content/70">
|
||||
@@ -400,31 +451,64 @@ onMounted(() => {
|
||||
<table class="table table-zebra w-full">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="cursor-pointer hover:bg-base-300" @click="toggleObjectiveSort('id')">
|
||||
Objective ID
|
||||
<i v-if="objectiveSortBy === 'id'"
|
||||
:class="['mdi ml-2', objectiveSortDesc ? 'mdi-arrow-down' : 'mdi-arrow-up']"></i>
|
||||
</th>
|
||||
<th class="text-right cursor-pointer hover:bg-base-300"
|
||||
@click="toggleObjectiveSort('total_points')">
|
||||
Total Points
|
||||
<i v-if="objectiveSortBy === 'total_points'"
|
||||
:class="['mdi ml-2', objectiveSortDesc ? 'mdi-arrow-down' : 'mdi-arrow-up']"></i>
|
||||
<th
|
||||
v-for="header in table.getHeaderGroups()[0]?.headers"
|
||||
:key="header.id"
|
||||
:class="[
|
||||
'cursor-pointer hover:bg-base-300',
|
||||
header.column.columnDef.id === 'objectiv_id' ? 'text-left' : 'text-right',
|
||||
]"
|
||||
@click="header.column.toggleSorting()"
|
||||
>
|
||||
<div class="flex items-center justify-between">
|
||||
<span v-if="header.column.columnDef.id === 'objectiv_id'">
|
||||
{{ header.isPlaceholder ? null : header.column.columnDef.header }}
|
||||
</span>
|
||||
<span v-else class="ml-auto">
|
||||
{{ header.isPlaceholder ? null : header.column.columnDef.header }}
|
||||
</span>
|
||||
<i
|
||||
v-if="header.column.getIsSorted()"
|
||||
:class="[
|
||||
'mdi ml-2',
|
||||
header.column.getIsSorted() === 'desc'
|
||||
? 'mdi-arrow-down'
|
||||
: 'mdi-arrow-up',
|
||||
]"
|
||||
></i>
|
||||
</div>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="obj in filteredObjectives" :key="obj.objectiv_id">
|
||||
<td class="font-medium">
|
||||
<a :href="`https://noita.wiki.gg/wiki/${obj.objectiv_id}`" target="_blank">
|
||||
{{ obj.objectiv_id }}
|
||||
<i class="mdi mdi-open-in-new"></i>
|
||||
</a>
|
||||
</td>
|
||||
<td class="text-right font-bold text-success">
|
||||
{{ obj.total_points || 0 }}
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<tr v-for="row in filteredObjectives" :key="row.id">
|
||||
<td
|
||||
v-for="cell in row.getVisibleCells()"
|
||||
:key="cell.id"
|
||||
:class="[
|
||||
cell.column.id === 'objectiv_id'
|
||||
? 'font-medium'
|
||||
: 'text-right',
|
||||
cell.column.id === 'total_points' ? 'font-bold text-primary' : '',
|
||||
]"
|
||||
>
|
||||
<template v-if="cell.column.id === 'objectiv_id'">
|
||||
<a
|
||||
:href="`https://noita.wiki.gg/wiki/${row.original.objectiv_id}`"
|
||||
target="_blank"
|
||||
>
|
||||
{{ row.original.objectiv_id }}
|
||||
<i class="mdi mdi-open-in-new"></i>
|
||||
</a>
|
||||
</template>
|
||||
<template v-else-if="cell.column.id === 'first_seen_at'">
|
||||
<span :title="getDateTooltip(row.original.first_seen_at)">
|
||||
{{ formatDate(row.original.first_seen_at) }}
|
||||
</span>
|
||||
</template>
|
||||
<template v-else>
|
||||
{{ cell.renderValue() }}
|
||||
</template>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
|
||||
Reference in New Issue
Block a user