From 3d30ab085fefe3bbb0c5abfc3a37da32900fd6c5 Mon Sep 17 00:00:00 2001 From: a Date: Sun, 25 May 2025 00:17:41 -0500 Subject: [PATCH] noot --- package.json | 7 + public/cursor.png | Bin 0 -> 4227 bytes src/App.tsx | 16 +- src/components/characters.tsx | 21 +- src/components/inventory/index.tsx | 149 ++++++++++++ .../movetarget.tsx} | 116 +-------- src/components/inventory/table.tsx | 90 +++++++ src/index.css | 23 +- src/index.tsx | 14 +- src/lib/columns/column.ts | 2 +- src/lib/lifeto/lifeto.ts | 1 + src/lib/storage.ts | 2 +- src/lib/superjson.ts | 1 + src/lib/table.ts | 67 +----- src/lib/table/defs.ts | 11 + src/lib/table/tanstack.tsx | 138 +++++++++++ src/lib/trickster.ts | 3 + src/main.ts | 12 - src/state/atoms.ts | 223 +++++++++++++++++- src/state/storage.ts | 116 +++++++++ tsconfig.json | 3 + vite.config.ts | 6 + yarn.lock | 59 +++++ 23 files changed, 861 insertions(+), 219 deletions(-) create mode 100644 public/cursor.png create mode 100644 src/components/inventory/index.tsx rename src/components/{inventory.tsx => inventory/movetarget.tsx} (57%) create mode 100644 src/components/inventory/table.tsx create mode 100644 src/lib/superjson.ts create mode 100644 src/lib/table/defs.ts create mode 100644 src/lib/table/tanstack.tsx delete mode 100644 src/main.ts create mode 100644 src/state/storage.ts diff --git a/package.json b/package.json index 25b4497..ff2b2f0 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ "@handsontable/react": "^15.3.0", "@mantine/hooks": "^8.0.0", "@tanstack/react-query": "^5.76.0", + "@tanstack/react-table": "^8.21.3", "@types/qs": "^6.9.18", "@types/react": "^19.1.4", "@types/react-dom": "^19.1.5", @@ -19,6 +20,7 @@ "@typescript-eslint/eslint-plugin": "^8.32.1", "@typescript-eslint/parser": "^8.32.1", "@vitejs/plugin-react": "^4.4.1", + "arktype": "^2.1.20", "axios": "^1.9.0", "eslint": "^9.26.0", "eslint-config-react-app": "^7.0.1", @@ -28,15 +30,19 @@ "fuse.js": "^7.1.0", "handsontable": "^15.3.0", "jotai": "^2.12.4", + "jotai-optics": "^0.4.0", "jotai-tanstack-query": "^0.9.0", "loglevel": "^1.9.2", + "optics-ts": "^2.4.1", "pinia": "^3.0.2", "prettier": "^3.5.3", "qs": "^6.14.0", "react": "^19.1.0", "react-dom": "^19.1.0", + "react-icons": "^5.5.0", "react-select": "^5.10.1", "react-spinners": "^0.17.0", + "superjson": "^2.2.2", "typescript-cookie": "^1.0.6", "use-local-storage": "^3.0.0", "usehooks-ts": "^3.1.1", @@ -44,6 +50,7 @@ }, "devDependencies": { "@tailwindcss/postcss": "^4.1.6", + "@types/node": "^22.15.18", "postcss": "^8.5.3", "tailwindcss": "^4.1.6", "typescript": "^5.8.3", diff --git a/public/cursor.png b/public/cursor.png new file mode 100644 index 0000000000000000000000000000000000000000..403f28398a7224939846f982fd070877705766ba GIT binary patch literal 4227 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 { return ( <> -
-
-
- -
+
+
+ +
-
- -
-
+
diff --git a/src/components/characters.tsx b/src/components/characters.tsx index 1be2dac..e8b1516 100644 --- a/src/components/characters.tsx +++ b/src/components/characters.tsx @@ -1,7 +1,6 @@ import { TricksterCharacter } from "../lib/trickster" -import { useSessionContext } from "../context/SessionContext" import Fuse from 'fuse.js' -import { useAtom, useSetAtom } from "jotai" +import { useAtom } from "jotai" import { charactersAtom, selectedCharacterAtom } from "../state/atoms" import { useMemo, useState } from "react"; import { @@ -136,24 +135,24 @@ export const CharacterRoulette = ()=>{ return } const searchResults = fuse.search(search || "!-----", { - limit: 20, - }).map((x)=>{ - return
- - -
- }) + limit: 20, + }).map((x)=>{ + return
+ + +
+ }) return <>
{ setSearch(e.target.value) }} > -
+
{searchResults ? searchResults : <> }
diff --git a/src/components/inventory/index.tsx b/src/components/inventory/index.tsx new file mode 100644 index 0000000..79c0e0a --- /dev/null +++ b/src/components/inventory/index.tsx @@ -0,0 +1,149 @@ +import { clearItemSelectionActionAtom, currentCharacterItemsAtom, filteredCharacterItemsAtom, inventoryFilterAtom, inventoryItemsCurrentPageAtom, inventoryPageRangeAtom, itemSelectionSelectAllFilterActionAtom, itemSelectionSelectAllPageActionAtom, paginateInventoryActionAtom, preferenceInventorySearch, selectedCharacterAtom, setInventoryFilterTabActionAtom} from "@/state/atoms"; +import {useAtom, useAtomValue, useSetAtom } from "jotai"; +import { InventoryTargetSelector } from './movetarget'; +import { InventoryTable } from './table'; +import { FaArrowLeft, FaArrowRight } from "react-icons/fa"; + + + + +const sections = [ + { name: 'all', value: '' }, + { name: 'consume', value: '1' }, + { name: 'equip', value: '2' }, + { name: 'drill', value: '3' }, + { name: 'pet', value: '4' }, + { name: 'etc', value: '5' }, + +] + +const cardSections = [ + { name: 'skill', value: '10' }, + { name: 'char', value: '11' }, + { name: 'mon', value: '12' }, + { name: 'fortune', value: '13' }, + { name: 'secret', value: '14' }, + { name: 'arcana', value: '15' }, +] + +const InventoryTabs = ()=> { + + const inventoryFilter= useAtomValue(inventoryFilterAtom) + const setInventoryFilterTab = useSetAtom(setInventoryFilterTabActionAtom) + const inventoryRange = useAtomValue(inventoryPageRangeAtom) + const items = useAtomValue(filteredCharacterItemsAtom) + console.log("items", items) + const sharedStyle = "hover:cursor-pointer hover:bg-gray-200 px-2 pr-4 border border-gray-200" + const selectedStyle = "bg-gray-200 border-b-2 border-black-1" + return
+
+
+ {sections.map(x=>{ + return
{ + setInventoryFilterTab(x.value) + }} + key={x.name} + className={`${sharedStyle} +${inventoryFilter.tab === x.value ? selectedStyle : ""}`} + >{x.name}
+ })} +
+
+ {cardSections.map(x=>{ + return
{ + setInventoryFilterTab(x.value) + }} + key={x.name} + className={`${sharedStyle} +${inventoryFilter.tab === x.value ? selectedStyle : ""}`} + >{x.name}
+ })} +
+
+
+
{inventoryRange.start}..{inventoryRange.end}/{items.length}
+
+
+} + +export const Inventory = () => { + + const selectedCharacter = useAtomValue(selectedCharacterAtom) + const clearItemSelection = useSetAtom(clearItemSelectionActionAtom) + + const addPageItemSelection = useSetAtom(itemSelectionSelectAllPageActionAtom) + const addFilterItemSelection = useSetAtom(itemSelectionSelectAllFilterActionAtom) + const [search, setSearch] = useAtom(preferenceInventorySearch) + + + const paginateInventory = useSetAtom(paginateInventoryActionAtom) + + if(!selectedCharacter){ + return
+ select a character +
+ } + return
+
+
+
+
{ + addPageItemSelection() + }} + >select filtered
+
{ + addFilterItemSelection() + }} + >select page
+
{ + clearItemSelection() + }} + >clear
+
+
+ +
{ + // sendOrders() + }} + className="hover:cursor-pointer whitespace-preborder border-black-1 bg-orange-200 hover:bg-orange-300 px-2 py-1">Move Selected
+
+
+ +
+
+ { + setSearch(e.target.value) + }} + /> +
{ + paginateInventory(-1) + }} + >
+
{ + paginateInventory(1) + }} + >
+
+
+
+ +
+ +
+
+} diff --git a/src/components/inventory.tsx b/src/components/inventory/movetarget.tsx similarity index 57% rename from src/components/inventory.tsx rename to src/components/inventory/movetarget.tsx index fd012a8..7b9c861 100644 --- a/src/components/inventory.tsx +++ b/src/components/inventory/movetarget.tsx @@ -1,39 +1,20 @@ -import { TricksterCharacter } from "../lib/trickster" -import { useSessionContext } from "../context/SessionContext" -import 'handsontable/dist/handsontable.full.min.css'; - -import { registerAllModules } from 'handsontable/registry'; -import { HotTable, HotTableClass } from '@handsontable/react'; -import { forwardRef, useCallback, useEffect, useId, useMemo, useRef, useState} from "react"; -import { InventoryTable } from "../lib/table"; -import { DotLoader } from "react-spinners"; -import { useResizeObserver } from "@mantine/hooks"; -import { Columns } from "../lib/columns"; -import { OrderDetails, OrderSender } from "../lib/lifeto/order_manager"; -import log from "loglevel"; -import { charactersAtom, currentCharacterInventoryAtom, currentCharacterItemsAtom, LTOApi, selectedTargetInventoryAtom } from "../state/atoms"; -import Select from 'react-select'; +import { forwardRef, useId, useMemo, useRef, useState} from "react"; import { useAtom, useAtomValue } from "jotai"; import { autoUpdate, flip, FloatingFocusManager, FloatingPortal, size, useDismiss, useFloating, useInteractions, useListNavigation, useRole } from "@floating-ui/react"; import Fuse from "fuse.js"; +import { charactersAtom, selectedTargetInventoryAtom } from "@/state/atoms"; -registerAllModules(); -type Size = { - width?: number - height?: number -} - - - -interface InventoryItemProps { +interface AccountInventorySelectorItemProps { children: React.ReactNode; active: boolean; } -const InventoryItem = forwardRef< + + +const AccountInventorySelectorItem = forwardRef< HTMLDivElement, - InventoryItemProps & React.HTMLProps + AccountInventorySelectorItemProps & React.HTMLProps >(({ children, active, ...rest }, ref) => { const id = useId(); return ( @@ -54,8 +35,7 @@ const InventoryItem = forwardRef<
); }); - -const InventoryTargetSelector = () => { +export const InventoryTargetSelector = () => { const [open, setOpen] = useState(false); const [inputValue, setInputValue] = useState(""); const [activeIndex, setActiveIndex] = useState(null); @@ -133,6 +113,9 @@ const InventoryTargetSelector = () => { value: selectedTargetInventory !== undefined ? selectedTargetInventory.name : inputValue, placeholder: "Target Inventory", "aria-autocomplete": "list", + onFocus() { + setOpen(true); + }, onKeyDown(event) { if ( event.key === "Enter" && @@ -166,7 +149,7 @@ const InventoryTargetSelector = () => { })} > {items.map((item, index) => ( - { active={activeIndex === index} > {item.name} - + ))}
@@ -191,76 +174,3 @@ const InventoryTargetSelector = () => { ); } -export const Inventory = () => { - const {activeTable, columns, tags, orders} = useSessionContext() - - const [ref, {height}] = useResizeObserver({}) - - const {data:character, isLoading, isFetching } = useAtomValue(currentCharacterInventoryAtom) -//const sendOrders = useCallback(()=>{ -// if(!hotTableComponent.current?.hotInstance){ -// return -// } -// const hott = hotTableComponent.current?.hotInstance -// const headers = hott.getColHeader() -// const dat = hott.getData() -// const idxNumber = headers.indexOf(Columns.MoveCount.displayName) -// const idxTarget = headers.indexOf(Columns.Move.displayName) -// const origin = activeTable -// const pending:OrderDetails[] = []; -// for(const row of dat) { -// try{ -// const nm = Number(row[idxNumber].replace("x","")) -// const target = (row[idxTarget] as string).replaceAll("-","").trim() -// if(!isNaN(nm) && nm > 0 && target.length > 0){ -// const info:OrderDetails = { -// item_uid: row[0].toString(), -// count: nm, -// origin_path: activeTable, -// target_path: target, -// } -// pending.push(info) -// } -// }catch(e){ -// } -// } -// log.debug("OrderDetails", pending) -// const chars = new Map() -// const manager = new OrderSender(orders, chars) -// for(const d of pending){ -// const order = manager.send(d) -// //order.tick(api) -// } -//}, [orders]) - - const Loading = ()=>{ - return
-
- -
-
- } - - const items = useAtomValue(currentCharacterItemsAtom) - - return
-
- -
{ - // sendOrders() - }} - className=" - hover:cursor-pointer - border border-black-1 - bg-green-200 - px-2 py-1 - ">Move Selected
-
- {(isLoading || isFetching) ? : <> -
- total: {items.size} -
- } -
-} diff --git a/src/components/inventory/table.tsx b/src/components/inventory/table.tsx new file mode 100644 index 0000000..a8653d2 --- /dev/null +++ b/src/components/inventory/table.tsx @@ -0,0 +1,90 @@ +import { StatsColumns } from "@/lib/columns" +import { ItemWithSelection } from "@/lib/table/defs" +import { InventoryColumns } from "@/lib/table/tanstack" +import { inventoryItemsCurrentPageAtom, preferenceInventoryTab } from "@/state/atoms" +import { flexRender, getCoreRowModel, useReactTable } from "@tanstack/react-table" +import { atom, useAtom, useAtomValue } from "jotai" +import { useMemo } from "react" + + +const columnVisibilityAtom = atom((get)=>{ + const itemTab = get(preferenceInventoryTab) + if(!["2","4"].includes(itemTab)) { + return Object.fromEntries([ + ...StatsColumns.map(x=>["stats."+x,false]), + ["slots",false] + ]) + } + return { + } +}) +export const InventoryTable = () => { + + const items = useAtomValue(inventoryItemsCurrentPageAtom) + + const columns = useMemo(()=>{ + return [ + ...Object.values(InventoryColumns) + ] + }, []) + + const columnVisibility = useAtomValue(columnVisibilityAtom) + console.log(columnVisibility) + + const table = useReactTable({ + getRowId: row =>row.item.unique_id.toString(), + data: items, + state: { + columnVisibility, + }, + columns, + getCoreRowModel: getCoreRowModel(), + }) + + return ( +
+ { + e.preventDefault() + return + }} + className="border-spacing-x-2 border-separate"> + + {table.getHeaderGroups().map(headerGroup => ( + + {headerGroup.headers.map(header => ( + + ))} + + ))} + + + {table.getRowModel().rows.map(row => ( + + {row.getVisibleCells().map(cell => ( + + ))} + + ))} + +
+ {header.isPlaceholder + ? null + : flexRender( + header.column.columnDef.header, + header.getContext() + )} +
+ {flexRender(cell.column.columnDef.cell, cell.getContext())} +
+
+ ) +} diff --git a/src/index.css b/src/index.css index ab3d907..73faccc 100644 --- a/src/index.css +++ b/src/index.css @@ -1,13 +1,22 @@ @import 'tailwindcss'; -/* - The default border color has changed to `currentcolor` in Tailwind CSS v4, - so we've added these compatibility styles to make sure everything still - looks the same as it did with Tailwind CSS v3. +html { + cursor: url(/public/cursor.png), auto !important; +} - If we ever want to remove these styles, we need to add an explicit border - color utility to any element that depends on these defaults. -*/ +@theme { + --cursor-default: url(/public/cursor.png), auto !important; + --cursor-pointer: url(/public/cursor.png), pointer !important; + --cursor-text: url(/public/cursor.png), pointer !important; +} +/* +The default border color has changed to `currentcolor` in Tailwind CSS v4, +so we've added these compatibility styles to make sure everything still +looks the same as it did with Tailwind CSS v3. + +If we ever want to remove these styles, we need to add an explicit border +color utility to any element that depends on these defaults. + */ @layer base { *, ::after, diff --git a/src/index.tsx b/src/index.tsx index 77dd385..605d8ad 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -4,16 +4,20 @@ import { App } from "./App"; import AppContext from "./context/AppContext"; +import "./lib/superjson"; import "./index.css"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; +import { Provider } from "jotai"; const queryClient = new QueryClient() ReactDOM.createRoot(document.getElementById("app") as HTMLElement).render( - - - - - + + + + + + + , ); diff --git a/src/lib/columns/column.ts b/src/lib/columns/column.ts index f66f406..2bfed72 100644 --- a/src/lib/columns/column.ts +++ b/src/lib/columns/column.ts @@ -21,7 +21,7 @@ export const EquipmentColumns = [ ] as const export const StatsColumns = [ - "AP","GunAP","AC","DX","MP","MA","MD","WT","DA","LK","HP","DP","HV", + "HV","AC","LK","WT","HP","MA","DP","DX","MP","AP","MD","DA","GunAP" ] as const diff --git a/src/lib/lifeto/lifeto.ts b/src/lib/lifeto/lifeto.ts index 1579a3b..379956c 100644 --- a/src/lib/lifeto/lifeto.ts +++ b/src/lib/lifeto/lifeto.ts @@ -73,6 +73,7 @@ export class LTOApiv0 implements LTOApi { galders, items: new Map((Object.entries(o.items) as any).map(([k, v]: [string, TricksterItem]):[string, TricksterItem]=>{ v.unique_id = Number(k) + v.id = k return [k, v] })), } diff --git a/src/lib/storage.ts b/src/lib/storage.ts index 860558e..a8aad42 100644 --- a/src/lib/storage.ts +++ b/src/lib/storage.ts @@ -60,7 +60,7 @@ export const StoreAccounts = { } export const StoreJsonable = { - Murder: (s:T):string=>JSON.stringify(Object.entries(s)), + Murder: (s:T):string=>JSON.stringify(Object.entries(s)), Revive: (s:string):T=>JSON.parse(s), } diff --git a/src/lib/superjson.ts b/src/lib/superjson.ts new file mode 100644 index 0000000..7458d71 --- /dev/null +++ b/src/lib/superjson.ts @@ -0,0 +1 @@ +import SuperJSON from "superjson"; diff --git a/src/lib/table.ts b/src/lib/table.ts index a0bad05..22cb8bb 100644 --- a/src/lib/table.ts +++ b/src/lib/table.ts @@ -1,7 +1,5 @@ import { TricksterInventory } from "./trickster" import {ColumnInfo, ColumnName, Columns, ColumnSorter, LazyColumn} from "./columns" -import { PredefinedMenuItemKey } from "handsontable/plugins/contextMenu" -import Handsontable from "handsontable" import { HotTableProps } from "@handsontable/react" @@ -95,25 +93,8 @@ export class InventoryTable { getTableColumnNames(): string[] { return this.o.columns.map(x=>x.displayName) } - getTableColumnSettings(): Handsontable.ColumnSettings[] { - return this.o.columns.map(x=>{ - let out:Handsontable.ColumnSettings = { - renderer: x.renderer ? x.renderer : "text", - filters: true, - dropdownMenu: x.filtering ? DefaultDropdownItems() : false, - readOnly: x.writable ? false : true, - selectionMode: (x.writable ? "multiple" : 'single') as any, - } - if(x.options) { - out.type = 'dropdown' - if(typeof x.options == "function") { - out.source = x.options(this.o.accounts) - }else { - out.source = x.options - } - } - return out - }) + getTableColumnSettings(){ + } getTableRows():any[][] { return Object.values(this.inv.items) @@ -162,47 +143,3 @@ export interface TableRecipe { settings: HotTableProps } -export const DefaultDropdownItems = ():PredefinedMenuItemKey[]=>['filter_by_condition' , 'filter_operators' ,'filter_by_condition2' , 'filter_by_value' , 'filter_action_bar'] -export const DefaultSettings = ():HotTableProps=>{ - return { - trimDropdown: true, - filters: true, - manualRowMove: false, - manualColumnMove: false, - allowInsertRow: false, - allowInsertColumn: false, - allowRemoveRow: false, - allowRemoveColumn: false, - allowHtml: true, - disableVisualSelection: false, - columnSorting: { - indicator: true, - headerAction: true, - }, - hiddenColumns: { - columns: [0], - }, - // renderAllRows: true, - viewportColumnRenderingOffset: 3, - viewportRowRenderingOffset: 10, - // dropdownMenu: DefaultDropdownItems(), - afterGetColHeader: (col, th) => { - if(!th.innerHTML.toLowerCase().includes("name")) { - const ct = th.querySelector('.changeType') - if(ct){ - ct.parentElement!.removeChild(ct) - } - } - }, - beforeOnCellMouseDown(event:any, coords) { - // Deselect the column after clicking on input. - if (coords.row === -1 && event.target.nodeName === 'INPUT') { - event.stopImmediatePropagation(); - } - }, - className: 'htLeft', - contextMenu: false, - readOnlyCellClassName: "", - licenseKey:"non-commercial-and-evaluation", - } -} diff --git a/src/lib/table/defs.ts b/src/lib/table/defs.ts new file mode 100644 index 0000000..d206db1 --- /dev/null +++ b/src/lib/table/defs.ts @@ -0,0 +1,11 @@ +import { TricksterItem } from "../trickster"; + +export interface ItemSelectionStatus { + selected: boolean; + amount?: number; +} + +export interface ItemWithSelection { + item: TricksterItem + status?: ItemSelectionStatus; +} diff --git a/src/lib/table/tanstack.tsx b/src/lib/table/tanstack.tsx new file mode 100644 index 0000000..31bb7d2 --- /dev/null +++ b/src/lib/table/tanstack.tsx @@ -0,0 +1,138 @@ +import { createColumnHelper } from '@tanstack/react-table'; +import { ItemWithSelection } from './defs'; +import { useAtomValue, useSetAtom } from 'jotai'; +import { currentItemSelectionAtom, itemSelectionSetActionAtom } from '@/state/atoms'; +import { useMemo } from 'react'; +import { StatsColumns } from '../columns'; + +const ch = createColumnHelper(); + +const columns = { + icon: ch.display({ + id: 'icon', + header: function Component(col) { + return
+ }, + cell: function Component({ row }){ + const setItemSelection= useSetAtom(itemSelectionSetActionAtom); + const c = useAtomValue(currentItemSelectionAtom); + const selected = useMemo(()=> { + return c[0].has(row.original.item.id); + }, [c]) + return
{ + setItemSelection({ + [row.original.item.id]: selected ? undefined : row.original.item.item_count, + }) + }} + > +
+ icon +
+
+ }, + }), + count: ch.display({ + id: 'count', + header: function Component(col){ + return
#
+ }, + cell: function Component({ row }){ + const c = useAtomValue(currentItemSelectionAtom); + const setItemSelection= useSetAtom(itemSelectionSetActionAtom); + const currentValue = useMemo(()=> { + const got = c[0].get(row.original.item.id); + if(got !== undefined) { + return got.toString(); + } + return "" + }, [c]) + const itemCount = row.original.item.item_count + return
+ { + if(e.target.value === ""){ + setItemSelection({[row.original.item.id]: undefined}); + return + } + if(e.target.value === "-"){ + setItemSelection({ + [row.original.item.id]: itemCount, + }) + } + let parsedInt = parseInt(e.target.value); + if (isNaN(parsedInt)) { + return; + } + if(parsedInt > itemCount){ + parsedInt = itemCount; + } + setItemSelection({ + [row.original.item.id]: parsedInt, + }) + }} + placeholder={itemCount.toString()} /> +
+ }, + }), + name: ch.display({ + id: 'name', + header: (col)=> { + return
name
+ }, + cell: function Component({ row }){ + return
+ {row.original.item.item_name} +
+ }, + }), + slots: ch.display({ + id: 'slots', + header: (col)=>{ + return
slots
+ }, + cell: function Component({ row }){ + return
+ {row.original.item.item_slots} +
+ }, + }), + stats: ch.group({ + id: 'stats', + header: (col)=>{ + return
stats
+ }, + columns: [ + ...StatsColumns.map((c)=>{ + return ch.display({ + id: 'stats.'+c, + header: (col)=>{ + return
{c}
+ }, + cell: function Component({ row }){ + const stats = row.original.item.stats + const stat = stats ? stats[c] : "" + return
+ {stat} +
+ }, + }) + }) + ] + }), +} as const; + +export const InventoryColumns = columns; diff --git a/src/lib/trickster.ts b/src/lib/trickster.ts index 1646f12..e1c3d23 100644 --- a/src/lib/trickster.ts +++ b/src/lib/trickster.ts @@ -1,10 +1,13 @@ export interface TricksterItem { + id: string; unique_id: number; item_name: string; item_count: number; item_comment: string; item_use: string; item_slots?: number; + item_tab: number + item_type: number, item_min_level?: number; is_equip?: boolean; is_drill?: boolean; diff --git a/src/main.ts b/src/main.ts deleted file mode 100644 index e12b34e..0000000 --- a/src/main.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { createApp } from 'vue' -import App from './App.vue' - - -import { createPinia } from 'pinia'; -import log from 'loglevel'; - -log.setLevel("debug") -const pinia = createPinia() -createApp(App). - use(pinia). - mount('#app') diff --git a/src/state/atoms.ts b/src/state/atoms.ts index 72c4001..cb8a13a 100644 --- a/src/state/atoms.ts +++ b/src/state/atoms.ts @@ -2,9 +2,13 @@ import { AxiosError } from 'axios'; import { LTOApiv0 } from '../lib/lifeto' import { LoginHelper, TokenSession } from '../lib/session' import { atomWithQuery } from 'jotai-tanstack-query' -import {atomFamily, atomWithRefresh, atomWithStorage} from "jotai/utils"; +import {atomWithStorage} from "jotai/utils"; import { atom } from 'jotai'; import { TricksterCharacter, TricksterInventory, TricksterItem } from '../lib/trickster'; +import {focusAtom} from "jotai-optics"; +import { createSuperjsonStorage, superJsonStorage } from './storage'; +import { ItemWithSelection } from '@/lib/table/defs'; +import Fuse from 'fuse.js'; export const LTOApi = new LTOApiv0(new TokenSession()) @@ -77,10 +81,9 @@ export const charactersAtom = atomWithQuery((get) => { } }) -export const selectedCharacterAtom = atom(undefined) +export const selectedCharacterAtom = atomWithStorage("lto_state.selected_character", undefined) export const selectedTargetInventoryAtom = atom(undefined) -export const pageSize = atomWithStorage("preference.page_size", 250) export const currentFilter = atom(undefined) @@ -96,9 +99,221 @@ export const currentCharacterInventoryAtom = atomWithQuery((get) => { } }) +const inventoryDisplaySettings= atomWithStorage<{ + page_size: number +}>("preference.inventory_display_settings", { + page_size: 25, +}, createSuperjsonStorage()) + +export const inventoryDisplaySettingsAtoms = { + pageSize: focusAtom(inventoryDisplaySettings, x=>x.prop('page_size')), +} + + export const currentCharacterItemsAtom = atom((get)=>{ const {data: inventory} = get(currentCharacterInventoryAtom) - return inventory?.items || new Map() + const items = inventory?.items || new Map() + return { + items, + searcher: new Fuse(Array.from(items.values()), { + keys: [ + 'item_name', + ], + useExtendedSearch: true, + }) + } }) +export interface InventoryFilter { + search: string + tab: string + sort: string + sort_reverse: boolean +} +export const inventoryFilterAtom = atomWithStorage("preference.inventory_filter", { + search: "", + tab: "", + sort: "", + sort_reverse:false, +}, createSuperjsonStorage()) + +export const preferenceInventorySearch = focusAtom(inventoryFilterAtom, x=>x.prop('search')) +export const preferenceInventoryTab = focusAtom(inventoryFilterAtom, x=>x.prop('tab')) +export const preferenceInventorySort = focusAtom(inventoryFilterAtom, x=>x.prop('sort')) +export const preferenceInventorySortReverse = focusAtom(inventoryFilterAtom, x=>x.prop('sort_reverse')) + + + + +export const setInventoryFilterTabActionAtom = atom(null, (_get, set, tab: string) => { + set(inventoryFilterAtom, x=>{ + return { + ...x, + tab, + } + }) +}) + +export const inventoryPageRangeAtom = atom({ + start: 0, + end: 25, +}) + +export const nextInventoryPageActionAtom = atom(null, (get, set) => { + const {start, end} = get(inventoryPageRangeAtom) + set(inventoryPageRangeAtom, { + start: start + end, + end: end + end, + }) +}) + +export const currentItemSelectionAtom = atom<[Map, number]>([new Map(), 0]) +export const currentInventorySearchQueryAtom = atom("") + +export const filteredCharacterItemsAtom = atom((get)=>{ + const { items, searcher } = get(currentCharacterItemsAtom) + const [selection] = get(currentItemSelectionAtom) + const filter = get(inventoryFilterAtom) + const out: ItemWithSelection[] = [] + for (const [_, value] of items.entries()) { + if(filter.search !== "") { + if(!value.item_name.toLowerCase().includes(filter.search)) { + continue + } + } + if(filter.tab !== "") { + if(value.item_tab !== parseInt(filter.tab)) { + continue + } + } + let status = undefined + if(selection.has(value.id)) { + status = { + selected: true, + } + } + out.push({item: value, status}) + } + + switch(filter.sort) { + case "count": + out.sort((a, b)=>{ + return b.item.item_count - a.item.item_count + }) + break; + case "type": + out.sort((a, b)=>{ + return a.item.item_tab - b.item.item_tab + }) + break; + case "name": + out.sort((a, b)=>{ + return a.item.item_name.localeCompare(b.item.item_name) + }) + break; + } + if(filter.sort && filter.sort_reverse) { + out.reverse() + } + return out +}) + +export const inventoryItemsCurrentPageAtom = atom((get)=>{ + const items = get(filteredCharacterItemsAtom) + const {start, end} = get(inventoryPageRangeAtom) + return items.slice(start, end).map((item): ItemWithSelection =>{ + return item + }) +}) + +export const rowSelectionLastActionAtom = atom<{ + index: number + action: "add" | "remove" +}| undefined>(undefined) + + +export const clearItemSelectionActionAtom = atom(null, (_get, set) => { + set(currentItemSelectionAtom, [new Map(), 0]) +}) + +export const itemSelectionSetActionAtom = atom(null, (get, set, arg: Record ) => { + const cur = get(currentItemSelectionAtom) + for(const [key, value] of Object.entries(arg)) { + if(value === undefined) { + cur[0].delete(key) + } else { + cur[0].set(key, value) + } + } + set(currentItemSelectionAtom, [cur[0], cur[1] + 1]) +}) + +export const itemSelectionSelectAllFilterActionAtom = atom(null, (get, set) => { + const cur = get(currentItemSelectionAtom) + const items = get(filteredCharacterItemsAtom) + for(const item of items) { + cur[0].set(item.item.id, item.item.item_count) + } + set(currentItemSelectionAtom, [cur[0], cur[1] + 1]) +}) + +export const itemSelectionSelectAllPageActionAtom = atom(null, (get, set) => { + const cur = get(currentItemSelectionAtom) + const items = get(inventoryItemsCurrentPageAtom) + for(const item of items) { + cur[0].set(item.item.id, item.item.item_count) + } + set(currentItemSelectionAtom, [cur[0], cur[1] + 1]) +}) + +export const paginateInventoryActionAtom = atom(null, (get, set, pages: number | undefined) => { + const inventoryRange = get(inventoryPageRangeAtom) + const pageSize = get(inventoryDisplaySettingsAtoms.pageSize) + const filteredItems = get(filteredCharacterItemsAtom) + if(pages === undefined) { + set(inventoryPageRangeAtom, { + start: 0, + end: pageSize, + }) + return + } + if(pageSize > filteredItems.length) { + set(inventoryPageRangeAtom, { + start: 0, + end: filteredItems.length, + }) + return + } + if(pages > 0) { + if(inventoryRange.end >= filteredItems.length) { + set(inventoryPageRangeAtom, { + start: 0, + end: pageSize, + }) + return + } + }else if(pages < 0) { + if(inventoryRange.start <= 0) { + set(inventoryPageRangeAtom, { + start: filteredItems.length - pageSize, + end: filteredItems.length, + }) + return + } + } + const delta = pages * pageSize + let newStart = inventoryRange.start + delta + let newEnd = inventoryRange.end + delta + if(newEnd > filteredItems.length) { + newEnd = filteredItems.length + } + if(newEnd - newStart != pageSize) { + newStart = newEnd - pageSize + } + + set(inventoryPageRangeAtom, { + start: newStart, + end: newEnd, + }) +}) diff --git a/src/state/storage.ts b/src/state/storage.ts new file mode 100644 index 0000000..b103ce0 --- /dev/null +++ b/src/state/storage.ts @@ -0,0 +1,116 @@ +import { AsyncStorage, AsyncStringStorage, SyncStorage, SyncStringStorage } from "jotai/vanilla/utils/atomWithStorage" +import superjson from 'superjson' + +const isPromiseLike = (x: unknown): x is PromiseLike => + typeof (x as any)?.then === 'function' + +type Unsubscribe = () => void + +type Subscribe = ( + key: string, + callback: (value: Value) => void, + initialValue: Value, +) => Unsubscribe | undefined + +type StringSubscribe = ( + key: string, + callback: (value: string | null) => void, +) => Unsubscribe | undefined + +export function createSuperjsonStorage(): SyncStorage +export function createSuperjsonStorage( + getStringStorage: () => + | AsyncStringStorage + | SyncStringStorage + | undefined = () => { + try { + return window.localStorage + } catch (e) { + if (import.meta.env?.MODE !== 'production') { + if (typeof window !== 'undefined') { + console.warn(e) + } + } + return undefined + } + }, +): AsyncStorage | SyncStorage { + let lastStr: string | undefined + let lastValue: Value + + const storage: AsyncStorage | SyncStorage = { + getItem: (key, initialValue) => { + const parse = (str: string | null) => { + str = str || '' + if (lastStr !== str) { + try { + lastValue = superjson.parse(str) + } catch { + return initialValue + } + lastStr = str + } + return lastValue + } + const str = getStringStorage()?.getItem(key) ?? null + if (isPromiseLike(str)) { + return str.then(parse) as never + } + return parse(str) as never + }, + setItem: (key, newValue) => + getStringStorage()?.setItem( + key, + superjson.stringify(newValue), + ), + removeItem: (key) => getStringStorage()?.removeItem(key), + } + + const createHandleSubscribe = + (subscriber: StringSubscribe): Subscribe => + (key, callback, initialValue) => + subscriber(key, (v) => { + let newValue: Value + try { + newValue = superjson.parse(v || '') + } catch { + newValue = initialValue + } + callback(newValue) + }) + + let subscriber: StringSubscribe | undefined + try { + subscriber = getStringStorage()?.subscribe + } catch { + // ignore + } + if ( + !subscriber && + typeof window !== 'undefined' && + typeof window.addEventListener === 'function' && + window.Storage + ) { + subscriber = (key, callback) => { + if (!(getStringStorage() instanceof window.Storage)) { + return () => {} + } + const storageEventCallback = (e: StorageEvent) => { + if (e.storageArea === getStringStorage() && e.key === key) { + callback(e.newValue) + } + } + window.addEventListener('storage', storageEventCallback) + return () => { + window.removeEventListener('storage', storageEventCallback) + } + } + } + + if (subscriber) { + storage.subscribe = createHandleSubscribe(subscriber) + } + return storage +} + +export const superJsonStorage = createSuperjsonStorage() diff --git a/tsconfig.json b/tsconfig.json index 62969de..d04b9d5 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -4,6 +4,9 @@ "target": "esnext", "useDefineForClassFields": true, "lib": ["DOM", "DOM.Iterable", "ESNext"], + "paths": { + "@/*": ["./src/*"] + }, "types": [ "node" ], "allowJs": true, "skipLibCheck": true, diff --git a/vite.config.ts b/vite.config.ts index 4801a9a..a3bd42f 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -1,9 +1,15 @@ import { defineConfig } from 'vite' import react from '@vitejs/plugin-react' +import path from 'path' export default defineConfig({ plugins: [react()], + resolve: { + alias: { + '@': path.resolve(__dirname, './src'), + }, + }, server: { proxy: { // with options diff --git a/yarn.lock b/yarn.lock index 78d647d..77c70b2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -15,6 +15,18 @@ "@jridgewell/gen-mapping" "^0.3.5" "@jridgewell/trace-mapping" "^0.3.24" +"@ark/schema@0.46.0": + version "0.46.0" + resolved "https://registry.yarnpkg.com/@ark/schema/-/schema-0.46.0.tgz#81a1a0dc1ff0f2faa098cba05de505a174bdc64e" + integrity sha512-c2UQdKgP2eqqDArfBqQIJppxJHvNNXuQPeuSPlDML4rjw+f1cu0qAlzOG4b8ujgm9ctIDWwhpyw6gjG5ledIVQ== + dependencies: + "@ark/util" "0.46.0" + +"@ark/util@0.46.0": + version "0.46.0" + resolved "https://registry.yarnpkg.com/@ark/util/-/util-0.46.0.tgz#aee240bdaf413793e5ca4c4e8e3707aa965f4be3" + integrity sha512-JPy/NGWn/lvf1WmGCPw2VGpBg5utZraE84I7wli18EDF3p3zc/e9WolT35tINeZO3l7C77SjqRJeAUoT0CvMRg== + "@babel/code-frame@^7.0.0", "@babel/code-frame@^7.24.7": version "7.24.7" resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.24.7.tgz#882fd9e09e8ee324e496bd040401c6f046ef4465" @@ -1989,6 +2001,18 @@ dependencies: "@tanstack/query-core" "5.76.0" +"@tanstack/react-table@^8.21.3": + version "8.21.3" + resolved "https://registry.yarnpkg.com/@tanstack/react-table/-/react-table-8.21.3.tgz#2c38c747a5731c1a07174fda764b9c2b1fb5e91b" + integrity sha512-5nNMTSETP4ykGegmVkhjcS8tTLW6Vl4axfEGQN3v0zdHYbK4UfoqfPChclTrJ4EoK9QynqAu9oUf8VEmrpZ5Ww== + dependencies: + "@tanstack/table-core" "8.21.3" + +"@tanstack/table-core@8.21.3": + version "8.21.3" + resolved "https://registry.yarnpkg.com/@tanstack/table-core/-/table-core-8.21.3.tgz#2977727d8fc8dfa079112d9f4d4c019110f1732c" + integrity sha512-ldZXEhOBb8Is7xLs01fR3YEc3DERiz5silj8tnGkFZytt1abEvl/GhUmCE0PMLaMPTa3Jk4HbKmRlHmu+gCftg== + "@tybys/wasm-util@^0.9.0": version "0.9.0" resolved "https://registry.yarnpkg.com/@tybys/wasm-util/-/wasm-util-0.9.0.tgz#3e75eb00604c8d6db470bf18c37b7d984a0e3355" @@ -2044,6 +2068,13 @@ resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee" integrity sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ== +"@types/node@^22.15.18": + version "22.15.18" + resolved "https://registry.yarnpkg.com/@types/node/-/node-22.15.18.tgz#2f8240f7e932f571c2d45f555ba0b6c3f7a75963" + integrity sha512-v1DKRfUdyW+jJhZNEI1PYy29S2YRxMV5AOO/x/SjKmW0acCIOqmbj6Haf9eHAhsPmrhlHSxEhv/1WszcLWV4cg== + dependencies: + undici-types "~6.21.0" + "@types/parse-json@^4.0.0": version "4.0.2" resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.2.tgz#5950e50960793055845e956c427fc2b0d70c5239" @@ -2355,6 +2386,14 @@ aria-query@~5.1.3: dependencies: deep-equal "^2.0.5" +arktype@^2.1.20: + version "2.1.20" + resolved "https://registry.yarnpkg.com/arktype/-/arktype-2.1.20.tgz#dd46726b0faf23c2753369876c77bb037e7089d9" + integrity sha512-IZCEEXaJ8g+Ijd59WtSYwtjnqXiwM8sWQ5EjGamcto7+HVN9eK0C4p0zDlCuAwWhpqr6fIBkxPuYDl4/Mcj/+Q== + dependencies: + "@ark/schema" "0.46.0" + "@ark/util" "0.46.0" + array-buffer-byte-length@^1.0.0, array-buffer-byte-length@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/array-buffer-byte-length/-/array-buffer-byte-length-1.0.1.tgz#1e5583ec16763540a27ae52eed99ff899223568f" @@ -4554,6 +4593,11 @@ jiti@^2.4.2: resolved "https://registry.yarnpkg.com/jiti/-/jiti-2.4.2.tgz#d19b7732ebb6116b06e2038da74a55366faef560" integrity sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A== +jotai-optics@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/jotai-optics/-/jotai-optics-0.4.0.tgz#aad207090a06390e61cc85324b623b2692e2e272" + integrity sha512-osbEt9AgS55hC4YTZDew2urXKZkaiLmLqkTS/wfW5/l0ib8bmmQ7kBXSFaosV6jDDWSp00IipITcJARFHdp42g== + jotai-tanstack-query@^0.9.0: version "0.9.0" resolved "https://registry.yarnpkg.com/jotai-tanstack-query/-/jotai-tanstack-query-0.9.0.tgz#f3c4f96c2ec88f6fdd0b39a1d2eca6a10a8878d6" @@ -5065,6 +5109,11 @@ once@^1.4.0: dependencies: wrappy "1" +optics-ts@^2.4.1: + version "2.4.1" + resolved "https://registry.yarnpkg.com/optics-ts/-/optics-ts-2.4.1.tgz#de94bda2b0ed7fde5b7631283031b9699459d40d" + integrity sha512-HaYzMHvC80r7U/LqAd4hQyopDezC60PO2qF5GuIwALut2cl5rK1VWHsqTp0oqoJJWjiv6uXKqsO+Q2OO0C3MmQ== + optionator@^0.9.3: version "0.9.4" resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.4.tgz#7ea1c1a5d91d764fb282139c88fe11e182a3a734" @@ -5274,6 +5323,11 @@ react-dom@^19.1.0: dependencies: scheduler "^0.26.0" +react-icons@^5.5.0: + version "5.5.0" + resolved "https://registry.yarnpkg.com/react-icons/-/react-icons-5.5.0.tgz#8aa25d3543ff84231685d3331164c00299cdfaf2" + integrity sha512-MEFcXdkP3dLo8uumGI5xN3lDFNsRtrjbOEKDLD7yv76v4wpnEq2Lt2qeHaQOr34I/wPN3s3+N08WkQ+CW37Xiw== + react-is@^16.13.1, react-is@^16.7.0: version "16.13.1" resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" @@ -6108,6 +6162,11 @@ unbox-primitive@^1.1.0: has-symbols "^1.1.0" which-boxed-primitive "^1.1.1" +undici-types@~6.21.0: + version "6.21.0" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.21.0.tgz#691d00af3909be93a7faa13be61b3a5b50ef12cb" + integrity sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ== + unicode-canonical-property-names-ecmascript@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz#301acdc525631670d39f6146e0e77ff6bbdebddc"