cant log out lol

This commit is contained in:
a 2022-07-06 02:48:11 -05:00
parent a75a1d774f
commit ef6bbaf009
19 changed files with 820 additions and 249 deletions

25
package-lock.json generated
View File

@ -11,6 +11,7 @@
"@handsontable/vue3": "^12.0.1",
"@types/numbro": "^1.9.3",
"@types/qs": "^6.9.7",
"@types/uuid": "^8.3.4",
"@vueuse/core": "^8.7.5",
"axios": "^0.27.2",
"handsontable": "^12.0.1",
@ -18,6 +19,7 @@
"pinia": "^2.0.14",
"qs": "^6.10.5",
"typescript-cookie": "^1.0.3",
"uuid": "^8.3.2",
"vue": "^3.2.25",
"vue3-cookies": "^1.0.6"
},
@ -70,6 +72,11 @@
"resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz",
"integrity": "sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw=="
},
"node_modules/@types/uuid": {
"version": "8.3.4",
"resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-8.3.4.tgz",
"integrity": "sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw=="
},
"node_modules/@types/web-bluetooth": {
"version": "0.0.14",
"resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.14.tgz",
@ -1195,6 +1202,14 @@
"node": ">= 0.4.0"
}
},
"node_modules/uuid": {
"version": "8.3.2",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
"bin": {
"uuid": "dist/bin/uuid"
}
},
"node_modules/vite": {
"version": "2.9.12",
"resolved": "https://registry.npmjs.org/vite/-/vite-2.9.12.tgz",
@ -1301,6 +1316,11 @@
"resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz",
"integrity": "sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw=="
},
"@types/uuid": {
"version": "8.3.4",
"resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-8.3.4.tgz",
"integrity": "sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw=="
},
"@types/web-bluetooth": {
"version": "0.0.14",
"resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.14.tgz",
@ -2000,6 +2020,11 @@
"integrity": "sha512-b2/KCUlYZUeA7JFUuRJZPUtr4gZvBh7tavtv4fvk4+KV9pfGiR6CQAQAWl49ZpR3ts2dk4FYkP7EIgDJoiOLDA==",
"optional": true
},
"uuid": {
"version": "8.3.2",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg=="
},
"vite": {
"version": "2.9.12",
"resolved": "https://registry.npmjs.org/vite/-/vite-2.9.12.tgz",

View File

@ -11,6 +11,7 @@
"@handsontable/vue3": "^12.0.1",
"@types/numbro": "^1.9.3",
"@types/qs": "^6.9.7",
"@types/uuid": "^8.3.4",
"@vueuse/core": "^8.7.5",
"axios": "^0.27.2",
"handsontable": "^12.0.1",
@ -18,6 +19,7 @@
"pinia": "^2.0.14",
"qs": "^6.10.5",
"typescript-cookie": "^1.0.3",
"uuid": "^8.3.2",
"vue": "^3.2.25",
"vue3-cookies": "^1.0.6"
},

View File

