diff --git a/exiled-exchange-2.code-workspace b/exiled-exchange-2.code-workspace index 77134235..9db379c4 100644 --- a/exiled-exchange-2.code-workspace +++ b/exiled-exchange-2.code-workspace @@ -40,7 +40,9 @@ ], "cSpell.words": [ "corruptionjewel", + "Defences", "DIVCARD", + "divinable", "edps", "ilvl", "keybinds", @@ -48,6 +50,8 @@ "nonunique", "onlineleague", "pdps", + "stormcloud's", + "Synthesised", "uniquefoil", "uniquejewel", "Warstaff", diff --git a/main/src/shortcuts/HostClipboard.ts b/main/src/shortcuts/HostClipboard.ts index 7bb64aa6..6b8d275a 100644 --- a/main/src/shortcuts/HostClipboard.ts +++ b/main/src/shortcuts/HostClipboard.ts @@ -41,12 +41,7 @@ export class HostClipboard { this.pollPromise = new Promise((resolve, reject) => { const poll = () => { - let textAfter = clipboard.readText(); - - if (isUncutSkillGem(textAfter)) { - // Insert item class line at start - textAfter = "Item Class: UncutSkillGem\n" + textAfter; - } + const textAfter = clipboard.readText(); if (isPoeItem(textAfter)) { if (this.shouldRestore) { @@ -99,83 +94,66 @@ export class HostClipboard { } function isPoeItem(text: string) { - return LANGUAGE_DETECTOR.find(({ firstLine }) => text.startsWith(firstLine)); + return LANGUAGE_DETECTOR.find( + ({ firstLine, uncutSkillGemLine }) => + text.startsWith(firstLine) || text.startsWith(uncutSkillGemLine), + ); } -function isUncutSkillGem(text: string) { - const lines = text.split("\n"); - if (lines.length < 2) return false; - - if ( - lines[0].startsWith("Rarity: Currency") && - UNCUT_SKILL_GEM_DETECTOR.find(({ firstLine }) => - lines[1].startsWith(firstLine), - ) - ) { - return true; - } - - return false; -} - -const UNCUT_SKILL_GEM_DETECTOR = [ - { - lang: "en", - firstLine: "Uncut Skill Gem", - }, - { - lang: "en", - firstLine: "Uncut Spirit Gem", - }, - { - lang: "en", - firstLine: "Uncut Support Gem", - }, -]; - const LANGUAGE_DETECTOR = [ { lang: "en", firstLine: "Item Class: ", + uncutSkillGemLine: "Rarity: Currency", }, { lang: "ru", firstLine: "Класс предмета: ", + uncutSkillGemLine: "Редкость: Валюта", }, { lang: "fr", firstLine: "Classe d'objet: ", + uncutSkillGemLine: "Rarity: unsupported", }, { lang: "de", firstLine: "Gegenstandsklasse: ", + uncutSkillGemLine: "Seltenheit: Währung", }, { lang: "pt", firstLine: "Classe do Item: ", + uncutSkillGemLine: "Rarity: unsupported", }, { lang: "es", firstLine: "Clase de objeto: ", + uncutSkillGemLine: "Rareza: Objetos monetarios", }, { lang: "th", firstLine: "ชนิดไอเทม: ", + uncutSkillGemLine: "Rarity: unsupported", }, { lang: "ko", firstLine: "아이템 종류: ", + uncutSkillGemLine: "아이템 희귀도: 화폐", }, { lang: "cmn-Hant", firstLine: "物品種類: ", + uncutSkillGemLine: "稀有度: 通貨", }, { lang: "cmn-Hans", firstLine: "物品类别: ", + uncutSkillGemLine: "Rarity: unsupported", }, { lang: "ja", firstLine: "アイテムクラス: ", + uncutSkillGemLine: "レアリティ: カレンシー", }, ]; diff --git a/renderer/specs/Parser/itemTextToSections.test.ts b/renderer/specs/Parser/itemTextToSections.test.ts index faa2d994..c1b0abfc 100644 --- a/renderer/specs/Parser/itemTextToSections.test.ts +++ b/renderer/specs/Parser/itemTextToSections.test.ts @@ -1,28 +1,42 @@ // itemTextToSections.test.ts import { __testExports } from "@/parser/Parser"; -import { describe, expect, test } from "vitest"; +import { beforeEach, describe, expect, test } from "vitest"; import { setupTests } from "../vitest.setup"; +import { + MagicItem, + NormalItem, + RareItem, + RareWithImplicit, + UniqueItem, +} from "./items"; +import { loadForLang } from "@/assets/data"; describe("itemTextToSections", () => { - setupTests(); + beforeEach(async () => { + setupTests(); + await loadForLang("en"); + }); test("empty string should not throw", () => { expect(() => __testExports.itemTextToSections("")).not.toThrow(); }); test("standard item", () => { - const sections = __testExports.itemTextToSections( - `Item Class: Staves -Rarity: Magic -Chiming Staff of Havoc --------- -Requirements: -Level: 58 -Int: 133 (unmet) --------- -Item Level: 62 --------- -+5 to Level of all Chaos Spell Skills -`, - ); - expect(sections.length).toBe(4); + const sections = __testExports.itemTextToSections(RareItem.rawText); + expect(sections.length).toBe(RareItem.sectionCount); + }); + test("magic item", () => { + const sections = __testExports.itemTextToSections(MagicItem.rawText); + expect(sections.length).toBe(MagicItem.sectionCount); + }); + test("normal item", () => { + const sections = __testExports.itemTextToSections(NormalItem.rawText); + expect(sections.length).toBe(NormalItem.sectionCount); + }); + test("unique item", () => { + const sections = __testExports.itemTextToSections(UniqueItem.rawText); + expect(sections.length).toBe(UniqueItem.sectionCount); + }); + test("rare item with implicit", () => { + const sections = __testExports.itemTextToSections(RareWithImplicit.rawText); + expect(sections.length).toBe(RareWithImplicit.sectionCount); }); }); diff --git a/renderer/specs/Parser/items.ts b/renderer/specs/Parser/items.ts new file mode 100644 index 00000000..448b11f8 --- /dev/null +++ b/renderer/specs/Parser/items.ts @@ -0,0 +1,344 @@ +import { BaseType } from "@/assets/data"; +import { ItemCategory, ItemInfluence, ItemRarity, ParsedItem } from "@/parser"; +import { ParsedModifier } from "@/parser/advanced-mod-desc"; +import { StatCalculated, ModifierType } from "@/parser/modifiers"; + +class TestItem implements ParsedItem { + // #region ParsedItem + rarity?: ItemRarity | undefined; + itemLevel?: number | undefined; + armourAR?: number | undefined; + armourEV?: number | undefined; + armourES?: number | undefined; + armourBLOCK?: number | undefined; + basePercentile?: number | undefined; + weaponCRIT?: number | undefined; + weaponAS?: number | undefined; + weaponPHYSICAL?: number | undefined; + weaponELEMENTAL?: number | undefined; + weaponFIRE?: number | undefined; + weaponCOLD?: number | undefined; + weaponLIGHTNING?: number | undefined; + weaponChaos?: number | undefined; + weaponReload?: number | undefined; + mapBlighted?: "Blighted" | "Blight-ravaged" | undefined; + mapTier?: number | undefined; + gemLevel?: number | undefined; + areaLevel?: number | undefined; + talismanTier?: number | undefined; + quality?: number | undefined; + runeSockets?: + | { + type: "armour" | "weapon" | "caster"; + empty: number; + current: number; + normal: number; + } + | undefined; + + gemSockets?: { number: number; linked?: number; white: number } | undefined; + stackSize?: { value: number; max: number } | undefined; + isUnidentified: boolean = false; + isCorrupted: boolean = false; + isUnmodifiable?: boolean | undefined; + isMirrored?: boolean | undefined; + influences: ItemInfluence[] = []; + logbookAreaMods?: ParsedModifier[][] | undefined; + sentinelCharge?: number | undefined; + isSynthesised?: boolean | undefined; + isFractured?: boolean | undefined; + isVeiled?: boolean | undefined; + isFoil?: boolean | undefined; + statsByType: StatCalculated[] = []; + newMods: ParsedModifier[] = []; + unknownModifiers: Array<{ text: string; type: ModifierType }> = []; + heist?: + | { + wingsRevealed?: number; + target?: "Enchants" | "Trinkets" | "Gems" | "Replicas"; + } + | undefined; + + category?: ItemCategory | undefined; + info: BaseType = { + name: "test", + refName: "test", + namespace: "ITEM", + icon: "test", + tags: [], + }; + + rawText: string; + fromChat?: boolean | undefined; + + // #endregion + + public get affixCount() { + return ( + this.prefixCount + + this.suffixCount + + this.uniqueAffixCount + + this.implicitCount + + this.enchantCount + ); + } + + public get explicitCount() { + return this.prefixCount + this.suffixCount + this.uniqueAffixCount; + } + + prefixCount: number = 0; + suffixCount: number = 0; + implicitCount: number = 0; + enchantCount: number = 0; + uniqueAffixCount: number = 0; + rollingUniqueAffixCount: number = 0; + + sectionCount: number = 0; + + constructor(text: string) { + this.rawText = text; + } +} + +// #region NormalItem +export const NormalItem = new TestItem(`Item Class: Helmets +Rarity: Normal +Superior Divine Crown +-------- +Quality: +9% (augmented) +Armour: 174 (augmented) +Energy Shield: 60 (augmented) +-------- +Requires: Level 75, 67 (augmented) Str, 67 (augmented) Int +-------- +Item Level: 81 +`); +NormalItem.category = ItemCategory.Helmet; +NormalItem.rarity = ItemRarity.Normal; +NormalItem.quality = 9; +NormalItem.armourAR = 174; +NormalItem.armourES = 60; +NormalItem.itemLevel = 81; + +NormalItem.sectionCount = 4; +// #endregion + +// #region MagicItem +export const MagicItem = new TestItem(`Item Class: Two Hand Maces +Rarity: Magic +Crackling Temple Maul of the Brute +-------- +Physical Damage: 35-72 +Lightning Damage: 1-50 (lightning) +Critical Hit Chance: 5.00% +Attacks per Second: 1.20 +-------- +Requires: Level 28, 57 (augmented) Str +-------- +Item Level: 32 +-------- +{ Prefix Modifier "Crackling" (Tier: 7) — Damage, Elemental, Lightning, Attack } +Adds 1(1-4) to 50(46-66) Lightning Damage +{ Suffix Modifier "of the Brute" (Tier: 8) — Attribute } ++8(5-8) to Strength +`); +MagicItem.category = ItemCategory.TwoHandedMace; +MagicItem.rarity = ItemRarity.Magic; +MagicItem.weaponPHYSICAL = 53.5; +MagicItem.weaponLIGHTNING = 25.5; +MagicItem.weaponCRIT = 5; +MagicItem.weaponAS = 1.2; +MagicItem.itemLevel = 32; + +MagicItem.sectionCount = 5; +MagicItem.prefixCount = 1; +MagicItem.suffixCount = 1; +// #endregion + +// #region RareItem +export const RareItem = new TestItem(`Item Class: Bows +Rarity: Rare +Oblivion Strike +Rider Bow +-------- +Physical Damage: 36-61 +Elemental Damage: 27-36 (fire), 9-13 (cold), 5-82 (lightning) +Critical Hit Chance: 5.00% +Attacks per Second: 1.20 +-------- +Requires: Level 51, 103 (augmented) Dex +-------- +Item Level: 80 +-------- +{ Prefix Modifier "Shocking" (Tier: 4) — Damage, Elemental, Lightning, Attack } +Adds 5(1-5) to 82(62-89) Lightning Damage +{ Prefix Modifier "Scorching" (Tier: 5) — Damage, Elemental, Fire, Attack } +Adds 27(20-30) to 36(31-46) Fire Damage +{ Prefix Modifier "Icy" (Tier: 8) — Damage, Elemental, Cold, Attack } +Adds 9(6-9) to 13(10-15) Cold Damage +{ Suffix Modifier "of Radiance" (Tier: 1) — Attack } ++57(41-60) to Accuracy Rating +15% increased Light Radius +`); +RareItem.category = ItemCategory.Bow; +RareItem.rarity = ItemRarity.Rare; +RareItem.weaponPHYSICAL = 48.5; +RareItem.weaponFIRE = 31.5; +RareItem.weaponCOLD = 11; +RareItem.weaponLIGHTNING = 43.5; +RareItem.weaponELEMENTAL = + RareItem.weaponFIRE + RareItem.weaponCOLD + RareItem.weaponLIGHTNING; +RareItem.itemLevel = 80; + +RareItem.sectionCount = 5; +RareItem.prefixCount = 3; +RareItem.suffixCount = 1; +// #endregion + +// #region UniqueItem +export const UniqueItem = new TestItem(`Item Class: Foci +Rarity: Unique +The Eternal Spark +Crystal Focus +-------- +Energy Shield: 44 (augmented) +-------- +Requires: Level 26, 43 (augmented) Int +-------- +Item Level: 81 +-------- +{ Unique Modifier — Defences } +56(50-70)% increased Energy Shield +{ Unique Modifier — Mana } +40% increased Mana Regeneration Rate while stationary +{ Unique Modifier — Elemental, Lightning, Resistance } ++26(20-30)% to Lightning Resistance +{ Unique Modifier — Elemental, Lightning, Resistance } ++5% to Maximum Lightning Resistance +{ Unique Modifier — Mana } +40% increased Mana Regeneration Rate +-------- +A flash of blue, a stormcloud's kiss, +her motionless dance the pulse of bliss +`); +UniqueItem.category = ItemCategory.Focus; +UniqueItem.rarity = ItemRarity.Unique; +UniqueItem.armourES = 44; +UniqueItem.itemLevel = 81; + +UniqueItem.sectionCount = 6; +UniqueItem.uniqueAffixCount = 5; +UniqueItem.rollingUniqueAffixCount = 2; +// #endregion + +// #region RareWithImplicit +export const RareWithImplicit = new TestItem(`Item Class: Rings +Rarity: Rare +Rune Loop +Prismatic Ring +-------- +Requires: Level 45 +-------- +Item Level: 79 +-------- +{ Implicit Modifier — Elemental, Fire, Cold, Lightning, Resistance } ++8(7-10)% to all Elemental Resistances (implicit) +-------- +{ Prefix Modifier "Vaporous" (Tier: 3) — Defences } ++143(124-151) to Evasion Rating +{ Suffix Modifier "of the Wrestler" (Tier: 7) — Attribute } ++12(9-12) to Strength +{ Suffix Modifier "of Warmth" (Tier: 3) — Mana } +8(8-12)% increased Mana Regeneration Rate +5% increased Light Radius +{ Suffix Modifier "of the Penguin" (Tier: 7) — Elemental, Cold, Resistance } ++15(11-15)% to Cold Resistance +`); +RareWithImplicit.category = ItemCategory.Ring; +RareWithImplicit.rarity = ItemRarity.Rare; +RareWithImplicit.itemLevel = 79; + +RareWithImplicit.sectionCount = 5; +RareWithImplicit.implicitCount = 1; +RareWithImplicit.prefixCount = 1; +RareWithImplicit.suffixCount = 3; +// #endregion + +// #region UncutSkillGem +export const UncutSkillGem = new TestItem(`Rarity: Currency +Uncut Skill Gem +-------- +Level: 19 +-------- +Item Level: 19 +-------- +Creates a Skill Gem or Level an existing gem to level 19 +-------- +Right Click to engrave a Skill Gem. +`); +UncutSkillGem.category = ItemCategory.UncutGem; +UncutSkillGem.gemLevel = 19; +UncutSkillGem.info = { + name: "Uncut Skill Gem", + refName: "Uncut Skill Gem", + namespace: "ITEM", + icon: "test", + tags: [], + craftable: { category: ItemCategory.UncutGem }, +}; + +UncutSkillGem.sectionCount = 5; +// #endregion + +// #region UncutSpiritGem +export const UncutSpiritGem = new TestItem(`Rarity: Currency +Uncut Spirit Gem +-------- +Level: 19 +-------- +Item Level: 19 +-------- +Creates a Persistent Buff Skill Gem or Level an existing gem to Level 19 +-------- +Right Click to engrave a Persistent Buff Skill Gem. +`); +UncutSpiritGem.category = ItemCategory.UncutGem; +UncutSpiritGem.gemLevel = 19; +UncutSpiritGem.info = { + name: "Uncut Spirit Gem", + refName: "Uncut Spirit Gem", + namespace: "ITEM", + icon: "test", + tags: [], + craftable: { category: ItemCategory.UncutGem }, +}; + +UncutSpiritGem.sectionCount = 5; +// #endregion + +// #region UncutSupportGem +export const UncutSupportGem = new TestItem(`Rarity: Currency +Uncut Support Gem +-------- +Level: 3 +-------- +Item Level: 3 +-------- +Creates a Support Gem up to level 3 +-------- +Right Click to engrave a Support Gem. +`); +UncutSupportGem.category = ItemCategory.UncutGem; +UncutSupportGem.gemLevel = 3; +UncutSupportGem.info = { + name: "Uncut Spirit Gem", + refName: "Uncut Spirit Gem", + namespace: "ITEM", + icon: "test", + tags: [], + craftable: { category: ItemCategory.UncutGem }, +}; + +UncutSupportGem.sectionCount = 5; +// #endregion diff --git a/renderer/specs/Parser/uncutSkillGem.test.ts b/renderer/specs/Parser/uncutSkillGem.test.ts new file mode 100644 index 00000000..2b6d033e --- /dev/null +++ b/renderer/specs/Parser/uncutSkillGem.test.ts @@ -0,0 +1,87 @@ +import { beforeEach, describe, expect, test } from "vitest"; +import { setupTests } from "../vitest.setup"; +import { __testExports, parseClipboard } from "@/parser/Parser"; +import { + RareItem, + UncutSkillGem, + UncutSpiritGem, + UncutSupportGem, +} from "./items"; +import { loadForLang } from "@/assets/data"; +import { createFilters } from "@/web/price-check/filters/create-item-filters"; + +describe("isUncutSkillGem", () => { + beforeEach(async () => { + setupTests(); + await loadForLang("en"); + }); + test("should return true for uncut skill gem", () => { + const sections = __testExports.itemTextToSections(UncutSkillGem.rawText); + expect(__testExports.isUncutSkillGem(sections[0])).toBe(true); + }); + test("should return true for uncut spirit gem", () => { + const sections = __testExports.itemTextToSections(UncutSpiritGem.rawText); + expect(__testExports.isUncutSkillGem(sections[0])).toBe(true); + }); + test("should return true for uncut skill gem", () => { + const sections = __testExports.itemTextToSections(UncutSupportGem.rawText); + expect(__testExports.isUncutSkillGem(sections[0])).toBe(true); + }); + test("should return false for any other item", () => { + const sections = __testExports.itemTextToSections(RareItem.rawText); + expect(__testExports.isUncutSkillGem(sections[0])).toBe(false); + }); +}); + +describe("Uncut gems parse correctly", () => { + beforeEach(async () => { + setupTests(); + await loadForLang("en"); + }); + test("should parse uncut skill gem", () => { + const result = parseClipboard(UncutSkillGem.rawText).unwrapOr(null); + expect(result).not.toBeNull(); + expect(result!.category).toBe(UncutSkillGem.category); + expect(result!.gemLevel).toBe(UncutSkillGem.gemLevel); + }); + test("should parse uncut spirit gem", () => { + const result = parseClipboard(UncutSpiritGem.rawText).unwrapOr(null); + expect(result).not.toBeNull(); + expect(result!.category).toBe(UncutSpiritGem.category); + expect(result!.gemLevel).toBe(UncutSpiritGem.gemLevel); + }); + test("should parse uncut support gem", () => { + const result = parseClipboard(UncutSupportGem.rawText).unwrapOr(null); + expect(result).not.toBeNull(); + expect(result!.category).toBe(UncutSupportGem.category); + expect(result!.gemLevel).toBe(UncutSupportGem.gemLevel); + }); +}); + +describe("Create Filter for uncut gems", () => { + beforeEach(async () => { + setupTests(); + await loadForLang("en"); + }); + test.each([ + { gem: UncutSkillGem }, + { gem: UncutSpiritGem }, + { gem: UncutSupportGem }, + ])("createFilters should call createUncutGemFilters, %s", async ({ gem }) => { + const opts = { + league: "Standard", + currency: "exalt", + collapseListings: "app" as const, + activateStockFilter: true, + exact: true, + useEn: true, + autoFillEmptyRuneSockets: false as const, + currencyRatio: undefined, + }; + + const result = createFilters(gem, opts); + + expect(result.searchExact).toBeTruthy(); + expect(result.gemLevel).toBeTruthy(); + }); +}); diff --git a/renderer/src/parser/Parser.ts b/renderer/src/parser/Parser.ts index 8fc4b3e1..f8cb803e 100644 --- a/renderer/src/parser/Parser.ts +++ b/renderer/src/parser/Parser.ts @@ -378,8 +378,14 @@ function pickCorrectVariant(item: ParserState) { function parseNamePlate(section: string[]) { let line = section.shift(); + let uncutSkillGem = false; if (!line?.startsWith(_$.ITEM_CLASS)) { - return err("item.parse_error"); + // HACK: Uncut skill gems + if (line && section.unshift(line) && isUncutSkillGem(section)) { + uncutSkillGem = true; + } else { + return err("item.parse_error"); + } } line = section.shift(); @@ -439,6 +445,9 @@ function parseNamePlate(section: string[]) { item.rarity = ItemRarity.Unique; break; } + if (uncutSkillGem) { + item.category = ItemCategory.UncutGem; + } return ok(item); } @@ -560,12 +569,21 @@ function parseVaalGemName(section: string[], item: ParserState) { } function parseGem(section: string[], item: ParsedItem) { - if (item.category !== ItemCategory.Gem) { + if ( + item.category !== ItemCategory.Gem && + item.category !== ItemCategory.UncutGem + ) { return "PARSER_SKIPPED"; } - if (section[1]?.startsWith(_$.GEM_LEVEL)) { + + const gemLevelLineNumber = item.category === ItemCategory.Gem ? 1 : 0; + + if (section[gemLevelLineNumber]?.startsWith(_$.GEM_LEVEL)) { // "Level: 20 (Max)" - item.gemLevel = parseInt(section[1].slice(_$.GEM_LEVEL.length), 10); + item.gemLevel = parseInt( + section[gemLevelLineNumber].slice(_$.GEM_LEVEL.length), + 10, + ); parseQualityNested(section, item); @@ -1624,8 +1642,16 @@ export function replaceHashWithValues(template: string, values: number[]) { return result; } +function isUncutSkillGem(section: string[]): boolean { + if (section.length !== 2) return false; + const translated = _$.RARITY + _$.RARITY_CURRENCY; + return section[0] === translated && section[1] !== undefined; +} + // Disable since this is export for tests // eslint-disable-next-line @typescript-eslint/naming-convention export const __testExports = { itemTextToSections, + parseNamePlate, + isUncutSkillGem, }; diff --git a/renderer/src/parser/meta.ts b/renderer/src/parser/meta.ts index ee794a08..0ed4e1ee 100644 --- a/renderer/src/parser/meta.ts +++ b/renderer/src/parser/meta.ts @@ -51,6 +51,7 @@ export enum ItemCategory { SkillGem = "Skill Gem", SupportGem = "Support Gem", MetaGem = "Meta Gem", + UncutGem = "UncutSkillGem", Focus = "Focus", Waystone = "Waystone", Relic = "Relic", diff --git a/renderer/src/web/price-check/filters/create-item-filters.ts b/renderer/src/web/price-check/filters/create-item-filters.ts index 4fc6c90e..6d7ec895 100644 --- a/renderer/src/web/price-check/filters/create-item-filters.ts +++ b/renderer/src/web/price-check/filters/create-item-filters.ts @@ -46,6 +46,9 @@ export function createFilters( if (item.category === ItemCategory.Gem) { return createGemFilters(item, filters, opts); } + if (item.category === ItemCategory.UncutGem) { + return createUncutGemFilters(item, filters, opts); + } if (item.category === ItemCategory.CapturedBeast) { filters.searchExact = { baseType: item.info.name, @@ -536,6 +539,30 @@ function createGemFilters( return filters; } +export function createUncutGemFilters( + item: ParsedItem, + filters: ItemFilters, + opts: CreateOptions, +) { + const normalGem = ITEM_BY_REF("ITEM", item.info.refName)![0]; + filters.searchExact = { + baseType: item.info.name, + baseTypeTrade: t(opts, normalGem), + }; + const range = + item.gemLevel! < 18 && item.info.refName !== "Uncut Support Gem" ? 1 : 0; + + filters.gemLevel = { + value: item.gemLevel! - range, + disabled: false, + }; + if (range) { + filters.gemLevel.max = item.gemLevel! + range; + } + + return filters; +} + function t(opts: CreateOptions, info: BaseType) { return opts.useEn ? info.refName : info.name; } diff --git a/renderer/src/web/price-check/trade/TradeItem.vue b/renderer/src/web/price-check/trade/TradeItem.vue index d71977f4..3e969083 100644 --- a/renderer/src/web/price-check/trade/TradeItem.vue +++ b/renderer/src/web/price-check/trade/TradeItem.vue @@ -24,14 +24,23 @@