forked from a/lifeto-shop
noot
This commit is contained in:
parent
79f90a7478
commit
3d30ab085f
@ -12,6 +12,7 @@
|
|||||||
"@handsontable/react": "^15.3.0",
|
"@handsontable/react": "^15.3.0",
|
||||||
"@mantine/hooks": "^8.0.0",
|
"@mantine/hooks": "^8.0.0",
|
||||||
"@tanstack/react-query": "^5.76.0",
|
"@tanstack/react-query": "^5.76.0",
|
||||||
|
"@tanstack/react-table": "^8.21.3",
|
||||||
"@types/qs": "^6.9.18",
|
"@types/qs": "^6.9.18",
|
||||||
"@types/react": "^19.1.4",
|
"@types/react": "^19.1.4",
|
||||||
"@types/react-dom": "^19.1.5",
|
"@types/react-dom": "^19.1.5",
|
||||||
@ -19,6 +20,7 @@
|
|||||||
"@typescript-eslint/eslint-plugin": "^8.32.1",
|
"@typescript-eslint/eslint-plugin": "^8.32.1",
|
||||||
"@typescript-eslint/parser": "^8.32.1",
|
"@typescript-eslint/parser": "^8.32.1",
|
||||||
"@vitejs/plugin-react": "^4.4.1",
|
"@vitejs/plugin-react": "^4.4.1",
|
||||||
|
"arktype": "^2.1.20",
|
||||||
"axios": "^1.9.0",
|
"axios": "^1.9.0",
|
||||||
"eslint": "^9.26.0",
|
"eslint": "^9.26.0",
|
||||||
"eslint-config-react-app": "^7.0.1",
|
"eslint-config-react-app": "^7.0.1",
|
||||||
@ -28,15 +30,19 @@
|
|||||||
"fuse.js": "^7.1.0",
|
"fuse.js": "^7.1.0",
|
||||||
"handsontable": "^15.3.0",
|
"handsontable": "^15.3.0",
|
||||||
"jotai": "^2.12.4",
|
"jotai": "^2.12.4",
|
||||||
|
"jotai-optics": "^0.4.0",
|
||||||
"jotai-tanstack-query": "^0.9.0",
|
"jotai-tanstack-query": "^0.9.0",
|
||||||
"loglevel": "^1.9.2",
|
"loglevel": "^1.9.2",
|
||||||
|
"optics-ts": "^2.4.1",
|
||||||
"pinia": "^3.0.2",
|
"pinia": "^3.0.2",
|
||||||
"prettier": "^3.5.3",
|
"prettier": "^3.5.3",
|
||||||
"qs": "^6.14.0",
|
"qs": "^6.14.0",
|
||||||
"react": "^19.1.0",
|
"react": "^19.1.0",
|
||||||
"react-dom": "^19.1.0",
|
"react-dom": "^19.1.0",
|
||||||
|
"react-icons": "^5.5.0",
|
||||||
"react-select": "^5.10.1",
|
"react-select": "^5.10.1",
|
||||||
"react-spinners": "^0.17.0",
|
"react-spinners": "^0.17.0",
|
||||||
|
"superjson": "^2.2.2",
|
||||||
"typescript-cookie": "^1.0.6",
|
"typescript-cookie": "^1.0.6",
|
||||||
"use-local-storage": "^3.0.0",
|
"use-local-storage": "^3.0.0",
|
||||||
"usehooks-ts": "^3.1.1",
|
"usehooks-ts": "^3.1.1",
|
||||||
@ -44,6 +50,7 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@tailwindcss/postcss": "^4.1.6",
|
"@tailwindcss/postcss": "^4.1.6",
|
||||||
|
"@types/node": "^22.15.18",
|
||||||
"postcss": "^8.5.3",
|
"postcss": "^8.5.3",
|
||||||
"tailwindcss": "^4.1.6",
|
"tailwindcss": "^4.1.6",
|
||||||
"typescript": "^5.8.3",
|
"typescript": "^5.8.3",
|
||||||
|
|||||||
BIN
public/cursor.png
Normal file
BIN
public/cursor.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.1 KiB |
16
src/App.tsx
16
src/App.tsx
@ -1,21 +1,17 @@
|
|||||||
import { FC } from "react";
|
import { FC } from "react";
|
||||||
import { LoginWidget } from "./components/login";
|
import { LoginWidget } from "./components/login";
|
||||||
import { CharacterRoulette } from "./components/characters";
|
import { CharacterRoulette } from "./components/characters";
|
||||||
import { Inventory } from "./components/inventory";
|
import { Inventory } from "./components/inventory/index";
|
||||||
|
|
||||||
export const App: FC = () => {
|
export const App: FC = () => {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="flex flex-col mx-auto p-4 w-full">
|
<div className="flex flex-row mx-auto p-4 gap-8 w-full h-full">
|
||||||
<div className="flex flex-row max-w-6xl">
|
<div className="flex flex-col">
|
||||||
<div className="flex flex-row justify-end w-full">
|
<LoginWidget/>
|
||||||
<LoginWidget/>
|
<CharacterRoulette/>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div className="flex-1">
|
||||||
<CharacterRoulette/>
|
|
||||||
</div>
|
|
||||||
<div className="overflow-hidden">
|
|
||||||
<Inventory/>
|
<Inventory/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
import { TricksterCharacter } from "../lib/trickster"
|
import { TricksterCharacter } from "../lib/trickster"
|
||||||
import { useSessionContext } from "../context/SessionContext"
|
|
||||||
import Fuse from 'fuse.js'
|
import Fuse from 'fuse.js'
|
||||||
import { useAtom, useSetAtom } from "jotai"
|
import { useAtom } from "jotai"
|
||||||
import { charactersAtom, selectedCharacterAtom } from "../state/atoms"
|
import { charactersAtom, selectedCharacterAtom } from "../state/atoms"
|
||||||
import { useMemo, useState } from "react";
|
import { useMemo, useState } from "react";
|
||||||
import {
|
import {
|
||||||
@ -136,24 +135,24 @@ export const CharacterRoulette = ()=>{
|
|||||||
return <PleaseLogin/>
|
return <PleaseLogin/>
|
||||||
}
|
}
|
||||||
const searchResults = fuse.search(search || "!-----", {
|
const searchResults = fuse.search(search || "!-----", {
|
||||||
limit: 20,
|
limit: 20,
|
||||||
}).map((x)=>{
|
}).map((x)=>{
|
||||||
return <div className="flex flex-col" key={`${x.item.character.account_id}`}>
|
return <div className="flex flex-col" key={`${x.item.character.account_id}`}>
|
||||||
<CharacterCard key={x.item.bank.id} character={x.item.bank} />
|
<CharacterCard key={x.item.bank.id} character={x.item.bank} />
|
||||||
<CharacterCard key={x.item.character.id} character={x.item.character} />
|
<CharacterCard key={x.item.character.id} character={x.item.character} />
|
||||||
</div>
|
</div>
|
||||||
})
|
})
|
||||||
return <>
|
return <>
|
||||||
<div className="flex flex-col gap-1">
|
<div className="flex flex-col gap-1">
|
||||||
<input
|
<input
|
||||||
className="border border-black-1 bg-gray-100 placeholder-gray-600 p-1 max-w-[200px]"
|
className="border border-black-1 bg-gray-100 placeholder-gray-600 p-1 max-w-[180px]"
|
||||||
placeholder="search character..."
|
placeholder="search character..."
|
||||||
value={search}
|
value={search}
|
||||||
onChange={(e)=>{
|
onChange={(e)=>{
|
||||||
setSearch(e.target.value)
|
setSearch(e.target.value)
|
||||||
}}
|
}}
|
||||||
></input>
|
></input>
|
||||||
<div className="flex flex-row overflow-x-scroll gap-1 h-full min-h-36">
|
<div className="flex flex-row flex-wrap overflow-x-scroll gap-1 h-full min-h-36 max-w-48">
|
||||||
{searchResults ? searchResults : <>
|
{searchResults ? searchResults : <>
|
||||||
</>}
|
</>}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
149
src/components/inventory/index.tsx
Normal file
149
src/components/inventory/index.tsx
Normal file
@ -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 <div className="flex flex-row gap-1 justify-between">
|
||||||
|
<div className="flex flex-col gap-1">
|
||||||
|
<div className="flex flex-row gap-1">
|
||||||
|
{sections.map(x=>{
|
||||||
|
return <div
|
||||||
|
onClick={()=>{
|
||||||
|
setInventoryFilterTab(x.value)
|
||||||
|
}}
|
||||||
|
key={x.name}
|
||||||
|
className={`${sharedStyle}
|
||||||
|
${inventoryFilter.tab === x.value ? selectedStyle : ""}`}
|
||||||
|
>{x.name}</div>
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-row gap-1">
|
||||||
|
{cardSections.map(x=>{
|
||||||
|
return <div
|
||||||
|
onClick={()=>{
|
||||||
|
setInventoryFilterTab(x.value)
|
||||||
|
}}
|
||||||
|
key={x.name}
|
||||||
|
className={`${sharedStyle}
|
||||||
|
${inventoryFilter.tab === x.value ? selectedStyle : ""}`}
|
||||||
|
>{x.name}</div>
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-row gap-1 items-center px-1 bg-yellow-100">
|
||||||
|
<div className="whitespace-pre select-none">{inventoryRange.start}..{inventoryRange.end}/{items.length} </div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
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 <div>
|
||||||
|
select a character
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
return <div className={`flex flex-col h-full w-full`}>
|
||||||
|
<div className="flex flex-col py-2 flex-0 justify-between h-full">
|
||||||
|
<div className="flex flex-row justify-between">
|
||||||
|
<div className="flex flex-row gap-2">
|
||||||
|
<div className="whitespace-pre bg-blue-200 px-2 py-1 rounded-xl hover:cursor-pointer hover:bg-blue-300"
|
||||||
|
onClick={()=>{
|
||||||
|
addPageItemSelection()
|
||||||
|
}}
|
||||||
|
>select filtered</div>
|
||||||
|
<div className="whitespace-pre bg-blue-200 px-2 py-1 rounded-xl hover:cursor-pointer hover:bg-blue-300"
|
||||||
|
onClick={()=>{
|
||||||
|
addFilterItemSelection()
|
||||||
|
}}
|
||||||
|
>select page</div>
|
||||||
|
<div className="whitespace-pre bg-blue-200 px-2 py-1 rounded-xl hover:cursor-pointer hover:bg-blue-300"
|
||||||
|
onClick={()=>{
|
||||||
|
clearItemSelection()
|
||||||
|
}}
|
||||||
|
>clear </div>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-row">
|
||||||
|
<InventoryTargetSelector/>
|
||||||
|
<div
|
||||||
|
onClick={(e)=>{
|
||||||
|
// sendOrders()
|
||||||
|
}}
|
||||||
|
className="hover:cursor-pointer whitespace-preborder border-black-1 bg-orange-200 hover:bg-orange-300 px-2 py-1">Move Selected</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex flex-row gap-2 justify-between">
|
||||||
|
<div className="flex flex-row gap-0 items-center">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={search}
|
||||||
|
className="border border-black-1 px-2 py-1"
|
||||||
|
placeholder="search..."
|
||||||
|
onChange={(e)=>{
|
||||||
|
setSearch(e.target.value)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
className="hover:cursor-pointer border border-black-1 bg-green-200 hover:bg-green-300 px-2 py-1 h-full flex items-center"
|
||||||
|
onClick={()=>{
|
||||||
|
paginateInventory(-1)
|
||||||
|
}}
|
||||||
|
><FaArrowLeft/></div>
|
||||||
|
<div
|
||||||
|
className="hover:cursor-pointer border border-black-1 bg-green-200 hover:bg-green-300 px-2 py-1 h-full flex items-center"
|
||||||
|
onClick={()=>{
|
||||||
|
paginateInventory(1)
|
||||||
|
}}
|
||||||
|
><FaArrowRight/></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<InventoryTabs />
|
||||||
|
<div className="flex flex-col flex-1 h-full border border-black-2">
|
||||||
|
<InventoryTable />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
@ -1,39 +1,20 @@
|
|||||||
import { TricksterCharacter } from "../lib/trickster"
|
|
||||||
import { useSessionContext } from "../context/SessionContext"
|
|
||||||
|
|
||||||
import 'handsontable/dist/handsontable.full.min.css';
|
import { forwardRef, useId, useMemo, useRef, useState} from "react";
|
||||||
|
|
||||||
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 { useAtom, useAtomValue } from "jotai";
|
import { useAtom, useAtomValue } from "jotai";
|
||||||
import { autoUpdate, flip, FloatingFocusManager, FloatingPortal, size, useDismiss, useFloating, useInteractions, useListNavigation, useRole } from "@floating-ui/react";
|
import { autoUpdate, flip, FloatingFocusManager, FloatingPortal, size, useDismiss, useFloating, useInteractions, useListNavigation, useRole } from "@floating-ui/react";
|
||||||
import Fuse from "fuse.js";
|
import Fuse from "fuse.js";
|
||||||
|
import { charactersAtom, selectedTargetInventoryAtom } from "@/state/atoms";
|
||||||
|
|
||||||
registerAllModules();
|
interface AccountInventorySelectorItemProps {
|
||||||
type Size = {
|
|
||||||
width?: number
|
|
||||||
height?: number
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
interface InventoryItemProps {
|
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
active: boolean;
|
active: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const InventoryItem = forwardRef<
|
|
||||||
|
|
||||||
|
const AccountInventorySelectorItem = forwardRef<
|
||||||
HTMLDivElement,
|
HTMLDivElement,
|
||||||
InventoryItemProps & React.HTMLProps<HTMLDivElement>
|
AccountInventorySelectorItemProps & React.HTMLProps<HTMLDivElement>
|
||||||
>(({ children, active, ...rest }, ref) => {
|
>(({ children, active, ...rest }, ref) => {
|
||||||
const id = useId();
|
const id = useId();
|
||||||
return (
|
return (
|
||||||
@ -54,8 +35,7 @@ const InventoryItem = forwardRef<
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
export const InventoryTargetSelector = () => {
|
||||||
const InventoryTargetSelector = () => {
|
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
const [inputValue, setInputValue] = useState("");
|
const [inputValue, setInputValue] = useState("");
|
||||||
const [activeIndex, setActiveIndex] = useState<number | null>(null);
|
const [activeIndex, setActiveIndex] = useState<number | null>(null);
|
||||||
@ -133,6 +113,9 @@ const InventoryTargetSelector = () => {
|
|||||||
value: selectedTargetInventory !== undefined ? selectedTargetInventory.name : inputValue,
|
value: selectedTargetInventory !== undefined ? selectedTargetInventory.name : inputValue,
|
||||||
placeholder: "Target Inventory",
|
placeholder: "Target Inventory",
|
||||||
"aria-autocomplete": "list",
|
"aria-autocomplete": "list",
|
||||||
|
onFocus() {
|
||||||
|
setOpen(true);
|
||||||
|
},
|
||||||
onKeyDown(event) {
|
onKeyDown(event) {
|
||||||
if (
|
if (
|
||||||
event.key === "Enter" &&
|
event.key === "Enter" &&
|
||||||
@ -166,7 +149,7 @@ const InventoryTargetSelector = () => {
|
|||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
{items.map((item, index) => (
|
{items.map((item, index) => (
|
||||||
<InventoryItem
|
<AccountInventorySelectorItem
|
||||||
{...getItemProps({
|
{...getItemProps({
|
||||||
key: item.path,
|
key: item.path,
|
||||||
ref(node) {
|
ref(node) {
|
||||||
@ -182,7 +165,7 @@ const InventoryTargetSelector = () => {
|
|||||||
active={activeIndex === index}
|
active={activeIndex === index}
|
||||||
>
|
>
|
||||||
{item.name}
|
{item.name}
|
||||||
</InventoryItem>
|
</AccountInventorySelectorItem>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</FloatingFocusManager>
|
</FloatingFocusManager>
|
||||||
@ -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<string,TricksterCharacter>()
|
|
||||||
// const manager = new OrderSender(orders, chars)
|
|
||||||
// for(const d of pending){
|
|
||||||
// const order = manager.send(d)
|
|
||||||
// //order.tick(api)
|
|
||||||
// }
|
|
||||||
//}, [orders])
|
|
||||||
|
|
||||||
const Loading = ()=>{
|
|
||||||
return <div role="status" className="flex align-center justify-center">
|
|
||||||
<div className="justify-center py-4">
|
|
||||||
<DotLoader color="#dddddd"/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
const items = useAtomValue(currentCharacterItemsAtom)
|
|
||||||
|
|
||||||
return <div ref={ref} className={``}>
|
|
||||||
<div className="flex flex-row py-2 justify-end">
|
|
||||||
<InventoryTargetSelector/>
|
|
||||||
<div
|
|
||||||
onClick={(e)=>{
|
|
||||||
// sendOrders()
|
|
||||||
}}
|
|
||||||
className="
|
|
||||||
hover:cursor-pointer
|
|
||||||
border border-black-1
|
|
||||||
bg-green-200
|
|
||||||
px-2 py-1
|
|
||||||
">Move Selected</div>
|
|
||||||
</div>
|
|
||||||
{(isLoading || isFetching) ? <Loading/> : <>
|
|
||||||
<div>
|
|
||||||
total: {items.size}
|
|
||||||
</div>
|
|
||||||
</> }
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
90
src/components/inventory/table.tsx
Normal file
90
src/components/inventory/table.tsx
Normal file
@ -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<ItemWithSelection>({
|
||||||
|
getRowId: row =>row.item.unique_id.toString(),
|
||||||
|
data: items,
|
||||||
|
state: {
|
||||||
|
columnVisibility,
|
||||||
|
},
|
||||||
|
columns,
|
||||||
|
getCoreRowModel: getCoreRowModel(),
|
||||||
|
})
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="overflow-y-auto h-full mb-32">
|
||||||
|
<table
|
||||||
|
onContextMenu={(e)=>{
|
||||||
|
e.preventDefault()
|
||||||
|
return
|
||||||
|
}}
|
||||||
|
className="border-spacing-x-2 border-separate">
|
||||||
|
<thead className="sticky top-0 z-10 select-none bg-white">
|
||||||
|
{table.getHeaderGroups().map(headerGroup => (
|
||||||
|
<tr
|
||||||
|
className=""
|
||||||
|
key={headerGroup.id}>
|
||||||
|
{headerGroup.headers.map(header => (
|
||||||
|
<th
|
||||||
|
key={header.id}
|
||||||
|
className="text-left"
|
||||||
|
>
|
||||||
|
{header.isPlaceholder
|
||||||
|
? null
|
||||||
|
: flexRender(
|
||||||
|
header.column.columnDef.header,
|
||||||
|
header.getContext()
|
||||||
|
)}
|
||||||
|
</th>
|
||||||
|
))}
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</thead>
|
||||||
|
<tbody className="divide-y divide-gray-200">
|
||||||
|
{table.getRowModel().rows.map(row => (
|
||||||
|
<tr
|
||||||
|
key={row.id}
|
||||||
|
className={""}
|
||||||
|
>
|
||||||
|
{row.getVisibleCells().map(cell => (
|
||||||
|
<td key={cell.id}>
|
||||||
|
{flexRender(cell.column.columnDef.cell, cell.getContext())}
|
||||||
|
</td>
|
||||||
|
))}
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
@ -1,13 +1,22 @@
|
|||||||
@import 'tailwindcss';
|
@import 'tailwindcss';
|
||||||
|
|
||||||
/*
|
html {
|
||||||
The default border color has changed to `currentcolor` in Tailwind CSS v4,
|
cursor: url(/public/cursor.png), auto !important;
|
||||||
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
|
@theme {
|
||||||
color utility to any element that depends on these defaults.
|
--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 {
|
@layer base {
|
||||||
*,
|
*,
|
||||||
::after,
|
::after,
|
||||||
|
|||||||
@ -4,16 +4,20 @@ import { App } from "./App";
|
|||||||
import AppContext from "./context/AppContext";
|
import AppContext from "./context/AppContext";
|
||||||
|
|
||||||
|
|
||||||
|
import "./lib/superjson";
|
||||||
import "./index.css";
|
import "./index.css";
|
||||||
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
||||||
|
import { Provider } from "jotai";
|
||||||
const queryClient = new QueryClient()
|
const queryClient = new QueryClient()
|
||||||
|
|
||||||
ReactDOM.createRoot(document.getElementById("app") as HTMLElement).render(
|
ReactDOM.createRoot(document.getElementById("app") as HTMLElement).render(
|
||||||
<React.StrictMode>
|
<React.StrictMode>
|
||||||
<QueryClientProvider client={queryClient}>
|
<Provider>
|
||||||
<AppContext>
|
<QueryClientProvider client={queryClient}>
|
||||||
<App />
|
<AppContext>
|
||||||
</AppContext>
|
<App />
|
||||||
</QueryClientProvider>
|
</AppContext>
|
||||||
|
</QueryClientProvider>
|
||||||
|
</Provider>
|
||||||
</React.StrictMode>,
|
</React.StrictMode>,
|
||||||
);
|
);
|
||||||
|
|||||||
@ -21,7 +21,7 @@ export const EquipmentColumns = [
|
|||||||
] as const
|
] as const
|
||||||
|
|
||||||
export const StatsColumns = [
|
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
|
] as const
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -73,6 +73,7 @@ export class LTOApiv0 implements LTOApi {
|
|||||||
galders,
|
galders,
|
||||||
items: new Map((Object.entries(o.items) as any).map(([k, v]: [string, TricksterItem]):[string, TricksterItem]=>{
|
items: new Map((Object.entries(o.items) as any).map(([k, v]: [string, TricksterItem]):[string, TricksterItem]=>{
|
||||||
v.unique_id = Number(k)
|
v.unique_id = Number(k)
|
||||||
|
v.id = k
|
||||||
return [k, v]
|
return [k, v]
|
||||||
})),
|
})),
|
||||||
}
|
}
|
||||||
|
|||||||
@ -60,7 +60,7 @@ export const StoreAccounts = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const StoreJsonable = {
|
export const StoreJsonable = {
|
||||||
Murder: <T>(s:T):string=>JSON.stringify(Object.entries(s)),
|
Murder: <T extends object>(s:T):string=>JSON.stringify(Object.entries(s)),
|
||||||
Revive: <T>(s:string):T=>JSON.parse(s),
|
Revive: <T>(s:string):T=>JSON.parse(s),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
1
src/lib/superjson.ts
Normal file
1
src/lib/superjson.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
import SuperJSON from "superjson";
|
||||||
@ -1,7 +1,5 @@
|
|||||||
import { TricksterInventory } from "./trickster"
|
import { TricksterInventory } from "./trickster"
|
||||||
import {ColumnInfo, ColumnName, Columns, ColumnSorter, LazyColumn} from "./columns"
|
import {ColumnInfo, ColumnName, Columns, ColumnSorter, LazyColumn} from "./columns"
|
||||||
import { PredefinedMenuItemKey } from "handsontable/plugins/contextMenu"
|
|
||||||
import Handsontable from "handsontable"
|
|
||||||
import { HotTableProps } from "@handsontable/react"
|
import { HotTableProps } from "@handsontable/react"
|
||||||
|
|
||||||
|
|
||||||
@ -95,25 +93,8 @@ export class InventoryTable {
|
|||||||
getTableColumnNames(): string[] {
|
getTableColumnNames(): string[] {
|
||||||
return this.o.columns.map(x=>x.displayName)
|
return this.o.columns.map(x=>x.displayName)
|
||||||
}
|
}
|
||||||
getTableColumnSettings(): Handsontable.ColumnSettings[] {
|
getTableColumnSettings(){
|
||||||
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
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
getTableRows():any[][] {
|
getTableRows():any[][] {
|
||||||
return Object.values(this.inv.items)
|
return Object.values(this.inv.items)
|
||||||
@ -162,47 +143,3 @@ export interface TableRecipe {
|
|||||||
settings: HotTableProps
|
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",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
11
src/lib/table/defs.ts
Normal file
11
src/lib/table/defs.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import { TricksterItem } from "../trickster";
|
||||||
|
|
||||||
|
export interface ItemSelectionStatus {
|
||||||
|
selected: boolean;
|
||||||
|
amount?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ItemWithSelection {
|
||||||
|
item: TricksterItem
|
||||||
|
status?: ItemSelectionStatus;
|
||||||
|
}
|
||||||
138
src/lib/table/tanstack.tsx
Normal file
138
src/lib/table/tanstack.tsx
Normal file
@ -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<ItemWithSelection>();
|
||||||
|
|
||||||
|
const columns = {
|
||||||
|
icon: ch.display({
|
||||||
|
id: 'icon',
|
||||||
|
header: function Component(col) {
|
||||||
|
return <div className="flex flex-row justify-center"></div>
|
||||||
|
},
|
||||||
|
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 <div
|
||||||
|
className={`no-select flex flex-row ${ row.original.status?.selected ? "animate-pulse" : ""}`}
|
||||||
|
onClick={(e)=>{
|
||||||
|
setItemSelection({
|
||||||
|
[row.original.item.id]: selected ? undefined : row.original.item.item_count,
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className="flex flex-row w-6 h-6 justify-center">
|
||||||
|
<img src={row.original.item.item_image || ""} alt="icon" className="select-none object-contain select-none"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
count: ch.display({
|
||||||
|
id: 'count',
|
||||||
|
header: function Component(col){
|
||||||
|
return <div className="flex flex-row justify-center">#</div>
|
||||||
|
},
|
||||||
|
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 <div
|
||||||
|
className={`flex flex-row select-none ${ row.original.status?.selected ? "bg-gray-200" : ""}`}
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
className="w-10 text-center "
|
||||||
|
value={currentValue}
|
||||||
|
onChange={(e)=>{
|
||||||
|
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()} />
|
||||||
|
</div>
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
name: ch.display({
|
||||||
|
id: 'name',
|
||||||
|
header: (col)=> {
|
||||||
|
return <div
|
||||||
|
className="flex flex-row text-sm"
|
||||||
|
>name</div>
|
||||||
|
},
|
||||||
|
cell: function Component({ row }){
|
||||||
|
return <div className="flex flex-row whitespace-pre">
|
||||||
|
<span>{row.original.item.item_name}</span>
|
||||||
|
</div>
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
slots: ch.display({
|
||||||
|
id: 'slots',
|
||||||
|
header: (col)=>{
|
||||||
|
return <div
|
||||||
|
className="flex flex-row text-sm"
|
||||||
|
>slots</div>
|
||||||
|
},
|
||||||
|
cell: function Component({ row }){
|
||||||
|
return <div className="flex flex-row justify-center">
|
||||||
|
<span>{row.original.item.item_slots}</span>
|
||||||
|
</div>
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
stats: ch.group({
|
||||||
|
id: 'stats',
|
||||||
|
header: (col)=>{
|
||||||
|
return <div
|
||||||
|
className="flex flex-row text-sm"
|
||||||
|
>stats</div>
|
||||||
|
},
|
||||||
|
columns: [
|
||||||
|
...StatsColumns.map((c)=>{
|
||||||
|
return ch.display({
|
||||||
|
id: 'stats.'+c,
|
||||||
|
header: (col)=>{
|
||||||
|
return <div
|
||||||
|
className="flex flex-row text-sm justify-center"
|
||||||
|
>{c}</div>
|
||||||
|
},
|
||||||
|
cell: function Component({ row }){
|
||||||
|
const stats = row.original.item.stats
|
||||||
|
const stat = stats ? stats[c] : ""
|
||||||
|
return <div className={`flex flex-row justify-start ${stat ? "border" : ""}`}>
|
||||||
|
<span>{stat}</span>
|
||||||
|
</div>
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
]
|
||||||
|
}),
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export const InventoryColumns = columns;
|
||||||
@ -1,10 +1,13 @@
|
|||||||
export interface TricksterItem {
|
export interface TricksterItem {
|
||||||
|
id: string;
|
||||||
unique_id: number;
|
unique_id: number;
|
||||||
item_name: string;
|
item_name: string;
|
||||||
item_count: number;
|
item_count: number;
|
||||||
item_comment: string;
|
item_comment: string;
|
||||||
item_use: string;
|
item_use: string;
|
||||||
item_slots?: number;
|
item_slots?: number;
|
||||||
|
item_tab: number
|
||||||
|
item_type: number,
|
||||||
item_min_level?: number;
|
item_min_level?: number;
|
||||||
is_equip?: boolean;
|
is_equip?: boolean;
|
||||||
is_drill?: boolean;
|
is_drill?: boolean;
|
||||||
|
|||||||
12
src/main.ts
12
src/main.ts
@ -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')
|
|
||||||
@ -2,9 +2,13 @@ import { AxiosError } from 'axios';
|
|||||||
import { LTOApiv0 } from '../lib/lifeto'
|
import { LTOApiv0 } from '../lib/lifeto'
|
||||||
import { LoginHelper, TokenSession } from '../lib/session'
|
import { LoginHelper, TokenSession } from '../lib/session'
|
||||||
import { atomWithQuery } from 'jotai-tanstack-query'
|
import { atomWithQuery } from 'jotai-tanstack-query'
|
||||||
import {atomFamily, atomWithRefresh, atomWithStorage} from "jotai/utils";
|
import {atomWithStorage} from "jotai/utils";
|
||||||
import { atom } from 'jotai';
|
import { atom } from 'jotai';
|
||||||
import { TricksterCharacter, TricksterInventory, TricksterItem } from '../lib/trickster';
|
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())
|
export const LTOApi = new LTOApiv0(new TokenSession())
|
||||||
|
|
||||||
@ -77,10 +81,9 @@ export const charactersAtom = atomWithQuery((get) => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
export const selectedCharacterAtom = atom<TricksterCharacter | undefined>(undefined)
|
export const selectedCharacterAtom = atomWithStorage<TricksterCharacter | undefined>("lto_state.selected_character", undefined)
|
||||||
export const selectedTargetInventoryAtom = atom<TricksterCharacter | undefined>(undefined)
|
export const selectedTargetInventoryAtom = atom<TricksterCharacter | undefined>(undefined)
|
||||||
|
|
||||||
export const pageSize = atomWithStorage("preference.page_size", 250)
|
|
||||||
export const currentFilter = atom<undefined>(undefined)
|
export const currentFilter = atom<undefined>(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)=>{
|
export const currentCharacterItemsAtom = atom((get)=>{
|
||||||
const {data: inventory} = get(currentCharacterInventoryAtom)
|
const {data: inventory} = get(currentCharacterInventoryAtom)
|
||||||
return inventory?.items || new Map<string, TricksterItem>()
|
const items = inventory?.items || new Map<string, TricksterItem>()
|
||||||
|
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<InventoryFilter>("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<string, number>, number]>([new Map<string, number>(), 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<string,number>(), 0])
|
||||||
|
})
|
||||||
|
|
||||||
|
export const itemSelectionSetActionAtom = atom(null, (get, set, arg: Record<string,number | undefined> ) => {
|
||||||
|
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,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|||||||
116
src/state/storage.ts
Normal file
116
src/state/storage.ts
Normal file
@ -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<unknown> =>
|
||||||
|
typeof (x as any)?.then === 'function'
|
||||||
|
|
||||||
|
type Unsubscribe = () => void
|
||||||
|
|
||||||
|
type Subscribe<Value> = (
|
||||||
|
key: string,
|
||||||
|
callback: (value: Value) => void,
|
||||||
|
initialValue: Value,
|
||||||
|
) => Unsubscribe | undefined
|
||||||
|
|
||||||
|
type StringSubscribe = (
|
||||||
|
key: string,
|
||||||
|
callback: (value: string | null) => void,
|
||||||
|
) => Unsubscribe | undefined
|
||||||
|
|
||||||
|
export function createSuperjsonStorage<Value>(): SyncStorage<Value>
|
||||||
|
export function createSuperjsonStorage<Value>(
|
||||||
|
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<Value> | SyncStorage<Value> {
|
||||||
|
let lastStr: string | undefined
|
||||||
|
let lastValue: Value
|
||||||
|
|
||||||
|
const storage: AsyncStorage<Value> | SyncStorage<Value> = {
|
||||||
|
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<Value> =>
|
||||||
|
(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()
|
||||||
@ -4,6 +4,9 @@
|
|||||||
"target": "esnext",
|
"target": "esnext",
|
||||||
"useDefineForClassFields": true,
|
"useDefineForClassFields": true,
|
||||||
"lib": ["DOM", "DOM.Iterable", "ESNext"],
|
"lib": ["DOM", "DOM.Iterable", "ESNext"],
|
||||||
|
"paths": {
|
||||||
|
"@/*": ["./src/*"]
|
||||||
|
},
|
||||||
"types": [ "node" ],
|
"types": [ "node" ],
|
||||||
"allowJs": true,
|
"allowJs": true,
|
||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
|
|||||||
@ -1,9 +1,15 @@
|
|||||||
import { defineConfig } from 'vite'
|
import { defineConfig } from 'vite'
|
||||||
import react from '@vitejs/plugin-react'
|
import react from '@vitejs/plugin-react'
|
||||||
|
import path from 'path'
|
||||||
|
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [react()],
|
plugins: [react()],
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
'@': path.resolve(__dirname, './src'),
|
||||||
|
},
|
||||||
|
},
|
||||||
server: {
|
server: {
|
||||||
proxy: {
|
proxy: {
|
||||||
// with options
|
// with options
|
||||||
|
|||||||
59
yarn.lock
59
yarn.lock
@ -15,6 +15,18 @@
|
|||||||
"@jridgewell/gen-mapping" "^0.3.5"
|
"@jridgewell/gen-mapping" "^0.3.5"
|
||||||
"@jridgewell/trace-mapping" "^0.3.24"
|
"@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":
|
"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.24.7":
|
||||||
version "7.24.7"
|
version "7.24.7"
|
||||||
resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.24.7.tgz#882fd9e09e8ee324e496bd040401c6f046ef4465"
|
resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.24.7.tgz#882fd9e09e8ee324e496bd040401c6f046ef4465"
|
||||||
@ -1989,6 +2001,18 @@
|
|||||||
dependencies:
|
dependencies:
|
||||||
"@tanstack/query-core" "5.76.0"
|
"@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":
|
"@tybys/wasm-util@^0.9.0":
|
||||||
version "0.9.0"
|
version "0.9.0"
|
||||||
resolved "https://registry.yarnpkg.com/@tybys/wasm-util/-/wasm-util-0.9.0.tgz#3e75eb00604c8d6db470bf18c37b7d984a0e3355"
|
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"
|
resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee"
|
||||||
integrity sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==
|
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":
|
"@types/parse-json@^4.0.0":
|
||||||
version "4.0.2"
|
version "4.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.2.tgz#5950e50960793055845e956c427fc2b0d70c5239"
|
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:
|
dependencies:
|
||||||
deep-equal "^2.0.5"
|
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:
|
array-buffer-byte-length@^1.0.0, array-buffer-byte-length@^1.0.1:
|
||||||
version "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"
|
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"
|
resolved "https://registry.yarnpkg.com/jiti/-/jiti-2.4.2.tgz#d19b7732ebb6116b06e2038da74a55366faef560"
|
||||||
integrity sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==
|
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:
|
jotai-tanstack-query@^0.9.0:
|
||||||
version "0.9.0"
|
version "0.9.0"
|
||||||
resolved "https://registry.yarnpkg.com/jotai-tanstack-query/-/jotai-tanstack-query-0.9.0.tgz#f3c4f96c2ec88f6fdd0b39a1d2eca6a10a8878d6"
|
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:
|
dependencies:
|
||||||
wrappy "1"
|
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:
|
optionator@^0.9.3:
|
||||||
version "0.9.4"
|
version "0.9.4"
|
||||||
resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.4.tgz#7ea1c1a5d91d764fb282139c88fe11e182a3a734"
|
resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.4.tgz#7ea1c1a5d91d764fb282139c88fe11e182a3a734"
|
||||||
@ -5274,6 +5323,11 @@ react-dom@^19.1.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
scheduler "^0.26.0"
|
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:
|
react-is@^16.13.1, react-is@^16.7.0:
|
||||||
version "16.13.1"
|
version "16.13.1"
|
||||||
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
|
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"
|
has-symbols "^1.1.0"
|
||||||
which-boxed-primitive "^1.1.1"
|
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:
|
unicode-canonical-property-names-ecmascript@^2.0.0:
|
||||||
version "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"
|
resolved "https://registry.yarnpkg.com/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz#301acdc525631670d39f6146e0e77ff6bbdebddc"
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user