@ -13,19 +13,22 @@ const props = defineProps(['character'])
const name = ref(props.character.split("/").pop())
const job = ref("")
const galders = ref(0)
const {invs, activeTable} = useStoreRef()
const {invs, activeTable, chars} = useStoreRef()
watch(invs.value,()=>{
const currentInv = invs.value.get(props.character)
if(currentInv){
if(currentInv.wallet){
name.value = currentInv.name!
job.value = currentInv.wallet?.job_img!
galders.value = currentInv.wallet?.galders!
}
name.value = currentInv.name!
galders.value = currentInv.galders!
}
},{deep:true})
const currentChar = chars.value.get(props.character)
if(currentChar){
name.value = currentChar.name!
job.value = JobNumberToString(currentChar.current_job)
}
const selectCharacter = () => {
activeTable.value = props.character
@ -38,6 +41,7 @@ import log from 'loglevel';
import { defineComponent, computed, PropType, defineProps, defineEmits, ref, watch, onMounted} from 'vue';
import { getLTOState, LTOApi, LTOApiv0 } from '../lib/lifeto';
import { LoginHelper, Session } from '../lib/session';
import { JobNumberToString } from '../lib/trickster';
import { storage } from '../session_storage';
import { useStore, useStoreRef } from '../state/state';
</script>

View File

@ -7,19 +7,21 @@
<script lang="ts" setup>
import CharacterCard from './CharacterCard.vue';
const { accounts, invs, activeTable } = useStoreRef()
const { chars, accounts, invs, activeTable } = useStoreRef()
const characters = ref([] as string[])
watch(invs, () => {
watch(chars, () => {
characters.value = [...new Set([...characters.value, ...invs.value.keys()])]
}, { deep: true })
const session = storage.GetSession()
const api:LTOApi = getLTOState(LTOApiv0, session, useStoreRef())
api.GetAccounts().then(xs => {
xs.forEach(x => {
characters.value.push(x)
characters.value.push(...x.characters.map(x=>x.path))
})
characters.value = [...new Set([...characters.value])]
})
onMounted(()=>{

75
src/lib/columns/column.ts Normal file
View File

@ -0,0 +1,75 @@
import Handsontable from "handsontable"
import numbro from 'numbro';
import { textRenderer } from "handsontable/renderers"
import { TricksterItem } from "../trickster"
import Core from "handsontable/core";
export const BasicColumns = [
"Image","Name","Count",
] as const
export const DetailsColumns = [
"Desc","Use",
] as const
export const MoveColumns = [
"MoveCount","Move",
] as const
export const TagColumns = [
"All","Equip","Drill","Card","Quest","Consume", "Compound"
] as const
export const EquipmentColumns = [
"MinLvl","Slots","RefineNumber","RefineState",
] as const
export const StatsColumns = [
"AP","GunAP","AC","DX","MP","MA","MD","WT","DA","LK","HP","DP","HV",
] as const
export const HackColumns = [
] as const
export const ColumnNames = [
...BasicColumns,
...MoveColumns,
...DetailsColumns,
...EquipmentColumns,
...StatsColumns,
...TagColumns,
...HackColumns,
] as const
export type ColumnName = typeof ColumnNames[number]
const c = (a:ColumnName | ColumnInfo):ColumnName => {
switch(typeof a) {
case "string":
return a
case "object":
return a.name
}
}
export const LazyColumn = c;
export const ColumnSorter = (a:ColumnName | ColumnInfo, b: ColumnName | ColumnInfo):number => {
let n1 = ColumnNames.indexOf(c(a))
let n2 = ColumnNames.indexOf(c(b))
if(n1 == n2) {
return 0
}
return n1 > n2 ? 1 : -1
}
export interface ColumnInfo {
name: ColumnName
displayName:string
options?:(s:string[])=>string[]
renderer?:any
filtering?:boolean
writable?:boolean
getter(item:TricksterItem):(string | number)
}

View File

@ -0,0 +1,452 @@
import Handsontable from "handsontable"
import Core from "handsontable/core"
import { textRenderer } from "handsontable/renderers"
import numbro from "numbro"
import { TricksterItem } from "../trickster"
import {ColumnName, ColumnInfo} from "./column"
export const ColumnByNames = (...n:ColumnName[]) => {
return n.map(ColumnByName)
}
export const ColumnByName = (n:ColumnName) => {
return Columns[n]
}
export const test = <T extends ColumnInfo>(n:(new ()=>T)):[string,T] => {
let nn = new n()
return [nn.name, nn]
}
class Image implements ColumnInfo {
name:ColumnName = 'Image'
displayName = " "
renderer = coverRenderer
getter(item:TricksterItem):(string|number) {
return item.image ? item.image : ""
}
}
function coverRenderer(instance:any, td:any, row:any, col:any, prop:any, value:any, cellProperties:any) {
const stringifiedValue = Handsontable.helper.stringify(value);
if (stringifiedValue.startsWith('http')) {
const img:any = document.createElement('IMG');
img.src = value;
Handsontable.dom.addEvent(img, 'mousedown', event =>{
event!.preventDefault();
});
Handsontable.dom.empty(td);
td.appendChild(img);
}
}
class Name implements ColumnInfo {
name:ColumnName = "Name"
displayName = "Name"
filtering = true
renderer = nameRenderer
getter(item:TricksterItem):(string|number){
return item.item_name
}
}
function nameRenderer(instance:any, td:any, row:any, col:any, prop:any, value:any, cellProperties:any) {
const stringifiedValue = Handsontable.helper.stringify(value);
let showText = stringifiedValue;
const div= document.createElement('div');
div.innerHTML = showText
div.title = showText
div.style.maxWidth = "20ch"
div.style.textOverflow = "ellipsis"
div.style.overflow= "hidden"
div.style.whiteSpace= "nowrap"
Handsontable.dom.addEvent(div, 'mousedown', event =>{
event!.preventDefault();
});
Handsontable.dom.empty(td);
td.appendChild(div);
td.classList.add("htLeft")
}
class Count implements ColumnInfo {
name:ColumnName = "Count"
displayName = "Count"
renderer = "numeric"
filtering = true
getter(item:TricksterItem):(string|number){
return item.item_count
}
}
class Move implements ColumnInfo {
name:ColumnName = "Move"
displayName = "Target"
writable = true
options = getMoveTargets
getter(item:TricksterItem):(string|number){
return "---------------------------------------------"
}
}
const getMoveTargets = (invs: string[]):string[] => {
let out:string[] = [];
for(const k of invs){
out.push(k)
}
out.push("")
out.push("")
out.push("!TRASH")
return out
}
class MoveCount implements ColumnInfo {
name:ColumnName = "MoveCount"
displayName = "Move #"
renderer = moveCountRenderer
writable = true
getter(item:TricksterItem):(string|number){
return ""
}
}
function moveCountRenderer(instance:Core, td:any, row:number, col:number, prop:any, value:any, cellProperties:any) {
let newValue = value;
if (Handsontable.helper.isNumeric(newValue)) {
const numericFormat = cellProperties.numericFormat;
const cellCulture = numericFormat && numericFormat.culture || '-';
const cellFormatPattern = numericFormat && numericFormat.pattern;
const className = cellProperties.className || '';
const classArr = className.length ? className.split(' ') : [];
if (typeof cellCulture !== 'undefined' && !numbro.languages()[cellCulture]) {
const shortTag:any = cellCulture.replace('-', '');
const langData = (numbro as any)[shortTag];
if (langData) {
numbro.registerLanguage(langData);
}
}
const totalCount = Number(instance.getCell(row,col-1)?.innerHTML)
numbro.setLanguage(cellCulture);
const num = numbro(newValue)
if(totalCount < num.value()) {
const newNum = numbro(totalCount)
newValue = newNum.format(cellFormatPattern || '0');
}else {
newValue = num.format(cellFormatPattern || '0');
}
if (classArr.indexOf('htLeft') < 0 && classArr.indexOf('htCenter') < 0 &&
classArr.indexOf('htRight') < 0 && classArr.indexOf('htJustify') < 0) {
classArr.push('htRight');
}
if (classArr.indexOf('htNumeric') < 0) {
classArr.push('htNumeric');
}
cellProperties.className = classArr.join(' ');
td.dir = 'ltr';
newValue = newValue + "x"
}else {
newValue = ""
}
textRenderer(instance, td, row, col, prop, newValue, cellProperties);
}
class Equip implements ColumnInfo {
name:ColumnName = "Equip"
displayName = "equip"
getter(item:TricksterItem):(string|number){
return item.is_equip ? 1 : 0
}
}
class Drill implements ColumnInfo {
name:ColumnName = "Drill"
displayName = "drill"
getter(item:TricksterItem):(string|number){
return item.is_drill ? 1 : 0
}
}
class All implements ColumnInfo {
name:ColumnName = "All"
displayName = "swap"
getter(_:TricksterItem):(string|number){
return -10000
}
}
class Card implements ColumnInfo {
name:ColumnName = "Card"
displayName = "card"
getter(item:TricksterItem):(string|number){
return cardFilter(item) ? 1 : 0
}
}
const cardFilter= (item:TricksterItem): boolean => {
return (item.item_name.endsWith(" Card") || item.item_name.startsWith("Star Card"))
}
class Compound implements ColumnInfo {
name:ColumnName = "Compound"
displayName = "comp"
getter(item:TricksterItem):(string|number){
return compFilter(item) ? 1 : 0
}
}
const compFilter= (item:TricksterItem): boolean => {
return (item.item_desc.toLowerCase().includes("compound item"))
}
class Quest implements ColumnInfo {
name:ColumnName = "Quest"
displayName = "quest"
getter(item:TricksterItem):(string|number){
return questFilter(item) ? 1 : 0
}
}
const questFilter= (item:TricksterItem): boolean => {
return false
}
class Consume implements ColumnInfo {
name:ColumnName = "Consume"
displayName = "eat"
getter(item:TricksterItem):(string|number){
return consumeFilter(item) ? 1 : 0
}
}
const consumeFilter= (item:TricksterItem): boolean => {
const tl = item.item_use.toLowerCase()
return tl.includes("recover") || tl.includes("restores")
}
class AP implements ColumnInfo {
name:ColumnName = "AP"
displayName = "AP"
getter(item:TricksterItem):(string|number){
return item.stats ? item.stats["AP"] : ""
}
}
class GunAP implements ColumnInfo {
name:ColumnName = "GunAP"
displayName = "Gun AP"
getter(item:TricksterItem):(string|number){
return item.stats ? item.stats["Gun AP"] : ""
}
}
class AC implements ColumnInfo {
name:ColumnName = "AC"
displayName = "AC"
getter(item:TricksterItem):(string|number){
return item.stats ? item.stats["AC"] : ""
}
}
class DX implements ColumnInfo {
name:ColumnName = "DX"
displayName = "DX"
getter(item:TricksterItem):(string|number){
return item.stats ? item.stats["DX"] : ""
}
}
class MP implements ColumnInfo {
name:ColumnName = "MP"
displayName = "MP"
getter(item:TricksterItem):(string|number){
return item.stats ? item.stats["MP"] : ""
}
}
class MA implements ColumnInfo {
name:ColumnName = "MA"
displayName = "MA"
getter(item:TricksterItem):(string|number){
return item.stats ? item.stats["MA"] : ""
}
}
class MD implements ColumnInfo {
name:ColumnName = "MD"
displayName = "MD"
getter(item:TricksterItem):(string|number){
return item.stats ? item.stats["MD"] : ""
}
}
class WT implements ColumnInfo {
name:ColumnName = "WT"
displayName = "WT"
getter(item:TricksterItem):(string|number){
return item.stats ? item.stats["WT"] : ""
}
}
class DA implements ColumnInfo {
name:ColumnName = "DA"
displayName = "DA"
getter(item:TricksterItem):(string|number){
return item.stats ? item.stats["DA"] : ""
}
}
class LK implements ColumnInfo {
name:ColumnName = "LK"
displayName = "LK"
getter(item:TricksterItem):(string|number){
return item.stats ? item.stats["LK"] : ""
}
}
class HP implements ColumnInfo {
name:ColumnName = "HP"
displayName = "HP"
getter(item:TricksterItem):(string|number){
return item.stats ? item.stats["HP"] : ""
}
}
class DP implements ColumnInfo {
name:ColumnName = "DP"
displayName = "DP"
getter(item:TricksterItem):(string|number){
return item.stats ? item.stats["DP"] : ""
}
}
class HV implements ColumnInfo {
name:ColumnName = "HV"
displayName = "HV"
getter(item:TricksterItem):(string|number){
return item.stats ? item.stats["HV"] : ""
}
}
class MinLvl implements ColumnInfo {
name:ColumnName = "MinLvl"
displayName = "lvl"
getter(item:TricksterItem):(string|number){
//TODO:
return item.item_min_level? item.item_min_level:""
}
}
class Slots implements ColumnInfo {
name:ColumnName = "Slots"
displayName = "slots"
getter(item:TricksterItem):(string|number){
//TODO:
return item.item_slots ? item.item_slots : ""
}
}
class RefineNumber implements ColumnInfo {
name:ColumnName = "RefineNumber"
displayName = "refine"
getter(item:TricksterItem):(string|number){
return item.refine_level ? item.refine_level : 0
}
}
class RefineState implements ColumnInfo {
name:ColumnName = "RefineState"
displayName = "bork"
getter(item:TricksterItem):(string|number){
return item.refine_state ? item.refine_state : 0
}
}
class Desc implements ColumnInfo {
name:ColumnName = "Desc"
displayName = "desc"
renderer = descRenderer
getter(item:TricksterItem):(string|number){
return item.item_desc
}
}
function descRenderer(instance:any, td:any, row:any, col:any, prop:any, value:any, cellProperties:any) {
const stringifiedValue = Handsontable.helper.stringify(value);
let showText = stringifiedValue;
const div= document.createElement('div');
div.innerHTML = showText
div.title = showText
div.style.maxWidth = "30ch"
div.style.textOverflow = "ellipsis"
div.style.overflow= "hidden"
div.style.whiteSpace= "nowrap"
Handsontable.dom.addEvent(div, 'mousedown', event =>{
event!.preventDefault();
});
Handsontable.dom.empty(td);
td.appendChild(div);
td.classList.add("htLeft")
}
class Use implements ColumnInfo {
name:ColumnName = "Use"
displayName = "use"
renderer= useRenderer;
getter(item:TricksterItem):(string|number){
return item.item_use
}
}
function useRenderer(instance:any, td:any, row:any, col:any, prop:any, value:any, cellProperties:any) {
const stringifiedValue = Handsontable.helper.stringify(value);
let showText = stringifiedValue;
const div= document.createElement('div');
div.title = showText
div.innerHTML = showText
div.style.maxWidth = "30ch"
div.style.textOverflow = "ellipsis"
div.style.overflow= "hidden"
div.style.whiteSpace= "nowrap"
Handsontable.dom.addEvent(div, 'mousedown', event =>{
event!.preventDefault();
});
Handsontable.dom.empty(td);
td.appendChild(div);
td.classList.add("htLeft")
}
export const Columns:{[Property in ColumnName]:ColumnInfo}= {
Use: new Use(),
Desc: new Desc(),
Image: new Image(),
Name: new Name(),
Count: new Count(),
Move: new Move(),
MoveCount: new MoveCount(),
Equip: new Equip(),
Drill: new Drill(),
Card: new Card(),
Quest: new Quest(),
Consume: new Consume(),
AP: new AP(),
GunAP: new GunAP(),
AC: new AC(),
DX: new DX(),
MP: new MP(),
MA: new MA(),
MD: new MD(),
WT: new WT(),
DA: new DA(),
LK: new LK(),
HP: new HP(),
DP: new DP(),
HV: new HV(),
MinLvl: new MinLvl(),
Slots: new Slots(),
RefineNumber: new RefineNumber(),
RefineState: new RefineState(),
All: new All(),
Compound: new Compound(),
}

3
src/lib/columns/index.ts Normal file
View File

@ -0,0 +1,3 @@
export * from "./column"
export * from "./column_impl"

View File

@ -1,149 +0,0 @@
import { Axios, AxiosResponse } from "axios"
import log from "loglevel"
import { RefStore } from "../state/state"
import { Session } from "./session"
import { dummyChar, TricksterInventory, TricksterItem, TricksterWallet } from "./trickster"
export interface LTOApi {
GetAccount:(name:string) =>Promise<[TricksterInventory, Array<string>]>
GetInventory:(path:string)=>Promise<TricksterInventory>
GetAccounts:() =>Promise<Array<string>>
GetLoggedin: ()=>Promise<boolean>
}
export interface SessionBinding {
new(s:Session):LTOApi
}
export const getLTOState = <A extends LTOApi>(c: new (s:Session) => A,s:Session, r:RefStore): LTOApi => {
return new StatefulLTOApi(new c(s),r);
}
export class StatefulLTOApi implements LTOApi {
u: LTOApi
r: RefStore
constructor(s:LTOApi, r:RefStore){
this.u = s
this.r=r
}
GetAccount = async (name:string):Promise<[TricksterInventory, Array<string>]> =>{
const account = await this.u.GetAccount(name)
this.r.invs.value.set(account[0].path,account[0])
account[1].forEach((s)=>{
const d = dummyChar(s)
this.r.invs.value.set(d.path,d)
})
this.r.dirty.value = this.r.dirty.value + 1
return account
}
GetInventory = async (path:string):Promise<TricksterInventory>=>{
if(!path.includes("/")) {
const a = await this.GetAccount(path)
return a[0]
}
const inv = await this.u.GetInventory(path)
this.r.invs.value.set(inv.path,inv)
this.r.dirty.value = this.r.dirty.value + 1
return inv
}
GetAccounts = async ():Promise<Array<string>> =>{
return this.u.GetAccounts()
}
GetLoggedin= async ():Promise<boolean>=>{
return this.u.GetLoggedin()
}
}
export class LTOApiv0 implements LTOApi {
s: Session
constructor(s:Session) {
this.s = s
}
GetAccount = async (account: string):Promise<[TricksterInventory, Array<string>]> => {
return this.s.authed_request("GET", `item-manager/items/account/${account}`,undefined).then(async (ans:AxiosResponse)=>{
const o = ans.data
log.debug("GetAccount", o)
let out:string[] = []
Object.entries(o.characters).forEach((x:[string,any])=>{
out.push(`${account}/${x[1].name}`)
})
return [{
name: account,
id:account,
wallet: {
galders: 0,
state: 0,
job_img: "bank",
},
path: account,
items:(Object.entries(o.items) as any).map(([k, v]: [string, TricksterItem]):TricksterItem=>{
v.unique_id = Number(k)
return v
}),
},out]
})
}
GetInventory = async (char_path: string):Promise<TricksterInventory> =>{
if(char_path.startsWith(":")) {
char_path = char_path.replace(":","")
}
return this.s.authed_request("GET", `item-manager/items/account/${char_path}`,undefined).then((ans:AxiosResponse)=>{
const o = ans.data
log.debug("GetInventory", o)
let name = ""
let id = ""
let wallet:TricksterWallet = {galders:0,state:0,job_img:""}
if(char_path.includes("/")) {
let [char, val] = Object.entries(o.characters)[0] as [string,any]
name = val.name
id = char
wallet = {
galders:val.galders as number,
state:val.state as number,
job_img: val.job_img as string
}
}else {
name = char_path
id = char_path
wallet = {
galders: 0,
state: 0,
job_img: "bank",
}
}
let out = {
name,
id,
wallet,
path: char_path,
items:(Object.entries(o.items) as any).map(([k, v]: [string, TricksterItem]):TricksterItem=>{
v.unique_id = Number(k)
return v
}),
} as TricksterInventory
return out
})
}
GetAccounts = async ():Promise<string[]> => {
return this.s.authed_request("POST", "accounts/list",undefined).then((ans:AxiosResponse)=>{
log.debug("GetAccounts", ans.data)
return ans.data.map((x:any)=>x.name)
})
}
GetLoggedin = async ():Promise<boolean> => {
return this.s.authed_request("POST", "accounts/list",undefined).then((ans:AxiosResponse)=>{
if(ans.status == 401) {
return false
}
if(ans.status == 200) {
return true
}
return false
})
}
}

40
src/lib/lifeto/api.ts Normal file
View File

@ -0,0 +1,40 @@
import { trace } from "loglevel"
import { TricksterAccount, TricksterInventory } from "../trickster"
import { v4 as uuidv4 } from 'uuid';
export interface LTOApi {
GetInventory:(path:string)=>Promise<TricksterInventory>
GetAccounts:() =>Promise<Array<TricksterAccount>>
GetLoggedin: ()=>Promise<boolean>
}
export const TxnStates = ["PENDING","INFLIGHT","WAITING","ERROR","SUCCESS"] as const
export type TxnState = typeof TxnStates[number]
export interface TxnDetails {
item_uid: string | "galders"
count:number
origin:string
target:string
}
export abstract class BankReceipt {
action_id: string
details:TxnDetails
created:Date
constructor(details:TxnDetails) {
this.details = details
this.created = new Date()
this.action_id = uuidv4();
}
abstract state():Promise<TxnState>
abstract status():string
abstract progress():[number, number]
abstract error():string
}
export interface BankApi {
SendTxn: (txn:TxnDetails) => Promise<BankReceipt>
}

3
src/lib/lifeto/index.ts Normal file
View File

@ -0,0 +1,3 @@
export * from "./lifeto"
export * from "./api"
export * from "./stateful"

78
src/lib/lifeto/lifeto.ts Normal file
View File

@ -0,0 +1,78 @@
import { Axios, AxiosResponse } from "axios"
import { zhCN } from "handsontable/i18n"
import log from "loglevel"
import { RefStore } from "../../state/state"
import { Session } from "../session"
import { dummyChar, TricksterAccount, TricksterInventory, TricksterItem, TricksterWallet } from "../trickster"
import { LTOApi } from "./api"
export class LTOApiv0 implements LTOApi {
s: Session
constructor(s:Session) {
this.s = s
}
GetInventory = async (char_path: string):Promise<TricksterInventory> =>{
if(char_path.startsWith(":")) {
char_path = char_path.replace(":","")
}
return this.s.authed_request("GET", `item-manager/items/account/${char_path}`,undefined).then((ans:AxiosResponse)=>{
const o = ans.data
log.debug("GetInventory", o)
let name = "bank"
let id = 0
let galders = 0
if(char_path.includes("/")) {
let [char, val] = Object.entries(o.characters)[0] as [string,any]
name = val.name
id = Number(char)
galders = val.galders as number
}else {
id = o.id
name = o.name
galders = o.galders
}
let out = {
name,
id,
path: char_path,
galders,
items:(Object.entries(o.items) as any).map(([k, v]: [string, TricksterItem]):TricksterItem=>{
v.unique_id = Number(k)
return v
}),
} as TricksterInventory
return out
})
}
GetAccounts = async ():Promise<TricksterAccount[]> => {
return this.s.authed_request("GET", "characters/list",undefined).then((ans:AxiosResponse)=>{
log.debug("GetAccounts", ans.data)
return ans.data.map((x:any)=>{
return {
name: x.name,
characters: [{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 {
id: z.id,
name: z.name,
path: x.name+"/"+z.name,
class: z.class,
base_job: z.base_job,
current_job: z.current_job,
}
})],
} as TricksterAccount
})
})
}
GetLoggedin = async ():Promise<boolean> => {
return this.s.authed_request("POST", "accounts/list",undefined).then((ans:AxiosResponse)=>{
if(ans.status == 401) {
return false
}
if(ans.status == 200) {
return true
}
return false
})
}
}

View File

@ -0,0 +1,39 @@
import { RefStore } from "../../state/state";
import { Session } from "../session";
import { TricksterAccount, TricksterInventory } from "../trickster";
import { LTOApi } from "./api";
export interface SessionBinding {
new(s:Session):LTOApi
}
export const getLTOState = <A extends LTOApi>(c: new (s:Session) => A,s:Session, r:RefStore): LTOApi => {
return new StatefulLTOApi(new c(s),r);
}
export class StatefulLTOApi implements LTOApi {
u: LTOApi
r: RefStore
constructor(s:LTOApi, r:RefStore){
this.u = s
this.r=r
}
GetInventory = async (path:string):Promise<TricksterInventory>=>{
const inv = await this.u.GetInventory(path)
this.r.invs.value.set(inv.path,inv)
this.r.dirty.value = this.r.dirty.value + 1
return inv
}
GetAccounts = async ():Promise<TricksterAccount[]> => {
const xs = await this.u.GetAccounts()
xs.forEach((x)=>{
x.characters.forEach((ch)=>{
this.r.chars.value.set(ch.path,ch)
})
})
return xs
}
GetLoggedin= async ():Promise<boolean>=>{
return this.u.GetLoggedin()
}
}

View File

@ -8,30 +8,30 @@ import { RefStore } from "../state/state";
export const BasicColumns = [
"Image","Name","Count",
]
] as const
export const DetailsColumns = [
"Desc","Use",
]
] as const
export const MoveColumns = [
"MoveCount","Move",
]
] as const
export const TagColumns = [
"All","Equip","Drill","Card","Quest","Consume", "Compound"
]
export const HackColumns = [
]
] as const
export const EquipmentColumns = [
"MinLvl","Slots","RefineNumber","RefineState",
]
] as const
export const StatsColumns = [
"AP","GunAP","AC","DX","MP","MA","MD","WT","DA","LK","HP","DP","HV",
]
] as const
export const HackColumns = [
] as const
export const ColumnNames = [
...BasicColumns,
@ -40,7 +40,8 @@ export const ColumnNames = [
...EquipmentColumns,
...StatsColumns,
...TagColumns,
]
...HackColumns,
] as const
export type ColumnName = typeof ColumnNames[number]
@ -472,11 +473,6 @@ function useRenderer(instance:any, td:any, row:any, col:any, prop:any, value:any
td.appendChild(div);
td.classList.add("htLeft")
}
export const ColumnByNames = (...n:ColumnName[]) => {
return n.map(ColumnByName)
}
@ -484,6 +480,10 @@ export const ColumnByNames = (...n:ColumnName[]) => {
export const ColumnByName = (n:ColumnName) => {
return Columns[n]
}
export const test = <T extends ColumnInfo>(n:(new ()=>T)):[string,T] => {
let nn = new n()
return [nn.name, nn]
}
export const Columns:{[Property in ColumnName]:ColumnInfo}= {
Use: new Use(),

View File

@ -14,7 +14,7 @@
//}
import { ColumnSet } from "./table"
import { dummyChar, TricksterInventory } from "./trickster"
import { TricksterCharacter, TricksterInventory } from "./trickster"
export const ARRAY_SEPERATOR = ","
@ -45,9 +45,9 @@ export const StoreStrSet = {
export const StoreColSet = {
Murder: (s:ColumnSet):string=>Array.from(s.s.values()).join(as),
Revive: (s:string):ColumnSet=>new ColumnSet(s.split(as))
Revive: (s:string):ColumnSet=>new ColumnSet(s.split(as) as any)
}
export const StoreInvs = {
Murder: (s:Map<string,TricksterInventory>):string=>Array.from(s.keys()).join(as),
Revive: (s:string):Map<string,TricksterInventory>=>new Map(s.split(as).map((x)=>[x, dummyChar(x)])),
export const StoreChars = {
Murder: (s:Map<string,TricksterCharacter>):string=>JSON.stringify(Object.entries(s)),
Revive: (s:string):Map<string,TricksterCharacter>=>new Map(JSON.parse(s)),
}

View File

@ -114,30 +114,18 @@ export class InventoryTable {
getTableRows():any[][] {
return Object.values(this.inv.items)
.filter((item):boolean=>{
let found = false
if(this.o.tags.has("All")) {
found = true
}
let found = true
let hasAll = this.o.tags.has("All")
if(this.o.tags.s.size > 0) {
found = hasAll
for(const tag of this.o.tags.values()) {
if(tag.name =="All") {
continue
}
let chk = tag.getter(item)
if(this.o.tags.has("All")) {
if(chk === 1) {
found = false
break
}
}else {
if(chk === 1) {
found = true
break
}
if(tag.getter(item) === 1) {
return !hasAll
}
}
}else {
found = true
}
return found
})

View File

@ -1,6 +1,3 @@
import sampleAyshe from "./sample/ayshe.json"
export interface ItemExpireTime {
text: string
us: string
@ -25,39 +22,41 @@ export interface TricksterItem {
stats?: {[key: string]:any}
}
export interface TricksterWallet {
galders:number
state:number
job_img:string
export interface TricksterAccount {
name:string
characters: TricksterCharacter[]
}
export interface TricksterInventory {
path:string
name:string
id:string
wallet?:TricksterWallet
export interface Identifier {
id: number
name: string
path: string
}
export interface TricksterCharacter extends Identifier {
class: number
base_job: number
current_job: number
}
export interface TricksterInventory extends Identifier{
galders?:number
items:{[key:string]:TricksterItem}
}
export const dummyChar = (s:string):TricksterInventory => {
return {
path: s,
name: s.split("/").pop()!,
id: s,
items:{}
const jobMap:{[key:number]:string} = {
31: "diva",
29: "scientist",
11: "sheep",
}
export const JobNumberToString = (n:number):string=> {
if(n == -8) {
return "bank"
}
}
export const SampleData:{[key:string]:TricksterInventory} = {
aysheBoyfriend: {
name: sampleAyshe.characters[100047311].name,
items: sampleAyshe.items,
wallet: {
galders: sampleAyshe.characters[100047311].galders,
job_img: sampleAyshe.characters[100047311].job_img,
state: sampleAyshe.characters[100047311].state
}
} as any
if(jobMap[n] != undefined) {
return jobMap[n]
}
return n.toString()
}

View File

@ -25,7 +25,14 @@
id="loginButton"
v-on:click="login()"
>Login</button>
<button
type="button"
id="logoutButton"
v-on:click="logout()"
>Logout</button>
</div>
</section>
</template>
<script lang="ts" setup>
@ -38,6 +45,7 @@ const login = () => {
.then((session)=>{
console.log(session, "adding to storage")
storage.AddSession(session)
window.location.reload()
}).catch((e)=>{
if(e.code == "ERR_BAD_REQUEST") {
alert("invalid username/password")
@ -48,6 +56,12 @@ const login = () => {
})
}
const logout = () => {
storage.RemoveSession()
localStorage.clear()
window.location.reload()
}
const s = storage.GetSession()
const api = new LTOApiv0(s)
if (s != undefined) {

View File

@ -1,4 +1,4 @@
import { getCookie, setCookie} from 'typescript-cookie'
import { Cookies, getCookie, removeCookie, setCookie} from 'typescript-cookie'
import { Session, TokenSession } from './lib/session'
@ -20,9 +20,9 @@ export class Storage {
}
RemoveSession() {
setCookie(nameCookie("user"),"")
setCookie(nameCookie("xsrf"),"")
setCookie(nameCookie("csrf"),"")
removeCookie(nameCookie("user"))
removeCookie(nameCookie("xsrf"))
removeCookie(nameCookie("csrf"))
}
AddSession(s:Session) {
setCookie(nameCookie("user"),s.user)
@ -32,3 +32,5 @@ export class Storage {
}
export const storage = new Storage()

View File

@ -2,9 +2,9 @@ import { defineStore, storeToRefs } from 'pinia'
import { getCookie, setCookie } from 'typescript-cookie'
import { useCookies } from 'vue3-cookies'
import { BasicColumns, ColumnInfo, ColumnName, Columns, DetailsColumns, MoveColumns } from '../lib/columns'
import { Reviver, StoreColSet, StoreInvs, StoreStr, StoreStrSet } from '../lib/storage'
import { Reviver, StoreChars, StoreColSet, StoreInvs, StoreStr, StoreStrSet } from '../lib/storage'
import { ColumnSet } from '../lib/table'
import { TricksterInventory } from '../lib/trickster'
import { TricksterCharacter, TricksterInventory } from '../lib/trickster'
import { nameCookie} from '../session_storage'
const _defaultColumn:(ColumnInfo| ColumnName)[] = [
@ -16,6 +16,7 @@ export const useStore = defineStore('state', {
state: ()=> {
let store = {
invs: new Map() as Map<string,TricksterInventory>,
chars: new Map() as Map<string,TricksterCharacter>,
accounts: new Set() as Set<string>,
activeTable: "none",
screen: "default",
@ -23,20 +24,13 @@ export const useStore = defineStore('state', {
tags: new ColumnSet(),
dirty: 0,
}
for(const [k, v] of Object.entries(StoreReviver)){
const coke = getCookie(nameCookie("last_"+k))
if(coke){
if((store[k as keyof StoreProps]) != undefined){
(store[k as keyof StoreProps] as any) = v.Revive(coke)
}
}
}
loadStore();
return store
}
})
export const StoreReviver = {
invs: StoreInvs,
chars: StoreChars,
accounts: StoreStrSet,
activeTable: StoreStr,
screen: StoreStr,
@ -46,6 +40,7 @@ export const StoreReviver = {
export interface StoreProps {
invs: Map<string,TricksterInventory>
chars: Map<string, TricksterCharacter>
accounts: Set<string>
activeTable: string
screen: string
@ -56,25 +51,24 @@ export interface StoreProps {
export const loadStore = ()=> {
let store = useStoreRef()
for(const [k, v] of Object.entries(StoreReviver)){
const coke = getCookie(nameCookie("last_"+k))
const coke = localStorage.getItem(nameCookie("last_"+k))
if(coke){
if((store[k as keyof StoreProps]) != undefined){
store[k as keyof StoreProps].value = v.Revive(coke)
if((store[k as keyof RefStore]) != undefined){
store[k as keyof RefStore].value = v.Revive(coke) as any
}
}
}
}
export const saveStore = ()=> {
let store = useStoreRef()
for(const [k, v] of Object.entries(StoreReviver)){
let coke;
if((store[k as keyof StoreProps]) != undefined){
coke = v.Murder(store[k as keyof StoreProps].value as any)
if((store[k as keyof RefStore]) != undefined){
coke = v.Murder(store[k as keyof RefStore].value as any)
}
if(coke){
setCookie(nameCookie("last_"+k),coke)
localStorage.setItem(nameCookie("last_"+k),coke)
}
}
}