pinia+track
This commit is contained in:
53
package-lock.json
generated
53
package-lock.json
generated
@@ -13,6 +13,7 @@
|
||||
"@vueuse/core": "^10.2.1",
|
||||
"@vueuse/integrations": "^10.2.1",
|
||||
"axios": "^1.4.0",
|
||||
"pinia": "^2.1.6",
|
||||
"pocketbase": "^0.18.0",
|
||||
"vue": "^3.3.4",
|
||||
"vue-router": "^4.2.4"
|
||||
@@ -1719,6 +1720,56 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/pinia": {
|
||||
"version": "2.1.6",
|
||||
"resolved": "https://registry.npmjs.org/pinia/-/pinia-2.1.6.tgz",
|
||||
"integrity": "sha512-bIU6QuE5qZviMmct5XwCesXelb5VavdOWKWaB17ggk++NUwQWWbP5YnsONTk3b752QkW9sACiR81rorpeOMSvQ==",
|
||||
"dependencies": {
|
||||
"@vue/devtools-api": "^6.5.0",
|
||||
"vue-demi": ">=0.14.5"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/posva"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@vue/composition-api": "^1.4.0",
|
||||
"typescript": ">=4.4.4",
|
||||
"vue": "^2.6.14 || ^3.3.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@vue/composition-api": {
|
||||
"optional": true
|
||||
},
|
||||
"typescript": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/pinia/node_modules/vue-demi": {
|
||||
"version": "0.14.6",
|
||||
"resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.6.tgz",
|
||||
"integrity": "sha512-8QA7wrYSHKaYgUxDA5ZC24w+eHm3sYCbp0EzcDwKqN3p6HqtTCGR/GVsPyZW92unff4UlcSh++lmqDWN3ZIq4w==",
|
||||
"hasInstallScript": true,
|
||||
"bin": {
|
||||
"vue-demi-fix": "bin/vue-demi-fix.js",
|
||||
"vue-demi-switch": "bin/vue-demi-switch.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/antfu"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@vue/composition-api": "^1.0.0-rc.1",
|
||||
"vue": "^3.0.0-0 || ^2.6.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@vue/composition-api": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/pirates": {
|
||||
"version": "4.0.6",
|
||||
"resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz",
|
||||
@@ -2111,7 +2162,7 @@
|
||||
"version": "5.2.2",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz",
|
||||
"integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"bin": {
|
||||
"tsc": "bin/tsc",
|
||||
"tsserver": "bin/tsserver"
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
"@vueuse/core": "^10.2.1",
|
||||
"@vueuse/integrations": "^10.2.1",
|
||||
"axios": "^1.4.0",
|
||||
"pinia": "^2.1.6",
|
||||
"pocketbase": "^0.18.0",
|
||||
"vue": "^3.3.4",
|
||||
"vue-router": "^4.2.4"
|
||||
|
||||
@@ -37,7 +37,7 @@ useEventListener('keyup', e => {
|
||||
<div class="fixed inset-0" @click="isOpen = false">
|
||||
<div class="absolute bg-black opacity-80 inset-0 z-0" />
|
||||
<div class="absolute grid inset-0">
|
||||
<div class="justify-self-center" @click.stop>
|
||||
<div class="justify-self-center m-auto" @click.stop>
|
||||
<slot />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { providePocketBase } from '@/pocketbase';
|
||||
import { createPinia } from 'pinia';
|
||||
import { createApp } from 'vue';
|
||||
import { createRouter, createWebHistory } from 'vue-router';
|
||||
import App from './App.vue';
|
||||
@@ -7,6 +8,7 @@ import './style.css';
|
||||
|
||||
const app = createApp(App);
|
||||
const pb = providePocketBase(app);
|
||||
const pinia = createPinia();
|
||||
const router = createRouter({
|
||||
history: createWebHistory(),
|
||||
routes,
|
||||
@@ -20,6 +22,7 @@ router.beforeEach(async to => {
|
||||
}
|
||||
});
|
||||
|
||||
app.use(pinia);
|
||||
app.use(router);
|
||||
|
||||
app.mount('#app');
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
export * from './HistoryQuartils';
|
||||
export * from './scan';
|
||||
|
||||
export { default as BuyModal } from './BuyModal.vue';
|
||||
export { default as ScanResultTable } from './ScanResultTable.vue';
|
||||
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
import { MarketOrderHistory, MarketType } from "@/market";
|
||||
import { usePocketBase, watchCollection } from "@/pocketbase";
|
||||
import { defineStore } from "pinia";
|
||||
import { RecordModel } from "pocketbase";
|
||||
import { computed, onMounted, ref } from "vue";
|
||||
|
||||
export type ScanResult = {
|
||||
type: MarketType;
|
||||
@@ -7,3 +11,33 @@ export type ScanResult = {
|
||||
sell: number
|
||||
}
|
||||
|
||||
interface MarketScan extends RecordModel {
|
||||
owner: string;
|
||||
types: number[];
|
||||
};
|
||||
|
||||
const marketScans = 'marketScans';
|
||||
|
||||
export const useMarkeyScanStore = defineStore(marketScans, () => {
|
||||
const pb = usePocketBase();
|
||||
const marketScan = ref<MarketScan>();
|
||||
|
||||
const types = computed(() => marketScan.value?.types ?? []);
|
||||
const setTypes = async (types: number[]) => {
|
||||
if (marketScan.value?.id) {
|
||||
pb.collection(marketScans).update(marketScan.value.id, { owner: pb.authStore.model!.id, types });
|
||||
} else {
|
||||
pb.collection(marketScans).create({ owner: pb.authStore.model!.id, types });
|
||||
}
|
||||
}
|
||||
|
||||
watchCollection<MarketScan>(marketScans, '*', data => {
|
||||
if (data.action === 'delete') {
|
||||
marketScan.value = undefined;
|
||||
} else if (!marketScan.value || data.record.id === marketScan.value.id) {
|
||||
marketScan.value = data.record;
|
||||
}
|
||||
});
|
||||
onMounted(async () => marketScan.value = await pb.collection(marketScans).getFirstListItem<MarketScan>('').catch(() => undefined));
|
||||
return { types, setTypes };
|
||||
});
|
||||
@@ -2,16 +2,11 @@
|
||||
import Modal from '@/Modal.vue';
|
||||
import { formatIsk } from '@/formaters';
|
||||
import { MarketType } from '@/market';
|
||||
import { useTrackedItemsStorage } from '@/market/track';
|
||||
import { ref } from 'vue';
|
||||
import { useTrackedItemStore } from './track';
|
||||
|
||||
interface Emit {
|
||||
(e: 'added'): void;
|
||||
}
|
||||
|
||||
const emit = defineEmits<Emit>();
|
||||
|
||||
const itemsStorage = useTrackedItemsStorage();
|
||||
const trackedItemStore = useTrackedItemStore();
|
||||
|
||||
const modalOpen = ref<boolean>(false);
|
||||
const type = ref<MarketType>();
|
||||
@@ -43,24 +38,7 @@ const add = () => {
|
||||
return;
|
||||
}
|
||||
|
||||
const oldItem = itemsStorage.value.find(i => i.typeID === id);
|
||||
|
||||
if (oldItem) {
|
||||
const item = {
|
||||
typeID: id,
|
||||
count: count.value + oldItem.count,
|
||||
averagePrice: ((price.value * count.value) + (oldItem.averagePrice * oldItem.count)) / (count.value + oldItem.count)
|
||||
};
|
||||
itemsStorage.value = itemsStorage.value.map(i => i.typeID === id ? item : i);
|
||||
} else {
|
||||
const item = {
|
||||
typeID: id,
|
||||
count: count.value,
|
||||
averagePrice: price.value
|
||||
};
|
||||
itemsStorage.value = [ ...itemsStorage.value, item ];
|
||||
}
|
||||
emit('added');
|
||||
trackedItemStore.addTrackedItem(id, count.value, price.value);
|
||||
modalOpen.value = false;
|
||||
}
|
||||
|
||||
@@ -69,7 +47,7 @@ defineExpose({ open });
|
||||
|
||||
<template>
|
||||
<Modal v-model:open="modalOpen">
|
||||
<div class="p-4 bg-slate-800 rounded mt-20 flex">
|
||||
<div class="p-4 bg-slate-800 rounded flex">
|
||||
<div class="flex me-2">
|
||||
<span>Price: </span>
|
||||
<div class="ms-2">
|
||||
@@ -1,16 +1,11 @@
|
||||
<script setup lang="ts">
|
||||
import Modal from '@/Modal.vue';
|
||||
import { MarketType } from '@/market';
|
||||
import { useTrackedItemsStorage } from '@/market/track';
|
||||
import { ref } from 'vue';
|
||||
import { useTrackedItemStore } from './track';
|
||||
|
||||
interface Emit {
|
||||
(e: 'removed'): void;
|
||||
}
|
||||
|
||||
const emit = defineEmits<Emit>();
|
||||
|
||||
const itemsStorage = useTrackedItemsStorage();
|
||||
const trackedItemStore = useTrackedItemStore();
|
||||
|
||||
const modalOpen = ref<boolean>(false);
|
||||
const type = ref<MarketType>();
|
||||
@@ -29,26 +24,7 @@ const remove = () => {
|
||||
return;
|
||||
}
|
||||
|
||||
const oldItem = itemsStorage.value.find(i => i.typeID === id);
|
||||
|
||||
if (!oldItem) {
|
||||
modalOpen.value = false;
|
||||
return;
|
||||
}
|
||||
|
||||
const c = oldItem.count - count.value;
|
||||
|
||||
if (c > 0) {
|
||||
const item = {
|
||||
typeID: id,
|
||||
count: oldItem.count - count.value,
|
||||
averagePrice: oldItem.averagePrice
|
||||
};
|
||||
itemsStorage.value = itemsStorage.value.map(i => i.typeID === id ? item : i);
|
||||
} else {
|
||||
itemsStorage.value = itemsStorage.value.filter(i => i.typeID !== id);
|
||||
}
|
||||
emit('removed');
|
||||
trackedItemStore.removeTrackedItem(id, count.value);
|
||||
modalOpen.value = false;
|
||||
}
|
||||
|
||||
@@ -57,7 +33,7 @@ defineExpose({ open });
|
||||
|
||||
<template>
|
||||
<Modal v-model:open="modalOpen">
|
||||
<div class="p-4 bg-slate-800 rounded mt-20 flex">
|
||||
<div class="p-4 bg-slate-800 rounded flex">
|
||||
<div class="flex me-2 mb-auto">
|
||||
<span>Count: </span>
|
||||
<input class="ms-2" type="number" min="0" step="1" v-model="count" />
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
export * from './TrackedItem';
|
||||
export * from './storage';
|
||||
export * from './track';
|
||||
|
||||
export { default as BuyModal } from './BuyModal.vue';
|
||||
export { default as SellModal } from './SellModal.vue';
|
||||
export { default as TrackResultTable } from './TrackResultTable.vue';
|
||||
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
import { createSharedComposable, useLocalStorage } from '@vueuse/core';
|
||||
|
||||
export type TrackedMarketItemStorage = {
|
||||
typeID: number;
|
||||
count: number;
|
||||
averagePrice: number;
|
||||
}
|
||||
|
||||
export const useTrackedItemsStorage = createSharedComposable(() => useLocalStorage<TrackedMarketItemStorage[]>('market-track-items', []));
|
||||
58
src/market/track/track.ts
Normal file
58
src/market/track/track.ts
Normal file
@@ -0,0 +1,58 @@
|
||||
import { useCollection, usePocketBase } from "@/pocketbase";
|
||||
import { createSharedComposable, useLocalStorage } from '@vueuse/core';
|
||||
import { defineStore } from "pinia";
|
||||
import { RecordModel } from "pocketbase";
|
||||
import { computed } from "vue";
|
||||
|
||||
export type TrackedMarketItemStorage = {
|
||||
typeID: number;
|
||||
count: number;
|
||||
averagePrice: number;
|
||||
}
|
||||
|
||||
interface TrackedMarketItem extends RecordModel {
|
||||
owner: string;
|
||||
typeID: number;
|
||||
count: number;
|
||||
averagePrice: number;
|
||||
}
|
||||
|
||||
const marketTrackings = 'marketTrackings';
|
||||
|
||||
export const useTrackedItemsStorage = createSharedComposable(() => useLocalStorage<TrackedMarketItemStorage[]>('market-track-items', []));
|
||||
|
||||
export const useTrackedItemStore = defineStore(marketTrackings, () => {
|
||||
const pb = usePocketBase();
|
||||
const trackedItems = useCollection<TrackedMarketItem>(marketTrackings);
|
||||
|
||||
const items = computed(() => trackedItems);
|
||||
const addTrackedItem = async (typeID: number, count: number, averagePrice: number) => {
|
||||
const oldItem = trackedItems.value.find(i => i.typeID === typeID);
|
||||
|
||||
if (oldItem?.id) {
|
||||
pb.collection(marketTrackings).update(oldItem.id, {
|
||||
...oldItem,
|
||||
count: count + oldItem.count,
|
||||
averagePrice: ((averagePrice * count) + (oldItem.averagePrice * oldItem.count)) / (count + oldItem.count)
|
||||
});
|
||||
} else {
|
||||
pb.collection(marketTrackings).create({ owner: pb.authStore.model!.id, typeID, count, averagePrice});
|
||||
}
|
||||
};
|
||||
const removeTrackedItem = async (typeID: number, count: number) => {
|
||||
const oldItem = trackedItems.value.find(i => i.typeID === typeID);
|
||||
|
||||
if (!oldItem?.id) {
|
||||
return;
|
||||
} else if (oldItem.count > count) {
|
||||
pb.collection(marketTrackings).update(oldItem.id, {
|
||||
...oldItem,
|
||||
count: oldItem.count - count
|
||||
});
|
||||
} else {
|
||||
pb.collection(marketTrackings).delete(oldItem.id);
|
||||
}
|
||||
};
|
||||
|
||||
return { items, addTrackedItem, removeTrackedItem };
|
||||
});
|
||||
@@ -19,12 +19,12 @@ const login = async () => {
|
||||
<div class="p-4 mx-auto mt-10 grid justify-center gap-2 w-64">
|
||||
<div class="grid">
|
||||
Login:
|
||||
<input type="text" autocomplete="username" v-model="username" />
|
||||
<input type="text" name="username" v-model="username" />
|
||||
</div>
|
||||
<div class="grid">
|
||||
Password:
|
||||
<input type="password" autocomplete="password" v-model="password" />
|
||||
<input type="password" name="password" v-model="password" />
|
||||
</div>
|
||||
<button class="justify-self-end" @click="login" >Login</button>
|
||||
<button class="justify-self-end" name="login" @click="login">Login</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -1,21 +1,15 @@
|
||||
<script setup lang="ts">
|
||||
import { MarketType, MarketTypePrice, getHistory, getMarketType, getMarketTypes, getPrice, getPrices, jitaId } from "@/market";
|
||||
import { BuyModal, ScanResult, ScanResultTable } from '@/market/scan';
|
||||
import { usePocketBase } from "@/pocketbase";
|
||||
import { onMounted, ref, watch } from 'vue';
|
||||
import { ScanResult, ScanResultTable, useMarkeyScanStore } from '@/market/scan';
|
||||
import { BuyModal } from '@/market/track';
|
||||
import { ref, watch } from 'vue';
|
||||
|
||||
const pb = usePocketBase();
|
||||
|
||||
type MarketScan = {
|
||||
id?: string;
|
||||
owner: string;
|
||||
types: number[];
|
||||
}
|
||||
|
||||
const buyModal = ref<typeof BuyModal>();
|
||||
|
||||
const item = ref("");
|
||||
|
||||
const markeyScanStore = useMarkeyScanStore();
|
||||
const items = ref<ScanResult[]>([]);
|
||||
const addOrRelaod = async (type: MarketType) => {
|
||||
const typeID = type.id;
|
||||
@@ -43,35 +37,24 @@ const addItem = async () => {
|
||||
addOrRelaod(type);
|
||||
}
|
||||
|
||||
const getMarketScan = () => pb.collection('marketScans').getFirstListItem<MarketScan>("").catch(() => null);
|
||||
|
||||
watch(items, async itms => {
|
||||
const types = itms.map(i => i.type.id);
|
||||
const marketScan = await getMarketScan();
|
||||
watch(items, async itms => markeyScanStore.setTypes(itms.map(i => i.type.id)));
|
||||
watch(() => markeyScanStore.types, async t => {
|
||||
const typesToLoad = t.filter(t => !items.value.some(i => i.type.id === t));
|
||||
|
||||
if (marketScan?.id) {
|
||||
pb.collection('marketScans').update(marketScan.id, { owner: pb.authStore.model!.id, types });
|
||||
} else {
|
||||
pb.collection('marketScans').create({ owner: pb.authStore.model!.id, types });
|
||||
}
|
||||
});
|
||||
onMounted(async () => {
|
||||
const marketScan = await getMarketScan();
|
||||
|
||||
if (!marketScan || marketScan.types.length === 0) {
|
||||
if (typesToLoad.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const prices = await getPrices(await getMarketTypes(marketScan.types));
|
||||
const prices = await getPrices(await getMarketTypes(typesToLoad));
|
||||
|
||||
items.value = await Promise.all(marketScan.types.map(async i => {
|
||||
items.value = [...items.value, ...(await Promise.all(typesToLoad.map(async i => {
|
||||
const price = prices.find(p => p.type.id === i) as MarketTypePrice;
|
||||
const history = await getHistory(jitaId, i);
|
||||
|
||||
return { id: i, history, ...price };
|
||||
}));
|
||||
});
|
||||
|
||||
})))];
|
||||
}, { immediate: true });
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
@@ -1,31 +1,27 @@
|
||||
<script setup lang="ts">
|
||||
import { MarketTypePrice, getMarketTypes, getPrices } from "@/market";
|
||||
import { BuyModal } from '@/market/scan';
|
||||
import { SellModal, TrackResultTable, TrackedItem, useTrackedItemsStorage } from '@/market/track';
|
||||
import { onMounted, ref, watch } from 'vue';
|
||||
import { BuyModal, SellModal, TrackResultTable, TrackedItem, useTrackedItemStore } from '@/market/track';
|
||||
import { ref, watch } from 'vue';
|
||||
|
||||
const buyModal = ref<typeof BuyModal>();
|
||||
const sellModal = ref<typeof SellModal>();
|
||||
|
||||
const itemsStorage = useTrackedItemsStorage();
|
||||
const trackedItemStore = useTrackedItemStore();
|
||||
const items = ref<TrackedItem[]>([]);
|
||||
|
||||
const relaod = async () => {
|
||||
if (itemsStorage.value.length === 0) {
|
||||
watch(() => trackedItemStore.items.value, async itms => {
|
||||
if (itms.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const prices = await getPrices(await getMarketTypes(itemsStorage.value.map(i => i.typeID)));
|
||||
const prices = await getPrices(await getMarketTypes(itms.map(i => i.typeID)));
|
||||
|
||||
items.value = itemsStorage.value.map(i => {
|
||||
items.value = itms.map(i => {
|
||||
const price = prices.find(p => p.type.id === i.typeID) as MarketTypePrice;
|
||||
|
||||
return { ...i, ...price };
|
||||
});
|
||||
};
|
||||
|
||||
watch(items, itms => itemsStorage.value = itms.map(i => ({ typeID: i.type.id, count: i.count, averagePrice: i.averagePrice })));
|
||||
onMounted(relaod);
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
@@ -34,8 +30,8 @@ onMounted(relaod);
|
||||
<template v-if="items.length > 0">
|
||||
<hr />
|
||||
<TrackResultTable :items="items" @buy="(type, price, buy, sell) => buyModal?.open(type, { 'Price': price, 'Buy': buy, 'Sell': sell })" @sell="type => sellModal?.open(type)" />
|
||||
<BuyModal ref="buyModal" @added="relaod" />
|
||||
<SellModal ref="sellModal" @removed="relaod" />
|
||||
<BuyModal ref="buyModal" />
|
||||
<SellModal ref="sellModal" />
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
31
src/pocketbase/collection.ts
Normal file
31
src/pocketbase/collection.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
|
||||
import { usePocketBase } from "@/pocketbase";
|
||||
import { RecordModel, RecordSubscription, UnsubscribeFunc } from "pocketbase";
|
||||
import { Ref, computed, onMounted, onUnmounted, ref } from "vue";
|
||||
|
||||
export const watchCollection = <T extends RecordModel = RecordModel>(collection: string, query: string, callback: (data: RecordSubscription<T>) => void) => {
|
||||
const pb = usePocketBase();
|
||||
let unsubscribe: UnsubscribeFunc = () => Promise.resolve();
|
||||
|
||||
onMounted(async () => {
|
||||
unsubscribe = await pb.collection(collection).subscribe<T>(query, callback);
|
||||
});
|
||||
onUnmounted(() => unsubscribe());
|
||||
};
|
||||
|
||||
export const useCollection = <T extends RecordModel = RecordModel>(collection: string) => {
|
||||
const pb = usePocketBase();
|
||||
const list = ref<T[]>([]) as Ref<T[]>;
|
||||
|
||||
watchCollection<T>(collection, '*', data => {
|
||||
if (data.action === 'delete') {
|
||||
list.value = list.value.filter(i => i.id !== data.record.id);
|
||||
} else if (data.action === 'update') {
|
||||
list.value = list.value.map(i => i.id === data.record.id ? data.record : i);
|
||||
} else if (data.action === 'create') {
|
||||
list.value = [...list.value, data.record];
|
||||
}
|
||||
});
|
||||
onMounted(async () => list.value = await pb.collection(collection).getFullList<T>().catch(() => [] as T[]));
|
||||
return computed(() => list.value);
|
||||
}
|
||||
@@ -1 +1,2 @@
|
||||
export * from './collection';
|
||||
export * from './pocketbase';
|
||||
|
||||
Reference in New Issue
Block a user