1
0
Fork 0
This commit is contained in:
a 2024-08-11 20:13:42 -05:00
parent e39ba4d052
commit 6e99ef1501
Signed by untrusted user: a
GPG Key ID: 374BC539FE795AF0
22 changed files with 3740 additions and 153 deletions

54
eslint.config.cjs Normal file
View 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,
},
}

View File

@ -6,8 +6,8 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>lto inventory</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/index.tsx"></script> <script type="module" src="/src/index.tsx"></script>
</body> </body>
</html> </html>

View File

@ -4,26 +4,38 @@
"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": "^18.3.3",
"@types/react-dom": "^18.3.0", "@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", "@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",
"react": "^18.3.1", "react": "^18.3.1",
"react-dom": "^18.3.1", "react-dom": "^18.3.1",
"typescript-cookie": "^1.0.4", "react-spinners": "^0.14.1",
"uuid": "^8.3.2", "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": {

6
postcss.config.cjs Normal file
View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 593 B

View File

@ -1,10 +1,30 @@
import { FC } from "react" import { FC } from "react";
import { LoginWidget } from "./components/login";
import { CharacterRoulette } from "./components/characters";
import { Inventory } from "./components/inventory";
export const App: FC = () => { export const App: FC = () => {
return <> return (
<div> <>
</div> <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>
</>
);
};

View 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>
</>
}

View 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
View 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>
</>
}

View 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;

View 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;
};

View 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;
};

View File

@ -1,10 +1,19 @@
import "./index.css" import React from "react";
import React from 'react' import ReactDOM from "react-dom/client";
import ReactDOM from 'react-dom/client' import { App } from "./App";
import {App} from './App' import AppContext from "./context/AppContext";
ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
import "./index.css";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
const queryClient = new QueryClient()
ReactDOM.createRoot(document.getElementById("app") as HTMLElement).render(
<React.StrictMode> <React.StrictMode>
<App/> <QueryClientProvider client={queryClient}>
</React.StrictMode> <AppContext>
) <App />
</AppContext>
</QueryClientProvider>
</React.StrictMode>,
);

View File

@ -64,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,
@ -73,28 +75,30 @@ 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: [
return { {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)=>{
account_id: x.id, return {
id: z.id, account_name:x.name,
name: z.name, account_id: x.id,
path: x.name+"/"+z.name, id: z.id,
class: z.class, name: z.name,
base_job: z.base_job, path: x.name+"/"+z.name,
current_job: z.current_job, class: z.class,
} base_job: z.base_job,
})], current_job: z.current_job,
} as TricksterAccount }
})],
}
}) })
}) })
} }

View File

@ -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,8 +118,8 @@ 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
} }
@ -128,8 +130,8 @@ export class OrderSender {
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
} }
@ -140,8 +142,8 @@ export class OrderSender {
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
} }
@ -150,8 +152,8 @@ export class OrderSender {
} }
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(),

View File

@ -10,7 +10,7 @@ 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)=> {
@ -89,16 +89,16 @@ export class TokenSession implements Session {
switch (verb.toLowerCase()){ switch (verb.toLowerCase()){
case "post": case "post":
promise = axios.post(c(url),data,this.genHeaders()) promise = axios.post(c(url),data,this.genHeaders())
break; break;
case "postform": case "postform":
promise = axios.postForm(c(url),data) promise = axios.postForm(c(url),data)
break; break;
case "postraw": case "postraw":
const querystring = qs.stringify(data) const querystring = qs.stringify(data)
promise = axios.post(c(url),querystring,this.genHeaders()) promise = axios.post(c(url),querystring,this.genHeaders())
break; break;
case "get": case "get":
default: default:
promise = axios.get(c(url),this.genHeaders()) promise = axios.get(c(url),this.genHeaders())
} }
return promise.then(x=>{ return promise.then(x=>{

View File

@ -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',

View File

@ -22,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

View File

@ -1,8 +0,0 @@
export const Login = () => {
return <></>
}

View File

@ -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"
}) })
} }

3168
yarn.lock

File diff suppressed because it is too large Load Diff