search item
This commit is contained in:
@@ -20,5 +20,15 @@ export const getMarketTypes = async (types: (string | number)[]): Promise<Market
|
||||
} else if (types.length === 1 && typeof types[0] === "number") {
|
||||
return [(await apiAxiosInstance.get<MarketType>(`/sde/types/${types[0]}/`)).data];
|
||||
}
|
||||
return (await apiAxiosInstance.post<MarketType[]>("/sde/types/search", types.map(t => [typeof t === "number" ? 'id' : "name", t]))).data;
|
||||
return (await apiAxiosInstance.post<MarketType[]>("/sde/types/search", types.map(t => {
|
||||
if (typeof t === "number") {
|
||||
return { id: t };
|
||||
} else {
|
||||
return { name: t };
|
||||
}
|
||||
}))).data;
|
||||
}
|
||||
|
||||
export const searchMarketTypes = async (search: string): Promise<MarketType[]> => {
|
||||
return (await apiAxiosInstance.post<MarketType[]>("/sde/types/search", [{ name_i: search }])).data;
|
||||
}
|
||||
102
src/market/type/MarketTypeInput.vue
Normal file
102
src/market/type/MarketTypeInput.vue
Normal file
@@ -0,0 +1,102 @@
|
||||
<script setup lang="ts">
|
||||
import { useFocus, useVirtualList, useVModel } from '@vueuse/core';
|
||||
import { nextTick, ref, watch, watchEffect } from 'vue';
|
||||
import { MarketType, searchMarketTypes } from './MarketType';
|
||||
import MarketTypeLabel from "./MarketTypeLabel.vue";
|
||||
|
||||
|
||||
interface Props {
|
||||
modelValue?: MarketType;
|
||||
}
|
||||
interface Emits {
|
||||
(e: 'update:modelValue', value?: MarketType): void;
|
||||
(e: 'submit'): void;
|
||||
}
|
||||
|
||||
const props = defineProps<Props>();
|
||||
const emit = defineEmits<Emits>();
|
||||
|
||||
const value = useVModel(props, 'modelValue', emit);
|
||||
|
||||
const input = ref<HTMLInputElement>();
|
||||
const {focused} = useFocus(input);
|
||||
|
||||
const name = ref('');
|
||||
const suggestions = ref<MarketType[]>([]);
|
||||
const currentIndex = ref(-1);
|
||||
const {list, scrollTo, containerProps, wrapperProps} = useVirtualList(suggestions, {
|
||||
itemHeight: 24,
|
||||
overscan: 3
|
||||
});
|
||||
|
||||
const moveDown = () => {
|
||||
if (currentIndex.value < 0 || currentIndex.value >= suggestions.value.length - 1) {
|
||||
currentIndex.value = 0;
|
||||
} else if (currentIndex.value < suggestions.value.length - 1) {
|
||||
currentIndex.value++;
|
||||
}
|
||||
scrollTo(currentIndex.value);
|
||||
}
|
||||
const moveUp = () => {
|
||||
if (currentIndex.value <= 0) {
|
||||
currentIndex.value = suggestions.value.length - 1;
|
||||
} else if (currentIndex.value > 0) {
|
||||
currentIndex.value--;
|
||||
}
|
||||
scrollTo(currentIndex.value);
|
||||
}
|
||||
const select = (type: MarketType) => {
|
||||
value.value = type;
|
||||
currentIndex.value = -1;
|
||||
suggestions.value = [];
|
||||
}
|
||||
const submit = async () => {
|
||||
if (currentIndex.value >= 0 && currentIndex.value < suggestions.value.length) {
|
||||
const v = suggestions.value[currentIndex.value];
|
||||
|
||||
value.value = v;
|
||||
await nextTick();
|
||||
} else if (props.modelValue === undefined && suggestions.value.length > 0) {
|
||||
value.value = suggestions.value[0];
|
||||
await nextTick();
|
||||
}
|
||||
|
||||
if (value.value === undefined) {
|
||||
return;
|
||||
}
|
||||
emit('submit');
|
||||
}
|
||||
|
||||
watch(() => props.modelValue, async v => {
|
||||
if (v === undefined) {
|
||||
name.value = '';
|
||||
} else {
|
||||
name.value = v.name;
|
||||
}
|
||||
})
|
||||
watchEffect(async () => {
|
||||
const search = name.value.split('\t')[0];
|
||||
|
||||
if (!focused.value || search.length < 3) {
|
||||
suggestions.value = [];
|
||||
} else {
|
||||
suggestions.value = await searchMarketTypes(search);
|
||||
}
|
||||
currentIndex.value = -1;
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<input ref="input" type="text" class="w-96" v-model="name" @keyup.enter="submit" @keyup.down="moveDown" @keyup.up="moveUp" />
|
||||
<div v-if="suggestions.length > 1" class="z-10 absolute w-96">
|
||||
<div v-bind="containerProps" style="height: 300px">
|
||||
<div v-bind="wrapperProps">
|
||||
<div v-for="s in list" :key="s.index" class="hover:bg-slate-700" :class="{'bg-slate-500': s.index !== currentIndex, 'bg-emerald-500': s.index === currentIndex}" @click="$emit('update:modelValue', s.data)">
|
||||
<MarketTypeLabel :id="s.data.id" :name="s.data.name" class="whitespace-nowrap overflow-hidden cursor-pointer" hideCopy @click="select(s.data)" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -6,11 +6,13 @@ import { ClipboardIcon } from '@heroicons/vue/24/outline';
|
||||
interface Props {
|
||||
name?: string;
|
||||
id?: number;
|
||||
hideCopy?: boolean;
|
||||
}
|
||||
|
||||
withDefaults(defineProps<Props>(), {
|
||||
name: "",
|
||||
id: 0
|
||||
id: 0,
|
||||
hideCopy: false
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -19,7 +21,7 @@ withDefaults(defineProps<Props>(), {
|
||||
<img v-if="id" :src="`https://images.evetech.net/types/${id}/icon`" class="inline-block w-5 h-5 me-1" />
|
||||
<template v-if="name">
|
||||
{{ name }}
|
||||
<button class="btn-icon-stroke" @click="copyToClipboard(name)"><ClipboardIcon class="relative top-0.5 !w-4 !h-4" /></button>
|
||||
<button v-if="!hideCopy" class="btn-icon-stroke" @click="copyToClipboard(name)"><ClipboardIcon class="relative top-0.5 !w-4 !h-4" /></button>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
@@ -1,3 +1,4 @@
|
||||
export * from './MarketType';
|
||||
export { default as MarketTypeLabel } from './MarketTypeLabel.vue';
|
||||
export { default as MarketTypeInput } from './MarketTypeInput.vue';
|
||||
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
<script setup lang="ts">
|
||||
import { MarketType, MarketTypePrice, getHistory, getMarketType, getMarketTypes, jitaId, useApraisalStore } from "@/market";
|
||||
import { MarketType, MarketTypePrice, getHistory, getMarketTypes, jitaId, useApraisalStore } from "@/market";
|
||||
import { ScanResult, ScanResultTable, useMarketScanStore } from '@/market/scan';
|
||||
import { BuyModal } from '@/market/track';
|
||||
import MarketTypeInput from "@/market/type/MarketTypeInput.vue";
|
||||
import { ref, watch } from 'vue';
|
||||
|
||||
|
||||
const buyModal = ref<typeof BuyModal>();
|
||||
|
||||
const item = ref("");
|
||||
const item = ref<MarketType>();
|
||||
|
||||
const apraisalStore = useApraisalStore();
|
||||
const markeyScanStore = useMarketScanStore();
|
||||
@@ -18,7 +19,7 @@ const addOrRelaod = async (type: MarketType) => {
|
||||
getHistory(jitaId, typeID),
|
||||
apraisalStore.getPrice(type)
|
||||
]);
|
||||
const item = {
|
||||
const itm = {
|
||||
type,
|
||||
history,
|
||||
buy: price.buy,
|
||||
@@ -27,20 +28,19 @@ const addOrRelaod = async (type: MarketType) => {
|
||||
};
|
||||
|
||||
if (items.value.some(i => i.type.id === typeID)) {
|
||||
items.value = items.value.map(i => i.type.id === typeID ? item : i);
|
||||
items.value = items.value.map(i => i.type.id === typeID ? itm : i);
|
||||
} else {
|
||||
items.value = [ ...items.value, item];
|
||||
items.value = [ ...items.value, itm];
|
||||
}
|
||||
}
|
||||
const addItem = async () => {
|
||||
const type = await getMarketType(item.value.split('\t')[0]);
|
||||
|
||||
item.value = "";
|
||||
if (!type) {
|
||||
// TODO: Show error
|
||||
if (!item.value) {
|
||||
// TODO error
|
||||
return;
|
||||
}
|
||||
addOrRelaod(type);
|
||||
|
||||
addOrRelaod(item.value);
|
||||
item.value = undefined;
|
||||
}
|
||||
const removeItem = (type: MarketType) => {
|
||||
items.value = items.value.filter(i => i.type.id !== type.id);
|
||||
@@ -68,9 +68,9 @@ watch(() => markeyScanStore.types, async t => {
|
||||
|
||||
<template>
|
||||
<div class="grid mb-2 mt-4">
|
||||
<div class="w-auto">
|
||||
<div class="w-auto flex">
|
||||
<span>Item: </span>
|
||||
<input type="text" class="w-96" v-model="item" @keyup.enter="addItem" />
|
||||
<MarketTypeInput class="ms-2" v-model="item" @submit="addItem"/>
|
||||
<button class="justify-self-end ms-2" @click="addItem">Add</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user