start using script setup SFC

This commit is contained in:
Alexander Drozdov
2023-08-17 19:47:50 +03:00
parent b21c936068
commit 00a17e50c7
18 changed files with 454 additions and 584 deletions
+2
View File
@@ -32,6 +32,8 @@ module.exports = {
'@typescript-eslint/prefer-reduce-type-parameter': 'off',
'@typescript-eslint/no-invalid-void-type': 'off',
'@typescript-eslint/consistent-indexed-object-style': 'off',
'import/first': 'off',
'import/no-duplicates': 'off',
// TODO: refactor IPC and enable
'@typescript-eslint/consistent-type-assertions': 'off'
},
+1 -6
View File
@@ -4,13 +4,8 @@
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue'
<script setup lang="ts">
import OverlayWindow from './overlay/OverlayWindow.vue'
export default defineComponent({
components: { OverlayWindow }
})
</script>
<style lang="postcss">
+25 -31
View File
@@ -17,41 +17,35 @@
</div>
</template>
<script lang="ts">
import { defineComponent, PropType, computed } from 'vue'
<script setup lang="ts">
import { computed } from 'vue'
import { useI18n } from 'vue-i18n'
import type { ParsedItem } from '@/parser'
import * as actions from './hotkeyable-actions'
export default defineComponent({
props: {
item: {
type: Object as PropType<ParsedItem>,
required: true
}
},
setup (props) {
const { t } = useI18n()
const props = defineProps<{
item: ParsedItem
}>()
return {
t,
stashSearch () { actions.findSimilarItems(props.item) },
openWiki () { actions.openWiki(props.item) },
openPoedb () { actions.openPoedb(props.item) },
openCoE () { actions.openCoE(props.item) },
showCoE: computed(() => {
const { item } = props
return item.info.craftable && !item.isCorrupted && !item.isMirrored
}),
weaponDPS: computed(() => {
const { item } = props
if (!item.weaponAS) return undefined
const pdps = Math.round(item.weaponAS * (item.weaponPHYSICAL ?? 0))
const edps = Math.round(item.weaponAS * (item.weaponELEMENTAL ?? 0))
return { phys: pdps, elem: edps, total: pdps + edps }
}),
itemName: computed(() => props.item.info.name)
}
}
const { t } = useI18n()
function stashSearch () { actions.findSimilarItems(props.item) }
function openWiki () { actions.openWiki(props.item) }
function openPoedb () { actions.openPoedb(props.item) }
function openCoE () { actions.openCoE(props.item) }
const showCoE = computed(() => {
const { item } = props
return item.info.craftable && !item.isCorrupted && !item.isMirrored
})
const weaponDPS = computed(() => {
const { item } = props
if (!item.weaponAS) return undefined
const pdps = Math.round(item.weaponAS * (item.weaponPHYSICAL ?? 0))
const edps = Math.round(item.weaponAS * (item.weaponELEMENTAL ?? 0))
return { phys: pdps, elem: edps, total: pdps + edps }
})
const itemName = computed(() => props.item.info.name)
</script>
+52 -68
View File
@@ -1,89 +1,73 @@
<template>
<widget :config="{ ...config, anchor }" move-handles="none" :removable="false" :inline-edit="false">
<Widget :config="{ ...config, anchor }" move-handles="none" :removable="false" :inline-edit="false">
<template v-if="item">
<map-check v-if="isMapLike"
<MapCheck v-if="isMapLike"
:item="item" />
<item-info v-else
<ItemInfo v-else
:item="item" />
</template>
</widget>
</Widget>
</template>
<script lang="ts">
import { defineComponent, PropType, computed, inject, ref } from 'vue'
import Widget from '../overlay/Widget.vue'
import MapCheck from '../map-check/MapCheck.vue'
import ItemInfo from './ItemInfo.vue'
<script setup lang="ts">
import { computed, inject, ref } from 'vue'
import { MainProcess } from '@/web/background/IPC'
import { ItemCategory, parseClipboard, ParsedItem } from '@/parser'
import { registerActions } from './hotkeyable-actions'
import type { ItemCheckWidget, WidgetManager } from '../overlay/interfaces'
export default defineComponent({
components: {
Widget,
MapCheck,
ItemInfo
},
props: {
config: {
type: Object as PropType<ItemCheckWidget>,
required: true
}
},
setup (props) {
const wm = inject<WidgetManager>('wm')!
import Widget from '../overlay/Widget.vue'
import MapCheck from '../map-check/MapCheck.vue'
import ItemInfo from './ItemInfo.vue'
const checkPosition = ref({ x: 1, y: 1 })
const item = ref<ParsedItem | null>(null)
const props = defineProps<{
config: ItemCheckWidget
}>()
registerActions()
const wm = inject<WidgetManager>('wm')!
MainProcess.onEvent('MAIN->CLIENT::item-text', (e) => {
if (e.target !== 'item-check') return
const checkPosition = ref({ x: 1, y: 1 })
const item = ref<ParsedItem | null>(null)
checkPosition.value = e.position
item.value = parseClipboard(e.clipboard).unwrapOr(null)
if (item.value) {
wm.show(props.config.wmId)
}
})
registerActions()
props.config.wmWants = 'hide'
MainProcess.onEvent('MAIN->CLIENT::item-text', (e) => {
if (e.target !== 'item-check') return
const anchor = computed(() => {
const width = wm.size.value.width
const poePanelWidth = wm.poePanelWidth.value
const side = checkPosition.value.x > (window.screenX + width / 2)
? 'inventory'
: 'stash'
return {
pos: side === 'stash' ? 'cl' : 'cr',
y: 50,
x: side === 'stash'
? (poePanelWidth / width) * 100
: ((width - poePanelWidth) / width) * 100
}
})
const isMapLike = computed(() => {
if (!item.value) return false
const { category, info: { refName } } = item.value
return (
category === ItemCategory.Map ||
category === ItemCategory.HeistContract ||
category === ItemCategory.HeistBlueprint ||
category === ItemCategory.Invitation ||
refName === 'Expedition Logbook')
})
return {
anchor,
item,
isMapLike
}
checkPosition.value = e.position
item.value = parseClipboard(e.clipboard).unwrapOr(null)
if (item.value) {
wm.show(props.config.wmId)
}
})
props.config.wmWants = 'hide'
const anchor = computed(() => {
const width = wm.size.value.width
const poePanelWidth = wm.poePanelWidth.value
const side = checkPosition.value.x > (window.screenX + width / 2)
? 'inventory'
: 'stash'
return {
pos: side === 'stash' ? 'cl' : 'cr',
y: 50,
x: side === 'stash'
? (poePanelWidth / width) * 100
: ((width - poePanelWidth) / width) * 100
}
})
const isMapLike = computed(() => {
if (!item.value) return false
const { category, info: { refName } } = item.value
return (
category === ItemCategory.Map ||
category === ItemCategory.HeistContract ||
category === ItemCategory.HeistBlueprint ||
category === ItemCategory.Invitation ||
refName === 'Expedition Logbook')
})
</script>
+35 -45
View File
@@ -18,7 +18,7 @@
{{ t('map_check.no_mods') }}
</div>
<div v-else class="py-2 flex flex-col">
<map-stat-button v-for="stat in mapStats" :key="stat.matcher"
<MapStatButton v-for="stat in mapStats" :key="stat.matcher"
:stat="stat" />
<div v-for="stat of item.unknownModifiers" :key="stat.type + '/' + stat.text"
class="py-1 px-8">
@@ -29,59 +29,49 @@
</div>
</template>
<script lang="ts">
import { defineComponent, PropType, computed } from 'vue'
<script setup lang="ts">
import { computed } from 'vue'
import { useI18n } from 'vue-i18n'
import { ItemRarity, ParsedItem } from '@/parser'
import MapStatButton from './MapStatButton.vue'
import { prepareMapStats } from './prepare-map-stats'
import { STAT_BY_MATCH_STR } from '@/assets/data'
import { ItemCheckWidget } from '../overlay/interfaces'
import { AppConfig } from '@/web/Config'
export default defineComponent({
components: {
MapStatButton
},
props: {
item: {
type: Object as PropType<ParsedItem>,
required: true
}
},
setup (props) {
const { t } = useI18n()
import MapStatButton from './MapStatButton.vue'
const config = computed(() => AppConfig<ItemCheckWidget>('item-check')!.maps)
const props = defineProps<{
item: ParsedItem
}>()
const hasOutdatedTranslation = computed<boolean>(() => {
const idx = config.value.profile - 1
return config.value.selectedStats
.some(entry =>
entry.decision[idx] !== '-' &&
entry.decision[idx] !== 's' &&
STAT_BY_MATCH_STR(entry.matcher) == null)
})
const { t } = useI18n()
return {
t,
mapName: computed(() => props.item.info.name),
image: computed(() =>
(props.item.rarity === ItemRarity.Unique && props.item.isUnidentified)
? undefined
: props.item.info.map?.screenshot
),
mapStats: computed(() => prepareMapStats(props.item)),
hasOutdatedTranslation,
profiles: computed(() => {
const ROMAN_NUMERALS = ['I', 'II', 'III']
return ROMAN_NUMERALS.map((text, i) => ({
text,
active: (config.value.profile === i + 1),
select: () => { config.value.profile = i + 1 }
}))
})
}
}
const config = computed(() => AppConfig<ItemCheckWidget>('item-check')!.maps)
const hasOutdatedTranslation = computed<boolean>(() => {
const idx = config.value.profile - 1
return config.value.selectedStats
.some(entry =>
entry.decision[idx] !== '-' &&
entry.decision[idx] !== 's' &&
STAT_BY_MATCH_STR(entry.matcher) == null)
})
const mapName = computed(() => props.item.info.name)
const image = computed(() =>
(props.item.rarity === ItemRarity.Unique && props.item.isUnidentified)
? undefined
: props.item.info.map?.screenshot)
const mapStats = computed(() => prepareMapStats(props.item))
const profiles = computed(() => {
const ROMAN_NUMERALS = ['I', 'II', 'III']
return ROMAN_NUMERALS.map((text, i) => ({
text,
active: (config.value.profile === i + 1),
select: () => { config.value.profile = i + 1 }
}))
})
</script>
+55 -62
View File
@@ -5,7 +5,7 @@
:class="btnStyle?.bg ?? 'hover:bg-gray-700'">
<i class="fas text-center w-4 shrink-0"
:class="btnStyle?.icon" />
<item-modifier-text class="truncate"
<ItemModifierText class="truncate"
:text="stat.matcher" :roll="stat.roll" />
</button>
<button @click="toggleSeenStatus" class="flex leading-none items-center text-gray-600 w-8 text-center justify-center">
@@ -15,75 +15,68 @@
</template>
<script lang="ts">
import { defineComponent, PropType, computed } from 'vue'
import ItemModifierText from '../ui/ItemModifierText.vue'
import { AppConfig } from '@/web/Config'
import { PreparedStat } from './prepare-map-stats'
import { ItemCheckWidget } from '../overlay/interfaces'
const BTN_STYLES = new Map([
['d', { bg: 'bg-red-700', icon: 'fa-skull-crossbones' }],
['w', { bg: 'bg-orange-600', icon: 'fa-exclamation-triangle' }],
['g', { bg: 'bg-green-700', icon: 'fa-check' }]
])
</script>
export default defineComponent({
components: { ItemModifierText },
props: {
stat: {
type: Object as PropType<PreparedStat>,
required: true
}
<script setup lang="ts">
import { computed } from 'vue'
import { AppConfig } from '@/web/Config'
import { PreparedStat } from './prepare-map-stats'
import { ItemCheckWidget } from '../overlay/interfaces'
import ItemModifierText from '../ui/ItemModifierText.vue'
const props = defineProps<{
stat: PreparedStat
}>()
const config = computed(() => AppConfig<ItemCheckWidget>('item-check')!.maps)
const entry = computed(() => config.value.selectedStats
.find(({ matcher }) => matcher === props.stat.matcher))
const decision = computed<string>({
get () {
if (!entry.value) return '-'
return entry.value.decision[config.value.profile - 1]
},
setup (props) {
const config = computed(() => AppConfig<ItemCheckWidget>('item-check')!.maps)
const entry = computed(() => config.value.selectedStats
.find(({ matcher }) => matcher === props.stat.matcher))
const decision = computed<string>({
get () {
if (!entry.value) return '-'
return entry.value.decision[config.value.profile - 1]
},
set (value) {
if (!entry.value) {
const decision = ['-', '-', '-']
decision[config.value.profile - 1] = value
config.value.selectedStats.push({
matcher: props.stat.matcher,
decision: decision.join('')
})
} else {
const decision = entry.value.decision.split('')
decision[config.value.profile - 1] = value
entry.value.decision = decision.join('')
}
}
})
const showNewStatIcon = computed(() =>
config.value.showNewStats && decision.value === '-')
function toggleSeenStatus () {
if (config.value.showNewStats) {
decision.value = (decision.value === '-') ? 's' : '-'
}
}
return {
btnStyle: computed(() => BTN_STYLES.get(decision.value)),
showNewStatIcon,
toggleSeenStatus,
handleClick () {
switch (decision.value) {
case '-': decision.value = 'd'; break
case 's': decision.value = 'd'; break
case 'd': decision.value = 'w'; break
case 'w': decision.value = 'g'; break
case 'g': decision.value = 's'; break
}
}
set (value) {
if (!entry.value) {
const decision = ['-', '-', '-']
decision[config.value.profile - 1] = value
config.value.selectedStats.push({
matcher: props.stat.matcher,
decision: decision.join('')
})
} else {
const decision = entry.value.decision.split('')
decision[config.value.profile - 1] = value
entry.value.decision = decision.join('')
}
}
})
const showNewStatIcon = computed(() =>
config.value.showNewStats && decision.value === '-')
function toggleSeenStatus () {
if (config.value.showNewStats) {
decision.value = (decision.value === '-') ? 's' : '-'
}
}
const btnStyle = computed(() => BTN_STYLES.get(decision.value))
function handleClick () {
switch (decision.value) {
case '-': decision.value = 'd'; break
case 's': decision.value = 'd'; break
case 'd': decision.value = 'w'; break
case 'w': decision.value = 'g'; break
case 'g': decision.value = 's'; break
}
}
</script>
+8 -14
View File
@@ -13,26 +13,20 @@
</transition>
</template>
<script lang="ts">
import { defineComponent, shallowRef } from 'vue'
<script setup lang="ts">
import { shallowRef } from 'vue'
import { useI18n } from 'vue-i18n'
import { Host } from '@/web/background/IPC'
import { AppConfig } from '@/web/Config'
export default defineComponent({
setup () {
const show = shallowRef(false)
const { t } = useI18n()
Host.onEvent('MAIN->OVERLAY::overlay-attached', () => {
if (!show.value && AppConfig().showAttachNotification) {
show.value = true
setTimeout(() => { show.value = false }, 2500)
}
})
const show = shallowRef(false)
const { t } = useI18n()
return { t, show }
Host.onEvent('MAIN->OVERLAY::overlay-attached', () => {
if (!show.value && AppConfig().showAttachNotification) {
show.value = true
setTimeout(() => { show.value = false }, 2500)
}
})
</script>
+44 -52
View File
@@ -1,5 +1,5 @@
<template>
<widget :config="config" v-slot="{ isEditing, isMoving }" move-handles="top-bottom">
<Widget :config="config" v-slot="{ isEditing, isMoving }" move-handles="top-bottom">
<div class="widget-default-style p-1" style="min-width: 5rem;">
<template v-if="true">
<div v-if="!isEditing" class="text-gray-100 m-1 leading-4 text-center">{{ config.wmTitle || 'Untitled' }}</div>
@@ -8,7 +8,7 @@
:placeholder="t('widget.title')"
v-model="config.wmTitle">
</template>
<dnd-container tag="div" class="flex gap-x-1"
<DndContainer tag="div" class="flex gap-x-1"
v-model="config.images" item-key="id"
handle="[data-qa=drag-handle]" :animation="200" :force-fallback="true">
<template #item="{ element: img }">
@@ -29,67 +29,59 @@
<label class="text-gray-400 hover:bg-gray-700 py-1 px-2 rounded cursor-pointer" for="file"><i class="fas fa-file-import"></i> {{ t('choose_file') }}</label>
</div>
</template>
</dnd-container>
</DndContainer>
</div>
</widget>
</Widget>
</template>
<script lang="ts">
import { defineComponent, inject, PropType, nextTick } from 'vue'
<script setup lang="ts">
import { inject, nextTick } from 'vue'
import { useI18n } from 'vue-i18n'
import Widget from './Widget.vue'
import DndContainer from 'vuedraggable'
import { Host } from '@/web/background/IPC'
import { WidgetManager, ImageStripWidget } from './interfaces'
export default defineComponent({
components: { Widget, DndContainer },
props: {
config: {
type: Object as PropType<ImageStripWidget>,
required: true
}
},
setup (props) {
const wm = inject<WidgetManager>('wm')!
import DndContainer from 'vuedraggable'
import Widget from './Widget.vue'
if (props.config.wmFlags[0] === 'uninitialized') {
props.config.wmFlags = ['invisible-on-blur']
props.config.anchor = {
pos: 'tc',
x: (Math.random() * (60 - 40) + 40),
y: (Math.random() * (15 - 5) + 5)
}
props.config.images = [{
id: 1, url: 'syndicate.jpg'
}]
nextTick(() => {
wm.show(props.config.wmId)
})
}
const props = defineProps<{
config: ImageStripWidget
}>()
const { t } = useI18n()
const { t } = useI18n()
return {
t,
async handleFile (e: Event) {
const target = (e as InputEvent).target as HTMLInputElement
try {
const name = await Host.importFile(target.files![0])
props.config.images.push({
id: Math.max(0, ...props.config.images.map(_ => _.id)) + 1,
url: name
})
} finally {
target.value = ''
}
},
remove (id: number) {
props.config.images = props.config.images.filter(_ => _.id !== id)
}
}
const wm = inject<WidgetManager>('wm')!
if (props.config.wmFlags[0] === 'uninitialized') {
props.config.wmFlags = ['invisible-on-blur']
props.config.anchor = {
pos: 'tc',
x: (Math.random() * (60 - 40) + 40),
y: (Math.random() * (15 - 5) + 5)
}
})
props.config.images = [{
id: 1, url: 'syndicate.jpg'
}]
nextTick(() => {
wm.show(props.config.wmId)
})
}
async function handleFile (e: Event) {
const target = (e as InputEvent).target as HTMLInputElement
try {
const name = await Host.importFile(target.files![0])
props.config.images.push({
id: Math.max(0, ...props.config.images.map(_ => _.id)) + 1,
url: name
})
} finally {
target.value = ''
}
}
function remove (id: number) {
props.config.images = props.config.images.filter(_ => _.id !== id)
}
</script>
<style lang="postcss" module>
+101 -106
View File
@@ -1,16 +1,16 @@
<template>
<widget :config="config" :move-handles="['tl', 'bl']" :removable="false" :inline-edit="false">
<Widget :config="config" :move-handles="['tl', 'bl']" :removable="false" :inline-edit="false">
<div class="widget-default-style flex flex-col p-1 gap-1" style="min-width: 24rem;">
<transition-group v-if="starred.length" tag="div"
:enter-active-class="$style.starredItemEnter"
class="flex gap-x-1 py-1 pr-1 bg-gray-800 rounded">
<div v-for="item in starred" :key="item.name + item.discr"
:class="$style.starredItem">
<item-quick-price
<ItemQuickPrice
:item-img="item.icon"
:price="item.price"
currency-text
></item-quick-price>
></ItemQuickPrice>
<div class="ml-1 truncate" style="max-width: 7rem;">{{ item.name }}</div>
<div v-if="item.discr"
class="ml-1 truncate" style="max-width: 7rem;">{{ t(item.discr) }}</div>
@@ -24,7 +24,7 @@
<div class="flex gap-x-1 p-1">
<input type="text" :placeholder="t(':input')" class="rounded bg-gray-900 px-1 flex-1"
v-model="searchValue">
<button @click="clearItems" class="btn"><i class="fas fa-times" /> {{ t(':reset') }}</button>
<button @click="clearSelectedItems" class="btn"><i class="fas fa-times" /> {{ t(':reset') }}</button>
</div>
<div class="flex gap-x-2 px-2 mb-px1 py-1">
<span>{{ t(':heist_target') }}</span>
@@ -62,20 +62,15 @@
</div>
</div>
</div>
</widget>
</Widget>
</template>
<script lang="ts">
import { defineComponent, PropType, shallowRef, ref, computed, nextTick, inject } from 'vue'
import { useI18nNs } from '@/web/i18n'
import { ref } from 'vue'
import { distance } from 'fastest-levenshtein'
import { ItemSearchWidget, WidgetManager } from './interfaces'
import ItemQuickPrice from '@/web/ui/ItemQuickPrice.vue'
import Widget from './Widget.vue'
import { BaseType, ITEMS_ITERATOR, CLIENT_STRINGS as _$, ALTQ_GEM_NAMES, ITEM_BY_TRANSLATED } from '@/assets/data'
import { BaseType, ITEMS_ITERATOR, CLIENT_STRINGS as _$, ALTQ_GEM_NAMES } from '@/assets/data'
import { AppConfig } from '@/web/Config'
import { usePoeninja, CurrencyValue } from '@/web/background/Prices'
import { Host } from '@/web/background/IPC'
import { CurrencyValue } from '@/web/background/Prices'
interface SelectedItem {
name: string
@@ -176,111 +171,111 @@ function fuzzyFindHeistGem (badStr: string) {
}
return bestMatch!
}
</script>
export default defineComponent({
components: { Widget, ItemQuickPrice },
props: {
config: {
type: Object as PropType<ItemSearchWidget>,
required: true
}
},
setup (props) {
const wm = inject<WidgetManager>('wm')!
const { t } = useI18nNs('item_search')
const { findPriceByQuery, autoCurrency, queuePricesFetch } = usePoeninja()
<script setup lang="ts">
import { shallowRef, computed, nextTick, inject } from 'vue'
import { useI18nNs } from '@/web/i18n'
import { ItemSearchWidget, WidgetManager } from './interfaces'
import { ITEM_BY_TRANSLATED } from '@/assets/data'
import { usePoeninja } from '@/web/background/Prices'
import { Host } from '@/web/background/IPC'
const showTimeout = shallowRef<{ reset:() => void } | null>(null)
import ItemQuickPrice from '@/web/ui/ItemQuickPrice.vue'
import Widget from './Widget.vue'
nextTick(() => {
props.config.wmFlags = ['invisible-on-blur']
})
const props = defineProps<{
config: ItemSearchWidget
}>()
const searchValue = shallowRef('')
const { items: starred, addItem, clearItems } = useSelectedItems()
const wm = inject<WidgetManager>('wm')!
const { t } = useI18nNs('item_search')
const { findPriceByQuery, autoCurrency, queuePricesFetch } = usePoeninja()
const typeFilter = shallowRef<'gem' | 'replica'>('gem')
const showTimeout = shallowRef<{ reset:() => void } | null>(null)
Host.onEvent('MAIN->CLIENT::ocr-text', (e) => {
if (e.target !== 'heist-gems') return
nextTick(() => {
props.config.wmFlags = ['invisible-on-blur']
})
for (const para of e.paragraphs) {
const res = fuzzyFindHeistGem(para)
selectItem(
ITEM_BY_TRANSLATED('GEM', res.name)![0],
{ altQuality: res.altQuality, withTimeout: true }
)
}
})
const searchValue = shallowRef('')
const { items: starred, addItem, clearItems } = useSelectedItems()
function selectItem (item: BaseType, opts: { altQuality?: string, unique?: true, withTimeout?: true }) {
queuePricesFetch()
const typeFilter = shallowRef<'gem' | 'replica'>('gem')
let price: ReturnType<typeof findPriceByQuery>
if (opts.altQuality) {
price = findPriceByQuery({
ns: item.namespace,
name: `${opts.altQuality} ${item.refName}`,
variant: '1'
})
} else {
price = findPriceByQuery({
ns: item.namespace,
name: item.refName,
variant: item.unique!.base
})
}
const isAdded = addItem({
name: item.name,
icon: item.icon,
discr: opts.altQuality,
chaos: price?.chaos,
price: (price != null) ? autoCurrency(price.chaos) : undefined
})
if (isAdded && opts.withTimeout) {
showTimeout.value?.reset()
props.config.wmFlags = []
}
searchValue.value = ''
}
Host.onEvent('MAIN->CLIENT::ocr-text', (e) => {
if (e.target !== 'heist-gems') return
return {
t,
searchValue,
typeFilter,
results: computed(() => {
if (typeFilter.value === 'gem') {
return findItems({
search: searchValue.value,
jsonIncludes: ['GEM'],
matchFn: (item) => Boolean(
item.namespace === 'GEM' &&
item.gem!.altQuality?.length)
})
} else {
return findItems({
search: searchValue.value,
jsonIncludes: ['UNIQUE', 'Replica '],
matchFn: (item) => Boolean(
item.namespace === 'UNIQUE' &&
item.refName.startsWith('Replica '))
})
}
}),
selectItem,
clearItems () {
clearItems()
props.config.wmFlags = ['invisible-on-blur']
},
starred,
showSearch: wm.active,
showTimeout,
makeInvisible () {
props.config.wmFlags = ['invisible-on-blur']
}
}
for (const para of e.paragraphs) {
const res = fuzzyFindHeistGem(para)
selectItem(
ITEM_BY_TRANSLATED('GEM', res.name)![0],
{ altQuality: res.altQuality, withTimeout: true }
)
}
})
function selectItem (item: BaseType, opts: { altQuality?: string, unique?: true, withTimeout?: true }) {
queuePricesFetch()
let price: ReturnType<typeof findPriceByQuery>
if (opts.altQuality) {
price = findPriceByQuery({
ns: item.namespace,
name: `${opts.altQuality} ${item.refName}`,
variant: '1'
})
} else {
price = findPriceByQuery({
ns: item.namespace,
name: item.refName,
variant: item.unique!.base
})
}
const isAdded = addItem({
name: item.name,
icon: item.icon,
discr: opts.altQuality,
chaos: price?.chaos,
price: (price != null) ? autoCurrency(price.chaos) : undefined
})
if (isAdded && opts.withTimeout) {
showTimeout.value?.reset()
props.config.wmFlags = []
}
searchValue.value = ''
}
const results = computed(() => {
if (typeFilter.value === 'gem') {
return findItems({
search: searchValue.value,
jsonIncludes: ['GEM'],
matchFn: (item) => Boolean(
item.namespace === 'GEM' &&
item.gem!.altQuality?.length)
})
} else {
return findItems({
search: searchValue.value,
jsonIncludes: ['UNIQUE', 'Replica '],
matchFn: (item) => Boolean(
item.namespace === 'UNIQUE' &&
item.refName.startsWith('Replica '))
})
}
})
function clearSelectedItems () {
clearItems()
props.config.wmFlags = ['invisible-on-blur']
}
const showSearch = wm.active
function makeInvisible () {
props.config.wmFlags = ['invisible-on-blur']
}
</script>
<style lang="postcss" module>
@@ -16,25 +16,17 @@
</div>
</template>
<script lang="ts">
import { defineComponent, PropType, computed } from 'vue'
<script setup lang="ts">
import { computed } from 'vue'
export default defineComponent({
props: {
position: {
type: Object as PropType<{ x: number, y: number }>,
required: true
}
},
setup (props) {
return {
relativePos: computed(() => {
return {
top: `calc(${props.position.y - window.screenY}px - 2.5rem)`,
left: `calc(${props.position.x - window.screenX}px - 2.5rem)`
}
})
}
const props = defineProps<{
position: { x: number, y: number }
}>()
const relativePos = computed(() => {
return {
top: `calc(${props.position.y - window.screenY}px - 2.5rem)`,
left: `calc(${props.position.x - window.screenX}px - 2.5rem)`
}
})
</script>
@@ -9,7 +9,7 @@
style="width: var(--game-panel);">
</div>
<div id="price-window" class="layout-column shrink-0 text-gray-200 pointer-events-auto" style="width: 28.75rem;">
<app-titlebar @close="closePriceCheck" @click="openLeagueSelection" :title="title">
<AppTitleBar @close="closePriceCheck" @click="openLeagueSelection" :title="title">
<ui-popover v-if="stableOrbCost" trigger="click" boundary="#price-window">
<template #target>
<button><i class="fas fa-exchange-alt" /> {{ stableOrbCost }}</button>
@@ -26,7 +26,7 @@
</ui-popover>
<i v-else-if="xchgRateLoading()" class="fas fa-dna fa-spin px-2" />
<div v-else class="w-8" />
</app-titlebar>
</AppTitleBar>
<div class="grow layout-column min-h-0 bg-gray-800">
<background-info />
<check-position-circle v-if="showCheckPos"
@@ -81,6 +81,7 @@ import RelatedItems from './related-items/RelatedItems.vue'
import RateLimiterState from './trade/RateLimiterState.vue'
import UnidentifiedResolver from './unidentified-resolver/UnidentifiedResolver.vue'
import CheckPositionCircle from './CheckPositionCircle.vue'
import AppTitleBar from '@/web/ui/AppTitlebar.vue'
import ItemQuickPrice from '@/web/ui/ItemQuickPrice.vue'
import { PriceCheckWidget, WidgetManager } from '../overlay/interfaces'
@@ -88,6 +89,7 @@ type ParseError = { name: string; message: string; rawText: ParsedItem['rawText'
export default defineComponent({
components: {
AppTitleBar,
CheckedItem,
UnidentifiedResolver,
BackgroundInfo,
@@ -9,51 +9,26 @@
</button>
</template>
<script lang="ts">
import { defineComponent, PropType } from 'vue'
<script setup lang="ts">
import { useI18n } from 'vue-i18n'
export default defineComponent({
emits: [], // mutates filter
props: {
filter: {
type: Object as PropType<{ disabled: boolean }>,
required: true
},
text: {
type: String,
required: true
},
img: {
type: String,
default: undefined
},
readonly: {
type: Boolean,
default: false
},
active: {
type: Boolean,
default: undefined
},
collapse: {
type: Boolean,
default: false
}
},
setup (props) {
const { t } = useI18n()
return {
t,
toggle () {
const { filter, readonly } = props
if (!readonly) {
filter.disabled = !filter.disabled
}
}
}
const props = defineProps<{
filter: { disabled: boolean } // will be mutated directly, instead of emit
text: string
img?: string
readonly?: boolean
active?: boolean
collapse?: boolean
}>()
const { t } = useI18n()
function toggle () {
const { filter, readonly } = props
if (!readonly) {
filter.disabled = !filter.disabled
}
})
}
</script>
<style lang="postcss" module>
@@ -2,94 +2,80 @@
<div>
<div :class="$style['modinfo']">{{ modText }}</div>
<div v-for="stat of stats" :key="stat.text" class="flex items-baseline">
<item-modifier-text :text="stat.text" :roll="stat.roll" :class="{ 'line-through': !stat.contributes }" />
<ItemModifierText :text="stat.text" :roll="stat.roll" :class="{ 'line-through': !stat.contributes }" />
<div v-if="stat.contributes && stat.contribution" :class="$style['contribution']">{{ stat.contribution }}</div>
</div>
</div>
</template>
<script lang="ts">
import { computed, defineComponent, PropType } from 'vue'
<script setup lang="ts">
import { computed } from 'vue'
import { useI18n } from 'vue-i18n'
import { applyIncr } from '@/parser/advanced-mod-desc'
import { roundRoll } from './util'
import ItemModifierText from '@/web/ui/ItemModifierText.vue'
import type { StatCalculated } from '@/parser/modifiers'
import type { StatFilter } from './interfaces'
export default defineComponent({
components: { ItemModifierText },
props: {
source: {
type: Object as PropType<StatCalculated['sources'][number]>,
required: true
},
filter: {
type: Object as PropType<StatFilter>,
required: true
}
},
setup (props) {
const { t } = useI18n()
import ItemModifierText from '@/web/ui/ItemModifierText.vue'
const modText = computed(() => {
const { info } = props.source.modifier
const props = defineProps<{
source: StatCalculated['sources'][number]
filter: StatFilter
}>()
let text = t(`item.mod_${info.type}`)
if (info.name) {
text += ` "${info.name}"`
}
if (info.tier != null) {
text += ` (${t('item.mod_tier', [info.tier])})`
}
if (info.rank != null) {
text += ` (${t('item.mod_rank', [info.rank])})`
}
return text
})
const { t } = useI18n()
const stats = computed(() => {
const { stats } = props.source.modifier
const { stat: contribStat } = props.source.stat
const modText = computed(() => {
const { info } = props.source.modifier
let contribution = props.source.contributes?.value
if (contribution != null) {
const filter = props.filter.roll!
contribution *= (filter.isNegated) ? -1 : 1
contribution = roundRoll(contribution, filter.dp)
}
return stats.map((parsed) => {
if (parsed.stat.ref !== contribStat.ref) {
return {
text: parsed.translation.string,
contributes: false
}
}
parsed = applyIncr(props.source.modifier.info, parsed) ?? parsed
if (!parsed.roll) {
return {
text: parsed.translation.string,
contribution: contribution,
contributes: true
}
}
const rollValue = parsed.roll.value * (parsed.translation.negate ? -1 : 1)
return {
text: parsed.translation.string,
roll: roundRoll(rollValue, parsed.roll.dp),
contribution: contribution!,
contributes: true
}
})
})
return {
modText,
stats
}
let text = t(`item.mod_${info.type}`)
if (info.name) {
text += ` "${info.name}"`
}
if (info.tier != null) {
text += ` (${t('item.mod_tier', [info.tier])})`
}
if (info.rank != null) {
text += ` (${t('item.mod_rank', [info.rank])})`
}
return text
})
const stats = computed(() => {
const { stats } = props.source.modifier
const { stat: contribStat } = props.source.stat
let contribution = props.source.contributes?.value
if (contribution != null) {
const filter = props.filter.roll!
contribution *= (filter.isNegated) ? -1 : 1
contribution = roundRoll(contribution, filter.dp)
}
return stats.map((parsed) => {
if (parsed.stat.ref !== contribStat.ref) {
return {
text: parsed.translation.string,
contributes: false
}
}
parsed = applyIncr(props.source.modifier.info, parsed) ?? parsed
if (!parsed.roll) {
return {
text: parsed.translation.string,
contribution: contribution,
contributes: true
}
}
const rollValue = parsed.roll.value * (parsed.translation.negate ? -1 : 1)
return {
text: parsed.translation.string,
roll: roundRoll(rollValue, parsed.roll.dp),
contribution: contribution!,
contributes: true
}
})
})
</script>
+3 -1
View File
@@ -17,7 +17,7 @@
</div>
</div>
<div :class="$style.window" class="grow layout-column" :onMouseenter="hidePodium">
<app-titlebar @close="cancel" :title="t('settings.title')" />
<AppTitleBar @close="cancel" :title="t('settings.title')" />
<div class="flex grow min-h-0">
<div class="pl-2 pt-2 bg-gray-900 flex flex-col gap-1" style="min-width: 10rem;">
<template v-for="item of menuItems">
@@ -55,6 +55,7 @@ import { AppConfig, updateConfig, saveConfig, pushHostConfig, Config } from '@/w
import { APP_PATRONS } from '@/assets/data'
import { Host } from '@/web/background/IPC'
import type { Widget, WidgetManager } from '@/web/overlay/interfaces'
import AppTitleBar from '@/web/ui/AppTitlebar.vue'
import SettingsHotkeys from './hotkeys.vue'
import SettingsChat from './chat.vue'
import SettingsGeneral from './general.vue'
@@ -86,6 +87,7 @@ function quit () {
}
export default defineComponent({
components: { AppTitleBar },
props: {
config: {
type: Object as PropType<Widget>,
+4 -4
View File
@@ -13,7 +13,7 @@
<i class="w-8 py-1 bg-green-700 fas fa-check"></i>
</div>
</div>
<virtual-scroll
<VirtualScroll
class="flex-1"
style="overflow-y: scroll;"
:items="filteredStats"
@@ -25,7 +25,7 @@
:matcher="props.item"
:selected-stats="selectedStats"
:profile="profile" />
</virtual-scroll>
</VirtualScroll>
</div>
</template>
@@ -36,7 +36,7 @@ import { configProp, findWidget } from '../utils'
import type { ItemCheckWidget } from '@/web/overlay/interfaces'
import { STATS_ITERATOR, STAT_BY_MATCH_STR } from '@/assets/data'
import MapsStatEntry from './MapsStatEntry.vue'
import VirtualScroll, { VirtualScrollT } from '../../ui/VirtualScroll.vue'
import VirtualScroll from '../../ui/VirtualScroll.vue'
import type { MapStatMatcher } from './interfaces'
function statToShowOrder (stat: Omit<MapStatMatcher, 'outdated'>) {
@@ -51,7 +51,7 @@ export default defineComponent({
name: 'map_check.name',
components: {
MapsStatEntry,
VirtualScroll: VirtualScroll as VirtualScrollT<MapStatMatcher>
VirtualScroll
},
props: configProp(),
setup (props) {
+10 -14
View File
@@ -1,25 +1,21 @@
<template>
<div :class="$style.titlebar">
<slot />
<button @click="$emit('click')" class="truncate">{{ title }}</button>
<button @click.stop="$emit('close')" tabindex="-1"
<button @click="emit('click')" class="truncate">{{ title }}</button>
<button @click.stop="emit('close')" tabindex="-1"
:class="[$style.button, $style.close]" title="Close"><i class="fas fa-window-close"></i></button>
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue'
<script setup lang="ts">
defineProps<{
title?: string
}>()
export default defineComponent({
emits: ['click', 'close'],
name: 'AppTitlebar',
props: {
title: {
type: String,
default: ''
}
}
})
const emit = defineEmits<{
click: []
close: []
}>()
</script>
<style lang="postcss" module>
+25 -45
View File
@@ -10,58 +10,38 @@
</div>
</template>
<script lang="ts">
import { defineComponent, computed, ref, triggerRef, PropType } from 'vue'
<script setup lang="ts" generic="T">
import { computed, ref, triggerRef } from 'vue'
function defineGenericComponent<T> () { /* eslint-disable indent */
return defineComponent({
props: {
items: {
type: Array as PropType<T[]>,
required: true
},
itemHeight: {
type: Number,
required: true
}
},
setup (props) {
const el = ref<HTMLElement>()
const props = defineProps<{
items: T[]
itemHeight: number
}>()
const renderItems = computed(() => {
if (!el.value) return []
const el = ref<HTMLElement>()
const scrollTop = el.value.scrollTop
const count = Math.floor(el.value.offsetHeight / props.itemHeight) + 2
const startIdx = Math.floor(scrollTop / props.itemHeight)
const renderItems = computed(() => {
if (!el.value) return []
const top = (startIdx * props.itemHeight)
const scrollTop = el.value.scrollTop
const count = Math.floor(el.value.offsetHeight / props.itemHeight) + 2
const startIdx = Math.floor(scrollTop / props.itemHeight)
return props.items
.slice(startIdx, startIdx + count)
.map((item, i) =>
({
top: top + (i * props.itemHeight),
item
})
)
})
const top = (startIdx * props.itemHeight)
return {
el,
renderItems,
handleScroll () {
triggerRef(el)
},
fullHeight: computed(() => props.items.length * props.itemHeight)
}
}
return props.items
.slice(startIdx, startIdx + count)
.map((item, i) =>
({
top: top + (i * props.itemHeight),
item
})
)
})
}
export default defineGenericComponent<unknown>()
class VirtualScrollGeneric<T> {
define () { return defineGenericComponent<T>() }
const fullHeight = computed(() => props.items.length * props.itemHeight)
function handleScroll () {
triggerRef(el)
}
export type VirtualScrollT<T> = ReturnType<VirtualScrollGeneric<T>['define']>
</script>
-2
View File
@@ -1,5 +1,4 @@
import { App } from 'vue'
import AppTitlebar from './AppTitlebar.vue'
import UiRadio from './UiRadio.vue'
import UiCheckbox from './UiCheckbox.vue'
import UiToggle from './UiToggle.vue'
@@ -9,7 +8,6 @@ import FullscreenImage from './FullscreenImage.vue'
import Popover from './Popover.vue'
export default function (app: App) {
app.component(AppTitlebar.name, AppTitlebar)
app.component(UiRadio.name, UiRadio)
app.component(UiCheckbox.name, UiCheckbox)
app.component(UiToggle.name, UiToggle)