forked from a/lifeto-shop
Compare commits
10 Commits
Author | SHA1 | Date | |
---|---|---|---|
6e99ef1501 | |||
e39ba4d052 | |||
140c604502 | |||
e913f85a0a | |||
cdbb83d6af | |||
182bcfc13e | |||
9869512d3c | |||
a7ecda47d0 | |||
|
dfbb8ea7af | ||
|
09b4dcbea9 |
7
Makefile
7
Makefile
@ -1,6 +1,9 @@
|
|||||||
|
VERSION=v0.0.2
|
||||||
|
|
||||||
build:
|
build:
|
||||||
docker build -t cr.aaaaa.news/lto:latest .
|
docker build -t tuxpa.in/a/lto:${VERSION} .
|
||||||
|
|
||||||
push:
|
push:
|
||||||
docker push cr.aaaaa.news/lto:latest
|
docker push tuxpa.in/a/lto:${VERSION}
|
||||||
|
|
||||||
|
|
||||||
|
54
eslint.config.cjs
Normal file
54
eslint.config.cjs
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
module.exports = {
|
||||||
|
settings: {
|
||||||
|
react: {
|
||||||
|
version: 'detect',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
root: true,
|
||||||
|
env: { browser: true, es2020: true },
|
||||||
|
extends: [
|
||||||
|
'eslint:recommended',
|
||||||
|
'plugin:@typescript-eslint/recommended',
|
||||||
|
'plugin:react/recommended',
|
||||||
|
'plugin:react-hooks/recommended',
|
||||||
|
'plugin:react/jsx-runtime',
|
||||||
|
],
|
||||||
|
ignorePatterns: ['dist', '.eslintrc.cjs', '**/vendor/**', '**/locales/**', 'generated.*'],
|
||||||
|
parser: '@typescript-eslint/parser',
|
||||||
|
parserOptions: {
|
||||||
|
project: true,
|
||||||
|
tsconfigRootDir: __dirname,
|
||||||
|
ecmaFeatures: {
|
||||||
|
jsx: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
plugins: ['react-refresh', 'react'],
|
||||||
|
rules: {
|
||||||
|
'no-extra-semi': 'off', // this one needs to stay off
|
||||||
|
'@react/no-children-prop': 'off', // tanstack form uses this as a pattern
|
||||||
|
'react/no-children-prop': 'off', // tanstack form uses this as a pattern
|
||||||
|
'no-duplicate-imports': 'warn',
|
||||||
|
'@typescript-eslint/no-extra-semi': 'off',
|
||||||
|
'sort-imports': 'off',
|
||||||
|
'react-hooks/exhaustive-deps': 'off',
|
||||||
|
'react-refresh/only-export-components': 'off',
|
||||||
|
'no-case-declarations': 'off',
|
||||||
|
'no-redeclare': 'off',
|
||||||
|
'no-undef': 'off',
|
||||||
|
'@typescript-eslint/no-explicit-any': 'off',
|
||||||
|
'@typescript-eslint/strict-boolean-expressions': ['error', {
|
||||||
|
"allowString": true,
|
||||||
|
"allowNumber": true,
|
||||||
|
"allowNullableObject": true,
|
||||||
|
"allowNullableBoolean": true,
|
||||||
|
"allowNullableString": true,
|
||||||
|
"allowNullableNumber": false,
|
||||||
|
"allowNullableEnum": true,
|
||||||
|
"allowAny": true
|
||||||
|
}],
|
||||||
|
'react/prop-types': 'off',
|
||||||
|
'no-lonely-if': 2,
|
||||||
|
'no-console': 2,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
@ -4,10 +4,10 @@
|
|||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<link rel="icon" href="/favicon.ico" />
|
<link rel="icon" href="/favicon.ico" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>Vite App</title>
|
<title>lto inventory</title>
|
||||||
</head>
|
</head>
|
||||||
<body style="overflow-y: hidden;">
|
<body style="overflow-y: hidden;" class="w-screen h-screen">
|
||||||
<div id="app"></div>
|
<div id="app" class="w-full h-full"></div>
|
||||||
<script type="module" src="/src/main.ts"></script>
|
<script type="module" src="/src/index.tsx"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
37
package.json
37
package.json
@ -4,27 +4,48 @@
|
|||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
"build": "vue-tsc --noEmit && vite build",
|
"build": "vite build",
|
||||||
"preview": "vite preview"
|
"preview": "vite preview"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@handsontable/vue3": "^12.0.1",
|
"@handsontable/react": "^14.5.0",
|
||||||
|
"@tanstack/react-query": "^5.51.21",
|
||||||
"@types/qs": "^6.9.7",
|
"@types/qs": "^6.9.7",
|
||||||
|
"@types/react": "^18.3.3",
|
||||||
|
"@types/react-dom": "^18.3.0",
|
||||||
"@types/uuid": "^8.3.4",
|
"@types/uuid": "^8.3.4",
|
||||||
|
"@typescript-eslint/eslint-plugin": "^8.0.1",
|
||||||
|
"@typescript-eslint/parser": "^8.0.1",
|
||||||
|
"@vitejs/plugin-react": "^4.3.1",
|
||||||
"@vueuse/core": "^8.7.5",
|
"@vueuse/core": "^8.7.5",
|
||||||
"axios": "^0.27.2",
|
"axios": "^0.27.2",
|
||||||
"handsontable": "^12.0.1",
|
"eslint": "^9.8.0",
|
||||||
|
"eslint-config-react-app": "^7.0.1",
|
||||||
|
"eslint-plugin-react": "^7.35.0",
|
||||||
|
"eslint-plugin-react-hooks": "^4.6.2",
|
||||||
|
"eslint-plugin-react-refresh": "^0.4.9",
|
||||||
|
"handsontable": "^14.5.0",
|
||||||
"loglevel": "^1.8.0",
|
"loglevel": "^1.8.0",
|
||||||
"pinia": "^2.0.14",
|
"pinia": "^2.0.14",
|
||||||
|
"prettier": "^3.3.3",
|
||||||
"qs": "^6.10.5",
|
"qs": "^6.10.5",
|
||||||
"typescript-cookie": "^1.0.4",
|
"react": "^18.3.1",
|
||||||
"uuid": "^8.3.2",
|
"react-dom": "^18.3.1",
|
||||||
|
"react-spinners": "^0.14.1",
|
||||||
|
"typescript-cookie": "^1.0.6",
|
||||||
|
"use-local-storage": "^3.0.0",
|
||||||
|
"usehooks-ts": "^3.1.0",
|
||||||
|
"uuid": "^10.0.0",
|
||||||
"vue": "^3.2.25"
|
"vue": "^3.2.25"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@vitejs/plugin-vue": "^2.3.3",
|
"@vitejs/plugin-vue": "^2.3.3",
|
||||||
"typescript": "^4.5.4",
|
"autoprefixer": "^10.4.20",
|
||||||
"vite": "^2.9.9",
|
"postcss": "^8.4.40",
|
||||||
|
"tailwindcss": "^3.4.7",
|
||||||
|
"typescript": "^5.5.4",
|
||||||
|
"vite": "^5.3.5",
|
||||||
"vue-tsc": "^0.34.7"
|
"vue-tsc": "^0.34.7"
|
||||||
}
|
},
|
||||||
|
"packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
|
||||||
}
|
}
|
||||||
|
6
postcss.config.cjs
Normal file
6
postcss.config.cjs
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
module.exports = {
|
||||||
|
plugins: {
|
||||||
|
tailwindcss: {},
|
||||||
|
autoprefixer: {},
|
||||||
|
},
|
||||||
|
}
|
Binary file not shown.
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 52 KiB |
BIN
public/favicon.png
Normal file
BIN
public/favicon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 593 B |
30
src/App.tsx
Normal file
30
src/App.tsx
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
import { FC } from "react";
|
||||||
|
import { LoginWidget } from "./components/login";
|
||||||
|
import { CharacterRoulette } from "./components/characters";
|
||||||
|
import { Inventory } from "./components/inventory";
|
||||||
|
|
||||||
|
export const App: FC = () => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="flex flex-col p-4 h-full">
|
||||||
|
<div className="grid grid-cols-6 gap-x-4">
|
||||||
|
<div className="col-span-1">
|
||||||
|
<LoginWidget/>
|
||||||
|
</div>
|
||||||
|
<div className="col-span-5 h-full">
|
||||||
|
<CharacterRoulette/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="grid grid-cols-6 h-full">
|
||||||
|
<div className="col-span-1">
|
||||||
|
</div>
|
||||||
|
<div className="col-span-5 h-full">
|
||||||
|
<div className="overflow-hidden h-5/6">
|
||||||
|
<Inventory/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
@ -36,7 +36,7 @@ watch(invs.value,()=>{
|
|||||||
const currentInv = invs.value.get(props.character)
|
const currentInv = invs.value.get(props.character)
|
||||||
if(currentInv){
|
if(currentInv){
|
||||||
if(currentInv.galders){
|
if(currentInv.galders){
|
||||||
galders.value = currentInv.galders
|
galders.value = currentInv.galders || currentInv.money
|
||||||
}
|
}
|
||||||
items.value = Object.values(currentInv.items).length
|
items.value = Object.values(currentInv.items).length
|
||||||
}
|
}
|
||||||
|
84
src/components/characters.tsx
Normal file
84
src/components/characters.tsx
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
import { useEffect, useState } from "react"
|
||||||
|
import { useLtoContext } from "../context/LtoContext"
|
||||||
|
import { JobNumberToString, TricksterAccount, TricksterCharacter } from "../lib/trickster"
|
||||||
|
import { keepPreviousData, useQuery } from "@tanstack/react-query"
|
||||||
|
import { useSessionContext } from "../context/SessionContext"
|
||||||
|
|
||||||
|
export const CharacterCard = ({character}:{
|
||||||
|
character: TricksterCharacter,
|
||||||
|
})=>{
|
||||||
|
|
||||||
|
const {activeTable, setActiveTable} = useSessionContext()
|
||||||
|
return <>
|
||||||
|
<div onClick={()=>{
|
||||||
|
setActiveTable(character.path)
|
||||||
|
}}
|
||||||
|
className={`
|
||||||
|
flex flex-col border border-black
|
||||||
|
hover:cursor-pointer
|
||||||
|
hover:bg-blue-100
|
||||||
|
h-full
|
||||||
|
p-2 ${character.path === activeTable ? `bg-blue-200 hover:bg-blue-100 border-double border-4` : ""}`}>
|
||||||
|
<div className="flex"></div>
|
||||||
|
<div className="flex flex-col justify-between h-full">
|
||||||
|
<div className="flex flex-col gap-2">
|
||||||
|
<div className="flex flex-row justify-center text-md">
|
||||||
|
<span>{character.name}</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-row justify-center">
|
||||||
|
{character.base_job === -8 ?
|
||||||
|
<img src={`https://knowledge.lifeto.co/animations/npc/npc041_5.png`}/>
|
||||||
|
:
|
||||||
|
<img
|
||||||
|
src={`https://knowledge.lifeto.co/animations/character/chr00${character.base_job}_13.png`}/>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-row gap-1">
|
||||||
|
<span>class: </span>
|
||||||
|
<span>{JobNumberToString(character.current_job)}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-row gap-1 text-xs">
|
||||||
|
<span>path: </span>
|
||||||
|
<span>{character.path}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
|
||||||
|
const PleaseLogin = () => {
|
||||||
|
return <><div className="align-center">no characters (not logged in?)</div></>
|
||||||
|
}
|
||||||
|
|
||||||
|
export const CharacterRoulette = ()=>{
|
||||||
|
const {API, loggedIn} = useLtoContext()
|
||||||
|
const {data:characters} = useQuery({
|
||||||
|
queryKey:["characters", API.s.user],
|
||||||
|
queryFn: async ()=> {
|
||||||
|
return API.GetAccounts().then(x=>{
|
||||||
|
if(!x) {
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
return x.flatMap(x=>{return x?.characters})
|
||||||
|
})
|
||||||
|
},
|
||||||
|
enabled: loggedIn,
|
||||||
|
placeholderData: keepPreviousData,
|
||||||
|
})
|
||||||
|
|
||||||
|
if(!characters || characters.length == 0) {
|
||||||
|
return <PleaseLogin/>
|
||||||
|
}
|
||||||
|
|
||||||
|
return <>
|
||||||
|
<div className="flex flex-row overflow-x-scroll gap-4 h-full">
|
||||||
|
{characters.map(x=>{
|
||||||
|
return <CharacterCard key={x.id} character={x} />
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
|
||||||
|
|
||||||
|
}
|
144
src/components/inventory.tsx
Normal file
144
src/components/inventory.tsx
Normal file
@ -0,0 +1,144 @@
|
|||||||
|
import { keepPreviousData, useQuery } from "@tanstack/react-query"
|
||||||
|
import { TricksterCharacter } from "../lib/trickster"
|
||||||
|
import { useSessionContext } from "../context/SessionContext"
|
||||||
|
import { useLtoContext } from "../context/LtoContext"
|
||||||
|
|
||||||
|
import 'handsontable/dist/handsontable.full.min.css';
|
||||||
|
|
||||||
|
import { registerAllModules } from 'handsontable/registry';
|
||||||
|
import { HotTable, HotTableClass } from '@handsontable/react';
|
||||||
|
import { useCallback, useEffect, useRef, useState } from "react";
|
||||||
|
import { InventoryTable } from "../lib/table";
|
||||||
|
import { DotLoader } from "react-spinners";
|
||||||
|
import { useDebounceCallback, useResizeObserver } from "usehooks-ts";
|
||||||
|
import { Columns } from "../lib/columns";
|
||||||
|
import { OrderDetails, OrderSender } from "../lib/lifeto/order_manager";
|
||||||
|
import log from "loglevel";
|
||||||
|
|
||||||
|
registerAllModules();
|
||||||
|
type Size = {
|
||||||
|
width?: number
|
||||||
|
height?: number
|
||||||
|
}
|
||||||
|
export const Inventory = () => {
|
||||||
|
const {activeTable, columns, tags, orders} = useSessionContext()
|
||||||
|
const {API, loggedIn} = useLtoContext()
|
||||||
|
|
||||||
|
const ref = useRef<HTMLDivElement>(null)
|
||||||
|
const [{ height }, setSize] = useState<Size>({
|
||||||
|
width: 100,
|
||||||
|
height: 100,
|
||||||
|
})
|
||||||
|
|
||||||
|
const onResize = useDebounceCallback(setSize, 200)
|
||||||
|
|
||||||
|
useResizeObserver({
|
||||||
|
ref,
|
||||||
|
onResize,
|
||||||
|
})
|
||||||
|
|
||||||
|
const hotTableComponent = useRef<HotTableClass>(null);
|
||||||
|
const {data:character, isLoading, isFetching } = useQuery({
|
||||||
|
queryKey:["inventory", activeTable],
|
||||||
|
queryFn: async ()=> {
|
||||||
|
return API.GetInventory(activeTable)
|
||||||
|
},
|
||||||
|
enabled: loggedIn,
|
||||||
|
// placeholderData: keepPreviousData,
|
||||||
|
})
|
||||||
|
const {data:characters} = useQuery({
|
||||||
|
queryKey:["characters", API.s.user],
|
||||||
|
queryFn: async ()=> {
|
||||||
|
return API.GetAccounts().then(x=>{
|
||||||
|
return x.flatMap(x=>{return x.characters})
|
||||||
|
})
|
||||||
|
},
|
||||||
|
enabled: loggedIn,
|
||||||
|
placeholderData: keepPreviousData,
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
useEffect(()=>{
|
||||||
|
if(!character) {
|
||||||
|
hotTableComponent.current?.hotInstance?.updateSettings({
|
||||||
|
data: [],
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const it = new InventoryTable(character, {
|
||||||
|
columns: columns,
|
||||||
|
tags: tags,
|
||||||
|
accounts: characters?.map(x=>{
|
||||||
|
return x.name
|
||||||
|
}) || [],
|
||||||
|
})
|
||||||
|
const build = it.BuildTable()
|
||||||
|
hotTableComponent.current?.hotInstance?.updateSettings(build.settings)
|
||||||
|
}, [hotTableComponent, character, height])
|
||||||
|
|
||||||
|
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>
|
||||||
|
}
|
||||||
|
return <div ref={ref} className={``}>
|
||||||
|
<div className="flex flex-row py-2 px-3">
|
||||||
|
<div
|
||||||
|
onClick={(e)=>{
|
||||||
|
sendOrders()
|
||||||
|
}}
|
||||||
|
className="
|
||||||
|
hover:cursor-pointer
|
||||||
|
border border-black-1
|
||||||
|
bg-green-200
|
||||||
|
px-2 py-1
|
||||||
|
">ayy lmao button</div>
|
||||||
|
</div>
|
||||||
|
{(isLoading || isFetching) ? <Loading/> : <></> }
|
||||||
|
<div
|
||||||
|
className={`${isLoading || isFetching ? "invisible" : ""}`}>
|
||||||
|
<HotTable
|
||||||
|
ref={hotTableComponent as any}
|
||||||
|
licenseKey="non-commercial-and-evaluation"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
62
src/components/login.tsx
Normal file
62
src/components/login.tsx
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
import { useEffect, useState } from "react"
|
||||||
|
import { useLtoContext } from "../context/LtoContext"
|
||||||
|
import useLocalStorage from "use-local-storage"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
export const LoginWidget = () => {
|
||||||
|
const {loggedIn, login, logout} = useLtoContext()
|
||||||
|
|
||||||
|
const [username, setUsername] = useLocalStorage("input_username","", {syncData: false})
|
||||||
|
const [password, setPassword] = useState("")
|
||||||
|
return <>
|
||||||
|
<div className="flex flex-col border border-gray-400">
|
||||||
|
<div className="flex flex-col">
|
||||||
|
<div className="flex flex-row bg-blue-400">
|
||||||
|
<span className="text-white pb-1 pl-2 m-y-1">
|
||||||
|
account
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-row flex-wrap gap-1 p-2 justify-center">
|
||||||
|
<div>
|
||||||
|
<input
|
||||||
|
onChange={(e)=>{
|
||||||
|
setUsername(e.target.value)
|
||||||
|
}}
|
||||||
|
value={username}
|
||||||
|
placeholder="username" className="w-32 pl-2 pb-1 border border-gray-600"/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<input
|
||||||
|
onChange={(e)=>{
|
||||||
|
setPassword(e.target.value)
|
||||||
|
}}
|
||||||
|
value={password}
|
||||||
|
type="password" placeholder="password" className="w-32 pl-2 pb-1 border border-gray-600"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-row p-2 justify-center gap-4">
|
||||||
|
<button
|
||||||
|
onClick={async ()=>{
|
||||||
|
login(username,password).catch((e)=>{
|
||||||
|
alert(e.toString())
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
className="border border-gray-600 px-2 py-1 hover:bg-blue-200">
|
||||||
|
login
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={()=>{
|
||||||
|
logout()
|
||||||
|
return
|
||||||
|
}}
|
||||||
|
disabled={!loggedIn} className="border border-gray-600 px-2 py-1 hover:bg-red-200 disabled:border-gray-400 disabled:text-gray-300 disabled:bg-white ">
|
||||||
|
logout
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
|
||||||
|
|
21
src/context/AppContext.tsx
Normal file
21
src/context/AppContext.tsx
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import { LtoContextProvider } from "./LtoContext";
|
||||||
|
import { SessionContextProvider } from "./SessionContext";
|
||||||
|
|
||||||
|
interface IContext {
|
||||||
|
children: React.ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
function AppContext(props: IContext): any {
|
||||||
|
const { children } = props;
|
||||||
|
const providers = [
|
||||||
|
SessionContextProvider,
|
||||||
|
LtoContextProvider
|
||||||
|
];
|
||||||
|
const res = providers.reduceRight(
|
||||||
|
(acc, CurrVal) => <CurrVal>{acc as any}</CurrVal>,
|
||||||
|
children,
|
||||||
|
);
|
||||||
|
return res as any;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default AppContext;
|
68
src/context/LtoContext.tsx
Normal file
68
src/context/LtoContext.tsx
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
import { createContext, useContext, useEffect, useState } from "react";
|
||||||
|
import { LTOApiv0 } from "../lib/lifeto";
|
||||||
|
import { storage } from "../session_storage";
|
||||||
|
import { LoginHelper, LogoutHelper } from "../lib/session";
|
||||||
|
|
||||||
|
interface LtoContextProps {
|
||||||
|
API: LTOApiv0;
|
||||||
|
login: (username:string, password:string)=>Promise<void>;
|
||||||
|
logout: ()=>void;
|
||||||
|
loggedIn: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
const LtoContext = createContext({} as LtoContextProps);
|
||||||
|
|
||||||
|
export const LtoContextProvider = ({ children }: { children: any }) => {
|
||||||
|
const [API, setAPI] = useState(new LTOApiv0(storage.GetSession()));
|
||||||
|
const login = async (username:string , password:string ) =>{
|
||||||
|
console.log("attempting logiun", username)
|
||||||
|
return new LoginHelper(username, password).login()
|
||||||
|
.catch((e)=>{
|
||||||
|
if(e.code == "ERR_BAD_REQUEST") {
|
||||||
|
throw "invalid username/password"
|
||||||
|
}
|
||||||
|
console.warn("throwing error", e)
|
||||||
|
throw "unknown error, please report"
|
||||||
|
})
|
||||||
|
.then((session)=>{
|
||||||
|
setAPI(new LTOApiv0(session))
|
||||||
|
storage.AddSession(session)
|
||||||
|
setLoggedIn(true)
|
||||||
|
}) }
|
||||||
|
|
||||||
|
const logout = () => {
|
||||||
|
new LogoutHelper().logout().then(()=>{
|
||||||
|
storage.RemoveSession()
|
||||||
|
localStorage.clear()
|
||||||
|
window.location.reload()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const [loggedIn, setLoggedIn] = useState(false)
|
||||||
|
useEffect(()=>{
|
||||||
|
if(!API) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
API?.GetLoggedin().then((x)=>{
|
||||||
|
setLoggedIn(x)
|
||||||
|
})
|
||||||
|
}, [API])
|
||||||
|
|
||||||
|
return <LtoContext.Provider value={{
|
||||||
|
API,
|
||||||
|
login,
|
||||||
|
logout,
|
||||||
|
loggedIn
|
||||||
|
}}>{children}</LtoContext.Provider>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useLtoContext = (): LtoContextProps => {
|
||||||
|
const context = useContext<LtoContextProps>(LtoContext);
|
||||||
|
if (context === null) {
|
||||||
|
throw new Error(
|
||||||
|
'"useLtoContext" should be used inside a "LtoContextProvider"',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return context;
|
||||||
|
};
|
72
src/context/SessionContext.tsx
Normal file
72
src/context/SessionContext.tsx
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
import { createContext, Dispatch, SetStateAction, useContext, useState } from "react";
|
||||||
|
import { LTOApiv0 } from "../lib/lifeto";
|
||||||
|
|
||||||
|
type Setter<T> = React.Dispatch<React.SetStateAction<T | undefined>>;
|
||||||
|
type MustSetter<T> = React.Dispatch<React.SetStateAction<T>>;
|
||||||
|
import useLocalStorage from "use-local-storage";
|
||||||
|
import { OrderTracker } from "../lib/lifeto/order_manager";
|
||||||
|
import { ColumnSet } from "../lib/table";
|
||||||
|
import { StoreAccounts, StoreChars, StoreColSet, StoreStr } from "../lib/storage";
|
||||||
|
import { BasicColumns, ColumnInfo, ColumnName, Columns, DetailsColumns, MoveColumns } from '../lib/columns'
|
||||||
|
|
||||||
|
interface SessionContextProps {
|
||||||
|
orders: OrderTracker;
|
||||||
|
activeTable: string;
|
||||||
|
screen: string;
|
||||||
|
columns: ColumnSet;
|
||||||
|
tags: ColumnSet;
|
||||||
|
dirty: number;
|
||||||
|
currentSearch: string;
|
||||||
|
|
||||||
|
setActiveTable: Setter<string>
|
||||||
|
setScreen: Setter<string>
|
||||||
|
setDirty: MustSetter<number>
|
||||||
|
setCurrentSearch: MustSetter<string>
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const _defaultColumn:(ColumnInfo| ColumnName)[] = [
|
||||||
|
...BasicColumns,
|
||||||
|
...MoveColumns,
|
||||||
|
...DetailsColumns,
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
const SessionContext = createContext({} as SessionContextProps);
|
||||||
|
|
||||||
|
const dotry = (x:any, d: any)=>{
|
||||||
|
try{
|
||||||
|
return x()
|
||||||
|
}catch{
|
||||||
|
return d
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const SessionContextProvider = ({ children }: { children: any }) => {
|
||||||
|
const [activeTable, setActiveTable] = useLocalStorage<string>("activeTable","")
|
||||||
|
const [screen, setScreen] = useLocalStorage<string>("screen","")
|
||||||
|
const [columns ] = useState<ColumnSet>(new ColumnSet(_defaultColumn));
|
||||||
|
const [tags ] = useState<ColumnSet>(dotry(()=>StoreColSet.Revive("tags"), new ColumnSet()));
|
||||||
|
|
||||||
|
const [orders ] = useState<OrderTracker>(new OrderTracker());
|
||||||
|
const [dirty, setDirty] = useState<number>(0);
|
||||||
|
const [currentSearch, setCurrentSearch] = useState<string>("");
|
||||||
|
return (
|
||||||
|
<SessionContext.Provider value={{
|
||||||
|
orders, activeTable, screen, columns, tags, dirty, currentSearch,
|
||||||
|
setActiveTable, setScreen, setDirty, setCurrentSearch,
|
||||||
|
}}>{children}</SessionContext.Provider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useSessionContext = (): SessionContextProps => {
|
||||||
|
const context = useContext<SessionContextProps>(SessionContext);
|
||||||
|
if (context === null) {
|
||||||
|
throw new Error(
|
||||||
|
'"useSessionContext" should be used inside a "SessionContextProvider"',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return context;
|
||||||
|
};
|
3
src/index.css
Normal file
3
src/index.css
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
@tailwind base;
|
||||||
|
@tailwind components;
|
||||||
|
@tailwind utilities;
|
19
src/index.tsx
Normal file
19
src/index.tsx
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import React from "react";
|
||||||
|
import ReactDOM from "react-dom/client";
|
||||||
|
import { App } from "./App";
|
||||||
|
import AppContext from "./context/AppContext";
|
||||||
|
|
||||||
|
|
||||||
|
import "./index.css";
|
||||||
|
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
||||||
|
const queryClient = new QueryClient()
|
||||||
|
|
||||||
|
ReactDOM.createRoot(document.getElementById("app") as HTMLElement).render(
|
||||||
|
<React.StrictMode>
|
||||||
|
<QueryClientProvider client={queryClient}>
|
||||||
|
<AppContext>
|
||||||
|
<App />
|
||||||
|
</AppContext>
|
||||||
|
</QueryClientProvider>
|
||||||
|
</React.StrictMode>,
|
||||||
|
);
|
@ -17,7 +17,7 @@ class Image implements ColumnInfo {
|
|||||||
displayName = " "
|
displayName = " "
|
||||||
renderer = coverRenderer
|
renderer = coverRenderer
|
||||||
getter(item:TricksterItem):(string|number) {
|
getter(item:TricksterItem):(string|number) {
|
||||||
return item.image ? item.image : ""
|
return item.item_image ? item.item_image : ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -207,7 +207,7 @@ class Compound implements ColumnInfo {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const compFilter= (item:TricksterItem): boolean => {
|
const compFilter= (item:TricksterItem): boolean => {
|
||||||
return (item.item_desc.toLowerCase().includes("compound item"))
|
return (item.item_comment.toLowerCase().includes("compound item"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -379,7 +379,7 @@ class Desc implements ColumnInfo {
|
|||||||
displayName = "desc"
|
displayName = "desc"
|
||||||
renderer = descRenderer
|
renderer = descRenderer
|
||||||
getter(item:TricksterItem):(string|number){
|
getter(item:TricksterItem):(string|number){
|
||||||
return item.item_desc
|
return item.item_comment
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
function descRenderer(instance:any, td:any, row:any, col:any, prop:any, value:any, cellProperties:any) {
|
function descRenderer(instance:any, td:any, row:any, col:any, prop:any, value:any, cellProperties:any) {
|
||||||
|
@ -46,7 +46,8 @@ export class LTOApiv0 implements LTOApi {
|
|||||||
if(char_path.startsWith(":")) {
|
if(char_path.startsWith(":")) {
|
||||||
char_path = char_path.replace(":","")
|
char_path = char_path.replace(":","")
|
||||||
}
|
}
|
||||||
return this.s.request("GET", `item-manager/items/account/${char_path}`,undefined).then((ans:AxiosResponse)=>{
|
let type = char_path.includes("/") ? "char" : "account"
|
||||||
|
return this.s.request("GET", `v2/item-manager/items/${type}/${char_path}`,undefined).then((ans:AxiosResponse)=>{
|
||||||
const o = ans.data
|
const o = ans.data
|
||||||
log.debug("GetInventory", o)
|
log.debug("GetInventory", o)
|
||||||
let name = "bank"
|
let name = "bank"
|
||||||
@ -63,7 +64,9 @@ export class LTOApiv0 implements LTOApi {
|
|||||||
id = Number(char)
|
id = Number(char)
|
||||||
galders = val.galders
|
galders = val.galders
|
||||||
}
|
}
|
||||||
let out = {
|
let out:TricksterInventory = {
|
||||||
|
account_name: o.account.account_gid,
|
||||||
|
account_id: o.account.account_code,
|
||||||
name,
|
name,
|
||||||
id,
|
id,
|
||||||
path: char_path,
|
path: char_path,
|
||||||
@ -72,18 +75,20 @@ export class LTOApiv0 implements LTOApi {
|
|||||||
v.unique_id = Number(k)
|
v.unique_id = Number(k)
|
||||||
return [k, v]
|
return [k, v]
|
||||||
})),
|
})),
|
||||||
} as TricksterInventory
|
}
|
||||||
return out
|
return out
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
GetAccounts = async ():Promise<TricksterAccount[]> => {
|
GetAccounts = async ():Promise<TricksterAccount[]> => {
|
||||||
return this.s.request("GET", "characters/list",undefined).then((ans:AxiosResponse)=>{
|
return this.s.request("GET", "characters/list",undefined).then((ans:AxiosResponse)=>{
|
||||||
log.debug("GetAccounts", ans.data)
|
log.debug("GetAccounts", ans.data)
|
||||||
return ans.data.map((x:any)=>{
|
return ans.data.map((x:any):TricksterAccount=>{
|
||||||
return {
|
return {
|
||||||
name: x.name,
|
name: x.name,
|
||||||
characters: [{id: x.id,account_id:x.id, path:x.name, name: x.name+'/bank', class:-8, base_job: -8, current_job: -8},...Object.values(x.characters).map((z:any)=>{
|
characters: [
|
||||||
|
{account_name:x.name, id: x.id,account_id:x.id, path:x.name, name: x.name+'/bank', class:-8, base_job: -8, current_job: -8},...Object.values(x.characters).map((z:any)=>{
|
||||||
return {
|
return {
|
||||||
|
account_name:x.name,
|
||||||
account_id: x.id,
|
account_id: x.id,
|
||||||
id: z.id,
|
id: z.id,
|
||||||
name: z.name,
|
name: z.name,
|
||||||
@ -93,7 +98,7 @@ export class LTOApiv0 implements LTOApi {
|
|||||||
current_job: z.current_job,
|
current_job: z.current_job,
|
||||||
}
|
}
|
||||||
})],
|
})],
|
||||||
} as TricksterAccount
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -117,9 +117,9 @@ export class InvalidOrder extends Order{
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface BasicResponse {
|
export interface BasicResponse {
|
||||||
status: number
|
status: string
|
||||||
data: any
|
data: any
|
||||||
msg?: string
|
message?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -153,14 +153,14 @@ export class InternalXfer extends BasicOrder{
|
|||||||
this.mark("WORKING")
|
this.mark("WORKING")
|
||||||
return api.BankAction<InternalXferRequest, InternalXferResponse>("internal-xfer-item",this.originalRequest)
|
return api.BankAction<InternalXferRequest, InternalXferResponse>("internal-xfer-item",this.originalRequest)
|
||||||
.then((x:InternalXferResponse)=>{
|
.then((x:InternalXferResponse)=>{
|
||||||
if(x.status == 200){
|
if(x.status === 'success'){
|
||||||
this.originalResponse = x
|
this.originalResponse = x
|
||||||
this.stage = 1
|
this.stage = 1
|
||||||
this.mark("SUCCESS")
|
this.mark("SUCCESS")
|
||||||
const origin_item = r.invs.value.get(this.details?.origin_path!)!.items[this.details?.item_uid!]!
|
const origin_item = r.invs.value.get(this.details?.origin_path!)!.items[this.details?.item_uid!]!
|
||||||
origin_item.item_count = origin_item.item_count - this.details?.count!
|
origin_item.item_count = origin_item.item_count - this.details?.count!
|
||||||
}else{
|
}else{
|
||||||
throw x.msg
|
throw x.message
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch((e)=>{
|
.catch((e)=>{
|
||||||
@ -207,14 +207,14 @@ export class BankItem extends BasicOrder{
|
|||||||
return api.BankAction<BankItemRequest, BankItemResponse>("bank-item",this.originalRequest)
|
return api.BankAction<BankItemRequest, BankItemResponse>("bank-item",this.originalRequest)
|
||||||
.then((x)=>{
|
.then((x)=>{
|
||||||
debug("BankItem",x)
|
debug("BankItem",x)
|
||||||
if(x.status == 200){
|
if(x.status === 'success'){
|
||||||
this.stage = 1
|
this.stage = 1
|
||||||
this.originalResponse = x
|
this.originalResponse = x
|
||||||
this.mark("SUCCESS")
|
this.mark("SUCCESS")
|
||||||
const origin_item = r.invs.value.get(this.details?.origin_path!)!.items[this.details?.item_uid!]!
|
const origin_item = r.invs.value.get(this.details?.origin_path!)!.items[this.details?.item_uid!]!
|
||||||
origin_item.item_count = origin_item.item_count - this.details?.count!
|
origin_item.item_count = origin_item.item_count - this.details?.count!
|
||||||
}else {
|
}else {
|
||||||
throw x.msg ? x.msg : "unknown error"
|
throw x.message ? x.message : "unknown error"
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch((e)=>{
|
.catch((e)=>{
|
||||||
@ -272,7 +272,7 @@ export class PrivateMarket extends BasicOrder{
|
|||||||
return api.BankAction<PrivateMarketRequest, PrivateMarketResponse>("sell-item",this.originalRequest)
|
return api.BankAction<PrivateMarketRequest, PrivateMarketResponse>("sell-item",this.originalRequest)
|
||||||
.then((x)=>{
|
.then((x)=>{
|
||||||
debug("PrivateMarket",x)
|
debug("PrivateMarket",x)
|
||||||
if(x.status == 200){
|
if(x.status === 'success'){
|
||||||
this.stage = 1
|
this.stage = 1
|
||||||
this.originalResponse = x
|
this.originalResponse = x
|
||||||
this.mark("SUCCESS")
|
this.mark("SUCCESS")
|
||||||
@ -284,7 +284,7 @@ export class PrivateMarket extends BasicOrder{
|
|||||||
}catch(e){
|
}catch(e){
|
||||||
}
|
}
|
||||||
}else {
|
}else {
|
||||||
throw x.msg ? x.msg : "unknown error"
|
throw x.message ? x.message : "unknown error"
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch((e)=>{
|
.catch((e)=>{
|
||||||
@ -364,7 +364,7 @@ export class MarketMove extends PrivateMarket {
|
|||||||
.then((x)=>{
|
.then((x)=>{
|
||||||
debug("MarketMove",x)
|
debug("MarketMove",x)
|
||||||
this.moveResponse = x
|
this.moveResponse = x
|
||||||
if(x.status == 200){
|
if(x.status === 'success'){
|
||||||
this.moveStage = 1
|
this.moveStage = 1
|
||||||
this.moveState = "SUCCESS"
|
this.moveState = "SUCCESS"
|
||||||
this.newUid = x.item_uid
|
this.newUid = x.item_uid
|
||||||
@ -440,7 +440,7 @@ export class MarketMoveToChar extends MarketMove {
|
|||||||
.then((x)=>{
|
.then((x)=>{
|
||||||
debug("MarketMoveToChar",x)
|
debug("MarketMoveToChar",x)
|
||||||
this.charResponse = x
|
this.charResponse = x
|
||||||
if(x.status == 200){
|
if(x.status === 'success'){
|
||||||
this.charStage = 1
|
this.charStage = 1
|
||||||
this.charState = "SUCCESS"
|
this.charState = "SUCCESS"
|
||||||
}else {
|
}else {
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { RefStore } from "../../state/state";
|
import { RefStore } from "../../state/state";
|
||||||
import { Serializable } from "../storage";
|
import { Serializable } from "../storage";
|
||||||
|
import { TricksterCharacter } from "../trickster";
|
||||||
import { LTOApi } from "./api";
|
import { LTOApi } from "./api";
|
||||||
import { pathIsBank, splitPath } from "./lifeto";
|
import { pathIsBank, splitPath } from "./lifeto";
|
||||||
import { BankItem, InternalXfer, InvalidOrder, MarketMove, Order,MarketMoveToChar, TxnDetails } from "./order";
|
import { BankItem, InternalXfer, InvalidOrder, MarketMove, Order,MarketMoveToChar, TxnDetails } from "./order";
|
||||||
@ -76,14 +77,15 @@ export class OrderTracker implements Serializable<OrderTracker> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class OrderSender {
|
export class OrderSender {
|
||||||
r: RefStore
|
constructor(
|
||||||
constructor(r:RefStore) {
|
private orders: OrderTracker,
|
||||||
this.r = r
|
private chars: Map<string,TricksterCharacter>,
|
||||||
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
send(o:OrderDetails):Order {
|
send(o:OrderDetails):Order {
|
||||||
const formed = this.form(o)
|
const formed = this.form(o)
|
||||||
this.r.orders.value.orders[formed.action_id] = formed
|
this.orders.orders[formed.action_id] = formed
|
||||||
return formed
|
return formed
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -107,8 +109,8 @@ export class OrderSender {
|
|||||||
return notSupported
|
return notSupported
|
||||||
}
|
}
|
||||||
bank_to_bank(o:OrderDetails): Order{
|
bank_to_bank(o:OrderDetails): Order{
|
||||||
const origin = this.r.chars.value.get(o.origin_path)
|
const origin = this.chars.get(o.origin_path)
|
||||||
const target = this.r.chars.value.get(o.target_path)
|
const target = this.chars.get(o.target_path)
|
||||||
if(!(origin && target)) {
|
if(!(origin && target)) {
|
||||||
return notFound
|
return notFound
|
||||||
}
|
}
|
||||||
@ -116,41 +118,42 @@ export class OrderSender {
|
|||||||
}
|
}
|
||||||
bank_to_user(o:OrderDetails): Order{
|
bank_to_user(o:OrderDetails): Order{
|
||||||
// get the uid of the bank
|
// get the uid of the bank
|
||||||
const origin = this.r.chars.value.get(o.origin_path)
|
const origin = this.chars.get(o.origin_path)
|
||||||
const target = this.r.chars.value.get(o.target_path)
|
const target = this.chars.get(o.target_path)
|
||||||
if(!(origin && target)) {
|
if(!(origin && target)) {
|
||||||
return notFound
|
return notFound
|
||||||
}
|
}
|
||||||
const [account, name] = splitPath(target.path)
|
const [account, name] = splitPath(target.path)
|
||||||
if(account != origin.path) {
|
/*if(account != origin.path) {
|
||||||
return new MarketMoveToChar(this.transformInternalOrder(o))
|
return new MarketMoveToChar(this.transformInternalOrder(o))
|
||||||
}
|
}*/
|
||||||
return new InternalXfer(this.transformInternalOrder(o))
|
return new InternalXfer(this.transformInternalOrder(o))
|
||||||
}
|
}
|
||||||
user_to_bank(o:OrderDetails): Order{
|
user_to_bank(o:OrderDetails): Order{
|
||||||
const origin = this.r.chars.value.get(o.origin_path)
|
const origin = this.chars.get(o.origin_path)
|
||||||
const target = this.r.chars.value.get(o.target_path)
|
const target = this.chars.get(o.target_path)
|
||||||
if(!(origin && target)) {
|
if(!(origin && target)) {
|
||||||
return notFound
|
return notFound
|
||||||
}
|
}
|
||||||
const [account, name] = splitPath(origin.path)
|
const [account, name] = splitPath(origin.path)
|
||||||
if(account != target.path) {
|
/*if(account != target.path) {
|
||||||
return new MarketMove(this.transformInternalOrder(o))
|
return new MarketMove(this.transformInternalOrder(o))
|
||||||
}
|
}*/
|
||||||
return new BankItem(this.transformInternalOrder(o))
|
return new BankItem(this.transformInternalOrder(o))
|
||||||
}
|
}
|
||||||
user_to_user(o:OrderDetails): Order{
|
user_to_user(o:OrderDetails): Order{
|
||||||
const origin = this.r.chars.value.get(o.origin_path)
|
const origin = this.chars.get(o.origin_path)
|
||||||
const target = this.r.chars.value.get(o.target_path)
|
const target = this.chars.get(o.target_path)
|
||||||
if(!(origin && target)) {
|
if(!(origin && target)) {
|
||||||
return notFound
|
return notFound
|
||||||
}
|
}
|
||||||
return new MarketMoveToChar(this.transformInternalOrder(o))
|
// return new MarketMoveToChar(this.transformInternalOrder(o))
|
||||||
|
return new InternalXfer(this.transformInternalOrder(o))
|
||||||
}
|
}
|
||||||
|
|
||||||
private transformInternalOrder(o:OrderDetails):TxnDetails {
|
private transformInternalOrder(o:OrderDetails):TxnDetails {
|
||||||
const origin = this.r.chars.value.get(o.origin_path)!
|
const origin = this.chars.get(o.origin_path)!
|
||||||
const target = this.r.chars.value.get(o.target_path)!
|
const target = this.chars.get(o.target_path)!
|
||||||
return {
|
return {
|
||||||
origin: origin.id.toString(),
|
origin: origin.id.toString(),
|
||||||
target: target.id.toString(),
|
target: target.id.toString(),
|
||||||
|
@ -80,7 +80,7 @@ class Image implements ColumnInfo {
|
|||||||
displayName = " "
|
displayName = " "
|
||||||
renderer = coverRenderer
|
renderer = coverRenderer
|
||||||
getter(item:TricksterItem):(string|number) {
|
getter(item:TricksterItem):(string|number) {
|
||||||
return item.image ? item.image : ""
|
return item.item_image ? item.item_image : ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -255,7 +255,7 @@ class Compound implements ColumnInfo {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const compFilter= (item:TricksterItem): boolean => {
|
const compFilter= (item:TricksterItem): boolean => {
|
||||||
return (item.item_desc.toLowerCase().includes("compound item"))
|
return (item.item_comment.toLowerCase().includes("compound item"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -427,7 +427,7 @@ class Desc implements ColumnInfo {
|
|||||||
displayName = "desc"
|
displayName = "desc"
|
||||||
renderer = descRenderer
|
renderer = descRenderer
|
||||||
getter(item:TricksterItem):(string|number){
|
getter(item:TricksterItem):(string|number){
|
||||||
return item.item_desc
|
return item.item_comment
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
function descRenderer(instance:any, td:any, row:any, col:any, prop:any, value:any, cellProperties:any) {
|
function descRenderer(instance:any, td:any, row:any, col:any, prop:any, value:any, cellProperties:any) {
|
||||||
|
@ -6,11 +6,11 @@ import { getCookie, removeCookie } from "typescript-cookie";
|
|||||||
export const SITE_ROOT = "/lifeto/"
|
export const SITE_ROOT = "/lifeto/"
|
||||||
|
|
||||||
export const API_ROOT = "api/lifeto/"
|
export const API_ROOT = "api/lifeto/"
|
||||||
export const BANK_ROOT = "item-manager-action/"
|
export const BANK_ROOT = "api/lifeto/v2/item-manager/"
|
||||||
export const MARKET_ROOT = "marketplace-api/"
|
export const MARKET_ROOT = "marketplace-api/"
|
||||||
|
|
||||||
const login_endpoint = (name:string)=>{
|
const login_endpoint = (name:string)=>{
|
||||||
return SITE_ROOT + name
|
return SITE_ROOT + name + "?canonical=1"
|
||||||
}
|
}
|
||||||
export const api_endpoint = (name:string):string =>{
|
export const api_endpoint = (name:string):string =>{
|
||||||
return SITE_ROOT+API_ROOT + name
|
return SITE_ROOT+API_ROOT + name
|
||||||
@ -48,18 +48,18 @@ export class LoginHelper {
|
|||||||
}
|
}
|
||||||
login = async ():Promise<TokenSession> =>{
|
login = async ():Promise<TokenSession> =>{
|
||||||
return axios.get(login_endpoint("login"),{withCredentials:false})
|
return axios.get(login_endpoint("login"),{withCredentials:false})
|
||||||
.then(async (x)=>{
|
.then(async ()=>{
|
||||||
return axios.post(login_endpoint("login"),{
|
return axios.post(login_endpoint("login"),{
|
||||||
login:this.user,
|
login:this.user,
|
||||||
password:this.pass,
|
password:this.pass,
|
||||||
redirectTo:"lifeto"
|
redirectTo:"lifeto"
|
||||||
},{withCredentials:false})
|
},{withCredentials:false})
|
||||||
.then(async (x)=>{
|
}).then(async ()=>{
|
||||||
await sleep(100)
|
await sleep(100)
|
||||||
let xsrf= getCookie("XSRF-TOKEN")
|
let xsrf= getCookie("XSRF-TOKEN")
|
||||||
return new TokenSession(this.user,this.csrf!, xsrf!)
|
return new TokenSession(this.user,this.csrf!, xsrf!)
|
||||||
})
|
})
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -67,7 +67,7 @@ export class LogoutHelper{
|
|||||||
constructor(){
|
constructor(){
|
||||||
}
|
}
|
||||||
logout = async ():Promise<void> =>{
|
logout = async ():Promise<void> =>{
|
||||||
return axios.get(login_endpoint("logout"),{withCredentials:false}).catch((e)=>{})
|
return axios.get(login_endpoint("logout"),{withCredentials:false}).catch(()=>{}).then(()=>{})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const sleep = async(ms:number)=> {
|
const sleep = async(ms:number)=> {
|
||||||
|
@ -1,10 +1,8 @@
|
|||||||
import { HotTableProps } from "@handsontable/vue3/types"
|
|
||||||
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 { ColumnSettings } from "handsontable/settings"
|
|
||||||
import { PredefinedMenuItemKey } from "handsontable/plugins/contextMenu"
|
import { PredefinedMenuItemKey } from "handsontable/plugins/contextMenu"
|
||||||
import { ref } from "vue"
|
|
||||||
import Handsontable from "handsontable"
|
import Handsontable from "handsontable"
|
||||||
|
import { HotTableProps } from "@handsontable/react"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -18,10 +16,10 @@ export interface InventoryTableOptions {
|
|||||||
export interface Mappable<T> {
|
export interface Mappable<T> {
|
||||||
map<U>(callbackfn: (value: T, index: number, array: T[]) => U, thisArg?: any): U[];
|
map<U>(callbackfn: (value: T, index: number, array: T[]) => U, thisArg?: any): U[];
|
||||||
}
|
}
|
||||||
export class ColumnSet implements Set<ColumnInfo>, Mappable<ColumnInfo>{
|
export class ColumnSet implements Mappable<ColumnInfo>{
|
||||||
s: Set<ColumnName> = new Set()
|
s: Set<ColumnName> = new Set()
|
||||||
size: number;
|
size: number;
|
||||||
dirty = ref(0)
|
dirty = 0
|
||||||
constructor(i?:Iterable<ColumnInfo | ColumnName>){
|
constructor(i?:Iterable<ColumnInfo | ColumnName>){
|
||||||
if(i){
|
if(i){
|
||||||
for (const a of i) {
|
for (const a of i) {
|
||||||
@ -59,7 +57,7 @@ export class ColumnSet implements Set<ColumnInfo>, Mappable<ColumnInfo>{
|
|||||||
}).values()
|
}).values()
|
||||||
}
|
}
|
||||||
mark() {
|
mark() {
|
||||||
this.dirty.value = this.dirty.value + 1
|
this.dirty= this.dirty+ 1
|
||||||
this.size = this.s.size
|
this.size = this.s.size
|
||||||
}
|
}
|
||||||
add(value: ColumnInfo | ColumnName): this {
|
add(value: ColumnInfo | ColumnName): this {
|
||||||
@ -97,9 +95,9 @@ export class InventoryTable {
|
|||||||
getTableColumnNames(): string[] {
|
getTableColumnNames(): string[] {
|
||||||
return this.o.columns.map(x=>x.displayName)
|
return this.o.columns.map(x=>x.displayName)
|
||||||
}
|
}
|
||||||
getTableColumnSettings(): ColumnSettings[] {
|
getTableColumnSettings(): Handsontable.ColumnSettings[] {
|
||||||
return this.o.columns.map(x=>{
|
return this.o.columns.map(x=>{
|
||||||
let out:any = {
|
let out:Handsontable.ColumnSettings = {
|
||||||
renderer: x.renderer ? x.renderer : "text",
|
renderer: x.renderer ? x.renderer : "text",
|
||||||
filters: true,
|
filters: true,
|
||||||
dropdownMenu: x.filtering ? DefaultDropdownItems() : false,
|
dropdownMenu: x.filtering ? DefaultDropdownItems() : false,
|
||||||
@ -200,7 +198,6 @@ export const DefaultSettings = ():HotTableProps=>{
|
|||||||
// Deselect the column after clicking on input.
|
// Deselect the column after clicking on input.
|
||||||
if (coords.row === -1 && event.target.nodeName === 'INPUT') {
|
if (coords.row === -1 && event.target.nodeName === 'INPUT') {
|
||||||
event.stopImmediatePropagation();
|
event.stopImmediatePropagation();
|
||||||
this.deselectCell();
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
className: 'htLeft',
|
className: 'htLeft',
|
||||||
|
@ -1,24 +1,18 @@
|
|||||||
export interface ItemExpireTime {
|
|
||||||
text: string
|
|
||||||
us: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface TricksterItem {
|
export interface TricksterItem {
|
||||||
unique_id: number;
|
unique_id: number;
|
||||||
item_name: string;
|
item_name: string;
|
||||||
item_id: number;
|
|
||||||
item_count: number;
|
item_count: number;
|
||||||
item_desc: string;
|
item_comment: string;
|
||||||
item_use: string;
|
item_use: string;
|
||||||
item_slots?: number;
|
item_slots?: number;
|
||||||
item_min_level?: number;
|
item_min_level?: number;
|
||||||
is_equip?: boolean;
|
is_equip?: boolean;
|
||||||
is_drill?: boolean;
|
is_drill?: boolean;
|
||||||
item_expire_time?: ItemExpireTime;
|
item_expire_time?: string;
|
||||||
refine_level?: number;
|
refine_level?: number;
|
||||||
refine_type?: number;
|
refine_type?: number;
|
||||||
refine_state?: number;
|
refine_state?: number;
|
||||||
image?: string;
|
item_image?: string;
|
||||||
stats?: {[key: string]:any}
|
stats?: {[key: string]:any}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -28,6 +22,7 @@ export interface TricksterAccount {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface Identifier {
|
export interface Identifier {
|
||||||
|
account_name: string
|
||||||
account_id: number
|
account_id: number
|
||||||
id: number
|
id: number
|
||||||
name: string
|
name: string
|
||||||
|
@ -48,11 +48,10 @@ const login = () => {
|
|||||||
window.location.reload()
|
window.location.reload()
|
||||||
}).catch((e)=>{
|
}).catch((e)=>{
|
||||||
if(e.code == "ERR_BAD_REQUEST") {
|
if(e.code == "ERR_BAD_REQUEST") {
|
||||||
alert("invalid username/password")
|
throw "invalid username/password"
|
||||||
return
|
|
||||||
}
|
}
|
||||||
alert("unknown error, please report")
|
console.warn(e)
|
||||||
console.log(e)
|
throw "unknown error, please report"
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
10
tailwind.config.js
Normal file
10
tailwind.config.js
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
export default {
|
||||||
|
content: [
|
||||||
|
"./index.html",
|
||||||
|
"./src/**/*.{js,ts,jsx,tsx}",
|
||||||
|
],
|
||||||
|
theme: {
|
||||||
|
extend: {},
|
||||||
|
},
|
||||||
|
plugins: [],
|
||||||
|
}
|
@ -1,18 +1,30 @@
|
|||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
|
"incremental": true,
|
||||||
"target": "esnext",
|
"target": "esnext",
|
||||||
"useDefineForClassFields": true,
|
"useDefineForClassFields": true,
|
||||||
"module": "esnext",
|
"lib": ["DOM", "DOM.Iterable", "ESNext"],
|
||||||
"moduleResolution": "node",
|
"types": [ "node" ],
|
||||||
"strict": true,
|
"allowJs": true,
|
||||||
"jsx": "preserve",
|
"skipLibCheck": true,
|
||||||
"sourceMap": true,
|
|
||||||
"resolveJsonModule": true,
|
|
||||||
"isolatedModules": true,
|
|
||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
"lib": ["esnext", "dom"],
|
"allowSyntheticDefaultImports": true,
|
||||||
"skipLibCheck": true
|
"strict": true,
|
||||||
|
"module": "ESNext",
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"allowImportingTsExtensions": true,
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"isolatedModules": false,
|
||||||
|
"noEmit": true,
|
||||||
|
"jsx": "react-jsx"
|
||||||
},
|
},
|
||||||
"include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"],
|
"include": [
|
||||||
|
"src",
|
||||||
|
"app",
|
||||||
|
"index",
|
||||||
|
],
|
||||||
|
"exclude": [
|
||||||
|
"node_modules",
|
||||||
|
],
|
||||||
"references": [{ "path": "./tsconfig.node.json" }]
|
"references": [{ "path": "./tsconfig.node.json" }]
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"composite": true,
|
"composite": true,
|
||||||
"module": "esnext",
|
"module": "ESNext",
|
||||||
"moduleResolution": "node"
|
"moduleResolution": "Node",
|
||||||
|
"allowSyntheticDefaultImports": true
|
||||||
},
|
},
|
||||||
"include": ["vite.config.ts"]
|
"include": ["vite.config.ts"]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
import { defineConfig } from 'vite'
|
import { defineConfig } from 'vite'
|
||||||
import vue from '@vitejs/plugin-vue'
|
import vue from '@vitejs/plugin-vue'
|
||||||
|
import react from '@vitejs/plugin-react'
|
||||||
|
|
||||||
|
|
||||||
// https://vitejs.dev/config/
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [vue()],
|
plugins: [react()],
|
||||||
server: {
|
server: {
|
||||||
proxy: {
|
proxy: {
|
||||||
// with options
|
// with options
|
||||||
@ -15,3 +16,18 @@ export default defineConfig({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// https://vitejs.dev/config/
|
||||||
|
//export default defineConfig({
|
||||||
|
// plugins: [vue()],
|
||||||
|
// server: {
|
||||||
|
// proxy: {
|
||||||
|
// // with options
|
||||||
|
// '/lifeto': {
|
||||||
|
// target: "https://beta.lifeto.co/",
|
||||||
|
// changeOrigin: true,
|
||||||
|
// rewrite: (path) => path.replace(/^\/lifeto/, ''),
|
||||||
|
// },
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//})
|
||||||
|
Loading…
Reference in New Issue
Block a user