forked from a/lifeto-shop
item moving
This commit is contained in:
parent
90106b844d
commit
8f0f0e715b
19
src/App.vue
19
src/App.vue
|
@ -3,10 +3,15 @@ import CharacterInventory from "./components/CharacterInventory.vue"
|
||||||
import Login from "./pages/login.vue"
|
import Login from "./pages/login.vue"
|
||||||
import CharacterRoulette from "./components/CharacterRoulette.vue";
|
import CharacterRoulette from "./components/CharacterRoulette.vue";
|
||||||
import Sidebar from "./components/Sidebar.vue";
|
import Sidebar from "./components/Sidebar.vue";
|
||||||
|
import { loadStore } from "./state/state";
|
||||||
|
import OrderDisplay from "./components/OrderDisplay.vue";
|
||||||
|
loadStore()
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
|
<OrderDisplay/>
|
||||||
<div class="parent">
|
<div class="parent">
|
||||||
<div class="splash"> </div>
|
<div class="splash">
|
||||||
|
</div>
|
||||||
<div class="main">
|
<div class="main">
|
||||||
<CharacterInventory />
|
<CharacterInventory />
|
||||||
</div>
|
</div>
|
||||||
|
@ -63,6 +68,14 @@ import Sidebar from "./components/Sidebar.vue";
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
.main {
|
||||||
|
overflow-x: scroll;
|
||||||
|
overflow-y: hidden;
|
||||||
|
}
|
||||||
|
.sidebar {
|
||||||
|
overflow-y: scroll;
|
||||||
|
}
|
||||||
|
|
||||||
.parent {
|
.parent {
|
||||||
display: grid;
|
display: grid;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
@ -76,13 +89,9 @@ import Sidebar from "./components/Sidebar.vue";
|
||||||
.splash {
|
.splash {
|
||||||
grid-area: 1 / 1 / 2 / 2;
|
grid-area: 1 / 1 / 2 / 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
.main{
|
.main{
|
||||||
grid-area: 2 / 2 / 5 / 4;
|
grid-area: 2 / 2 / 5 / 4;
|
||||||
overflow-x: scroll;
|
|
||||||
overflow-y: hidden;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebar {
|
.sidebar {
|
||||||
grid-area: 2 / 1 / 5 / 2;
|
grid-area: 2 / 1 / 5 / 2;
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,9 @@
|
||||||
<div>
|
<div>
|
||||||
<div
|
<div
|
||||||
v-on:click="selectCharacter()"
|
v-on:click="selectCharacter()"
|
||||||
>{{name}} the <span v-html="job" /> ({{galders.toLocaleString()}}g) ({{props.character}})</div>
|
>
|
||||||
|
{{activeTable == props.character ? "*" :""}}
|
||||||
|
{{name}} the <span v-html="job" /> ({{galders.toLocaleString()}}g) ({{props.character}}, {{items}})</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
@ -10,16 +12,19 @@ const session = storage.GetSession()
|
||||||
const api:LTOApi = getLTOState(LTOApiv0, session, useStoreRef())
|
const api:LTOApi = getLTOState(LTOApiv0, session, useStoreRef())
|
||||||
|
|
||||||
const props = defineProps(['character'])
|
const props = defineProps(['character'])
|
||||||
const name = ref(props.character.split("/").pop())
|
const name = ref("")
|
||||||
const job = ref("")
|
const job = ref("")
|
||||||
|
const items = ref(0)
|
||||||
const galders = ref(0)
|
const galders = ref(0)
|
||||||
const {invs, activeTable, chars} = useStoreRef()
|
const {invs, activeTable, chars} = useStoreRef()
|
||||||
|
|
||||||
watch(invs.value,()=>{
|
watch(invs.value,()=>{
|
||||||
const currentInv = invs.value.get(props.character)
|
const currentInv = invs.value.get(props.character)
|
||||||
if(currentInv){
|
if(currentInv){
|
||||||
name.value = currentInv.name!
|
if(currentInv.galders){
|
||||||
galders.value = currentInv.galders!
|
galders.value = currentInv.galders
|
||||||
|
}
|
||||||
|
items.value = Object.values(currentInv.items).length
|
||||||
}
|
}
|
||||||
},{deep:true})
|
},{deep:true})
|
||||||
|
|
||||||
|
@ -29,7 +34,6 @@ if(currentChar){
|
||||||
job.value = JobNumberToString(currentChar.current_job)
|
job.value = JobNumberToString(currentChar.current_job)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
const selectCharacter = () => {
|
const selectCharacter = () => {
|
||||||
activeTable.value = props.character
|
activeTable.value = props.character
|
||||||
api.GetInventory(props.character)
|
api.GetInventory(props.character)
|
||||||
|
|
|
@ -1,4 +1,9 @@
|
||||||
<template>
|
<template>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
id="logoutButton"
|
||||||
|
v-on:click="send_orders()"
|
||||||
|
>ayy lmao button</button>
|
||||||
<HotTable
|
<HotTable
|
||||||
ref="hotTableComponent"
|
ref="hotTableComponent"
|
||||||
:settings="DefaultSettings()"
|
:settings="DefaultSettings()"
|
||||||
|
@ -6,11 +11,19 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref } from 'vue';
|
import { onMounted, ref } from 'vue';
|
||||||
import { HotTable, HotColumn } from '@handsontable/vue3';
|
import { HotTable, HotColumn } from '@handsontable/vue3';
|
||||||
|
|
||||||
const {invs, activeTable, columns, tags, dirty} = useStoreRef()
|
const storeRefs = useStoreRef()
|
||||||
|
const {invs, activeTable, columns, tags, dirty, chars, currentSearch, orders} = storeRefs
|
||||||
|
|
||||||
const hotTableComponent = ref<any>(null)
|
const hotTableComponent = ref<any>(null)
|
||||||
|
const hott = ():Handsontable =>{
|
||||||
|
return hotTableComponent.value.hotInstance as any
|
||||||
|
}
|
||||||
|
const session = storage.GetSession()
|
||||||
|
const api:LTOApi = getLTOState(LTOApiv0, session, useStoreRef())
|
||||||
|
const manager = new OrderSender(storeRefs)
|
||||||
|
|
||||||
const updateTable = ():TableRecipe | undefined => {
|
const updateTable = ():TableRecipe | undefined => {
|
||||||
if (invs.value.has(activeTable.value)) {
|
if (invs.value.has(activeTable.value)) {
|
||||||
|
@ -19,25 +32,76 @@ const updateTable = ():TableRecipe | undefined => {
|
||||||
const it = new InventoryTable(chardat, {
|
const it = new InventoryTable(chardat, {
|
||||||
columns: columns.value,
|
columns: columns.value,
|
||||||
tags: tags.value,
|
tags: tags.value,
|
||||||
accounts: Array.from(invs.value.keys()),
|
accounts: Array.from(chars.value.keys()),
|
||||||
} as InventoryTableOptions)
|
} as InventoryTableOptions)
|
||||||
const hot = (hotTableComponent.value.hotInstance as Handsontable)
|
|
||||||
const build = it.BuildTable()
|
const build = it.BuildTable()
|
||||||
hot.updateSettings(build.settings)
|
hott().updateSettings(build.settings)
|
||||||
return build
|
return build
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return undefined
|
return undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
|
watch(currentSearch, ()=>{
|
||||||
|
filterTable()
|
||||||
|
})
|
||||||
|
|
||||||
|
const send_orders = () => {
|
||||||
|
if(hott()) {
|
||||||
|
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) {
|
||||||
|
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: origin.value,
|
||||||
|
target_path: target,
|
||||||
|
}
|
||||||
|
pending.push(info)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
log.debug("OrderDetails", pending)
|
||||||
|
for(const d of pending){
|
||||||
|
const order = manager.send(d)
|
||||||
|
order.tick(storeRefs, api)
|
||||||
|
}
|
||||||
|
saveStore();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
onMounted(()=>{
|
||||||
|
window.setInterval(tick_orders, 1000)
|
||||||
|
})
|
||||||
|
const tick_orders = () => {
|
||||||
|
if(orders && storeRefs && api){
|
||||||
|
orders.value.tick(storeRefs, api)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const filterTable = () => {
|
||||||
|
if(hott()){
|
||||||
|
const fp = hott().getPlugin('filters')
|
||||||
|
fp.removeConditions(2)
|
||||||
|
fp.addCondition(2,'contains', [currentSearch.value])
|
||||||
|
fp.filter()
|
||||||
|
}
|
||||||
|
}
|
||||||
// register Handsontable's modules
|
// register Handsontable's modules
|
||||||
registerAllModules();
|
registerAllModules();
|
||||||
|
|
||||||
watch([columns.value.dirty, tags.value.dirty, activeTable, dirty], () => {
|
watch([columns.value.dirty, tags.value.dirty, activeTable, dirty], () => {
|
||||||
log.debug(`${dirty.value} rendering inventory`, activeTable.value)
|
log.debug(`${dirty.value} rendering inventory`, activeTable.value)
|
||||||
let u = updateTable()
|
let u = updateTable()
|
||||||
if(u != undefined){
|
|
||||||
saveStore()
|
saveStore()
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
@ -47,12 +111,13 @@ import { defineComponent, computed, PropType, defineProps, defineEmits, watch} f
|
||||||
import { registerAllModules } from 'handsontable/registry';
|
import { registerAllModules } from 'handsontable/registry';
|
||||||
import { DefaultSettings, InventoryTable, InventoryTableOptions, TableRecipe } from '../lib/table';
|
import { DefaultSettings, InventoryTable, InventoryTableOptions, TableRecipe } from '../lib/table';
|
||||||
import { Columns, ColumnByNames, ColumnInfo } from '../lib/columns';
|
import { Columns, ColumnByNames, ColumnInfo } from '../lib/columns';
|
||||||
import { TricksterItem, SampleData } from '../lib/trickster';
|
import { TricksterItem} from '../lib/trickster';
|
||||||
import Handsontable from 'handsontable';
|
import Handsontable from 'handsontable';
|
||||||
import { useStoreRef, saveStore } from '../state/state';
|
import { useStoreRef, saveStore } from '../state/state';
|
||||||
import { storage } from '../session_storage';
|
import { storage } from '../session_storage';
|
||||||
import { LTOApi, LTOApiv0 } from '../lib/lifeto';
|
import { getLTOState, LTOApi, LTOApiv0 } from '../lib/lifeto';
|
||||||
import log from 'loglevel';
|
import log, { info } from 'loglevel';
|
||||||
|
import { OrderDetails, OrderSender } from '../lib/lifeto/order_manager';
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style src="handsontable/dist/handsontable.full.css">
|
<style src="handsontable/dist/handsontable.full.css">
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import CharacterCard from './CharacterCard.vue';
|
import CharacterCard from './CharacterCard.vue';
|
||||||
|
|
||||||
const { chars, accounts, invs, activeTable } = useStoreRef()
|
const { chars, invs, activeTable } = useStoreRef()
|
||||||
|
|
||||||
const characters = ref([] as string[])
|
const characters = ref([] as string[])
|
||||||
watch(chars, () => {
|
watch(chars, () => {
|
||||||
|
@ -22,6 +22,7 @@ api.GetAccounts().then(xs => {
|
||||||
characters.value.push(...x.characters.map(x=>x.path))
|
characters.value.push(...x.characters.map(x=>x.path))
|
||||||
})
|
})
|
||||||
characters.value = [...new Set([...characters.value])]
|
characters.value = [...new Set([...characters.value])]
|
||||||
|
saveStore();
|
||||||
})
|
})
|
||||||
|
|
||||||
onMounted(()=>{
|
onMounted(()=>{
|
||||||
|
@ -38,7 +39,7 @@ import { defineComponent, computed, PropType, defineProps, defineEmits, ref, wat
|
||||||
import { getLTOState, LTOApi, LTOApiv0 } from '../lib/lifeto';
|
import { getLTOState, LTOApi, LTOApiv0 } from '../lib/lifeto';
|
||||||
import { LoginHelper, Session } from '../lib/session';
|
import { LoginHelper, Session } from '../lib/session';
|
||||||
import { storage } from '../session_storage';
|
import { storage } from '../session_storage';
|
||||||
import { useStore, useStoreRef } from '../state/state';
|
import { saveStore, useStore, useStoreRef } from '../state/state';
|
||||||
import log from 'loglevel';
|
import log from 'loglevel';
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -0,0 +1,127 @@
|
||||||
|
<template>
|
||||||
|
<div id="order-display">
|
||||||
|
<div id="order-titlebar"></div>
|
||||||
|
<table>
|
||||||
|
<tr
|
||||||
|
v-for="v in orders.orders"
|
||||||
|
:key="dirty"
|
||||||
|
>
|
||||||
|
<td>{{v.action_id}}</td>
|
||||||
|
<td>[{{v.progress()[0]}} / {{v.progress()[1]}}]</td>
|
||||||
|
<td>{{v.order_type}}</td>
|
||||||
|
<td>{{v.state}}</td>
|
||||||
|
<td>{{(((new Date()).getTime() - new Date(v.created).getTime())/(60 *1000)).toFixed(0)}} min ago</td>
|
||||||
|
<td>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
id="logoutButton"
|
||||||
|
v-on:click="tick_order(v.action_id)"
|
||||||
|
>tick</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
|
||||||
|
const storeRefs = useStoreRef()
|
||||||
|
const {orders, dirty} = storeRefs;
|
||||||
|
const session = storage.GetSession()
|
||||||
|
const api:LTOApi = getLTOState(LTOApiv0, session, useStoreRef())
|
||||||
|
const tick_order = (action_id:string)=> {
|
||||||
|
const deet = orders.value.orders[action_id]
|
||||||
|
console.log(deet)
|
||||||
|
if(deet){
|
||||||
|
deet.tick(storeRefs, api)
|
||||||
|
}else {
|
||||||
|
console.log(`tried to send ${action_id} but undefined`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
onMounted(()=>{
|
||||||
|
dragElement(document.getElementById("order-display"));
|
||||||
|
function dragElement(elmnt:any) {
|
||||||
|
var pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0;
|
||||||
|
document.getElementById("order-titlebar")!.onmousedown = dragMouseDown;
|
||||||
|
function dragMouseDown(e:any) {
|
||||||
|
e = e || window.event;
|
||||||
|
e.preventDefault();
|
||||||
|
// get the mouse cursor position at startup:
|
||||||
|
pos3 = e.clientX;
|
||||||
|
pos4 = e.clientY;
|
||||||
|
document.onmouseup = closeDragElement;
|
||||||
|
// call a function whenever the cursor moves:
|
||||||
|
document.onmousemove = elementDrag;
|
||||||
|
}
|
||||||
|
|
||||||
|
function elementDrag(e:any) {
|
||||||
|
e = e || window.event;
|
||||||
|
e.preventDefault();
|
||||||
|
// calculate the new cursor position:
|
||||||
|
pos1 = pos3 - e.clientX;
|
||||||
|
pos2 = pos4 - e.clientY;
|
||||||
|
pos3 = e.clientX;
|
||||||
|
pos4 = e.clientY;
|
||||||
|
// set the element's new position:
|
||||||
|
elmnt.style.top = (elmnt.offsetTop - pos2) + "px";
|
||||||
|
elmnt.style.left = (elmnt.offsetLeft - pos1) + "px";
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeDragElement() {
|
||||||
|
// stop moving when mouse button is released:
|
||||||
|
document.onmouseup = null;
|
||||||
|
document.onmousemove = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { onMounted, watch } from 'vue';
|
||||||
|
import { getLTOState, LTOApi, LTOApiv0 } from '../lib/lifeto';
|
||||||
|
import { storage } from '../session_storage';
|
||||||
|
import { useStoreRef } from '../state/state';
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<style>
|
||||||
|
#order-display{
|
||||||
|
position: absolute;
|
||||||
|
z-index: 9;
|
||||||
|
background-color: #f1f1f1;
|
||||||
|
border: 1px solid #d3d3d3;
|
||||||
|
text-align: center;
|
||||||
|
width: 300px;
|
||||||
|
}
|
||||||
|
#order-titlebar {
|
||||||
|
padding: 10px;
|
||||||
|
cursor: move;
|
||||||
|
z-index: 10;
|
||||||
|
background-color: #2196F3;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
|
@ -1,22 +1,30 @@
|
||||||
<template>
|
<template>
|
||||||
Filters:
|
search:
|
||||||
|
<div class="filter_field">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
id="searchbox"
|
||||||
|
v-model="currentSearch"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
<br>
|
<br>
|
||||||
<FilterCheckboxGroup :header="'tags:'" :columns="TagColumns" />
|
<FilterCheckboxGroup :header="'tags:'" :columns="[...TagColumns]" />
|
||||||
<br>
|
<br>
|
||||||
Columns:
|
Columns:
|
||||||
<br>
|
<br>
|
||||||
<ColumnCheckboxGroup :header="'action:'" :columns="MoveColumns" :default="true" />
|
<ColumnCheckboxGroup :header="'action:'" :columns="[...MoveColumns]" :default="true" />
|
||||||
<ColumnCheckboxGroup :header="'details:'" :columns="DetailsColumns" :default="true"/>
|
<ColumnCheckboxGroup :header="'details:'" :columns="[...DetailsColumns]" :default="true"/>
|
||||||
<ColumnCheckboxGroup :header="'equipment:'" :columns="EquipmentColumns" />
|
<ColumnCheckboxGroup :header="'equipment:'" :columns="[...EquipmentColumns]" />
|
||||||
<ColumnCheckboxGroup :header="'stats:'" :columns="StatsColumns" />
|
<ColumnCheckboxGroup :header="'stats:'" :columns="[...StatsColumns]" />
|
||||||
|
<ColumnCheckboxGroup :header="'debug:'" :columns="[...DebugColumns]" />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import ColumnCheckboxGroup from './ColumnCheckboxGroup.vue';
|
import ColumnCheckboxGroup from './ColumnCheckboxGroup.vue';
|
||||||
import FilterCheckboxGroup from './FilterCheckboxGroup.vue';
|
import FilterCheckboxGroup from './FilterCheckboxGroup.vue';
|
||||||
import { StatsColumns, MoveColumns, TagColumns, EquipmentColumns, DetailsColumns } from '../lib/columns';
|
import { DebugColumns, StatsColumns, MoveColumns, TagColumns, EquipmentColumns, DetailsColumns } from '../lib/columns';
|
||||||
|
|
||||||
const { accounts, invs } = useStoreRef()
|
const { currentSearch} = useStoreRef()
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,7 @@ import { TricksterItem } from "../trickster"
|
||||||
import Core from "handsontable/core";
|
import Core from "handsontable/core";
|
||||||
|
|
||||||
export const BasicColumns = [
|
export const BasicColumns = [
|
||||||
"Image","Name","Count",
|
"uid","Image","Name","Count",
|
||||||
] as const
|
] as const
|
||||||
|
|
||||||
export const DetailsColumns = [
|
export const DetailsColumns = [
|
||||||
|
@ -28,6 +28,9 @@ export const StatsColumns = [
|
||||||
"AP","GunAP","AC","DX","MP","MA","MD","WT","DA","LK","HP","DP","HV",
|
"AP","GunAP","AC","DX","MP","MA","MD","WT","DA","LK","HP","DP","HV",
|
||||||
] as const
|
] as const
|
||||||
|
|
||||||
|
|
||||||
|
export const DebugColumns = [
|
||||||
|
]
|
||||||
export const HackColumns = [
|
export const HackColumns = [
|
||||||
] as const
|
] as const
|
||||||
|
|
||||||
|
|
|
@ -11,11 +11,6 @@ export const ColumnByNames = (...n:ColumnName[]) => {
|
||||||
export const ColumnByName = (n:ColumnName) => {
|
export const ColumnByName = (n:ColumnName) => {
|
||||||
return Columns[n]
|
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 {
|
class Image implements ColumnInfo {
|
||||||
name:ColumnName = 'Image'
|
name:ColumnName = 'Image'
|
||||||
|
@ -76,24 +71,26 @@ class Count implements ColumnInfo {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const spacer = "-------------------------------"
|
||||||
class Move implements ColumnInfo {
|
class Move implements ColumnInfo {
|
||||||
name:ColumnName = "Move"
|
name:ColumnName = "Move"
|
||||||
displayName = "Target"
|
displayName = "Target"
|
||||||
writable = true
|
writable = true
|
||||||
options = getMoveTargets
|
options = getMoveTargets
|
||||||
getter(item:TricksterItem):(string|number){
|
getter(item:TricksterItem):(string|number){
|
||||||
return "---------------------------------------------"
|
return spacer
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const getMoveTargets = (invs: string[]):string[] => {
|
const getMoveTargets = (invs: string[]):string[] => {
|
||||||
let out:string[] = [];
|
let out:string[] = [];
|
||||||
|
out.push(spacer)
|
||||||
for(const k of invs){
|
for(const k of invs){
|
||||||
out.push(k)
|
out.push(k)
|
||||||
}
|
}
|
||||||
out.push("")
|
out.push("")
|
||||||
out.push("")
|
out.push("")
|
||||||
out.push("!TRASH")
|
out.push("TRASH")
|
||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -175,6 +172,20 @@ class All implements ColumnInfo {
|
||||||
return -10000
|
return -10000
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
class uid implements ColumnInfo {
|
||||||
|
name:ColumnName = "uid"
|
||||||
|
displayName = "id"
|
||||||
|
renderer = invisibleRenderer
|
||||||
|
getter(item:TricksterItem):(string|number){
|
||||||
|
return item.unique_id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function invisibleRenderer(instance:Core, td:any, row:number, col:number, prop:any, value:any, cellProperties:any) {
|
||||||
|
Handsontable.dom.empty(td);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class Card implements ColumnInfo {
|
class Card implements ColumnInfo {
|
||||||
name:ColumnName = "Card"
|
name:ColumnName = "Card"
|
||||||
|
@ -447,6 +458,7 @@ export const Columns:{[Property in ColumnName]:ColumnInfo}= {
|
||||||
RefineState: new RefineState(),
|
RefineState: new RefineState(),
|
||||||
All: new All(),
|
All: new All(),
|
||||||
Compound: new Compound(),
|
Compound: new Compound(),
|
||||||
|
uid: new uid(),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,40 +1,15 @@
|
||||||
import { trace } from "loglevel"
|
import { trace } from "loglevel"
|
||||||
import { TricksterAccount, TricksterInventory } from "../trickster"
|
import { TricksterAccount, TricksterInventory } from "../trickster"
|
||||||
import { v4 as uuidv4 } from 'uuid';
|
import { v4 as uuidv4 } from 'uuid';
|
||||||
|
import axios, { AxiosRequestConfig, AxiosResponse } from "axios";
|
||||||
|
|
||||||
|
export const BankEndpoints = ["internal-xfer-item", "bank-item"] as const
|
||||||
|
export type BankEndpoint = typeof BankEndpoints[number]
|
||||||
|
|
||||||
export interface LTOApi {
|
export interface LTOApi {
|
||||||
GetInventory:(path:string)=>Promise<TricksterInventory>
|
GetInventory:(path:string)=>Promise<TricksterInventory>
|
||||||
GetAccounts:() =>Promise<Array<TricksterAccount>>
|
GetAccounts:() =>Promise<Array<TricksterAccount>>
|
||||||
GetLoggedin:() =>Promise<boolean>
|
GetLoggedin:() =>Promise<boolean>
|
||||||
}
|
|
||||||
|
|
||||||
export const TxnStates = ["PENDING","INFLIGHT","WAITING","ERROR","SUCCESS"] as const
|
BankAction:<T, D>(e:BankEndpoint, t:T) => Promise<D>
|
||||||
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>
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,27 +1,48 @@
|
||||||
import { Axios, AxiosResponse } from "axios"
|
import { Axios, AxiosResponse } from "axios"
|
||||||
import { zhCN } from "handsontable/i18n"
|
|
||||||
import log from "loglevel"
|
import log from "loglevel"
|
||||||
import { RefStore } from "../../state/state"
|
import { bank_endpoint, Session } from "../session"
|
||||||
import { Session } from "../session"
|
|
||||||
import { dummyChar, TricksterAccount, TricksterInventory, TricksterItem, TricksterWallet } from "../trickster"
|
import { dummyChar, TricksterAccount, TricksterInventory, TricksterItem, TricksterWallet } from "../trickster"
|
||||||
import { LTOApi } from "./api"
|
import { BankEndpoint, LTOApi } from "./api"
|
||||||
|
|
||||||
|
export const pathIsBank = (path:string):boolean => {
|
||||||
|
if(!path.includes("/")) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
export const splitPath = (path:string):[string,string]=>{
|
||||||
|
const spl = path.split("/")
|
||||||
|
switch(spl.length) {
|
||||||
|
case 1:
|
||||||
|
return [spl[0], ""]
|
||||||
|
case 2:
|
||||||
|
return [spl[0],spl[1]]
|
||||||
|
}
|
||||||
|
return ["",""]
|
||||||
|
}
|
||||||
export class LTOApiv0 implements LTOApi {
|
export class LTOApiv0 implements LTOApi {
|
||||||
s: Session
|
s: Session
|
||||||
constructor(s:Session) {
|
constructor(s:Session) {
|
||||||
this.s = s
|
this.s = s
|
||||||
}
|
}
|
||||||
|
|
||||||
|
BankAction = async <T,D>(e: BankEndpoint, t: T):Promise<D> => {
|
||||||
|
return this.s.request("POST",e,t,bank_endpoint).then((x)=>{
|
||||||
|
return x.data
|
||||||
|
})
|
||||||
|
}
|
||||||
GetInventory = async (char_path: string):Promise<TricksterInventory> =>{
|
GetInventory = async (char_path: string):Promise<TricksterInventory> =>{
|
||||||
if(char_path.startsWith(":")) {
|
if(char_path.startsWith(":")) {
|
||||||
char_path = char_path.replace(":","")
|
char_path = char_path.replace(":","")
|
||||||
}
|
}
|
||||||
return this.s.authed_request("GET", `item-manager/items/account/${char_path}`,undefined).then((ans:AxiosResponse)=>{
|
return this.s.request("GET", `item-manager/items/account/${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"
|
||||||
let id = 0
|
let id = 0
|
||||||
let galders = 0
|
let galders = 0
|
||||||
if(char_path.includes("/")) {
|
if(pathIsBank("char_path")){
|
||||||
let [char, val] = Object.entries(o.characters)[0] as [string,any]
|
let [char, val] = Object.entries(o.characters)[0] as [string,any]
|
||||||
name = val.name
|
name = val.name
|
||||||
id = Number(char)
|
id = Number(char)
|
||||||
|
@ -36,16 +57,16 @@ export class LTOApiv0 implements LTOApi {
|
||||||
id,
|
id,
|
||||||
path: char_path,
|
path: char_path,
|
||||||
galders,
|
galders,
|
||||||
items:(Object.entries(o.items) as any).map(([k, v]: [string, TricksterItem]):TricksterItem=>{
|
items: Object.fromEntries((Object.entries(o.items) as any).map(([k, v]: [string, TricksterItem]):[string, TricksterItem]=>{
|
||||||
v.unique_id = Number(k)
|
v.unique_id = Number(k)
|
||||||
return v
|
return [k, v]
|
||||||
}),
|
})),
|
||||||
} as TricksterInventory
|
} as TricksterInventory
|
||||||
return out
|
return out
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
GetAccounts = async ():Promise<TricksterAccount[]> => {
|
GetAccounts = async ():Promise<TricksterAccount[]> => {
|
||||||
return this.s.authed_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)=>{
|
||||||
return {
|
return {
|
||||||
|
@ -65,7 +86,7 @@ export class LTOApiv0 implements LTOApi {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
GetLoggedin = async ():Promise<boolean> => {
|
GetLoggedin = async ():Promise<boolean> => {
|
||||||
return this.s.authed_request("POST", "accounts/list",undefined).then((ans:AxiosResponse)=>{
|
return this.s.request("POST", "accounts/list",undefined).then((ans:AxiosResponse)=>{
|
||||||
if(ans.status == 401) {
|
if(ans.status == 401) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,226 @@
|
||||||
|
import { LTOApi } from "./api"
|
||||||
|
import { v4 as uuidv4 } from 'uuid';
|
||||||
|
import { RefStore } from "../../state/state";
|
||||||
|
import { debug } from "loglevel";
|
||||||
|
|
||||||
|
export const TxnStates = ["PENDING","INFLIGHT","WORKING","ERROR","SUCCESS"] as const
|
||||||
|
|
||||||
|
export type TxnState = typeof TxnStates[number]
|
||||||
|
|
||||||
|
export interface TxnDetails {
|
||||||
|
item_uid: string | "galders"
|
||||||
|
count:number
|
||||||
|
origin:string
|
||||||
|
target:string
|
||||||
|
|
||||||
|
origin_path:string
|
||||||
|
target_path:string
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export abstract class Order {
|
||||||
|
action_id: string
|
||||||
|
details?:TxnDetails
|
||||||
|
created:Date
|
||||||
|
state: TxnState
|
||||||
|
constructor(details?:TxnDetails) {
|
||||||
|
this.state = "PENDING"
|
||||||
|
this.details = details
|
||||||
|
this.created = new Date()
|
||||||
|
this.action_id = uuidv4();
|
||||||
|
}
|
||||||
|
|
||||||
|
mark(t:TxnState) {
|
||||||
|
this.state = t
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract tick(r:RefStore, api:LTOApi):Promise<any>
|
||||||
|
abstract status():string
|
||||||
|
abstract progress():[number, number]
|
||||||
|
abstract error():string
|
||||||
|
|
||||||
|
abstract order_type:OrderType
|
||||||
|
|
||||||
|
parse(i:any):Order {
|
||||||
|
this.action_id = i.action_id
|
||||||
|
this.details = i.details
|
||||||
|
this.created = new Date(i.created)
|
||||||
|
this.state = i.state
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export abstract class BasicOrder extends Order {
|
||||||
|
stage: number
|
||||||
|
err?: string
|
||||||
|
|
||||||
|
constructor(details:TxnDetails) {
|
||||||
|
super(details)
|
||||||
|
this.stage = 0
|
||||||
|
}
|
||||||
|
progress():[number,number]{
|
||||||
|
return [this.stage, 1]
|
||||||
|
}
|
||||||
|
status():string {
|
||||||
|
return this.state
|
||||||
|
}
|
||||||
|
error():string {
|
||||||
|
return this.err ? this.err : ""
|
||||||
|
}
|
||||||
|
parse(i:any):BasicOrder {
|
||||||
|
this.stage = i.stage
|
||||||
|
this.err = i.err
|
||||||
|
super.parse(i)
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// start user defined
|
||||||
|
|
||||||
|
export const OrderTypes = ["InvalidOrder","BankItem","InternalXfer"]
|
||||||
|
export type OrderType = typeof OrderTypes[number]
|
||||||
|
|
||||||
|
export class InvalidOrder extends Order{
|
||||||
|
order_type = "InvalidOrder"
|
||||||
|
|
||||||
|
msg:string
|
||||||
|
constructor(msg: string){
|
||||||
|
super(undefined)
|
||||||
|
this.msg = msg
|
||||||
|
this.mark("ERROR")
|
||||||
|
}
|
||||||
|
status():string {
|
||||||
|
return "ERROR"
|
||||||
|
}
|
||||||
|
progress():[number, number] {
|
||||||
|
return [0,0]
|
||||||
|
}
|
||||||
|
error(): string {
|
||||||
|
return this.msg
|
||||||
|
}
|
||||||
|
async tick(r:RefStore, api:LTOApi):Promise<void> {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
parse(i:any):InvalidOrder {
|
||||||
|
super.parse(i)
|
||||||
|
this.msg = i.msg
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface BasicResponse {
|
||||||
|
status: number
|
||||||
|
data: any
|
||||||
|
msg?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export interface InternalXferRequest {
|
||||||
|
item_uid:string
|
||||||
|
qty:string
|
||||||
|
account:string
|
||||||
|
new_char:string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface InternalXferResponse extends BasicResponse {}
|
||||||
|
|
||||||
|
export class InternalXfer extends BasicOrder{
|
||||||
|
order_type = "InternalXfer"
|
||||||
|
|
||||||
|
originalRequest:InternalXferRequest
|
||||||
|
originalResponse?:InternalXferResponse
|
||||||
|
constructor(details:TxnDetails) {
|
||||||
|
super(details)
|
||||||
|
this.originalRequest = {
|
||||||
|
item_uid: details.item_uid,
|
||||||
|
qty: details.count.toString(),
|
||||||
|
new_char: details.target,
|
||||||
|
account: details.origin,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
async tick(r:RefStore, api:LTOApi):Promise<void> {
|
||||||
|
if(this.state !== "PENDING") {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.mark("WORKING")
|
||||||
|
return api.BankAction<InternalXferRequest, InternalXferResponse>("internal-xfer-item",this.originalRequest)
|
||||||
|
.then((x:InternalXferResponse)=>{
|
||||||
|
if(x.status == 200){
|
||||||
|
this.originalResponse = x
|
||||||
|
this.stage = 1
|
||||||
|
this.mark("SUCCESS")
|
||||||
|
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!
|
||||||
|
r.dirty.value++
|
||||||
|
}else{
|
||||||
|
throw x.msg
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((e)=>{
|
||||||
|
debug("InternalXfer",e)
|
||||||
|
this.stage = 1
|
||||||
|
this.err = e
|
||||||
|
this.mark("ERROR")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
parse(i:any):InternalXfer {
|
||||||
|
super.parse(i)
|
||||||
|
this.originalRequest = i.originalRequest
|
||||||
|
this.originalResponse = i.originalResponse
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface BankItemRequest {
|
||||||
|
item_uid:string
|
||||||
|
qty:string
|
||||||
|
account:string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface BankItemResponse extends BasicResponse {}
|
||||||
|
|
||||||
|
export class BankItem extends BasicOrder{
|
||||||
|
order_type = "BankItem";
|
||||||
|
|
||||||
|
originalRequest:BankItemRequest
|
||||||
|
originalResponse?:BankItemResponse
|
||||||
|
constructor(details:TxnDetails) {
|
||||||
|
super(details)
|
||||||
|
this.originalRequest = {
|
||||||
|
item_uid: details.item_uid,
|
||||||
|
qty: details.count.toString(),
|
||||||
|
account: details.target,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
async tick(r:RefStore, api:LTOApi):Promise<void> {
|
||||||
|
if(this.state !== "PENDING" ){
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.mark("WORKING")
|
||||||
|
return api.BankAction<BankItemRequest, BankItemResponse>("bank-item",this.originalRequest)
|
||||||
|
.then((x)=>{
|
||||||
|
debug("BankItem",x)
|
||||||
|
if(x.status == 200){
|
||||||
|
this.stage = 1
|
||||||
|
this.originalResponse = x
|
||||||
|
this.mark("SUCCESS")
|
||||||
|
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!
|
||||||
|
r.dirty.value++
|
||||||
|
}else {
|
||||||
|
throw x.msg ? "unknown error" : ""
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((e)=>{
|
||||||
|
this.stage = 1
|
||||||
|
this.err = e
|
||||||
|
this.mark("ERROR")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
parse(i:any):BankItem {
|
||||||
|
super.parse(i)
|
||||||
|
this.originalRequest = i.originalRequest
|
||||||
|
this.originalResponse = i.originalResponse
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,155 @@
|
||||||
|
import log from "loglevel";
|
||||||
|
import { Ref } from "vue";
|
||||||
|
import { RefStore } from "../../state/state";
|
||||||
|
import { JsonLoadable, Serializable } from "../storage";
|
||||||
|
import { LTOApi } from "./api";
|
||||||
|
import { pathIsBank, splitPath } from "./lifeto";
|
||||||
|
import { BankItem, BankXfer, InternalXfer, InvalidOrder, Order, TxnDetails } from "./order";
|
||||||
|
|
||||||
|
export interface OrderDetails {
|
||||||
|
item_uid: string | "galders"
|
||||||
|
count:number
|
||||||
|
origin_path:string
|
||||||
|
target_path:string
|
||||||
|
}
|
||||||
|
|
||||||
|
const notSupported = new InvalidOrder("not supported yet")
|
||||||
|
const notFound = new InvalidOrder("character not found")
|
||||||
|
|
||||||
|
export class OrderTracker implements Serializable<OrderTracker> {
|
||||||
|
orders: {[key:string]:Order} = {}
|
||||||
|
|
||||||
|
async tick(r:RefStore, api:LTOApi):Promise<any> {
|
||||||
|
let hasDirty = false
|
||||||
|
console.log("ticking")
|
||||||
|
for(const [id, order] of Object.entries(this.orders)) {
|
||||||
|
if(order.state == "SUCCESS" || order.state == "ERROR") {
|
||||||
|
console.log("finished order", order)
|
||||||
|
hasDirty = true
|
||||||
|
delete this.orders[id]
|
||||||
|
}
|
||||||
|
order.tick(r,api)
|
||||||
|
}
|
||||||
|
if(hasDirty){
|
||||||
|
r.dirty.value++
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
parse(s: any): OrderTracker {
|
||||||
|
if(s == undefined) {
|
||||||
|
return new OrderTracker()
|
||||||
|
}
|
||||||
|
if(s.orders == undefined) {
|
||||||
|
return new OrderTracker()
|
||||||
|
}
|
||||||
|
this.orders = {}
|
||||||
|
const raw: Order[] = Object.values(s.orders)
|
||||||
|
for(const o of raw) {
|
||||||
|
let newOrder:Order | undefined = undefined
|
||||||
|
console.log("loading", o)
|
||||||
|
if(o.details){
|
||||||
|
if(o.state == "SUCCESS" || o.state == "ERROR") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
switch(o.order_type) {
|
||||||
|
case "InternalXfer":
|
||||||
|
newOrder = new InternalXfer(o.details).parse(o)
|
||||||
|
break;
|
||||||
|
case "BankItem":
|
||||||
|
newOrder = new BankItem(o.details).parse(o)
|
||||||
|
break;
|
||||||
|
case "InvalidOrder":
|
||||||
|
newOrder = new InvalidOrder("").parse(o)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if(newOrder) {
|
||||||
|
this.orders[newOrder.action_id] = newOrder
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class OrderSender {
|
||||||
|
r: RefStore
|
||||||
|
constructor(r:RefStore) {
|
||||||
|
this.r = r
|
||||||
|
}
|
||||||
|
|
||||||
|
send(o:OrderDetails):Order {
|
||||||
|
const formed = this.form(o)
|
||||||
|
this.r.orders.value.orders[formed.action_id] = formed
|
||||||
|
return formed
|
||||||
|
}
|
||||||
|
|
||||||
|
form(o:OrderDetails):Order {
|
||||||
|
// bank to bank
|
||||||
|
if(pathIsBank(o.origin_path) && pathIsBank(o.target_path)) {
|
||||||
|
return this.bank_to_bank(o)
|
||||||
|
}
|
||||||
|
// bank to user
|
||||||
|
if(pathIsBank(o.origin_path) && !pathIsBank(o.target_path)) {
|
||||||
|
return this.bank_to_user(o)
|
||||||
|
}
|
||||||
|
// user to bank
|
||||||
|
if(!pathIsBank(o.origin_path) && pathIsBank(o.target_path)) {
|
||||||
|
return this.user_to_bank(o)
|
||||||
|
}
|
||||||
|
// user to user
|
||||||
|
if(!pathIsBank(o.origin_path) && !pathIsBank(o.target_path)) {
|
||||||
|
return this.user_to_user(o)
|
||||||
|
}
|
||||||
|
return notSupported
|
||||||
|
}
|
||||||
|
bank_to_bank(o:OrderDetails): Order{
|
||||||
|
const origin = this.r.chars.value.get(o.origin_path)
|
||||||
|
const target = this.r.chars.value.get(o.target_path)
|
||||||
|
return notSupported
|
||||||
|
}
|
||||||
|
bank_to_user(o:OrderDetails): Order{
|
||||||
|
// get the uid of the bank
|
||||||
|
const origin = this.r.chars.value.get(o.origin_path)
|
||||||
|
const target = this.r.chars.value.get(o.target_path)
|
||||||
|
if(!(origin && target)) {
|
||||||
|
return notFound
|
||||||
|
}
|
||||||
|
const [account, name] = splitPath(target.path)
|
||||||
|
if(account != origin.path) {
|
||||||
|
return notSupported
|
||||||
|
}
|
||||||
|
return new InternalXfer(this.transformOrder(o))
|
||||||
|
}
|
||||||
|
user_to_bank(o:OrderDetails): Order{
|
||||||
|
const origin = this.r.chars.value.get(o.origin_path)
|
||||||
|
const target = this.r.chars.value.get(o.target_path)
|
||||||
|
if(!(origin && target)) {
|
||||||
|
return notFound
|
||||||
|
}
|
||||||
|
const [account, name] = splitPath(origin.path)
|
||||||
|
if(account != target.path) {
|
||||||
|
return notSupported
|
||||||
|
}
|
||||||
|
return new BankItem(this.transformOrder(o))
|
||||||
|
}
|
||||||
|
user_to_user(o:OrderDetails): Order{
|
||||||
|
const origin = this.r.chars.value.get(o.origin_path)
|
||||||
|
const target = this.r.chars.value.get(o.target_path)
|
||||||
|
return notSupported
|
||||||
|
}
|
||||||
|
|
||||||
|
transformOrder(o:OrderDetails):TxnDetails {
|
||||||
|
const origin = this.r.chars.value.get(o.origin_path)!
|
||||||
|
const target = this.r.chars.value.get(o.target_path)!
|
||||||
|
return {
|
||||||
|
origin: origin.id.toString(),
|
||||||
|
target: target.id.toString(),
|
||||||
|
item_uid: o.item_uid,
|
||||||
|
count: o.count,
|
||||||
|
origin_path: o.origin_path,
|
||||||
|
target_path: o.target_path,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { RefStore } from "../../state/state";
|
import { RefStore } from "../../state/state";
|
||||||
import { Session } from "../session";
|
import { bank_endpoint, Session } from "../session";
|
||||||
import { TricksterAccount, TricksterInventory } from "../trickster";
|
import { TricksterAccount, TricksterInventory } from "../trickster";
|
||||||
import { LTOApi } from "./api";
|
import { BankEndpoint, LTOApi } from "./api";
|
||||||
|
|
||||||
export interface SessionBinding {
|
export interface SessionBinding {
|
||||||
new(s:Session):LTOApi
|
new(s:Session):LTOApi
|
||||||
|
@ -17,9 +17,19 @@ export class StatefulLTOApi implements LTOApi {
|
||||||
this.u = s
|
this.u = s
|
||||||
this.r=r
|
this.r=r
|
||||||
}
|
}
|
||||||
|
BankAction = <T,D>(e: BankEndpoint, t: T):Promise<D> => {
|
||||||
|
return this.u.BankAction(e,t)
|
||||||
|
}
|
||||||
GetInventory = async (path:string):Promise<TricksterInventory>=>{
|
GetInventory = async (path:string):Promise<TricksterInventory>=>{
|
||||||
const inv = await this.u.GetInventory(path)
|
const inv = await this.u.GetInventory(path)
|
||||||
|
if(this.r.invs.value.get(inv.path)){
|
||||||
|
this.r.invs.value.get(inv.path)!.items = inv.items
|
||||||
|
}else{
|
||||||
this.r.invs.value.set(inv.path,inv)
|
this.r.invs.value.set(inv.path,inv)
|
||||||
|
}
|
||||||
|
if(inv.galders) {
|
||||||
|
this.r.invs.value.get(inv.path)!.galders = inv.galders
|
||||||
|
}
|
||||||
this.r.dirty.value = this.r.dirty.value + 1
|
this.r.dirty.value = this.r.dirty.value + 1
|
||||||
return inv
|
return inv
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,24 +2,37 @@ import axios, { AxiosResponse, Method } from "axios";
|
||||||
import qs from "qs";
|
import qs from "qs";
|
||||||
import { getCookie, removeCookie } from "typescript-cookie";
|
import { getCookie, removeCookie } from "typescript-cookie";
|
||||||
|
|
||||||
|
|
||||||
|
export const SITE_ROOT = "/lifeto/"
|
||||||
|
|
||||||
|
export const API_ROOT = "api/lifeto/"
|
||||||
|
export const BANK_ROOT = "item-manager-action/"
|
||||||
|
|
||||||
|
const login_endpoint = (name:string)=>{
|
||||||
|
return SITE_ROOT + name
|
||||||
|
}
|
||||||
|
export const api_endpoint = (name:string):string =>{
|
||||||
|
return SITE_ROOT+API_ROOT + name
|
||||||
|
}
|
||||||
|
export const bank_endpoint = (name:string):string =>{
|
||||||
|
return SITE_ROOT+BANK_ROOT + name
|
||||||
|
}
|
||||||
|
|
||||||
|
export const EndpointCreators = [
|
||||||
|
api_endpoint,
|
||||||
|
bank_endpoint,
|
||||||
|
]
|
||||||
|
|
||||||
|
type EndpointCreator = typeof EndpointCreators[number]
|
||||||
|
|
||||||
export interface Session {
|
export interface Session {
|
||||||
user:string
|
user:string
|
||||||
xsrf:string
|
xsrf:string
|
||||||
csrf:string
|
csrf:string
|
||||||
authed_request:(verb:Method,url:string,data:any)=>Promise<any>
|
request:(verb:Method,url:string,data:any,c?:EndpointCreator)=>Promise<any>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export const SITE_ROOT = "/lifeto/"
|
|
||||||
export const API_ROOT = "api/lifeto/"
|
|
||||||
|
|
||||||
const login_endpoint = (name:string)=>{
|
|
||||||
return SITE_ROOT + name
|
|
||||||
}
|
|
||||||
const api_endpoint = (name:string)=>{
|
|
||||||
return SITE_ROOT+API_ROOT + name
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
export class LoginHelper {
|
export class LoginHelper {
|
||||||
user:string
|
user:string
|
||||||
|
@ -32,13 +45,6 @@ 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 (x)=>{
|
||||||
console.log(x)
|
|
||||||
if(x.data){
|
|
||||||
try{
|
|
||||||
this.csrf = x.data.split("csrf-token")[1].split('\">')[0].replace("\" content=\"",'')
|
|
||||||
}catch(e){
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return axios.post(login_endpoint("login"),{
|
return axios.post(login_endpoint("login"),{
|
||||||
login:this.user,
|
login:this.user,
|
||||||
password:this.pass,
|
password:this.pass,
|
||||||
|
@ -74,19 +80,19 @@ export class TokenSession implements Session {
|
||||||
this.xsrf = xsrf;
|
this.xsrf = xsrf;
|
||||||
}
|
}
|
||||||
|
|
||||||
authed_request = async (verb:string,url:string,data:any):Promise<AxiosResponse> => {
|
request = async (verb:string,url:string,data:any, c:EndpointCreator = api_endpoint):Promise<AxiosResponse> => {
|
||||||
let promise
|
let promise
|
||||||
switch (verb.toLowerCase()){
|
switch (verb.toLowerCase()){
|
||||||
case "post":
|
case "post":
|
||||||
promise = axios.post(api_endpoint(url),data,this.genHeaders())
|
promise = axios.post(c(url),data,this.genHeaders())
|
||||||
break;
|
break;
|
||||||
case "postraw":
|
case "postraw":
|
||||||
const querystring = qs.stringify(data)
|
const querystring = qs.stringify(data)
|
||||||
promise = axios.post(api_endpoint(url),querystring,this.genHeaders())
|
promise = axios.post(c(url),querystring,this.genHeaders())
|
||||||
break;
|
break;
|
||||||
case "get":
|
case "get":
|
||||||
default:
|
default:
|
||||||
promise = axios.get(api_endpoint(url),this.genHeaders())
|
promise = axios.get(c(url),this.genHeaders())
|
||||||
}
|
}
|
||||||
return promise.then(x=>{
|
return promise.then(x=>{
|
||||||
if(x.data){
|
if(x.data){
|
||||||
|
|
|
@ -20,9 +20,6 @@ export const ARRAY_SEPERATOR = ","
|
||||||
|
|
||||||
let as = ARRAY_SEPERATOR
|
let as = ARRAY_SEPERATOR
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export interface Reviver<T> {
|
export interface Reviver<T> {
|
||||||
Murder(t:T):string
|
Murder(t:T):string
|
||||||
Revive(s:string):T
|
Revive(s:string):T
|
||||||
|
@ -48,6 +45,25 @@ export const StoreColSet = {
|
||||||
Revive: (s:string):ColumnSet=>new ColumnSet(s.split(as) as any)
|
Revive: (s:string):ColumnSet=>new ColumnSet(s.split(as) as any)
|
||||||
}
|
}
|
||||||
export const StoreChars = {
|
export const StoreChars = {
|
||||||
Murder: (s:Map<string,TricksterCharacter>):string=>JSON.stringify(Object.entries(s)),
|
Murder: (s:Map<string,TricksterCharacter>):string=>{
|
||||||
|
let o = JSON.stringify(Array.from(s.entries()))
|
||||||
|
return o
|
||||||
|
},
|
||||||
Revive: (s:string):Map<string,TricksterCharacter>=>new Map(JSON.parse(s)),
|
Revive: (s:string):Map<string,TricksterCharacter>=>new Map(JSON.parse(s)),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const StoreJsonable = {
|
||||||
|
Murder: <T>(s:T):string=>JSON.stringify(Object.entries(s)),
|
||||||
|
Revive: <T>(s:string):T=>JSON.parse(s),
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Serializable<T> {
|
||||||
|
parse(s:any):T
|
||||||
|
}
|
||||||
|
|
||||||
|
export const StoreSerializable = <T extends Serializable<T>>(n:(new ()=>T))=>{
|
||||||
|
return {
|
||||||
|
Murder: (s:T):string=>JSON.stringify(s),
|
||||||
|
Revive: (s:string):T=>new n().parse(JSON.parse(s))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@ import {ColumnInfo, ColumnName, Columns, ColumnSorter, LazyColumn} from "./colum
|
||||||
import { ColumnSettings } from "handsontable/settings"
|
import { ColumnSettings } from "handsontable/settings"
|
||||||
import { PredefinedMenuItemKey } from "handsontable/plugins/contextMenu"
|
import { PredefinedMenuItemKey } from "handsontable/plugins/contextMenu"
|
||||||
import { ref } from "vue"
|
import { ref } from "vue"
|
||||||
|
import Handsontable from "handsontable"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -100,13 +101,18 @@ export class InventoryTable {
|
||||||
return this.o.columns.map(x=>{
|
return this.o.columns.map(x=>{
|
||||||
let out:any = {
|
let out:any = {
|
||||||
renderer: x.renderer ? x.renderer : "text",
|
renderer: x.renderer ? x.renderer : "text",
|
||||||
|
filters: true,
|
||||||
dropdownMenu: x.filtering ? DefaultDropdownItems() : false,
|
dropdownMenu: x.filtering ? DefaultDropdownItems() : false,
|
||||||
readOnly: x.writable ? false : true,
|
readOnly: x.writable ? false : true,
|
||||||
selectionMode: (x.writable ? "multiple" : 'single') as any,
|
selectionMode: (x.writable ? "multiple" : 'single') as any,
|
||||||
}
|
}
|
||||||
if(x.options) {
|
if(x.options) {
|
||||||
out.type = 'dropdown'
|
out.type = 'dropdown'
|
||||||
out.source = Array.from(this.o.accounts.values())
|
if(typeof x.options == "function") {
|
||||||
|
out.source = x.options(this.o.accounts)
|
||||||
|
}else {
|
||||||
|
out.source = x.options
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return out
|
return out
|
||||||
})
|
})
|
||||||
|
@ -114,6 +120,9 @@ export class InventoryTable {
|
||||||
getTableRows():any[][] {
|
getTableRows():any[][] {
|
||||||
return Object.values(this.inv.items)
|
return Object.values(this.inv.items)
|
||||||
.filter((item):boolean=>{
|
.filter((item):boolean=>{
|
||||||
|
if(item.item_count <= 0) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
let found = true
|
let found = true
|
||||||
let hasAll = this.o.tags.has("All")
|
let hasAll = this.o.tags.has("All")
|
||||||
if(this.o.tags.s.size > 0) {
|
if(this.o.tags.s.size > 0) {
|
||||||
|
@ -172,9 +181,30 @@ export const DefaultSettings = ():HotTableProps=>{
|
||||||
indicator: true,
|
indicator: true,
|
||||||
headerAction: true,
|
headerAction: true,
|
||||||
},
|
},
|
||||||
|
hiddenColumns: {
|
||||||
|
columns: [0],
|
||||||
|
},
|
||||||
|
// renderAllRows: true,
|
||||||
|
viewportColumnRenderingOffset: 3,
|
||||||
|
viewportRowRenderingOffset: 100,
|
||||||
|
// dropdownMenu: DefaultDropdownItems(),
|
||||||
|
afterGetColHeader: (col, th) => {
|
||||||
|
if(!th.innerHTML.toLowerCase().includes("name")) {
|
||||||
|
const ct = th.querySelector('.changeType')
|
||||||
|
if(ct){
|
||||||
|
ct.parentElement!.removeChild(ct)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
beforeOnCellMouseDown(event:any, coords) {
|
||||||
|
// Deselect the column after clicking on input.
|
||||||
|
if (coords.row === -1 && event.target.nodeName === 'INPUT') {
|
||||||
|
event.stopImmediatePropagation();
|
||||||
|
this.deselectCell();
|
||||||
|
}
|
||||||
|
},
|
||||||
className: 'htLeft',
|
className: 'htLeft',
|
||||||
contextMenu: false,
|
contextMenu: false,
|
||||||
dropdownMenu: false,
|
|
||||||
readOnlyCellClassName: "",
|
readOnlyCellClassName: "",
|
||||||
licenseKey:"non-commercial-and-evaluation",
|
licenseKey:"non-commercial-and-evaluation",
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,8 @@ import { defineStore, storeToRefs } from 'pinia'
|
||||||
import { getCookie, setCookie } from 'typescript-cookie'
|
import { getCookie, setCookie } from 'typescript-cookie'
|
||||||
import { useCookies } from 'vue3-cookies'
|
import { useCookies } from 'vue3-cookies'
|
||||||
import { BasicColumns, ColumnInfo, ColumnName, Columns, DetailsColumns, MoveColumns } from '../lib/columns'
|
import { BasicColumns, ColumnInfo, ColumnName, Columns, DetailsColumns, MoveColumns } from '../lib/columns'
|
||||||
import { Reviver, StoreChars, StoreColSet, StoreInvs, StoreStr, StoreStrSet } from '../lib/storage'
|
import { OrderTracker } from '../lib/lifeto/order_manager'
|
||||||
|
import { Reviver, StoreChars, StoreColSet, StoreInvs, StoreSerializable, StoreStr, StoreStrSet } from '../lib/storage'
|
||||||
import { ColumnSet } from '../lib/table'
|
import { ColumnSet } from '../lib/table'
|
||||||
import { TricksterCharacter, TricksterInventory } from '../lib/trickster'
|
import { TricksterCharacter, TricksterInventory } from '../lib/trickster'
|
||||||
import { nameCookie} from '../session_storage'
|
import { nameCookie} from '../session_storage'
|
||||||
|
@ -12,42 +13,47 @@ const _defaultColumn:(ColumnInfo| ColumnName)[] = [
|
||||||
...MoveColumns,
|
...MoveColumns,
|
||||||
...DetailsColumns,
|
...DetailsColumns,
|
||||||
]
|
]
|
||||||
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",
|
|
||||||
columns:new ColumnSet(_defaultColumn),
|
|
||||||
tags: new ColumnSet(),
|
|
||||||
dirty: 0,
|
|
||||||
}
|
|
||||||
loadStore();
|
|
||||||
return store
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
|
// if you wish for the thing to persist
|
||||||
export const StoreReviver = {
|
export const StoreReviver = {
|
||||||
chars: StoreChars,
|
chars: StoreChars,
|
||||||
accounts: StoreStrSet,
|
|
||||||
activeTable: StoreStr,
|
activeTable: StoreStr,
|
||||||
screen: StoreStr,
|
screen: StoreStr,
|
||||||
columns: StoreColSet,
|
columns: StoreColSet,
|
||||||
tags: StoreColSet,
|
tags: StoreColSet,
|
||||||
|
// orders: StoreSerializable(OrderTracker)
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface StoreProps {
|
export interface StoreProps {
|
||||||
invs: Map<string,TricksterInventory>
|
invs: Map<string,TricksterInventory>
|
||||||
chars: Map<string, TricksterCharacter>
|
chars: Map<string, TricksterCharacter>
|
||||||
accounts: Set<string>
|
orders: OrderTracker
|
||||||
activeTable: string
|
activeTable: string
|
||||||
screen: string
|
screen: string
|
||||||
columns: ColumnSet
|
columns: ColumnSet
|
||||||
tags: ColumnSet
|
tags: ColumnSet
|
||||||
dirty: number
|
dirty: number
|
||||||
|
currentSearch: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const useStore = defineStore('state', {
|
||||||
|
state: ()=> {
|
||||||
|
let store = {
|
||||||
|
invs: new Map() as Map<string,TricksterInventory>,
|
||||||
|
chars: new Map() as Map<string,TricksterCharacter>,
|
||||||
|
orders: new OrderTracker(),
|
||||||
|
activeTable: "none",
|
||||||
|
screen: "default",
|
||||||
|
columns:new ColumnSet(_defaultColumn),
|
||||||
|
tags: new ColumnSet(),
|
||||||
|
dirty: 0,
|
||||||
|
currentSearch: "",
|
||||||
|
}
|
||||||
|
return store
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
export const loadStore = ()=> {
|
export const loadStore = ()=> {
|
||||||
let store = useStoreRef()
|
let store = useStoreRef()
|
||||||
for(const [k, v] of Object.entries(StoreReviver)){
|
for(const [k, v] of Object.entries(StoreReviver)){
|
||||||
|
@ -63,7 +69,6 @@ export const saveStore = ()=> {
|
||||||
let store = useStoreRef()
|
let store = useStoreRef()
|
||||||
for(const [k, v] of Object.entries(StoreReviver)){
|
for(const [k, v] of Object.entries(StoreReviver)){
|
||||||
let coke;
|
let coke;
|
||||||
|
|
||||||
if((store[k as keyof RefStore]) != undefined){
|
if((store[k as keyof RefStore]) != undefined){
|
||||||
coke = v.Murder(store[k as keyof RefStore].value as any)
|
coke = v.Murder(store[k as keyof RefStore].value as any)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue