mirror of
https://github.com/Kvan7/Exiled-Exchange-2.git
synced 2026-09-27 07:05:46 +00:00
start using script setup SFC
This commit is contained in:
@@ -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'
|
||||
},
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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>,
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user