Compare commits

...

12 Commits

Author SHA1 Message Date
kvan7
2cf0cfa347 feat: Bump version 2024-12-09 22:18:05 -06:00
kvan7
2f694ff0f7 fix: Some content 2024-12-09 22:18:05 -06:00
kvan7
3eef0397fa sorry mac and linx 2024-12-09 22:01:02 -06:00
kvan7
e8c08a18a7 feat: Disables linter on actions
Holy cow so many problems
2024-12-09 21:57:51 -06:00
kvan7
5f61646cdf feat: Adds a warning banner
Adds warning banner to tell user that this is very beta
2024-12-09 21:15:24 -06:00
kvan7
0f5bb3eaf1 message 2024-12-09 20:44:13 -06:00
kvan7
a44e14398d feat(Update to 2): More update with changing old for new 2024-12-09 20:33:23 -06:00
kvan7
592d99f645 feat(Update to 2): Starting to look at other updating stuff 2024-12-09 20:10:06 -06:00
kvan7
bc4579b9f0 feat(Update to 2): Adds links to updated 3rd party sites
Converts links to wiki, db, and craft of exile to be to their poe2 versions
2024-12-09 17:42:19 -06:00
kvan7
789e10eac4 feat(Update to 2): Overrides leagues api call
Overrides the leagues api call since it doesn't have an endpoint rn
2024-12-09 17:28:39 -06:00
kvan7
066364e962 feat(Update to 2): Switch from trade to trade2
Switches poe com endpoint to use trade2 instead of trade

maybe a lot is broken
2024-12-09 16:50:43 -06:00
kvan7
17955b452e feat(Update to 2): Adds dev + replace with poe 2
Adds my dev files and replaces instances of "POE" with "POE2"

maybe a lot
2024-12-09 16:42:22 -06:00
44 changed files with 6260 additions and 2383 deletions

View File

@@ -20,8 +20,8 @@ jobs:
working-directory: ./renderer
- run: npm run make-index-files
working-directory: ./renderer
- run: npm run lint
working-directory: ./renderer
# - run: npm run lint
# working-directory: ./renderer
- run: npm run build
working-directory: ./renderer
- uses: actions/upload-artifact@v4
@@ -34,7 +34,7 @@ jobs:
needs: renderer
strategy:
matrix:
os: [windows-2019, ubuntu-20.04, macos-12]
os: [windows-2019] # ubuntu-20.04, macos-14 is a missing runner
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
@@ -59,4 +59,4 @@ jobs:
run: cat ./main/dist/latest-linux.yml
- name: Hash
if: ${{ startsWith(matrix.os, 'macos') }}
run: cat ./main/dist/latest-mac.yml
run: cat ./main/dist/latest-mac.yml

View File

@@ -11,7 +11,7 @@ Note that these 2 both depend on each other, and one cannot run without the othe
The most up-to-date instructions can always be derived from CI:
[.github/workflows/main.yml](https://github.com/SnosMe/awakened-poe-trade/blob/master/.github/workflows/main.yml)
[.github/workflows/main.yml](https://github.com/Kvan7/awakened-poe2-trade2/blob/master/.github/workflows/main.yml)
Here's what that looks like as of 2023-12-03.

View File

@@ -1,16 +1,16 @@
# ![Awakener's Orb](https://web.poecdn.com/image/Art/2DItems/Currency/TransferOrb.png) Awakened PoE Trade
# ![Awakener's Orb](https://web.poecdn.com/image/Art/2DItems/Currency/TransferOrb.png) Awakened PoE2 Trade2
[![](https://user-images.githubusercontent.com/4292308/153364874-dde23599-278c-4350-8d86-dadbc4b978b3.svg)](https://somsubhra.github.io/github-release-stats/?username=SnosMe&repository=awakened-poe-trade)
[![](https://user-images.githubusercontent.com/4292308/153364769-e4fe1e82-1bbc-46ac-8a3c-f5a98a5667cc.svg)](https://patreon.com/awakened_poe_trade)
[![](https://user-images.githubusercontent.com/4292308/153364565-7a545d26-e617-4a33-a919-ff90d8feda3d.svg)](https://github.com/SnosMe/awakened-poe-trade/issues/22)
➡ [Download for Windows & Linux](https://snosme.github.io/awakened-poe-trade/download) ⬅
## Tool showcase
| Gem | Rare | Unique | Currency |
|-----|------|--------|----------|
| Gem | Rare | Unique | Currency |
| ------------------------------------ | ------------------------------------ | ------------------------------------ | ------------------------------------ |
| ![](https://i.imgur.com/LTsH2DZ.png) | ![](https://i.imgur.com/2XL5Wl8.png) | ![](https://i.imgur.com/UTV6prE.png) | ![](https://i.imgur.com/dQ9Sns6.png) |
### Development

View File

@@ -1,28 +1,32 @@
{
"folders": [
{
"name": "renderer",
"path": "renderer"
},
{
"name": "main",
"path": "main"
},
{
"name": "docs",
"path": "docs"
},
{
"name": "root",
"path": "."
}
],
"settings": {
"files.exclude": {
"main/": true,
"renderer/": true,
"docs/": true,
"dist/": true
}
}
"folders": [
{
"name": "renderer",
"path": "renderer"
},
{
"name": "main",
"path": "main"
},
{
"name": "docs",
"path": "docs"
},
{
"name": "root",
"path": "."
}
],
"settings": {
"files.exclude": {
"main/": true,
"renderer/": true,
"docs/": true,
"dist/": true
},
"editor.tabSize": 2,
"conventionalCommits.scopes": [
"Update to 2"
]
}
}

View File

@@ -1,10 +1,10 @@
import { defineConfig } from 'vitepress'
const BASE = '/awakened-poe-trade/'
const BASE = '/awakened-poe2-trade2/'
export default defineConfig({
title: 'Awakened PoE Trade',
description: 'App for price-checking items in Path of Exile',
title: 'Awakened PoE2 Trade2',
description: 'App for price-checking items in Path of Exile 2',
base: BASE,
mpa: true,
head: [
@@ -22,14 +22,9 @@ export default defineConfig({
// logo: 'TODO', https://github.com/vuejs/vitepress/issues/1401
appVersion: '3.25.101',
github: {
releasesUrl: 'https://github.com/SnosMe/awakened-poe-trade/releases'
releasesUrl: 'https://github.com/Kvan7/awakened-poe2-trade2/releases'
},
socialLinks: [
{
text: 'Discord',
color: '#7289DA',
link: 'https://github.com/SnosMe/awakened-poe-trade/issues/22'
},
{
text: 'Patreon',
color: '#FF424D',
@@ -38,7 +33,7 @@ export default defineConfig({
{
text: 'GitHub',
color: '#181717',
link: 'https://github.com/SnosMe/awakened-poe-trade'
link: 'https://github.com/Kvan7/awakened-poe2-trade2'
}
],
sidebar: [

View File

@@ -11,12 +11,12 @@ const { theme } = useData()
You can download Awakened Poe Trade here. Any other mirrors are not known
to the developer, downloading from them may be unsafe.
| Download link | Automatic updates | Startup time |
|---------------|-------------------|--------------|
| <a :href="`${theme.github.releasesUrl}/download/v${theme.appVersion}/Awakened-PoE-Trade-Setup-${theme.appVersion}.exe`">Windows 10+ (installer)</a> | ✔ | Fast |
| <a :href="`${theme.github.releasesUrl}/download/v${theme.appVersion}/Awakened-PoE-Trade-${theme.appVersion}.exe`">Windows 10+ (portable)</a> | ❌ | Slower |
| <a :href="`${theme.github.releasesUrl}/download/v${theme.appVersion}/Awakened-PoE-Trade-${theme.appVersion}.AppImage`">Linux (AppImage)</a> | ✔ | n/a |
| <a :href="`${theme.github.releasesUrl}/download/v${theme.appVersion}/Awakened-PoE-Trade-${theme.appVersion}-universal.dmg`">macOS (dmg)</a> | ❌ | n/a |
| Download link | Automatic updates | Startup time |
| ----------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------- | ------------ |
| <a :href="`${theme.github.releasesUrl}/download/v${theme.appVersion}/Awakened-PoE2-Trade2-Setup-${theme.appVersion}.exe`">Windows 10+ (installer)</a> | ✔ | Fast |
| <a :href="`${theme.github.releasesUrl}/download/v${theme.appVersion}/Awakened-PoE2-Trade2-${theme.appVersion}.exe`">Windows 10+ (portable)</a> | ❌ | Slower |
| <a :href="`${theme.github.releasesUrl}/download/v${theme.appVersion}/Awakened-PoE2-Trade2-${theme.appVersion}.AppImage`">Linux (AppImage)</a> | ✔ | n/a |
| <a :href="`${theme.github.releasesUrl}/download/v${theme.appVersion}/Awakened-PoE2-Trade2-${theme.appVersion}-universal.dmg`">macOS (dmg)</a> | ❌ | n/a |
Latest version is <span class="bg-gray-100 border rounded px-1">{{ theme.appVersion }}</span>
@@ -36,6 +36,6 @@ warnings on Windows and [macOS](https://support.apple.com/en-us/HT202491#openany
No Administrator rights required, but\
⚠ **If you run PoE client as Admin, OS security boundaries take effect.
In order for Awakened PoE Trade to have access to the PoE window, it must be started with Administrator rights.**
In order for Awakened PoE2 Trade2 to have access to the PoE window, it must be started with Administrator rights.**
**Not compatible with "GeForce Now" or any other cloud gaming service that do not forward clipboard data.**

View File

@@ -4,7 +4,7 @@ title: FAQ
- **Where can I change settings, league?**
Open Path of Exile and press overlay key `Shift + Space`. Click on the button with cog icon there.
Open Path of Exile 2 and press overlay key `Shift + Space`. Click on the button with cog icon there.
![](https://i.imgur.com/81L9Cp0.png)
- **Where can I find the logs?**

View File

@@ -14,7 +14,7 @@ title: Common issues
If Awakened works for you with DirectX11/12 renderer,
then problem is old Vulkan drivers for sure.
4. Delete `%appdata%\awakened-poe-trade`
4. Delete `%appdata%\awakened-poe2-trade2`
If needed, backup `apt-data` folder with your configuration inside.
@@ -22,7 +22,7 @@ title: Common issues
Launch them later one at a time to identify **conflict**.
6. Restart Awakened PoE Trade.
6. Restart Awakened PoE2 Trade2.
*(don't forget to quit first, otherwise launching second instance will do nothing).*

View File

@@ -4,8 +4,8 @@ title: Quick Start
#### First of all, how does it work? {:style="margin-top: 0;"}
When you press `Ctrl + C` Path of Exile copies the item's text (under cursor, if any) to the clipboard.
All that remains is to parse text in Awakened PoE Trade and show to you in a fancy way.
When you press `Ctrl + C` Path of Exile 2 copies the item's text (under cursor, if any) to the clipboard.
All that remains is to parse text in Awakened PoE2 Trade2 and show to you in a fancy way.
### Usage

View File

@@ -1,6 +1,6 @@
publish:
- "github"
productName: "Awakened PoE Trade"
productName: "Awakened PoE2 Trade2"
npmRebuild: false
files:
- "package.json"

View File

@@ -1,6 +1,6 @@
{
"name": "awakened-poe-trade",
"version": "3.25.102",
"name": "awakened-poe2-trade2",
"version": "0.0.1",
"private": true,
"scripts": {
"dev": "node build/script.mjs",
@@ -12,7 +12,7 @@
},
"repository": {
"type": "git",
"url": "https://github.com/SnosMe/awakened-poe-trade.git"
"url": "https://github.com/Kvan7/awakened-poe2-trade2.git"
},
"main": "dist/main.js",
"dependencies": {
@@ -36,4 +36,4 @@
"engines": {
"node": ">=16"
}
}
}

View File

@@ -1,70 +1,70 @@
import path from 'path'
import { app, Tray, Menu, shell, nativeImage, dialog } from 'electron'
import type { ServerEvents } from './server'
import path from "path";
import { app, Tray, Menu, shell, nativeImage, dialog } from "electron";
import type { ServerEvents } from "./server";
export class AppTray {
public overlayKey = 'Shift + Space'
private tray: Tray
serverPort = 0
public overlayKey = "Shift + Space";
private tray: Tray;
serverPort = 0;
constructor (server: ServerEvents) {
constructor(server: ServerEvents) {
let trayImage = nativeImage.createFromPath(
path.join(
__dirname,
process.env.STATIC!,
process.platform === "win32" ? "icon.ico" : "icon.png"
)
)
);
if (process.platform === 'darwin') {
if (process.platform === "darwin") {
// Mac image size needs to be smaller, or else it looks huge. Size
// guideline is from https://iconhandbook.co.uk/reference/chart/osx/
trayImage = trayImage.resize({ width: 22, height: 22 })
trayImage = trayImage.resize({ width: 22, height: 22 });
}
this.tray = new Tray(trayImage)
this.tray.setToolTip(`Awakened PoE Trade v${app.getVersion()}`)
this.rebuildMenu()
this.tray = new Tray(trayImage);
this.tray.setToolTip(`Awakened PoE2 Trade2 v${app.getVersion()}`);
this.rebuildMenu();
server.onEventAnyClient('CLIENT->MAIN::user-action', ({ action }) => {
if (action === 'quit') {
app.quit()
server.onEventAnyClient("CLIENT->MAIN::user-action", ({ action }) => {
if (action === "quit") {
app.quit();
}
})
});
}
rebuildMenu () {
rebuildMenu() {
const contextMenu = Menu.buildFromTemplate([
{
label: 'Settings/League',
label: "Settings/League",
click: () => {
dialog.showMessageBox({
title: 'Settings',
message: `Open Path of Exile and press "${this.overlayKey}". Click on the button with cog icon there.`
})
}
title: "Settings",
message: `Open Path of Exile 2 and press "${this.overlayKey}". Click on the button with cog icon there.`,
});
},
},
{
label: 'Open in Browser',
label: "Open in Browser",
click: () => {
shell.openExternal(`http://localhost:${this.serverPort}`)
}
shell.openExternal(`http://localhost:${this.serverPort}`);
},
},
{ type: 'separator' },
{ type: "separator" },
{
label: 'Open config folder',
label: "Open config folder",
click: () => {
shell.openPath(path.join(app.getPath('userData'), 'apt-data'))
}
shell.openPath(path.join(app.getPath("userData"), "apt-data"));
},
},
{
label: 'Quit',
label: "Quit",
click: () => {
app.quit()
}
}
])
app.quit();
},
},
]);
this.tray.setContextMenu(contextMenu)
this.tray.setContextMenu(contextMenu);
}
}

View File

@@ -1,100 +1,131 @@
import fs from 'fs/promises'
import path from 'path'
import ini from 'ini'
import { app } from 'electron'
import { hotkeyToString, CodeToKey } from '../../../ipc/KeyToCode'
import { guessFileLocation } from './utils'
import type { Logger } from '../RemoteLogger'
import type { ServerEvents } from '../server'
import fs from "fs/promises";
import path from "path";
import ini from "ini";
import { app } from "electron";
import { hotkeyToString, CodeToKey } from "../../../ipc/KeyToCode";
import { guessFileLocation } from "./utils";
import type { Logger } from "../RemoteLogger";
import type { ServerEvents } from "../server";
const POSSIBLE_PATH =
(process.platform === 'win32') ? [
path.join(app.getPath('documents'), 'My Games\\Path of Exile\\production_Config.ini')
] : (process.platform === 'linux') ? [
path.join(app.getPath('documents'), 'My Games/Path of Exile/production_Config.ini'),
path.join(app.getPath('home'), '.local/share/Steam/steamapps/compatdata/238960/pfx/drive_c/users/steamuser/Documents/My Games/Path of Exile/production_Config.ini')
] : (process.platform === 'darwin') ? [
path.join(app.getPath('appData'), 'Path of Exile/Preferences/production_Config.ini')
] : []
process.platform === "win32"
? [
path.join(
app.getPath("documents"),
"My Games\\Path of Exile 2\\production_Config.ini"
),
]
: process.platform === "linux"
? [
path.join(
app.getPath("documents"),
"My Games/Path of Exile 2/production_Config.ini"
),
path.join(
app.getPath("home"),
".local/share/Steam/steamapps/compatdata/238960/pfx/drive_c/users/steamuser/Documents/My Games/Path of Exile 2/production_Config.ini"
),
]
: process.platform === "darwin"
? [
path.join(
app.getPath("appData"),
"Path of Exile 2/Preferences/production_Config.ini"
),
]
: [];
export class GameConfig {
private _wantedPath: string | null = null
private _actualPath: string | null = null
get actualPath () { return this._actualPath }
private _wantedPath: string | null = null;
private _actualPath: string | null = null;
get actualPath() {
return this._actualPath;
}
private _showModsKey: string | null = null
get showModsKeyNullable () { return this._showModsKey }
get showModsKey () { return this._showModsKey ?? 'Alt' }
private _showModsKey: string | null = null;
get showModsKeyNullable() {
return this._showModsKey;
}
get showModsKey() {
return this._showModsKey ?? "Alt";
}
constructor (
private server: ServerEvents,
private logger: Logger
) {}
constructor(private server: ServerEvents, private logger: Logger) {}
async readConfig (filePath: string) {
async readConfig(filePath: string) {
if (this._wantedPath !== filePath) {
this._wantedPath = filePath
this._actualPath = null
this._wantedPath = filePath;
this._actualPath = null;
} else {
return
return;
}
if (!filePath.length) {
const guessedPath = await guessFileLocation(POSSIBLE_PATH)
const guessedPath = await guessFileLocation(POSSIBLE_PATH);
if (guessedPath != null) {
filePath = guessedPath
filePath = guessedPath;
} else {
this.logger.write('error [GameConfig] Failed to find game configuration file in the default location.')
return
this.logger.write(
"error [GameConfig] Failed to find game configuration file in the default location."
);
return;
}
}
try {
let contents = await fs.readFile(filePath, { encoding: 'utf-8', flag: 'r' })
contents = contents.trimStart() // remove BOM
const parsed = ini.parse(contents)
let contents = await fs.readFile(filePath, {
encoding: "utf-8",
flag: "r",
});
contents = contents.trimStart(); // remove BOM
const parsed = ini.parse(contents);
this._showModsKey = this.parseConfigHotkey(
parsed['ACTION_KEYS']?.['show_advanced_item_descriptions'])
parsed["ACTION_KEYS"]?.["show_advanced_item_descriptions"]
);
this._actualPath = filePath
this._actualPath = filePath;
} catch {
this.logger.write('error [GameConfig] Failed to read game configuration file.')
this.logger.write(
"error [GameConfig] Failed to read game configuration file."
);
}
}
private parseConfigHotkey (cfgKey?: string): string | null {
if (!cfgKey) return null
private parseConfigHotkey(cfgKey?: string): string | null {
if (!cfgKey) return null;
const [keyMain, keyMod] = cfgKey.split(' ')
const [keyMain, keyMod] = cfgKey.split(" ");
let key1: string
let key1: string;
if (CodeToKey[keyMain]) {
key1 = CodeToKey[keyMain]
key1 = CodeToKey[keyMain];
} else {
this.logger.write(`error [GameConfig] Failed to read key: ${cfgKey}.`)
return null
this.logger.write(`error [GameConfig] Failed to read key: ${cfgKey}.`);
return null;
}
let key2: string | undefined
let key2: string | undefined;
if (keyMod) {
if (keyMod === '1') {
key2 = 'Shift'
} else if (keyMod === '2') {
key2 = 'Ctrl'
} else if (keyMod === '3') {
key2 = 'Alt'
if (keyMod === "1") {
key2 = "Shift";
} else if (keyMod === "2") {
key2 = "Ctrl";
} else if (keyMod === "3") {
key2 = "Alt";
} else {
this.logger.write(`error [GameConfig] Failed to read modifier key: ${cfgKey}.`)
return null
this.logger.write(
`error [GameConfig] Failed to read modifier key: ${cfgKey}.`
);
return null;
}
}
return hotkeyToString(
[key1],
key2 === 'Ctrl',
key2 === 'Shift',
key2 === 'Alt'
)
key2 === "Ctrl",
key2 === "Shift",
key2 === "Alt"
);
}
}

View File

@@ -1,101 +1,118 @@
import { promises as fs, watchFile, unwatchFile } from 'fs'
import path from 'path'
import { app } from 'electron'
import { guessFileLocation } from './utils'
import { ServerEvents } from '../server'
import { Logger } from '../RemoteLogger'
import { promises as fs, watchFile, unwatchFile } from "fs";
import path from "path";
import { app } from "electron";
import { guessFileLocation } from "./utils";
import { ServerEvents } from "../server";
import { Logger } from "../RemoteLogger";
const POSSIBLE_PATH =
(process.platform === 'win32') ? [
'C:\\Program Files (x86)\\Grinding Gear Games\\Path of Exile\\logs\\Client.txt',
'C:\\Program Files (x86)\\Steam\\steamapps\\common\\Path of Exile\\logs\\Client.txt'
] : (process.platform === 'linux') ? [
path.join(app.getPath('home'), '.wine/drive_c/Program Files (x86)/Grinding Gear Games/Path of Exile/logs/Client.txt'),
path.join(app.getPath('home'), '.local/share/Steam/steamapps/common/Path of Exile/logs/Client.txt')
] : (process.platform === 'darwin') ? [
path.join(app.getPath('home'), 'Library/Caches/com.GGG.PathOfExile/Logs/Client.txt')
] : []
process.platform === "win32"
? [
"C:\\Program Files (x86)\\Grinding Gear Games\\Path of Exile 2\\logs\\Client.txt",
"C:\\Program Files (x86)\\Steam\\steamapps\\common\\Path of Exile 2\\logs\\Client.txt",
]
: process.platform === "linux"
? [
path.join(
app.getPath("home"),
".wine/drive_c/Program Files (x86)/Grinding Gear Games/Path of Exile 2/logs/Client.txt"
),
path.join(
app.getPath("home"),
".local/share/Steam/steamapps/common/Path of Exile 2/logs/Client.txt"
),
]
: process.platform === "darwin"
? [
path.join(
app.getPath("home"),
"Library/Caches/com.GGG.PathOfExile/Logs/Client.txt"
),
]
: [];
export class GameLogWatcher {
private _wantedPath: string | null = null
get actualPath () { return this._state?.path ?? null }
private _wantedPath: string | null = null;
get actualPath() {
return this._state?.path ?? null;
}
private _state: {
offset: number
path: string
file: fs.FileHandle
isReading: boolean
readBuff: Buffer
} | null = null
offset: number;
path: string;
file: fs.FileHandle;
isReading: boolean;
readBuff: Buffer;
} | null = null;
constructor (
private server: ServerEvents,
private logger: Logger,
) {}
constructor(private server: ServerEvents, private logger: Logger) {}
async restart (logFile: string) {
async restart(logFile: string) {
if (this._wantedPath !== logFile) {
this._wantedPath = logFile
this._wantedPath = logFile;
if (this._state) {
unwatchFile(this._state.path)
await this._state.file.close()
this._state = null
unwatchFile(this._state.path);
await this._state.file.close();
this._state = null;
}
} else {
return
return;
}
if (!logFile.length) {
const guessedPath = await guessFileLocation(POSSIBLE_PATH)
const guessedPath = await guessFileLocation(POSSIBLE_PATH);
if (guessedPath != null) {
logFile = guessedPath
logFile = guessedPath;
} else {
return
return;
}
}
try {
const file = await fs.open(logFile, 'r')
const stats = await file.stat()
watchFile(logFile, { interval: 450 }, this.handleFileChange.bind(this))
const file = await fs.open(logFile, "r");
const stats = await file.stat();
watchFile(logFile, { interval: 450 }, this.handleFileChange.bind(this));
this._state = {
path: logFile,
file: file,
offset: stats.size,
isReading: false,
readBuff: Buffer.allocUnsafe(64 * 1024),
}
};
} catch {
this.logger.write('error [GameLogWatcher] Failed to watch file.')
this.logger.write("error [GameLogWatcher] Failed to watch file.");
}
}
private handleFileChange () {
private handleFileChange() {
if (this._state && !this._state.isReading) {
this._state.isReading = true
this.readToEOF()
this._state.isReading = true;
this.readToEOF();
}
}
private async readToEOF () {
if (!this._state) return
private async readToEOF() {
if (!this._state) return;
const { file, readBuff, offset } = this._state
const { bytesRead } = await file.read(readBuff, 0, readBuff.length, offset)
const { file, readBuff, offset } = this._state;
const { bytesRead } = await file.read(readBuff, 0, readBuff.length, offset);
if (bytesRead) {
const str = readBuff.toString('utf8', 0, bytesRead)
const lines = str.split('\n').map(line => line.trim()).filter(line => line.length)
this.server.sendEventTo('broadcast', {
name: 'MAIN->CLIENT::game-log',
payload: { lines }
})
const str = readBuff.toString("utf8", 0, bytesRead);
const lines = str
.split("\n")
.map((line) => line.trim())
.filter((line) => line.length);
this.server.sendEventTo("broadcast", {
name: "MAIN->CLIENT::game-log",
payload: { lines },
});
}
if (bytesRead) {
this._state.offset += bytesRead
this.readToEOF()
this._state.offset += bytesRead;
this.readToEOF();
} else {
this._state.isReading = false
this._state.isReading = false;
}
}
}

View File

@@ -1,172 +1,188 @@
import path from 'path'
import { BrowserWindow, dialog, shell, Menu, WebContents } from 'electron'
import { OverlayController, OVERLAY_WINDOW_OPTS } from 'electron-overlay-window'
import type { ServerEvents } from '../server'
import type { Logger } from '../RemoteLogger'
import type { GameWindow } from './GameWindow'
import path from "path";
import { BrowserWindow, dialog, shell, Menu, WebContents } from "electron";
import {
OverlayController,
OVERLAY_WINDOW_OPTS,
} from "electron-overlay-window";
import type { ServerEvents } from "../server";
import type { Logger } from "../RemoteLogger";
import type { GameWindow } from "./GameWindow";
export class OverlayWindow {
public isInteractable = false
public wasUsedRecently = true
private window?: BrowserWindow
private overlayKey: string = 'Shift + Space'
private isOverlayKeyUsed = false
public isInteractable = false;
public wasUsedRecently = true;
private window?: BrowserWindow;
private overlayKey: string = "Shift + Space";
private isOverlayKeyUsed = false;
constructor (
constructor(
private server: ServerEvents,
private logger: Logger,
private poeWindow: GameWindow,
private poeWindow: GameWindow
) {
this.server.onEventAnyClient('OVERLAY->MAIN::focus-game', this.assertGameActive)
this.poeWindow.on('active-change', this.handlePoeWindowActiveChange)
this.poeWindow.onAttach(this.handleOverlayAttached)
this.server.onEventAnyClient(
"OVERLAY->MAIN::focus-game",
this.assertGameActive
);
this.poeWindow.on("active-change", this.handlePoeWindowActiveChange);
this.poeWindow.onAttach(this.handleOverlayAttached);
this.server.onEventAnyClient('CLIENT->MAIN::used-recently', (e) => {
this.wasUsedRecently = e.isOverlay
})
this.server.onEventAnyClient("CLIENT->MAIN::used-recently", (e) => {
this.wasUsedRecently = e.isOverlay;
});
if (process.argv.includes('--no-overlay')) return
if (process.argv.includes("--no-overlay")) return;
this.window = new BrowserWindow({
icon: path.join(__dirname, process.env.STATIC!, 'icon.png'),
icon: path.join(__dirname, process.env.STATIC!, "icon.png"),
...OVERLAY_WINDOW_OPTS,
width: 800,
height: 600,
webPreferences: {
allowRunningInsecureContent: false,
webviewTag: true,
spellcheck: false
spellcheck: false,
},
});
this.window.setMenu(
Menu.buildFromTemplate([
{ role: "editMenu" },
{ role: "reload" },
{ role: "toggleDevTools" },
])
);
this.window.webContents.on("before-input-event", this.handleExtraCommands);
this.window.webContents.on(
"did-attach-webview",
(_, webviewWebContents) => {
webviewWebContents.on("before-input-event", this.handleExtraCommands);
}
})
this.window.setMenu(Menu.buildFromTemplate([
{ role: 'editMenu' },
{ role: 'reload' },
{ role: 'toggleDevTools' }
]))
this.window.webContents.on('before-input-event', this.handleExtraCommands)
this.window.webContents.on('did-attach-webview', (_, webviewWebContents) => {
webviewWebContents.on('before-input-event', this.handleExtraCommands)
})
);
this.window.webContents.setWindowOpenHandler((details) => {
shell.openExternal(details.url)
return { action: 'deny' }
})
shell.openExternal(details.url);
return { action: "deny" };
});
}
loadAppPage (port: number) {
const url = process.env.VITE_DEV_SERVER_URL ||
`http://localhost:${port}/index.html`
loadAppPage(port: number) {
const url =
process.env.VITE_DEV_SERVER_URL || `http://localhost:${port}/index.html`;
if (!this.window) {
shell.openExternal(url)
return
shell.openExternal(url);
return;
}
if (process.env.VITE_DEV_SERVER_URL) {
this.window.loadURL(url)
this.window.webContents.openDevTools({ mode: 'detach', activate: false })
this.window.loadURL(url);
this.window.webContents.openDevTools({ mode: "detach", activate: false });
} else {
this.window.loadURL(url)
this.window.loadURL(url);
}
}
assertOverlayActive = () => {
if (!this.isInteractable) {
this.isInteractable = true
OverlayController.activateOverlay()
this.poeWindow.isActive = false
this.isInteractable = true;
OverlayController.activateOverlay();
this.poeWindow.isActive = false;
}
}
};
assertGameActive = () => {
if (this.isInteractable) {
this.isInteractable = false
OverlayController.focusTarget()
this.poeWindow.isActive = true
this.isInteractable = false;
OverlayController.focusTarget();
this.poeWindow.isActive = true;
}
}
};
toggleActiveState = () => {
this.isOverlayKeyUsed = true
this.isOverlayKeyUsed = true;
if (this.isInteractable) {
this.assertGameActive()
this.assertGameActive();
} else {
this.assertOverlayActive()
this.assertOverlayActive();
}
};
updateOpts(overlayKey: string, windowTitle: string) {
this.overlayKey = overlayKey;
this.poeWindow.attach(this.window, windowTitle);
}
updateOpts (overlayKey: string, windowTitle: string) {
this.overlayKey = overlayKey
this.poeWindow.attach(this.window, windowTitle)
}
private handleExtraCommands = (
event: Electron.Event,
input: Electron.Input
) => {
if (input.type !== "keyDown") return;
private handleExtraCommands = (event: Electron.Event, input: Electron.Input) => {
if (input.type !== 'keyDown') return
let { code, control: ctrlKey, shift: shiftKey, alt: altKey } = input;
let { code, control: ctrlKey, shift: shiftKey, alt: altKey } = input
if (code.startsWith('Key')) {
code = code.slice('Key'.length)
} else if (code.startsWith('Digit')) {
code = code.slice('Digit'.length)
if (code.startsWith("Key")) {
code = code.slice("Key".length);
} else if (code.startsWith("Digit")) {
code = code.slice("Digit".length);
}
if (shiftKey && altKey) code = `Shift + Alt + ${code}`
else if (ctrlKey && shiftKey) code = `Ctrl + Shift + ${code}`
else if (ctrlKey && altKey) code = `Ctrl + Alt + ${code}`
else if (altKey) code = `Alt + ${code}`
else if (ctrlKey) code = `Ctrl + ${code}`
else if (shiftKey) code = `Shift + ${code}`
if (shiftKey && altKey) code = `Shift + Alt + ${code}`;
else if (ctrlKey && shiftKey) code = `Ctrl + Shift + ${code}`;
else if (ctrlKey && altKey) code = `Ctrl + Alt + ${code}`;
else if (altKey) code = `Alt + ${code}`;
else if (ctrlKey) code = `Ctrl + ${code}`;
else if (shiftKey) code = `Shift + ${code}`;
switch (code) {
case 'Escape':
case 'Ctrl + W': {
event.preventDefault()
process.nextTick(this.assertGameActive)
break
case "Escape":
case "Ctrl + W": {
event.preventDefault();
process.nextTick(this.assertGameActive);
break;
}
case this.overlayKey: {
event.preventDefault()
process.nextTick(this.toggleActiveState)
break
event.preventDefault();
process.nextTick(this.toggleActiveState);
break;
}
}
}
};
private handleOverlayAttached = (hasAccess?: boolean) => {
if (hasAccess === false) {
this.logger.write('error [Overlay] PoE is running with administrator rights')
this.logger.write(
"error [Overlay] PoE is running with administrator rights"
);
dialog.showErrorBox(
'PoE window - No access',
"PoE window - No access",
// ----------------------
'Path of Exile is running with administrator rights.\n' +
'\n' +
'You need to restart Awakened PoE Trade with administrator rights.'
)
"Path of Exile 2 is running with administrator rights.\n" +
"\n" +
"You need to restart Awakened PoE2 Trade2 with administrator rights."
);
} else {
this.server.sendEventTo('broadcast', {
name: 'MAIN->OVERLAY::overlay-attached',
payload: undefined
})
this.server.sendEventTo("broadcast", {
name: "MAIN->OVERLAY::overlay-attached",
payload: undefined,
});
}
}
};
private handlePoeWindowActiveChange = (isActive: boolean) => {
if (isActive && this.isInteractable) {
this.isInteractable = false
this.isInteractable = false;
}
this.server.sendEventTo('broadcast', {
name: 'MAIN->OVERLAY::focus-change',
this.server.sendEventTo("broadcast", {
name: "MAIN->OVERLAY::focus-change",
payload: {
game: isActive,
overlay: this.isInteractable,
usingHotkey: this.isOverlayKeyUsed
}
})
this.isOverlayKeyUsed = false
}
usingHotkey: this.isOverlayKeyUsed,
},
});
this.isOverlayKeyUsed = false;
};
}

View File

@@ -1,56 +1,60 @@
module.exports = {
root: true,
env: {
node: true
},
plugins: [
'@typescript-eslint'
// 'only-warn'
],
extends: [
'plugin:vue/base',
'standard-with-typescript'
],
rules: {
'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
'quote-props': ['error', 'consistent-as-needed'],
'no-labels': ['error', { allowLoop: true }],
'multiline-ternary': 'off',
'no-unused-vars': 'off',
'no-undef': 'off',
'@typescript-eslint/no-unused-vars': ['error'],
'@typescript-eslint/strict-boolean-expressions': 'off',
'@typescript-eslint/no-non-null-assertion': 'off',
'@typescript-eslint/explicit-function-return-type': 'off',
'@typescript-eslint/restrict-template-expressions': 'off',
'@typescript-eslint/prefer-nullish-coalescing': 'off',
'@typescript-eslint/prefer-optional-chain': 'off',
'@typescript-eslint/prefer-readonly': 'off',
'@typescript-eslint/no-floating-promises': 'off',
'@typescript-eslint/no-misused-promises': 'off',
'@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',
'func-call-spacing': 'off',
// TODO: refactor IPC and enable
'@typescript-eslint/consistent-type-assertions': 'off'
},
overrides: [{
files: ['src/main/**/*'],
root: true,
env: {
node: true
}
}, {
files: ['*.ts'],
node: true
},
plugins: [
'@typescript-eslint',
'prettier'
// 'only-warn'
],
extends: [
'plugin:vue/base',
'standard-with-typescript',
'plugin:prettier/recommended',
'eslint-plugin-prettier/recommended',
],
rules: {
'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
'quote-props': ['error', 'consistent-as-needed'],
'no-labels': ['error', { allowLoop: true }],
'multiline-ternary': 'off',
'no-unused-vars': 'off',
'no-undef': 'off',
'@typescript-eslint/no-unused-vars': ['error'],
'@typescript-eslint/strict-boolean-expressions': 'off',
'@typescript-eslint/no-non-null-assertion': 'off',
'@typescript-eslint/explicit-function-return-type': 'off',
'@typescript-eslint/restrict-template-expressions': 'off',
'@typescript-eslint/prefer-nullish-coalescing': 'off',
'@typescript-eslint/prefer-optional-chain': 'off',
'@typescript-eslint/prefer-readonly': 'off',
'@typescript-eslint/no-floating-promises': 'off',
'@typescript-eslint/no-misused-promises': 'off',
'@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',
'func-call-spacing': 'off',
// TODO: refactor IPC and enable
'@typescript-eslint/consistent-type-assertions': 'off',
"indent": ["error", "tab"]
},
overrides: [{
files: ['src/main/**/*'],
env: {
node: true
}
}, {
files: ['*.ts'],
parserOptions: {
project: './tsconfig.json'
}
}],
parserOptions: {
project: './tsconfig.json'
parser: '@typescript-eslint/parser',
extraFileExtensions: ['.vue']
}
}],
parserOptions: {
parser: '@typescript-eslint/parser',
extraFileExtensions: ['.vue']
}
}

7
renderer/.prettierrc Normal file
View File

@@ -0,0 +1,7 @@
{
"singleQuote": true,
"endOfLine": "lf",
"tabWidth": 2,
"useTabs": true,
"trailingComma": "es5"
}

View File

@@ -6,7 +6,7 @@
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<meta name="color-scheme" content="dark">
<link rel="icon" href="/icon.ico">
<title>Awakened PoE Trade</title>
<title>Awakened PoE Trade2</title>
</head>
<body>
<div id="app"></div>

View File

@@ -1,11 +1,11 @@
{
"name": "awakened-poe-trade",
"name": "awakened-poe2-trade2",
"version": "0.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "awakened-poe-trade",
"name": "awakened-poe2-trade2",
"version": "0.0.0",
"dependencies": {
"@fortawesome/fontawesome-free": "6.x.x",
@@ -33,7 +33,10 @@
"@types/object-hash": "^3.0.0",
"@vitejs/plugin-vue": "^4.0.0",
"autoprefixer": "^10.0.2",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-prettier": "^5.2.1",
"postcss": "^8.2.14",
"prettier": "3.4.2",
"typescript": "5.6.x",
"vite": "^5.0.0",
"vue-tsc": "^2.0.0"
@@ -502,8 +505,8 @@
"version": "4.4.1",
"resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.1.tgz",
"integrity": "sha512-s3O3waFUrMV8P/XaF/+ZTp1X9XBZW1a4B97ZnjQF2KYWaFD2A8KyFBsrsfSjEmjn3RGWAIuvlneuZm3CUK3jbA==",
"devOptional": true,
"license": "MIT",
"optional": true,
"dependencies": {
"eslint-visitor-keys": "^3.4.3"
},
@@ -521,8 +524,8 @@
"version": "4.12.1",
"resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz",
"integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==",
"devOptional": true,
"license": "MIT",
"optional": true,
"engines": {
"node": "^12.0.0 || ^14.0.0 || >=16.0.0"
}
@@ -531,8 +534,8 @@
"version": "2.1.4",
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz",
"integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==",
"devOptional": true,
"license": "MIT",
"optional": true,
"dependencies": {
"ajv": "^6.12.4",
"debug": "^4.3.2",
@@ -555,8 +558,8 @@
"version": "8.57.1",
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz",
"integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==",
"devOptional": true,
"license": "MIT",
"optional": true,
"engines": {
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
}
@@ -575,8 +578,8 @@
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz",
"integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==",
"deprecated": "Use @eslint/config-array instead",
"devOptional": true,
"license": "Apache-2.0",
"optional": true,
"dependencies": {
"@humanwhocodes/object-schema": "^2.0.3",
"debug": "^4.3.1",
@@ -590,8 +593,8 @@
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz",
"integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==",
"devOptional": true,
"license": "Apache-2.0",
"optional": true,
"engines": {
"node": ">=12.22"
},
@@ -605,8 +608,8 @@
"resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz",
"integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==",
"deprecated": "Use @eslint/object-schema instead",
"license": "BSD-3-Clause",
"optional": true
"devOptional": true,
"license": "BSD-3-Clause"
},
"node_modules/@intlify/core-base": {
"version": "10.0.4",
@@ -789,6 +792,19 @@
"node": ">=14"
}
},
"node_modules/@pkgr/core": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.1.1.tgz",
"integrity": "sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA==",
"dev": true,
"license": "MIT",
"engines": {
"node": "^12.20.0 || ^14.18.0 || >=16.0.0"
},
"funding": {
"url": "https://opencollective.com/unts"
}
},
"node_modules/@popperjs/core": {
"version": "2.11.8",
"resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz",
@@ -1383,8 +1399,8 @@
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz",
"integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==",
"license": "ISC",
"optional": true
"devOptional": true,
"license": "ISC"
},
"node_modules/@vitejs/plugin-vue": {
"version": "4.6.2",
@@ -1739,8 +1755,8 @@
"version": "8.14.0",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz",
"integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==",
"devOptional": true,
"license": "MIT",
"optional": true,
"bin": {
"acorn": "bin/acorn"
},
@@ -1752,8 +1768,8 @@
"version": "5.3.2",
"resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz",
"integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==",
"devOptional": true,
"license": "MIT",
"optional": true,
"peerDependencies": {
"acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
}
@@ -1762,8 +1778,8 @@
"version": "6.12.6",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
"devOptional": true,
"license": "MIT",
"optional": true,
"dependencies": {
"fast-deep-equal": "^3.1.1",
"fast-json-stable-stringify": "^2.0.0",
@@ -1855,8 +1871,8 @@
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
"license": "Python-2.0",
"optional": true
"devOptional": true,
"license": "Python-2.0"
},
"node_modules/array-buffer-byte-length": {
"version": "1.0.1",
@@ -2071,8 +2087,8 @@
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
"devOptional": true,
"license": "MIT",
"optional": true,
"dependencies": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
@@ -2157,8 +2173,8 @@
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
"integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
"devOptional": true,
"license": "MIT",
"optional": true,
"engines": {
"node": ">=6"
}
@@ -2197,8 +2213,8 @@
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
"integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
"devOptional": true,
"license": "MIT",
"optional": true,
"dependencies": {
"ansi-styles": "^4.1.0",
"supports-color": "^7.1.0"
@@ -2277,8 +2293,8 @@
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
"license": "MIT",
"optional": true
"devOptional": true,
"license": "MIT"
},
"node_modules/cross-spawn": {
"version": "7.0.6",
@@ -2377,8 +2393,8 @@
"version": "4.3.7",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
"integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==",
"devOptional": true,
"license": "MIT",
"optional": true,
"dependencies": {
"ms": "^2.1.3"
},
@@ -2395,8 +2411,8 @@
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
"integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==",
"license": "MIT",
"optional": true
"devOptional": true,
"license": "MIT"
},
"node_modules/define-data-property": {
"version": "1.1.4",
@@ -2463,8 +2479,8 @@
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz",
"integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==",
"devOptional": true,
"license": "Apache-2.0",
"optional": true,
"dependencies": {
"esutils": "^2.0.2"
},
@@ -2712,8 +2728,8 @@
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
"integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
"devOptional": true,
"license": "MIT",
"optional": true,
"engines": {
"node": ">=10"
},
@@ -2726,8 +2742,8 @@
"resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz",
"integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==",
"deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.",
"devOptional": true,
"license": "MIT",
"optional": true,
"dependencies": {
"@eslint-community/eslint-utils": "^4.2.0",
"@eslint-community/regexpp": "^4.6.1",
@@ -2778,6 +2794,19 @@
"url": "https://opencollective.com/eslint"
}
},
"node_modules/eslint-config-prettier": {
"version": "9.1.0",
"resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-9.1.0.tgz",
"integrity": "sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw==",
"dev": true,
"license": "MIT",
"bin": {
"eslint-config-prettier": "bin/cli.js"
},
"peerDependencies": {
"eslint": ">=7.0.0"
}
},
"node_modules/eslint-config-standard": {
"version": "17.0.0",
"resolved": "https://registry.npmjs.org/eslint-config-standard/-/eslint-config-standard-17.0.0.tgz",
@@ -3014,6 +3043,37 @@
"eslint": ">=7.0.0"
}
},
"node_modules/eslint-plugin-prettier": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.2.1.tgz",
"integrity": "sha512-gH3iR3g4JfF+yYPaJYkN7jEl9QbweL/YfkoRlNnuIEHEz1vHVlCmWOS+eGGiRuzHQXdJFCOTxRgvju9b8VUmrw==",
"dev": true,
"license": "MIT",
"dependencies": {
"prettier-linter-helpers": "^1.0.0",
"synckit": "^0.9.1"
},
"engines": {
"node": "^14.18.0 || >=16.0.0"
},
"funding": {
"url": "https://opencollective.com/eslint-plugin-prettier"
},
"peerDependencies": {
"@types/eslint": ">=8.0.0",
"eslint": ">=8.0.0",
"eslint-config-prettier": "*",
"prettier": ">=3.0.0"
},
"peerDependenciesMeta": {
"@types/eslint": {
"optional": true
},
"eslint-config-prettier": {
"optional": true
}
}
},
"node_modules/eslint-plugin-promise": {
"version": "6.6.0",
"resolved": "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-6.6.0.tgz",
@@ -3100,8 +3160,8 @@
"version": "3.4.3",
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz",
"integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==",
"devOptional": true,
"license": "Apache-2.0",
"optional": true,
"engines": {
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
},
@@ -3113,8 +3173,8 @@
"version": "7.2.2",
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz",
"integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==",
"devOptional": true,
"license": "BSD-2-Clause",
"optional": true,
"dependencies": {
"esrecurse": "^4.3.0",
"estraverse": "^5.2.0"
@@ -3130,8 +3190,8 @@
"version": "5.3.0",
"resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
"integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
"devOptional": true,
"license": "BSD-2-Clause",
"optional": true,
"engines": {
"node": ">=4.0"
}
@@ -3140,8 +3200,8 @@
"version": "9.6.1",
"resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz",
"integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==",
"devOptional": true,
"license": "BSD-2-Clause",
"optional": true,
"dependencies": {
"acorn": "^8.9.0",
"acorn-jsx": "^5.3.2",
@@ -3158,8 +3218,8 @@
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz",
"integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==",
"devOptional": true,
"license": "BSD-3-Clause",
"optional": true,
"dependencies": {
"estraverse": "^5.1.0"
},
@@ -3171,8 +3231,8 @@
"version": "5.3.0",
"resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
"integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
"devOptional": true,
"license": "BSD-2-Clause",
"optional": true,
"engines": {
"node": ">=4.0"
}
@@ -3181,8 +3241,8 @@
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz",
"integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==",
"devOptional": true,
"license": "BSD-2-Clause",
"optional": true,
"dependencies": {
"estraverse": "^5.2.0"
},
@@ -3194,8 +3254,8 @@
"version": "5.3.0",
"resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
"integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
"devOptional": true,
"license": "BSD-2-Clause",
"optional": true,
"engines": {
"node": ">=4.0"
}
@@ -3220,8 +3280,8 @@
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
"integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
"devOptional": true,
"license": "BSD-2-Clause",
"optional": true,
"engines": {
"node": ">=0.10.0"
}
@@ -3232,6 +3292,13 @@
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
"license": "MIT"
},
"node_modules/fast-diff": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz",
"integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==",
"dev": true,
"license": "Apache-2.0"
},
"node_modules/fast-glob": {
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz",
@@ -3264,15 +3331,15 @@
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
"integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
"license": "MIT",
"optional": true
"devOptional": true,
"license": "MIT"
},
"node_modules/fast-levenshtein": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
"integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==",
"license": "MIT",
"optional": true
"devOptional": true,
"license": "MIT"
},
"node_modules/fastest-levenshtein": {
"version": "1.0.16",
@@ -3296,8 +3363,8 @@
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz",
"integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==",
"devOptional": true,
"license": "MIT",
"optional": true,
"dependencies": {
"flat-cache": "^3.0.4"
},
@@ -3321,8 +3388,8 @@
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
"integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==",
"devOptional": true,
"license": "MIT",
"optional": true,
"dependencies": {
"locate-path": "^6.0.0",
"path-exists": "^4.0.0"
@@ -3338,8 +3405,8 @@
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz",
"integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==",
"devOptional": true,
"license": "MIT",
"optional": true,
"dependencies": {
"flatted": "^3.2.9",
"keyv": "^4.5.3",
@@ -3353,8 +3420,8 @@
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.2.tgz",
"integrity": "sha512-AiwGJM8YcNOaobumgtng+6NHuOqC3A7MixFeDafM3X9cIUM+xUXoS5Vfgf+OihAYe20fxqNM9yPBXJzRtZ/4eA==",
"license": "ISC",
"optional": true
"devOptional": true,
"license": "ISC"
},
"node_modules/for-each": {
"version": "0.3.3",
@@ -3400,8 +3467,8 @@
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
"integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==",
"license": "ISC",
"optional": true
"devOptional": true,
"license": "ISC"
},
"node_modules/fsevents": {
"version": "2.3.3",
@@ -3498,8 +3565,8 @@
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
"integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
"deprecated": "Glob versions prior to v9 are no longer supported",
"devOptional": true,
"license": "ISC",
"optional": true,
"dependencies": {
"fs.realpath": "^1.0.0",
"inflight": "^1.0.4",
@@ -3531,8 +3598,8 @@
"version": "13.24.0",
"resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz",
"integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==",
"devOptional": true,
"license": "MIT",
"optional": true,
"dependencies": {
"type-fest": "^0.20.2"
},
@@ -3547,8 +3614,8 @@
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz",
"integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==",
"devOptional": true,
"license": "(MIT OR CC0-1.0)",
"optional": true,
"engines": {
"node": ">=10"
},
@@ -3611,8 +3678,8 @@
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz",
"integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==",
"license": "MIT",
"optional": true
"devOptional": true,
"license": "MIT"
},
"node_modules/has-bigints": {
"version": "1.0.2",
@@ -3628,8 +3695,8 @@
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
"devOptional": true,
"license": "MIT",
"optional": true,
"engines": {
"node": ">=8"
}
@@ -3715,8 +3782,8 @@
"version": "5.3.2",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
"integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==",
"devOptional": true,
"license": "MIT",
"optional": true,
"engines": {
"node": ">= 4"
}
@@ -3725,8 +3792,8 @@
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz",
"integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==",
"devOptional": true,
"license": "MIT",
"optional": true,
"dependencies": {
"parent-module": "^1.0.0",
"resolve-from": "^4.0.0"
@@ -3742,8 +3809,8 @@
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
"integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==",
"devOptional": true,
"license": "MIT",
"optional": true,
"engines": {
"node": ">=0.8.19"
}
@@ -3753,8 +3820,8 @@
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
"integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
"deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.",
"devOptional": true,
"license": "ISC",
"optional": true,
"dependencies": {
"once": "^1.3.0",
"wrappy": "1"
@@ -3764,8 +3831,8 @@
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
"license": "ISC",
"optional": true
"devOptional": true,
"license": "ISC"
},
"node_modules/internal-slot": {
"version": "1.0.7",
@@ -4031,8 +4098,8 @@
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz",
"integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==",
"devOptional": true,
"license": "MIT",
"optional": true,
"engines": {
"node": ">=8"
}
@@ -4215,8 +4282,8 @@
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
"integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
"devOptional": true,
"license": "MIT",
"optional": true,
"dependencies": {
"argparse": "^2.0.1"
},
@@ -4228,22 +4295,22 @@
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz",
"integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==",
"license": "MIT",
"optional": true
"devOptional": true,
"license": "MIT"
},
"node_modules/json-schema-traverse": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
"license": "MIT",
"optional": true
"devOptional": true,
"license": "MIT"
},
"node_modules/json-stable-stringify-without-jsonify": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz",
"integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==",
"license": "MIT",
"optional": true
"devOptional": true,
"license": "MIT"
},
"node_modules/json5": {
"version": "1.0.2",
@@ -4262,8 +4329,8 @@
"version": "4.5.4",
"resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
"integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==",
"devOptional": true,
"license": "MIT",
"optional": true,
"dependencies": {
"json-buffer": "3.0.1"
}
@@ -4272,8 +4339,8 @@
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
"integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==",
"devOptional": true,
"license": "MIT",
"optional": true,
"dependencies": {
"prelude-ls": "^1.2.1",
"type-check": "~0.4.0"
@@ -4301,8 +4368,8 @@
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
"integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==",
"devOptional": true,
"license": "MIT",
"optional": true,
"dependencies": {
"p-locate": "^5.0.0"
},
@@ -4324,8 +4391,8 @@
"version": "4.6.2",
"resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
"integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
"license": "MIT",
"optional": true
"devOptional": true,
"license": "MIT"
},
"node_modules/lru-cache": {
"version": "10.4.3",
@@ -4377,8 +4444,8 @@
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
"devOptional": true,
"license": "ISC",
"optional": true,
"dependencies": {
"brace-expansion": "^1.1.7"
},
@@ -4409,8 +4476,8 @@
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
"license": "MIT",
"optional": true
"devOptional": true,
"license": "MIT"
},
"node_modules/muggle-string": {
"version": "0.4.1",
@@ -4452,8 +4519,8 @@
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
"integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==",
"license": "MIT",
"optional": true
"devOptional": true,
"license": "MIT"
},
"node_modules/natural-compare-lite": {
"version": "1.4.0",
@@ -4629,8 +4696,8 @@
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
"integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
"devOptional": true,
"license": "ISC",
"optional": true,
"dependencies": {
"wrappy": "1"
}
@@ -4639,8 +4706,8 @@
"version": "0.9.4",
"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",
"integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==",
"devOptional": true,
"license": "MIT",
"optional": true,
"dependencies": {
"deep-is": "^0.1.3",
"fast-levenshtein": "^2.0.6",
@@ -4657,8 +4724,8 @@
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
"integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
"devOptional": true,
"license": "MIT",
"optional": true,
"dependencies": {
"yocto-queue": "^0.1.0"
},
@@ -4673,8 +4740,8 @@
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz",
"integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==",
"devOptional": true,
"license": "MIT",
"optional": true,
"dependencies": {
"p-limit": "^3.0.2"
},
@@ -4695,8 +4762,8 @@
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
"integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
"devOptional": true,
"license": "MIT",
"optional": true,
"dependencies": {
"callsites": "^3.0.0"
},
@@ -4715,8 +4782,8 @@
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
"integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
"devOptional": true,
"license": "MIT",
"optional": true,
"engines": {
"node": ">=8"
}
@@ -4725,8 +4792,8 @@
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
"integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==",
"devOptional": true,
"license": "MIT",
"optional": true,
"engines": {
"node": ">=0.10.0"
}
@@ -4977,18 +5044,47 @@
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
"integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==",
"devOptional": true,
"license": "MIT",
"optional": true,
"engines": {
"node": ">= 0.8.0"
}
},
"node_modules/prettier": {
"version": "3.4.2",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.4.2.tgz",
"integrity": "sha512-e9MewbtFo+Fevyuxn/4rrcDAaq0IYxPGLvObpQjiZBMAzB9IGmzlnG9RZy3FFas+eBMu2vA0CszMeduow5dIuQ==",
"dev": true,
"license": "MIT",
"bin": {
"prettier": "bin/prettier.cjs"
},
"engines": {
"node": ">=14"
},
"funding": {
"url": "https://github.com/prettier/prettier?sponsor=1"
}
},
"node_modules/prettier-linter-helpers": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz",
"integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==",
"dev": true,
"license": "MIT",
"dependencies": {
"fast-diff": "^1.1.2"
},
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/punycode": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
"integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
"devOptional": true,
"license": "MIT",
"optional": true,
"engines": {
"node": ">=6"
}
@@ -5109,8 +5205,8 @@
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
"integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
"devOptional": true,
"license": "MIT",
"optional": true,
"engines": {
"node": ">=4"
}
@@ -5130,8 +5226,8 @@
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
"integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
"deprecated": "Rimraf versions prior to v4 are no longer supported",
"devOptional": true,
"license": "ISC",
"optional": true,
"dependencies": {
"glob": "^7.1.3"
},
@@ -5542,8 +5638,8 @@
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
"integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==",
"devOptional": true,
"license": "MIT",
"optional": true,
"engines": {
"node": ">=8"
},
@@ -5621,8 +5717,8 @@
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
"devOptional": true,
"license": "MIT",
"optional": true,
"dependencies": {
"has-flag": "^4.0.0"
},
@@ -5642,6 +5738,30 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/synckit": {
"version": "0.9.2",
"resolved": "https://registry.npmjs.org/synckit/-/synckit-0.9.2.tgz",
"integrity": "sha512-vrozgXDQwYO72vHjUb/HnFbQx1exDjoKzqx23aXEg2a9VIg2TSFZ8FmeZpTjUCFMYw7mpX4BE2SFu8wI7asYsw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@pkgr/core": "^0.1.0",
"tslib": "^2.6.2"
},
"engines": {
"node": "^14.18.0 || >=16.0.0"
},
"funding": {
"url": "https://opencollective.com/unts"
}
},
"node_modules/synckit/node_modules/tslib": {
"version": "2.8.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
"dev": true,
"license": "0BSD"
},
"node_modules/tailwindcss": {
"version": "3.4.15",
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.15.tgz",
@@ -5683,8 +5803,8 @@
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
"integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==",
"license": "MIT",
"optional": true
"devOptional": true,
"license": "MIT"
},
"node_modules/thenify": {
"version": "3.3.1",
@@ -5774,8 +5894,8 @@
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
"integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==",
"devOptional": true,
"license": "MIT",
"optional": true,
"dependencies": {
"prelude-ls": "^1.2.1"
},
@@ -5945,8 +6065,8 @@
"version": "4.4.1",
"resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
"integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
"devOptional": true,
"license": "BSD-2-Clause",
"optional": true,
"dependencies": {
"punycode": "^2.1.0"
}
@@ -6251,8 +6371,8 @@
"version": "1.2.5",
"resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz",
"integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==",
"devOptional": true,
"license": "MIT",
"optional": true,
"engines": {
"node": ">=0.10.0"
}
@@ -6355,8 +6475,8 @@
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
"license": "ISC",
"optional": true
"devOptional": true,
"license": "ISC"
},
"node_modules/xml-name-validator": {
"version": "4.0.0",
@@ -6384,8 +6504,8 @@
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
"integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
"devOptional": true,
"license": "MIT",
"optional": true,
"engines": {
"node": ">=10"
},

View File

@@ -1,6 +1,6 @@
{
"name": "awakened-poe-trade",
"version": "0.0.0",
"name": "awakened-poe2-trade2",
"version": "0.0.1",
"private": true,
"scripts": {
"dev": "vite",
@@ -34,7 +34,10 @@
"@types/object-hash": "^3.0.0",
"@vitejs/plugin-vue": "^4.0.0",
"autoprefixer": "^10.0.2",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-prettier": "^5.2.1",
"postcss": "^8.2.14",
"prettier": "3.4.2",
"typescript": "5.6.x",
"vite": "^5.0.0",
"vue-tsc": "^2.0.0"
@@ -59,4 +62,4 @@
"browserslist": [
"chrome >= 101"
]
}
}

View File

@@ -7,7 +7,6 @@
"league": "League",
"realm": "Realm",
"realm_intl": "International",
"app": {
"leagues_loading": "Loading leagues\u2026",
"leagues_failed": "Failed to load leagues",
@@ -21,12 +20,10 @@
"report_bug": "Report a bug on GitHub",
"quit": "Quit"
},
"map.mods.heist": "heist",
"map.mods.uber": "T17",
"map.mods.outdated": "outdated",
"Support development on": "Support development\u00A0on",
"Blighted": "Blighted",
"Blight-ravaged": "Blight-ravaged",
"Magic": "Magic",
@@ -40,7 +37,6 @@
"Anomalous": "Anomalous",
"Divergent": "Divergent",
"Phantasmal": "Phantasmal",
"item": {
"prop_quality": "Q {0}%",
"base_percentile": "Base Percentile: {0}%",
@@ -153,7 +149,6 @@
"hide_anointment": "Buyer will likely change anointment",
"hide_for_crafting": "Select only if price-checking as base item for crafting",
"hide_empty_mod": "Select only if item has 6 modifiers (1 of which is crafted) or if it has 5 modifiers",
"tag_implicit": "implicit",
"tag_fractured": "fractured",
"tag_crafted": "crafted",
@@ -254,15 +249,15 @@
"new_mods_icon": "Show icon for new mods"
},
"trade_result": {
"error" :"Trade site request failed",
"matched" :"Matched: {0}",
"trade" :"Trade",
"price" :"Price",
"bulk" :"bulk",
"stock" :"Stock",
"fulfill" :"Fulfill",
"listed" :"Listed",
"seller" :"Seller",
"error": "Trade site request failed",
"matched": "Matched: {0}",
"trade": "Trade",
"price": "Price",
"bulk": "bulk",
"stock": "Stock",
"fulfill": "Fulfill",
"listed": "Listed",
"seller": "Seller",
"item_level": "iLvl",
"gem_level": "Level",
"quality": "Quality",
@@ -274,7 +269,7 @@
"stack": "Stack"
},
"settings": {
"title": "Settings - Awakened PoE Trade",
"title": "Settings - Awakened PoE2 Trade2",
"language": "Language",
"private_league": "or Private League",
"account_name": "Account name",
@@ -283,9 +278,9 @@
"chat_cmd_send": "press Enter",
"no_key": "Not Set",
"clear_hotkey": "You can clear hotkey by pressing Backspace",
"overlay" :"Overlay",
"stash_scroll" :"Stash tab scrolling",
"delve_grid" :"Grid for Delve Chart",
"overlay": "Overlay",
"stash_scroll": "Stash tab scrolling",
"delve_grid": "Grid for Delve Chart",
"window_title": "PoE window title",
"thank_you": "App development continues thanks to:",
"hotkeys": "Hotkeys",
@@ -293,20 +288,20 @@
"general": "General",
"debug": "Debug",
"about": "About",
"font_size" :"Font size",
"overlay_bg" :"Background when the Overlay is clickable",
"overlay_bg_none" :"Transparent",
"overlay_bg_focus_game" :"Clicking on the background focuses the game",
"poe_log_file" :"PoE log file",
"poe_cfg_file" :"PoE config file",
"restore_clipboard" :"Restore clipboard",
"show_overlay_ready" :"Show a notification when the Overlay detects a PoE window",
"font_size": "Font size",
"overlay_bg": "Background when the Overlay is clickable",
"overlay_bg_none": "Transparent",
"overlay_bg_focus_game": "Clicking on the background focuses the game",
"poe_log_file": "PoE log file",
"poe_cfg_file": "PoE config file",
"restore_clipboard": "Restore clipboard",
"show_overlay_ready": "Show a notification when the Overlay detects a PoE window",
"debug_hotkeys": "Record all key presses"
},
"price_check": {
"name": "Price check",
"hotkey" :"Auto-hide Mode",
"hotkey_locked" :"Open without auto-hide",
"hotkey": "Auto-hide Mode",
"hotkey_locked": "Open without auto-hide",
"enable_browser": "Enable builtin browser",
"builtin_browser_warning": "I am aware that future releases can potentially contain malicious code that can steal my POESESSID.",
"highlight_hint": "Your items will be highlighted even if this setting is off",
@@ -322,4 +317,4 @@
"show_prediction": "Show price prediction",
"remember_currency": "Remember the Buyout Currency filter"
}
}
}

View File

@@ -5,7 +5,6 @@
"reopen_settings": "{0}을 눌러서 편집을 계속하세요.",
"seconds": "초",
"league": "리그",
"app": {
"leagues_loading": "리그를 불러오는 중\u2026",
"leagues_failed": "리그 불러오기를 실패하였습니다",
@@ -19,11 +18,9 @@
"report_bug": "버그 발생 시 GitHub에 보고해주세요",
"quit": "종료"
},
"map.mods.heist": "강탈",
"map.mods.outdated": "사용X",
"Support development on": "개발을 지원하세요",
"Blighted": "역병 걸린",
"Blight-ravaged": "역병에 유린당한",
"Magic": "매직",
@@ -37,7 +34,6 @@
"Anomalous": "기묘한",
"Divergent": "상이한",
"Phantasmal": "몽환적인",
"item": {
"prop_quality": "{0}% 퀄리티",
"base_percentile": "기본 백분위수: {0}%",
@@ -150,7 +146,6 @@
"hide_anointment": "Buyer will likely change anointment",
"hide_for_crafting": "Select only if price-checking as base item for crafting",
"hide_empty_mod": "Select only if item has 6 modifiers (1 of which is crafted) or if it has 5 modifiers",
"tag_implicit": "고정 속성",
"tag_fractured": "분열된",
"tag_crafted": "제작된",
@@ -271,7 +266,7 @@
"stack": "스택"
},
"settings": {
"title": "세팅 - Awakened PoE Trade",
"title": "세팅 - Awakened PoE2 Trade2",
"language": "언어",
"private_league": "개인리그",
"account_name": "계정명",
@@ -302,8 +297,8 @@
},
"price_check": {
"name": "Price check",
"hotkey" :"Auto-hide Mode",
"hotkey_locked" :"Open without auto-hide",
"hotkey": "Auto-hide Mode",
"hotkey_locked": "Open without auto-hide",
"enable_browser": "Enable builtin browser",
"builtin_browser_warning": "I am aware that future releases can potentially contain malicious code that can steal my POESESSID.",
"highlight_hint": "Your items will be highlighted even if this setting is off",

View File

@@ -19,14 +19,12 @@
"Select": "Выбрать",
"Not recognized modifier": "Нераспознанное свойство",
"Refresh": "Обновить",
"please_wait": "Пожалуйста, подождите\u2026",
"choose_file": "Выберите файл",
"app_is_ready": "Запущен и работает в фоновом режиме",
"reopen_settings": "Нажмите {0} чтобы продолжить редактирование.",
"seconds": "секунды",
"league": "Лига",
"app": {
"leagues_loading": "Загрузка лиг\u2026",
"leagues_failed": "Не удалось загрузить лиги",
@@ -40,11 +38,9 @@
"report_bug": "Сообщить о баге на GitHub",
"quit": "Выход"
},
"map.mods.heist": "кража",
"map.mods.outdated": "устарело",
"Support development on": "Поддержите разработку\u00A0на",
"Blighted": "Заражённая",
"Blight-ravaged": "Разорённая Скверной",
"Shaper": "Создатель",
@@ -57,7 +53,6 @@
"Anomalous": "Аномальный",
"Divergent": "Искривлённый",
"Phantasmal": "Фантомный",
"item": {
"prop_quality": "К-во: {0}%",
"base_percentile": "Ролл значений базы: {0}%",
@@ -168,7 +163,6 @@
"hide_anointment": "Покупатель, скорее всего, поменяет зачарование",
"hide_for_crafting": "Отмечайте, если проверяете цену в качестве базового предмета для крафта",
"hide_empty_mod": "Выбирайте, только если у предмета 6 свойств (1 из которых ремесленное) или если у него 5 свойств",
"tag_crafted": "мастер",
"tag_fractured": "расколотый",
"tag_scourge": "преображен",
@@ -268,15 +262,15 @@
"new_mods_icon": "Иконка у новых модов"
},
"trade_result": {
"error" :"Запрос к сайту не удался",
"matched" :"Найдено: {0}",
"trade" :"Трейд",
"price" :"Цена",
"bulk" :"опт",
"stock" :"Запас",
"fulfill" :"Сделки",
"listed" :"Выставлен",
"seller" :"Продавец",
"error": "Запрос к сайту не удался",
"matched": "Найдено: {0}",
"trade": "Трейд",
"price": "Цена",
"bulk": "опт",
"stock": "Запас",
"fulfill": "Сделки",
"listed": "Выставлен",
"seller": "Продавец",
"item_level": "Ур.",
"gem_level": "Уровень",
"quality": "К-во",
@@ -288,7 +282,7 @@
"stack": "Стак"
},
"settings": {
"title": "Настройки - Awakened PoE Trade",
"title": "Настройки - Awakened PoE2 Trade2",
"language": "Язык",
"private_league": "или Приватная лига",
"account_name": "Имя учетной записи",
@@ -297,9 +291,9 @@
"chat_cmd_send": "нажимать Enter",
"no_key": "Не назначено",
"clear_hotkey": "Вы можете отключить сочетание, нажав клавишу Backspace",
"overlay" :"Оверлей",
"stash_scroll" :"Прокрутка вкладок тайника",
"delve_grid" :"Сетка для \"Спуска\"",
"overlay": "Оверлей",
"stash_scroll": "Прокрутка вкладок тайника",
"delve_grid": "Сетка для \"Спуска\"",
"window_title": "Заголовок окна игры",
"thank_you": "Разработка приложения продолжается благодаря:",
"hotkeys": "Быстрые клавиши",
@@ -307,20 +301,20 @@
"general": "Общие",
"debug": "Debug",
"about": "О программе",
"font_size" :"Размер шрифта",
"overlay_bg" :"Фон, когда окно APT кликабельно",
"overlay_bg_none" :"Прозрачный",
"overlay_bg_focus_game" :"Нажатие по фону активирует окно игры",
"poe_log_file" :"Файл логов PoE",
"poe_cfg_file" :"Файл настроек PoE",
"restore_clipboard" :"Восстанавливать буфер обмена",
"show_overlay_ready" :"Показывать уведомление при открытии PoE",
"font_size": "Размер шрифта",
"overlay_bg": "Фон, когда окно APT кликабельно",
"overlay_bg_none": "Прозрачный",
"overlay_bg_focus_game": "Нажатие по фону активирует окно игры",
"poe_log_file": "Файл логов PoE",
"poe_cfg_file": "Файл настроек PoE",
"restore_clipboard": "Восстанавливать буфер обмена",
"show_overlay_ready": "Показывать уведомление при открытии PoE",
"debug_hotkeys": "Запись всех нажатий клавиш"
},
"price_check": {
"name": "Прайс-чек",
"hotkey" :"Режим авто-скрытия",
"hotkey_locked" :"Открыть без авто-скрытия",
"hotkey": "Режим авто-скрытия",
"hotkey_locked": "Открыть без авто-скрытия",
"enable_browser": "Включить встроенный браузер",
"builtin_browser_warning": "Я осознаю, что в будущие релизы могут потенциально содержать вредоносный код, который может украсть мой POESESSID.",
"highlight_hint": "Ваши предметы будут подсвечены, даже если эта настройка выключена",
@@ -336,4 +330,4 @@
"show_prediction": "Показывать приблизительную цену",
"remember_currency": "Запоминать фильтр \"Валюты выкупа\""
}
}
}

View File

@@ -1,96 +1,97 @@
export enum ItemCategory {
Map = 'Map',
CapturedBeast = 'Captured Beast',
MetamorphSample = 'Metamorph Sample',
Helmet = 'Helmet',
BodyArmour = 'Body Armour',
Gloves = 'Gloves',
Boots = 'Boots',
Shield = 'Shield',
Amulet = 'Amulet',
Belt = 'Belt',
Ring = 'Ring',
Flask = 'Flask',
AbyssJewel = 'Abyss Jewel',
Jewel = 'Jewel',
Quiver = 'Quiver',
Claw = 'Claw',
Bow = 'Bow',
Sceptre = 'Sceptre',
Wand = 'Wand',
FishingRod = 'Fishing Rod',
Staff = 'Staff',
Warstaff = 'Warstaff',
Dagger = 'Dagger',
RuneDagger = 'Rune Dagger',
OneHandedAxe = 'One-Handed Axe',
TwoHandedAxe = 'Two-Handed Axe',
OneHandedMace = 'One-Handed Mace',
TwoHandedMace = 'Two-Handed Mace',
OneHandedSword = 'One-Handed Sword',
TwoHandedSword = 'Two-Handed Sword',
ClusterJewel = 'Cluster Jewel',
HeistBlueprint = 'Heist Blueprint',
HeistContract = 'Heist Contract',
HeistTool = 'Heist Tool',
HeistBrooch = 'Heist Brooch',
HeistGear = 'Heist Gear',
HeistCloak = 'Heist Cloak',
Trinket = 'Trinket',
Invitation = 'Invitation',
Gem = 'Gem',
Currency = 'Currency',
DivinationCard = 'Divination Card',
Voidstone = 'Voidstone',
Sentinel = 'Sentinel',
MemoryLine = 'Memory Line',
SanctumRelic = 'Sanctum Relic',
Tincture = 'Tincture',
Charm = 'Charm',
Map = 'Map',
CapturedBeast = 'Captured Beast',
MetamorphSample = 'Metamorph Sample',
Helmet = 'Helmet',
BodyArmour = 'Body Armour',
Gloves = 'Gloves',
Boots = 'Boots',
Shield = 'Shield',
Amulet = 'Amulet',
Belt = 'Belt',
Ring = 'Ring',
Flask = 'Flask',
AbyssJewel = 'Abyss Jewel',
Jewel = 'Jewel',
Quiver = 'Quiver',
Claw = 'Claw',
Bow = 'Bow',
Sceptre = 'Sceptre',
Wand = 'Wand',
FishingRod = 'Fishing Rod',
Staff = 'Staff',
Warstaff = 'Warstaff',
Dagger = 'Dagger',
RuneDagger = 'Rune Dagger',
OneHandedAxe = 'One-Handed Axe',
TwoHandedAxe = 'Two-Handed Axe',
OneHandedMace = 'One-Handed Mace',
TwoHandedMace = 'Two-Handed Mace',
OneHandedSword = 'One-Handed Sword',
TwoHandedSword = 'Two-Handed Sword',
ClusterJewel = 'Cluster Jewel',
HeistBlueprint = 'Heist Blueprint',
HeistContract = 'Heist Contract',
HeistTool = 'Heist Tool',
HeistBrooch = 'Heist Brooch',
HeistGear = 'Heist Gear',
HeistCloak = 'Heist Cloak',
Trinket = 'Trinket',
Invitation = 'Invitation',
Gem = 'Gem',
Currency = 'Currency',
DivinationCard = 'Divination Card',
Voidstone = 'Voidstone',
Sentinel = 'Sentinel',
MemoryLine = 'Memory Line',
SanctumRelic = 'Sanctum Relic',
Tincture = 'Tincture',
Charm = 'Charm',
Crossbow = 'Crossbow',
}
export const WEAPON_ONE_HANDED_MELEE = new Set([
ItemCategory.OneHandedAxe,
ItemCategory.OneHandedMace,
ItemCategory.OneHandedSword,
ItemCategory.Claw,
ItemCategory.Dagger,
ItemCategory.RuneDagger,
ItemCategory.Sceptre
])
ItemCategory.OneHandedAxe,
ItemCategory.OneHandedMace,
ItemCategory.OneHandedSword,
ItemCategory.Claw,
ItemCategory.Dagger,
ItemCategory.RuneDagger,
ItemCategory.Sceptre,
]);
export const WEAPON_ONE_HANDED = new Set([
ItemCategory.Wand,
...WEAPON_ONE_HANDED_MELEE
])
ItemCategory.Wand,
...WEAPON_ONE_HANDED_MELEE,
]);
export const WEAPONE_TWO_HANDED_MELEE = new Set([
ItemCategory.TwoHandedAxe,
ItemCategory.TwoHandedMace,
ItemCategory.TwoHandedSword,
ItemCategory.Staff,
ItemCategory.Warstaff
])
ItemCategory.TwoHandedAxe,
ItemCategory.TwoHandedMace,
ItemCategory.TwoHandedSword,
ItemCategory.Staff,
ItemCategory.Warstaff,
]);
export const WEAPON = new Set([
ItemCategory.FishingRod,
ItemCategory.Bow,
...WEAPON_ONE_HANDED,
...WEAPONE_TWO_HANDED_MELEE
])
ItemCategory.FishingRod,
ItemCategory.Bow,
...WEAPON_ONE_HANDED,
...WEAPONE_TWO_HANDED_MELEE,
]);
export const ARMOUR = new Set([
ItemCategory.BodyArmour,
ItemCategory.Boots,
ItemCategory.Gloves,
ItemCategory.Helmet,
ItemCategory.Shield
])
ItemCategory.BodyArmour,
ItemCategory.Boots,
ItemCategory.Gloves,
ItemCategory.Helmet,
ItemCategory.Shield,
]);
export const ACCESSORY = new Set([
ItemCategory.Amulet,
ItemCategory.Belt,
ItemCategory.Ring,
ItemCategory.Trinket
// ItemCategory.Quiver
])
ItemCategory.Amulet,
ItemCategory.Belt,
ItemCategory.Ring,
ItemCategory.Trinket,
// ItemCategory.Quiver
]);

File diff suppressed because it is too large Load Diff

View File

@@ -1,97 +1,118 @@
import { computed, shallowRef, readonly } from 'vue'
import { createGlobalState } from '@vueuse/core'
import { AppConfig, poeWebApi } from '@/web/Config'
import { Host } from './IPC'
import { computed, shallowRef, readonly } from 'vue';
import { createGlobalState } from '@vueuse/core';
import { AppConfig, poeWebApi } from '@/web/Config';
import { Host } from './IPC';
// pc-ggg, pc-garena
// const PERMANENT_SC = ['Standard', '標準模式']
const PERMANENT_HC = ['Hardcore', '專家模式']
const PERMANENT_HC = ['Hardcore', '專家模式'];
interface ApiLeague {
id: string
event?: boolean
rules: Array<{ id: string }>
id: string;
event?: boolean;
rules: Array<{ id: string }>;
}
const DEFAULT_POE2_LEAGUES: ApiLeague[] = [
{ id: 'Standard', rules: [] },
{
id: 'Hardcore',
rules: [
{
id: 'Hardcore',
},
],
},
];
interface League {
id: string
isPopular: boolean
id: string;
isPopular: boolean;
}
export const useLeagues = createGlobalState(() => {
const isLoading = shallowRef(false)
const error = shallowRef<string | null>(null)
const tradeLeagues = shallowRef<League[]>([])
const isLoading = shallowRef(false);
const error = shallowRef<string | null>(null);
const tradeLeagues = shallowRef<League[]>([]);
const selectedId = computed<string | undefined>({
get () {
return (tradeLeagues.value.length)
? AppConfig().leagueId
: undefined
},
set (id) {
AppConfig().leagueId = id
}
})
const selectedId = computed<string | undefined>({
get() {
return tradeLeagues.value.length ? AppConfig().leagueId : undefined;
},
set(id) {
AppConfig().leagueId = id;
},
});
const selected = computed(() => {
const { leagueId } = AppConfig()
if (!tradeLeagues.value || !leagueId) return undefined
const listed = tradeLeagues.value.find(league => league.id === leagueId)
return {
id: leagueId,
realm: AppConfig().realm,
isPopular: !isPrivateLeague(leagueId) && Boolean(listed?.isPopular)
}
})
const selected = computed(() => {
const { leagueId } = AppConfig();
if (!tradeLeagues.value || !leagueId) return undefined;
const listed = tradeLeagues.value.find((league) => league.id === leagueId);
return {
id: leagueId,
realm: AppConfig().realm,
isPopular: !isPrivateLeague(leagueId) && Boolean(listed?.isPopular),
};
});
async function load () {
isLoading.value = true
error.value = null
async function load() {
isLoading.value = true;
error.value = null;
try {
const response = await Host.proxy(`${poeWebApi()}/api/leagues?type=main&realm=pc`)
if (!response.ok) throw new Error(JSON.stringify(Object.fromEntries(response.headers)))
const leagues: ApiLeague[] = await response.json()
tradeLeagues.value = leagues
.filter(league =>
!PERMANENT_HC.includes(league.id) &&
!league.rules.some(rule => rule.id === 'NoParties' ||
(rule.id === 'HardMode' && !league.event)))
.map(league => {
return { id: league.id, isPopular: true }
})
try {
const response = await Host.proxy(
`${poeWebApi()}/api/leagues?type=main&realm=pc`
);
if (!response.ok)
throw new Error(JSON.stringify(Object.fromEntries(response.headers)));
// const leagues: ApiLeague[] = await response.json();
const leagues: ApiLeague[] = DEFAULT_POE2_LEAGUES;
tradeLeagues.value = leagues
.filter(
(league) =>
!PERMANENT_HC.includes(league.id) &&
!league.rules.some(
(rule) =>
rule.id === 'NoParties' ||
(rule.id === 'HardMode' && !league.event)
)
)
.map((league) => {
return { id: league.id, isPopular: true };
});
const leagueIsAlive = tradeLeagues.value.some(league => league.id === selectedId.value)
if (!leagueIsAlive && !isPrivateLeague(selectedId.value ?? '')) {
if (tradeLeagues.value.length > 1) {
const TMP_CHALLENGE = 1
selectedId.value = tradeLeagues.value[TMP_CHALLENGE].id
} else {
const STANDARD = 0
selectedId.value = tradeLeagues.value[STANDARD].id
}
}
} catch (e) {
error.value = (e as Error).message
} finally {
isLoading.value = false
}
}
const leagueIsAlive = tradeLeagues.value.some(
(league) => league.id === selectedId.value
);
if (!leagueIsAlive && !isPrivateLeague(selectedId.value ?? '')) {
if (tradeLeagues.value.length > 1) {
const TMP_CHALLENGE = 1;
selectedId.value = tradeLeagues.value[TMP_CHALLENGE].id;
} else {
const STANDARD = 0;
selectedId.value = tradeLeagues.value[STANDARD].id;
}
}
} catch (e) {
error.value = (e as Error).message;
} finally {
isLoading.value = false;
}
}
return {
isLoading,
error,
selectedId,
selected,
list: readonly(tradeLeagues),
load
}
})
return {
isLoading,
error,
selectedId,
selected,
list: readonly(tradeLeagues),
load,
};
});
function isPrivateLeague (id: string) {
if (id.includes('Ruthless')) {
return true
}
return /\(PL\d+\)$/.test(id)
function isPrivateLeague(id: string) {
if (id.includes('Ruthless')) {
return true;
}
return /\(PL\d+\)$/.test(id);
}

View File

@@ -0,0 +1,13 @@
<template>
<div class="bg-orange-400 text-gray-900 text-center text-sm font-bold text-shadow-lg">
This is in BETA for POE2, things do not work as expected. Please report any issues.
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue'
export default defineComponent({
})
</script>

View File

@@ -1,10 +1,9 @@
<template>
<Widget :config="{ ...config, anchor }" move-handles="none" :removable="false" :inline-edit="false">
<template v-if="item">
<MapCheck v-if="isMapLike"
:item="item" :config="config.maps" />
<ItemInfo v-else
:item="item" />
<ConversionWarningBanner />
<MapCheck v-if="isMapLike" :item="item" :config="config.maps" />
<ItemInfo v-else :item="item" />
</template>
</Widget>
</template>
@@ -20,6 +19,7 @@ import type { ItemCheckWidget } from './widget.js'
import Widget from '../overlay/Widget.vue'
import MapCheck from '../map-check/MapCheck.vue'
import ItemInfo from './ItemInfo.vue'
import ConversionWarningBanner from "../conversion-warn-banner/ConversionWarningBanner.vue";
const props = defineProps<{
config: ItemCheckWidget

View File

@@ -1,42 +1,54 @@
import { Host } from '@/web/background/IPC'
import { AppConfig } from '@/web/Config'
import { ParsedItem, parseClipboard } from '@/parser'
import { Host } from '@/web/background/IPC';
import { AppConfig } from '@/web/Config';
import { ParsedItem, parseClipboard } from '@/parser';
const POEDB_LANGS = { 'en': 'us', 'ru': 'ru', 'cmn-Hant': 'tw', 'ko': 'kr' }
const POEDB_LANGS = { en: 'us', ru: 'ru', 'cmn-Hant': 'tw', ko: 'kr' };
export function registerActions () {
Host.onEvent('MAIN->CLIENT::item-text', (e) => {
if (!['open-wiki', 'open-craft-of-exile', 'open-poedb', 'search-similar'].includes(e.target)) return
const parsed = parseClipboard(e.clipboard)
if (!parsed.isOk()) return
export function registerActions() {
Host.onEvent('MAIN->CLIENT::item-text', (e) => {
if (
![
'open-wiki',
'open-craft-of-exile',
'open-poedb',
'search-similar',
].includes(e.target)
)
return;
const parsed = parseClipboard(e.clipboard);
if (!parsed.isOk()) return;
if (e.target === 'open-wiki') {
openWiki(parsed.value)
} else if (e.target === 'open-craft-of-exile') {
openCoE(parsed.value)
} else if (e.target === 'open-poedb') {
openPoedb(parsed.value)
} else if (e.target === 'search-similar') {
findSimilarItems(parsed.value)
}
})
if (e.target === 'open-wiki') {
openWiki(parsed.value);
} else if (e.target === 'open-craft-of-exile') {
openCoE(parsed.value);
} else if (e.target === 'open-poedb') {
openPoedb(parsed.value);
} else if (e.target === 'search-similar') {
findSimilarItems(parsed.value);
}
});
}
export function openWiki (item: ParsedItem) {
window.open(`https://www.poewiki.net/wiki/${item.info.refName}`)
export function openWiki(item: ParsedItem) {
window.open(`https://www.poe2wiki.net/wiki/${item.info.refName}`);
}
export function openPoedb (item: ParsedItem) {
window.open(`https://poedb.tw/${POEDB_LANGS[AppConfig().language]}/search?q=${item.info.refName}`)
export function openPoedb(item: ParsedItem) {
window.open(
`https://poe2db.tw/${POEDB_LANGS[AppConfig().language]}/search?q=${item.info.refName}`
);
}
export function openCoE (item: ParsedItem) {
const encodedClipboard = encodeURIComponent(item.rawText)
window.open(`https://craftofexile.com/?eimport=${encodedClipboard}`)
export function openCoE(item: ParsedItem) {
const encodedClipboard = encodeURIComponent(item.rawText);
window.open(
`https://craftofexile.com/?game=poe2&eimport=${encodedClipboard}`
);
}
export function findSimilarItems (item: ParsedItem) {
const text = JSON.stringify(item.info.name)
Host.sendEvent({
name: 'CLIENT->MAIN::user-action',
payload: { action: 'stash-search', text }
})
export function findSimilarItems(item: ParsedItem) {
const text = JSON.stringify(item.info.name);
Host.sendEvent({
name: 'CLIENT->MAIN::user-action',
payload: { action: 'stash-search', text },
});
}

View File

@@ -5,12 +5,8 @@
<div class="flex-1 text-center">{{ mapName }}</div>
<div class="ml-8 text-gray-400">{{ t('map_check.profile') }}</div>
<div class="flex gap-0.5">
<button
v-for="profile in profiles" :key="profile.text"
@click="profile.select"
:class="{ 'border border-gray-600': profile.active }"
class="w-6 bg-gray-800"
>{{ profile.text }}</button>
<button v-for="profile in profiles" :key="profile.text" @click="profile.select"
:class="{ 'border border-gray-600': profile.active }" class="w-6 bg-gray-800">{{ profile.text }}</button>
</div>
</div>
<FullscreenImage v-if="image" :src="image" style="height: auto;" />
@@ -18,10 +14,8 @@
{{ t('map_check.no_mods') }}
</div>
<div v-else class="py-2 flex flex-col">
<MapStatButton v-for="stat in mapStats" :key="stat.matcher"
:stat="stat" :config="config" />
<div v-for="stat of item.unknownModifiers" :key="stat.type + '/' + stat.text"
class="py-1 px-8">
<MapStatButton v-for="stat in mapStats" :key="stat.matcher" :stat="stat" :config="config" />
<div v-for="stat of item.unknownModifiers" :key="stat.type + '/' + stat.text" class="py-1 px-8">
<span class="text-orange-400">{{ t('Not recognized modifier') }} &mdash;</span> {{ stat.text }}
</div>
</div>

View File

@@ -1,11 +1,10 @@
<template>
<transition
enter-active-class="animate__animated animate__fadeIn"
<transition enter-active-class="animate__animated animate__fadeIn"
leave-active-class="animate__animated animate__backOutDown">
<div :class="$style.widget" v-if="show">
<div :class="$style.box">
<div class="py-2 px-4">
<div class="text-base">Awakened PoE Trade</div>
<div class="text-base">Awakened PoE2 Trade2</div>
<p>{{ t('app_is_ready') }}</p>
</div>
</div>

View File

@@ -1,12 +1,11 @@
<template>
<widget :config="config" :hideable="false" :removable="false" move-handles="corners" v-slot="{ isEditing }">
<div class="widget-default-style">
<ConversionWarningBanner />
<div class="p-1 flex gap-1 items-center text-base">
<template v-for="widget in widgets" :key="widget.wmId">
<button @click="toggle(widget)"
:class="widget.wmWants === 'show' ? 'border-gray-500' : 'border-gray-800'"
class="bg-gray-800 rounded text-gray-100 p-2 leading-none whitespace-nowrap border"
>
<button @click="toggle(widget)" :class="widget.wmWants === 'show' ? 'border-gray-500' : 'border-gray-800'"
class="bg-gray-800 rounded text-gray-100 p-2 leading-none whitespace-nowrap border">
<i v-if="widget.wmType === 'settings'" class="fas fa-cog align-bottom" />
<i v-else-if="widget.wmType === 'item-search'" class="fas fa-search align-bottom" />
<template v-else>{{ widget.wmTitle || `#${widget.wmId}` }}</template>
@@ -22,9 +21,12 @@
<!-- <button class="text-left hover:bg-gray-400 rounded px-1 whitespace-nowrap">Screen saver</button> -->
<!-- add widget -->
<div class="text-gray-600 text-sm px-1 select-none whitespace-nowrap">{{ t(':add') }}</div>
<button class="text-left hover:bg-gray-400 rounded px-1 whitespace-nowrap" @click="createOfType('timer')">{{ t('stopwatch.name') }}</button>
<button class="text-left hover:bg-gray-400 rounded px-1 whitespace-nowrap" @click="createOfType('stash-search')">{{ t('stash_search.name') }}</button>
<button class="text-left hover:bg-gray-400 rounded px-1 whitespace-nowrap" @click="createOfType('image-strip')">{{ t('image_strip.name') }}</button>
<button class="text-left hover:bg-gray-400 rounded px-1 whitespace-nowrap"
@click="createOfType('timer')">{{ t('stopwatch.name') }}</button>
<button class="text-left hover:bg-gray-400 rounded px-1 whitespace-nowrap"
@click="createOfType('stash-search')">{{ t('stash_search.name') }}</button>
<button class="text-left hover:bg-gray-400 rounded px-1 whitespace-nowrap"
@click="createOfType('image-strip')">{{ t('image_strip.name') }}</button>
<!-- <button class="text-left hover:bg-gray-400 rounded px-1 whitespace-nowrap" @click="createOfType('TODO')">Image</button> -->
</div>
</template>
@@ -34,8 +36,7 @@
<ui-toggle v-model="config.alwaysShow">{{ t(':always_show') }}</ui-toggle>
</div>
<div v-else class="px-1 pb-1">
<textarea class="px-2 py-1.5 bg-gray-700 rounded resize-none block"
rows="1" spellcheck="false"
<textarea class="px-2 py-1.5 bg-gray-700 rounded resize-none block" rows="1" spellcheck="false"
:placeholder="t(':price_check')" @input="handleItemPaste"></textarea>
</div>
</div>
@@ -50,16 +51,17 @@ import { Widget as IWidget, WidgetManager, WidgetMenu } from './interfaces'
import { Host } from '@/web/background/IPC'
import Widget from './Widget.vue'
import { useI18nNs } from '@/web/i18n'
import ConversionWarningBanner from '../conversion-warn-banner/ConversionWarningBanner.vue'
export default defineComponent({
components: { Widget, UiToggle, UiPopover },
components: { Widget, UiToggle, UiPopover, ConversionWarningBanner },
props: {
config: {
type: Object as PropType<WidgetMenu>,
required: true
}
},
setup (props) {
setup(props) {
const wm = inject<WidgetManager>('wm')!
const widgets = computed(() => {
@@ -77,17 +79,17 @@ export default defineComponent({
return {
t,
widgets,
createOfType (type: string) {
createOfType(type: string) {
wm.create(type)
},
toggle (widget: IWidget) {
toggle(widget: IWidget) {
if (widget.wmWants === 'hide') {
wm.show(widget.wmId)
} else {
wm.hide(widget.wmId)
}
},
handleItemPaste (e: Event) {
handleItemPaste(e: Event) {
const target = e.target as HTMLInputElement
const inputRect = target.getBoundingClientRect()
Host.selfDispatch({

View File

@@ -1,42 +1,24 @@
<template>
<div v-if="show" class="p-4 layout-column min-h-0">
<filter-name
:filters="itemFilters"
:item="item" />
<price-prediction v-if="showPredictedPrice" class="mb-4"
:item="item" />
<price-trend v-else
:item="item"
:filters="itemFilters" />
<filters-block
ref="filtersComponent"
:filters="itemFilters"
:stats="itemStats"
:item="item"
:presets="presets"
@preset="selectPreset"
@submit="doSearch = true" />
<trade-listing
v-if="tradeAPI === 'trade' && doSearch"
ref="tradeService"
:filters="itemFilters"
:stats="itemStats"
:item="item" />
<trade-bulk
v-if="tradeAPI === 'bulk' && doSearch"
ref="tradeService"
:filters="itemFilters"
<filter-name :filters="itemFilters" :item="item" />
<price-prediction v-if="showPredictedPrice" class="mb-4" :item="item" />
<price-trend v-else :item="item" :filters="itemFilters" />
<filters-block ref="filtersComponent" :filters="itemFilters" :stats="itemStats" :item="item" :presets="presets"
@preset="selectPreset" @submit="doSearch = true" />
<trade-listing v-if="tradeAPI === 'trade' && doSearch" ref="tradeService" :filters="itemFilters" :stats="itemStats"
:item="item" />
<trade-bulk v-if="tradeAPI === 'bulk' && doSearch" ref="tradeService" :filters="itemFilters" :item="item" />
<div v-if="!doSearch" class="flex justify-between items-center">
<div class="flex w-40" @mouseenter="handleSearchMouseenter">
<button class="btn" @click="doSearch = true" style="min-width: 5rem;">{{ t('Search') }}</button>
</div>
<trade-links v-if="tradeAPI === 'trade'"
:get-link="makeTradeLink" />
<trade-links v-if="tradeAPI === 'trade'" :get-link="makeTradeLink" />
</div>
<stack-value :filters="itemFilters" :item="item"/>
<stack-value :filters="itemFilters" :item="item" />
<div v-if="showSupportLinks" class="mt-auto border border-dashed p-2">
<div class="mb-1">{{ t('Support development on') }} <a href="https://patreon.com/awakened_poe_trade" class="inline-flex align-middle animate__animated animate__fadeInRight" target="_blank"><img class="inline h-5" src="/images/Patreon.svg"></a></div>
<div class="mb-1">{{ t('Support development on') }} <a href="https://patreon.com/awakened_poe_trade"
class="inline-flex align-middle animate__animated animate__fadeInRight" target="_blank"><img
class="inline h-5" src="/images/Patreon.svg"></a></div>
<i18n-t keypath="app.thanks_3rd_party" tag="div">
<a href="https://poeprices.info" target="_blank" class="bg-gray-900 px-1 rounded">poeprices.info</a>
<a href="https://poe.ninja/support" target="_blank" class="bg-gray-900 px-1 rounded">poe.ninja</a>
@@ -89,7 +71,7 @@ export default defineComponent({
required: true
}
},
setup (props) {
setup(props) {
const widget = computed(() => AppConfig<PriceCheckWidget>('price-check')!)
const leagues = useLeagues()
@@ -120,7 +102,7 @@ export default defineComponent({
})
if ((!props.advancedCheck && !widget.value.smartInitialSearch) ||
(props.advancedCheck && !widget.value.lockedInitialSearch)) {
(props.advancedCheck && !widget.value.lockedInitialSearch)) {
doSearch.value = false
} else {
doSearch.value = Boolean(
@@ -174,8 +156,8 @@ export default defineComponent({
const showPredictedPrice = computed(() => {
if (!widget.value.requestPricePrediction ||
AppConfig().language !== 'en' ||
!leagues.selected.value!.isPopular) return false
AppConfig().language !== 'en' ||
!leagues.selected.value!.isPopular) return false
if (presets.value.active === 'filters.preset_base_item') return false
@@ -195,7 +177,7 @@ export default defineComponent({
props.item.info.unique == null)
})
function handleSearchMouseenter (e: MouseEvent) {
function handleSearchMouseenter(e: MouseEvent) {
if ((filtersComponent.value.$el as HTMLElement).contains(e.relatedTarget as HTMLElement)) {
doSearch.value = true
@@ -234,11 +216,11 @@ export default defineComponent({
showSupportLinks,
presets: computed(() => presets.value.presets.map(preset =>
({ id: preset.id, active: (preset.id === presets.value.active) }))),
selectPreset (id: string) {
selectPreset(id: string) {
presets.value.active = id
},
makeTradeLink () {
return `https://${getTradeEndpoint()}/trade/search/${itemFilters.value.trade.league}?q=${JSON.stringify(createTradeRequest(itemFilters.value, itemStats.value, props.item))}`
makeTradeLink() {
return `https://${getTradeEndpoint()}/trade2/search/poe2/${itemFilters.value.trade.league}?q=${JSON.stringify(createTradeRequest(itemFilters.value, itemStats.value, props.item))}`
}
}
}

View File

@@ -1,24 +1,21 @@
<template>
<div
style="top: 0; left: 0; height: 100%; width: 100%; position: absolute;"
<div style="top: 0; left: 0; height: 100%; width: 100%; position: absolute;"
class="flex grow h-full pointer-events-none" :class="{
'flex-row': clickPosition === 'stash',
'flex-row-reverse': clickPosition === 'inventory',
}">
<div v-if="!isBrowserShown" class="layout-column shrink-0"
style="width: var(--game-panel);">
'flex-row': clickPosition === 'stash',
'flex-row-reverse': clickPosition === 'inventory',
}">
<div v-if="!isBrowserShown" class="layout-column shrink-0" 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;">
<ConversionWarningBanner />
<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>
</template>
<template #content>
<item-quick-price class="text-base"
:price="{ min: stableOrbCost, max: stableOrbCost, currency: 'chaos' }"
item-img="/images/divine.png"
/>
<item-quick-price class="text-base" :price="{ min: stableOrbCost, max: stableOrbCost, currency: 'chaos' }"
item-img="/images/divine.png" />
<div v-for="i in 9" :key="i">
<div class="pl-1">{{ i / 10 }} div {{ Math.round(stableOrbCost * i / 10) }} c</div>
</div>
@@ -29,8 +26,7 @@
</AppTitleBar>
<div class="grow layout-column min-h-0 bg-gray-800">
<background-info />
<check-position-circle v-if="showCheckPos"
:position="checkPosition" style="z-index: -1;" />
<check-position-circle v-if="showCheckPos" :position="checkPosition" style="z-index: -1;" />
<template v-if="item?.isErr()">
<ui-error-box class="m-4">
<template #name>{{ t(item.error.name) }}</template>
@@ -40,8 +36,7 @@
</template>
<template v-else-if="item?.isOk()">
<unidentified-resolver :item="item.value" @identify="handleIdentification($event)" />
<checked-item v-if="isLeagueSelected"
:item="item.value" :advanced-check="advancedCheck" />
<checked-item v-if="isLeagueSelected" :item="item.value" :advanced-check="advancedCheck" />
</template>
<div v-if="isBrowserShown" class="bg-gray-900 px-6 py-2 truncate">
<i18n-t keypath="app.toggle_browser_hint" tag="div">
@@ -50,16 +45,14 @@
</div>
</div>
</div>
<webview v-if="isBrowserShown" ref="iframeEl"
class="pointer-events-auto flex-1"
width="100%" height="100%" />
<webview v-if="isBrowserShown" ref="iframeEl" class="pointer-events-auto flex-1" width="100%" height="100%" />
<div v-else class="layout-column flex-1 min-w-0">
<div class="flex" :class="{
'flex-row': clickPosition === 'stash',
'flex-row-reverse': clickPosition === 'inventory'
}">
<related-items v-if="item?.isOk()" class="pointer-events-auto"
:item="item.value" :click-position="clickPosition" />
<related-items v-if="item?.isOk()" class="pointer-events-auto" :item="item.value"
:click-position="clickPosition" />
<rate-limiter-state class="pointer-events-auto" />
</div>
</div>
@@ -86,6 +79,7 @@ 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'
import ConversionWarningBanner from "../conversion-warn-banner/ConversionWarningBanner.vue";
type ParseError = { name: string; message: string; rawText: ParsedItem['rawText'] }
@@ -100,7 +94,8 @@ export default defineComponent({
CheckPositionCircle,
ItemQuickPrice,
UiErrorBox,
UiPopover
UiPopover,
ConversionWarningBanner
},
props: {
config: {
@@ -108,7 +103,7 @@ export default defineComponent({
required: true
}
},
setup (props) {
setup(props) {
const wm = inject<WidgetManager>('wm')!
const { xchgRate, initialLoading: xchgRateLoading, queuePricesFetch } = usePoeninja()
@@ -168,7 +163,7 @@ export default defineComponent({
}
})
function handleIdentification (identified: ParsedItem) {
function handleIdentification(identified: ParsedItem) {
item.value = ok(identified)
}
@@ -183,7 +178,7 @@ export default defineComponent({
})
const leagues = useLeagues()
const title = computed(() => leagues.selectedId.value || 'Awakened PoE Trade')
const title = computed(() => leagues.selectedId.value || 'Awakened PoE2 Trade2')
const stableOrbCost = computed(() => (xchgRate.value) ? Math.round(xchgRate.value) : null)
const isBrowserShown = computed(() => props.config.wmFlags.includes('has-browser'))
const overlayKey = computed(() => AppConfig().overlayKey)
@@ -196,7 +191,7 @@ export default defineComponent({
return checkPosition.value.x > (window.screenX + window.innerWidth / 2)
? 'inventory'
: 'stash'
// or {chat, vendor, center of screen}
// or {chat, vendor, center of screen}
}
})
@@ -210,7 +205,7 @@ export default defineComponent({
}
})
function closePriceCheck () {
function closePriceCheck() {
if (isBrowserShown.value || !Host.isElectron) {
wm.hide(props.config.wmId)
} else {
@@ -218,7 +213,7 @@ export default defineComponent({
}
}
function openLeagueSelection () {
function openLeagueSelection() {
const settings = wm.widgets.value.find(w => w.wmType === 'settings')!
wm.setFlag(settings.wmId, `settings:widget:${props.config.wmId}`, true)
wm.show(settings.wmId)
@@ -226,14 +221,14 @@ export default defineComponent({
const iframeEl = shallowRef<HTMLIFrameElement | null>(null)
function showBrowser (url: string) {
function showBrowser(url: string) {
wm.setFlag(props.config.wmId, 'has-browser', true)
nextTick(() => {
iframeEl.value!.src = url
})
}
function closeBrowser () {
function closeBrowser() {
wm.setFlag(props.config.wmId, 'has-browser', false)
}

View File

@@ -1,133 +1,144 @@
import { ParsedItem } from '@/parser'
import { useLeagues } from '@/web/background/Leagues'
import { Host } from '@/web/background/IPC'
import { Cache } from '../trade/Cache'
import { usePoeninja } from '@/web/background/Prices'
import { ParsedItem } from '@/parser';
import { useLeagues } from '@/web/background/Leagues';
import { Host } from '@/web/background/IPC';
import { Cache } from '../trade/Cache';
import { usePoeninja } from '@/web/background/Prices';
const cache = new Cache()
const cache = new Cache();
interface PoepricesApiResponse { /* eslint-disable camelcase */
currency: 'chaos' | 'divine' | 'exalt'
error: number
error_msg: string
warning_msg: string
max: number
min: number
pred_confidence_score: number
pred_explanation: Array<[string, number]>
interface PoepricesApiResponse {
/* eslint-disable camelcase */ currency: 'chaos' | 'divine' | 'exalt';
error: number;
error_msg: string;
warning_msg: string;
max: number;
min: number;
pred_confidence_score: number;
pred_explanation: Array<[string, number]>;
}
export interface RareItemPrice {
max: number
min: number
confidence: number
currency: 'chaos' | 'div'
explanation: Array<{
name: string
contrib: number
}>
max: number;
min: number;
confidence: number;
currency: 'chaos' | 'div';
explanation: Array<{
name: string;
contrib: number;
}>;
}
export async function requestPoeprices (item: ParsedItem): Promise<RareItemPrice> {
const query = querystring({
i: utf8ToBase64(transformItemText(item.rawText)),
l: useLeagues().selectedId.value,
s: 'awakened-poe-trade'
})
export async function requestPoeprices(
item: ParsedItem
): Promise<RareItemPrice> {
const query = querystring({
i: utf8ToBase64(transformItemText(item.rawText)),
l: useLeagues().selectedId.value,
s: 'awakened-poe-trade', // might be required name here
});
let data = cache.get<PoepricesApiResponse>(query)
if (!data) {
const response = await Host.proxy(`www.poeprices.info/api?${query}`)
try {
data = await response.json() as PoepricesApiResponse
} catch (e) {
throw new Error(`${response.status}, poeprices.info API is under load or down.`)
}
let data = cache.get<PoepricesApiResponse>(query);
if (!data) {
const response = await Host.proxy(`www.poeprices.info/api?${query}`);
try {
data = (await response.json()) as PoepricesApiResponse;
} catch (e) {
throw new Error(
`${response.status}, poeprices.info API is under load or down.`
);
}
if (data.error !== 0) {
throw new Error(data.error_msg)
}
if (data.error !== 0) {
throw new Error(data.error_msg);
}
cache.set<PoepricesApiResponse>(query, data, 300)
}
cache.set<PoepricesApiResponse>(query, data, 300);
}
if (data.currency === 'exalt') {
const { findPriceByQuery, autoCurrency } = usePoeninja()
const xchgExalted = findPriceByQuery({ ns: 'ITEM', name: 'Exalted Orb', variant: undefined })
if (!xchgExalted) {
throw new Error('poeprices.info gave the price in Exalted Orbs.')
}
const converted = autoCurrency([data.min * xchgExalted.chaos, data.max * xchgExalted.chaos])
data.min = converted.min
data.max = converted.max
data.currency = (converted.currency === 'div') ? 'divine' : 'chaos'
} else if (data.currency !== 'divine' && data.currency !== 'chaos') {
throw new Error('poeprices.info gave the price in unknown currency.')
}
if (data.currency === 'exalt') {
const { findPriceByQuery, autoCurrency } = usePoeninja();
const xchgExalted = findPriceByQuery({
ns: 'ITEM',
name: 'Exalted Orb',
variant: undefined,
});
if (!xchgExalted) {
throw new Error('poeprices.info gave the price in Exalted Orbs.');
}
const converted = autoCurrency([
data.min * xchgExalted.chaos,
data.max * xchgExalted.chaos,
]);
data.min = converted.min;
data.max = converted.max;
data.currency = converted.currency === 'div' ? 'divine' : 'chaos';
} else if (data.currency !== 'divine' && data.currency !== 'chaos') {
throw new Error('poeprices.info gave the price in unknown currency.');
}
return {
currency: (data.currency === 'divine') ? 'div' : 'chaos',
min: data.min,
max: data.max,
confidence: Math.round(data.pred_confidence_score),
explanation: data.pred_explanation.map(expl => ({
name: expl[0],
contrib: Math.round(expl[1] * 100)
}))
}
return {
currency: data.currency === 'divine' ? 'div' : 'chaos',
min: data.min,
max: data.max,
confidence: Math.round(data.pred_confidence_score),
explanation: data.pred_explanation.map((expl) => ({
name: expl[0],
contrib: Math.round(expl[1] * 100),
})),
};
}
export function getExternalLink (item: ParsedItem): string {
const query = querystring({
i: utf8ToBase64(transformItemText(item.rawText)),
l: useLeagues().selectedId.value,
s: 'awakened-poe-trade',
w: 1
})
return `https://www.poeprices.info/api?${query}`
export function getExternalLink(item: ParsedItem): string {
const query = querystring({
i: utf8ToBase64(transformItemText(item.rawText)),
l: useLeagues().selectedId.value,
s: 'awakened-poe-trade',
w: 1,
});
return `https://www.poeprices.info/api?${query}`;
}
export async function sendFeedback (
feedback: { text: string, option: 'fair' | 'low' | 'high' },
prediction: Pick<PoepricesApiResponse, 'min' | 'max' | 'currency'>,
item: ParsedItem
export async function sendFeedback(
feedback: { text: string; option: 'fair' | 'low' | 'high' },
prediction: Pick<PoepricesApiResponse, 'min' | 'max' | 'currency'>,
item: ParsedItem
): Promise<void> {
const body = new FormData()
body.append('selector', feedback.option)
body.append('feedbacktxt', feedback.text)
body.append('qitem_txt', utf8ToBase64(transformItemText(item.rawText)))
body.append('source', 'awakened-poe-trade')
body.append('min', String(prediction.min))
body.append('max', String(prediction.max))
body.append('currency', prediction.currency)
body.append('league', useLeagues().selectedId.value!)
// body.append('debug', String(1))
const body = new FormData();
body.append('selector', feedback.option);
body.append('feedbacktxt', feedback.text);
body.append('qitem_txt', utf8ToBase64(transformItemText(item.rawText)));
body.append('source', 'awakened-poe-trade');
body.append('min', String(prediction.min));
body.append('max', String(prediction.max));
body.append('currency', prediction.currency);
body.append('league', useLeagues().selectedId.value!);
// body.append('debug', String(1))
const response = await Host.proxy('www.poeprices.info/send_feedback', {
method: 'POST',
body
})
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const text = await response.text()
// console.assert(text === `"${feedback.option}"`)
const response = await Host.proxy('www.poeprices.info/send_feedback', {
method: 'POST',
body,
});
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const text = await response.text();
// console.assert(text === `"${feedback.option}"`)
}
function utf8ToBase64 (value: string) {
return btoa(unescape(encodeURIComponent(value)))
function utf8ToBase64(value: string) {
return btoa(unescape(encodeURIComponent(value)));
}
function querystring (q: Record<string, any>) {
return Object.entries(q)
.map(pair => pair.map(encodeURIComponent).join('='))
.join('&')
function querystring(q: Record<string, any>) {
return Object.entries(q)
.map((pair) => pair.map(encodeURIComponent).join('='))
.join('&');
}
/**
* @deprecated TODO blocked by poeprices.info not supporting advanced text
*/
function transformItemText (rawText: string) {
// this may not account for all cases
return rawText
.replace(/(?<=\d)(\([^)]+\))/gm, '')
.replace(/^\{.+\}$\n/gm, '')
function transformItemText(rawText: string) {
// this may not account for all cases
return rawText
.replace(/(?<=\d)(\([^)]+\))/gm, '')
.replace(/^\{.+\}$\n/gm, '');
}

View File

@@ -6,12 +6,14 @@
<span class="mr-1">{{ t(':matched') }}</span>
<span v-if="!result" class="text-gray-600">...</span>
<div v-else class="flex items-center">
<button class="btn flex items-center mr-1" :style="{ background: selectedCurr !== 'xchgChaos' ? 'transparent' : undefined }"
<button class="btn flex items-center mr-1"
:style="{ background: selectedCurr !== 'xchgChaos' ? 'transparent' : undefined }"
@click="selectedCurr = 'xchgChaos'">
<img src="/images/chaos.png" class="trade-bulk-currency-icon">
<span>{{ result.xchgChaos.listed.value?.total ?? '?' }}</span>
</button>
<button class="btn flex items-center mr-1" :style="{ background: selectedCurr !== 'xchgStable' ? 'transparent' : undefined }"
<button class="btn flex items-center mr-1"
:style="{ background: selectedCurr !== 'xchgStable' ? 'transparent' : undefined }"
@click="selectedCurr = 'xchgStable'">
<img src="/images/divine.png" class="trade-bulk-currency-icon">
<span>{{ result.xchgStable.listed.value?.total ?? '?' }}</span>
@@ -19,8 +21,7 @@
<span class="ml-1"><online-filter :filters="filters" /></span>
</div>
</div>
<trade-links v-if="result"
:get-link="makeTradeLink" />
<trade-links v-if="result" :get-link="makeTradeLink" />
</div>
<div class="layout-column overflow-y-auto overflow-x-hidden">
<table class="table-stripped w-full">
@@ -30,7 +31,10 @@
<div class="px-2">{{ t(':price') }}</div>
</th>
<th class="trade-table-heading">
<div class="pl-1 pr-2 flex text-xs" style="line-height: 1.3125rem;"><span class="w-8 inline-block text-right -ml-px mr-px">{{ (selectedCurr === 'xchgChaos') ? 'chaos' : 'div' }}</span><span>{{ '\u2009' }}/{{ '\u2009' }}</span><span class="w-8 inline-block">{{ t(':bulk') }}</span></div>
<div class="pl-1 pr-2 flex text-xs" style="line-height: 1.3125rem;"><span
class="w-8 inline-block text-right -ml-px mr-px">{{ (selectedCurr === 'xchgChaos') ? 'chaos' : 'div'
}}</span><span>{{ '\u2009' }}/{{ '\u2009' }}</span><span class="w-8 inline-block">{{ t(':bulk')
}}</span></div>
</th>
<th class="trade-table-heading">
<div class="px-1">{{ t(':stock') }}</div>
@@ -55,19 +59,25 @@
</tr>
<tr v-else :key="result.id">
<td class="px-2">{{ Number((result.exchangeAmount / result.itemAmount).toFixed(4)) }}</td>
<td class="pl-1 whitespace-nowrap"><span class="w-8 inline-block text-right">{{ result.exchangeAmount }}</span><span>{{ '\u2009' }}/{{ '\u2009' }}</span><span class="w-8 inline-block">{{ result.itemAmount }}</span></td>
<td class="pl-1 whitespace-nowrap"><span class="w-8 inline-block text-right">{{ result.exchangeAmount
}}</span><span>{{ '\u2009' }}/{{ '\u2009' }}</span><span class="w-8 inline-block">{{ result.itemAmount
}}</span></td>
<td class="px-1 text-right">{{ result.stock }}</td>
<td class="px-1 text-right"><i v-if="result.stock < result.itemAmount" class="fas fa-exclamation-triangle mr-1 text-gray-500"></i>{{ Math.floor(result.stock / result.itemAmount) }}</td>
<td class="px-1 text-right"><i v-if="result.stock < result.itemAmount"
class="fas fa-exclamation-triangle mr-1 text-gray-500"></i>{{ Math.floor(result.stock /
result.itemAmount) }}</td>
<td class="pr-2 pl-4 whitespace-nowrap">
<div class="inline-flex items-center">
<div class="account-status" :class="result.accountStatus"></div>
<div class="ml-1 font-sans text-xs">{{ result.relativeDate }}</div>
</div>
<span v-if="!showSeller && result.isMine" class="rounded px-1 text-gray-800 bg-gray-400 ml-1">{{ t('You') }}</span>
<span v-if="!showSeller && result.isMine" class="rounded px-1 text-gray-800 bg-gray-400 ml-1">{{
t('You') }}</span>
</td>
<td v-if="showSeller" class="px-2 whitespace-nowrap">
<span v-if="result.isMine" class="rounded px-1 text-gray-800 bg-gray-400">{{ t('You') }}</span>
<span v-else class="font-sans text-xs">{{ showSeller === 'ign' ? result.ign : result.accountName }}</span>
<span v-else class="font-sans text-xs">{{ showSeller === 'ign' ? result.ign : result.accountName
}}</span>
</td>
</tr>
</template>
@@ -101,7 +111,7 @@ import TradeLinks from './TradeLinks.vue'
const slowdown = artificialSlowdown(900)
function useBulkApi () {
function useBulkApi() {
type BulkSearchExtended = Record<'xchgChaos' | 'xchgStable', {
listed: Ref<BulkSearch | null>
listedLazy: ComputedRef<PricingResult[]>
@@ -111,7 +121,7 @@ function useBulkApi () {
const error = shallowRef<string | null>(null)
const result = shallowRef<BulkSearchExtended | null>(null)
async function search (item: ParsedItem, filters: ItemFilters) {
async function search(item: ParsedItem, filters: ItemFilters) {
try {
searchId += 1
error.value = null
@@ -123,8 +133,8 @@ function useBulkApi () {
const have = (item.info.refName === 'Chaos Orb')
? ['divine']
: (item.info.refName === 'Divine Orb')
? ['chaos']
: ['divine', 'chaos']
? ['chaos']
: ['divine', 'chaos']
const optimisticSearch = await execBulkSearch(
item, filters, have, { accountName: AppConfig().accountName })
@@ -139,7 +149,7 @@ function useBulkApi () {
}
}
function getResultsByHave (
function getResultsByHave(
item: ParsedItem,
filters: ItemFilters,
preloaded: Array<BulkSearch | null>,
@@ -154,7 +164,7 @@ function useBulkApi () {
const listedLazy = computed(() => {
if (!requested) {
;(async function () {
; (async function () {
try {
requested = true
_result.value = shallowReactive((await execBulkSearch(
@@ -193,7 +203,7 @@ export default defineComponent({
required: true
}
},
setup (props) {
setup(props) {
const widget = computed(() => AppConfig<PriceCheckWidget>('price-check')!)
const { error, result, search } = useBulkApi()
@@ -226,11 +236,11 @@ export default defineComponent({
}
})
function makeTradeLink (_have?: string[]) {
function makeTradeLink(_have?: string[]) {
const have = _have ?? ((selectedCurr.value === 'xchgStable') ? ['divine'] : ['chaos'])
const httpPostBody = createTradeRequest(props.filters, props.item, have)
const httpGetQuery = { exchange: httpPostBody.query }
return `https://${getTradeEndpoint()}/trade/exchange/${props.filters.trade.league}?q=${JSON.stringify(httpGetQuery)}`
return `https://${getTradeEndpoint()}/trade2/exchange/poe2/${props.filters.trade.league}?q=${JSON.stringify(httpGetQuery)}`
}
const { t } = useI18nNs('trade_result')
@@ -244,7 +254,7 @@ export default defineComponent({
execSearch: () => { search(props.item, props.filters) },
showSeller: computed(() => widget.value.showSeller),
makeTradeLink,
openTradeLink () {
openTradeLink() {
showBrowser(makeTradeLink(['mirror']))
}
}

View File

@@ -8,8 +8,7 @@
</div>
<online-filter v-if="list" :by-time="true" :filters="filters" />
<div class="flex-1"></div>
<trade-links v-if="list"
:get-link="makeTradeLink" />
<trade-links v-if="list" :get-link="makeTradeLink" />
</div>
<div class="layout-column overflow-y-auto overflow-x-hidden">
<table class="table-stripped w-full">
@@ -46,21 +45,28 @@
<td colspan="100" class="text-transparent">***</td>
</tr>
<tr v-else :key="result.id">
<td class="px-2 whitespace-nowrap"><span :class="{ 'line-through': result.priceCurrency === 'exalted' }">{{ result.priceAmount }} {{ result.priceCurrency }}</span> <span v-if="result.listedTimes > 2" class="rounded px-1 text-gray-800 bg-gray-400 -mr-2"><span class="font-sans">×</span> {{ result.listedTimes }}</span><i v-else-if="!result.hasNote" class="fas fa-question" /></td>
<td class="px-2 whitespace-nowrap"><span
:class="{ 'line-through': result.priceCurrency === 'exalted' }">{{ result.priceAmount }} {{
result.priceCurrency }}</span> <span v-if="result.listedTimes > 2"
class="rounded px-1 text-gray-800 bg-gray-400 -mr-2"><span class="font-sans">×</span> {{
result.listedTimes }}</span><i v-else-if="!result.hasNote" class="fas fa-question" /></td>
<td v-if="item.stackSize" class="px-2 text-right">{{ result.stackSize }}</td>
<td v-if="filters.itemLevel" class="px-2 whitespace-nowrap text-right">{{ result.itemLevel }}</td>
<td v-if="item.category === 'Gem'" class="pl-2 whitespace-nowrap">{{ result.level }}</td>
<td v-if="filters.quality || item.category === 'Gem'" class="px-2 whitespace-nowrap text-blue-400 text-right">{{ result.quality }}</td>
<td v-if="filters.quality || item.category === 'Gem'"
class="px-2 whitespace-nowrap text-blue-400 text-right">{{ result.quality }}</td>
<td class="pr-2 pl-4 whitespace-nowrap">
<div class="inline-flex items-center">
<div class="account-status" :class="result.accountStatus"></div>
<div class="ml-1 font-sans text-xs">{{ result.relativeDate }}</div>
</div>
<span v-if="!showSeller && result.isMine" class="rounded px-1 text-gray-800 bg-gray-400 ml-1">{{ t('You') }}</span>
<span v-if="!showSeller && result.isMine" class="rounded px-1 text-gray-800 bg-gray-400 ml-1">{{
t('You') }}</span>
</td>
<td v-if="showSeller" class="px-2 whitespace-nowrap">
<span v-if="result.isMine" class="rounded px-1 text-gray-800 bg-gray-400">{{ t('You') }}</span>
<span v-else class="font-sans text-xs">{{ showSeller === 'ign' ? result.ign : result.accountName }}</span>
<span v-else class="font-sans text-xs">{{ showSeller === 'ign' ? result.ign : result.accountName
}}</span>
</td>
</tr>
</template>
@@ -99,7 +105,7 @@ const API_FETCH_LIMIT = 100
const MIN_NOT_GROUPED = 7
const MIN_GROUPED = 10
function useTradeApi () {
function useTradeApi() {
let searchId = 0
const error = shallowRef<string | null>(null)
const searchResult = shallowRef<SearchResult | null>(null)
@@ -137,7 +143,7 @@ function useTradeApi () {
return out
})
async function search (filters: ItemFilters, stats: StatFilter[], item: ParsedItem) {
async function search(filters: ItemFilters, stats: StatFilter[], item: ParsedItem) {
try {
searchId += 1
error.value = null
@@ -168,7 +174,7 @@ function useTradeApi () {
}
let fetched = 20
async function fetchMore (): Promise<void> {
async function fetchMore(): Promise<void> {
if (_searchId !== searchId) return
const totalGrouped = groupedResults.value.length
const totalNotGrouped = groupedResults.value.reduce((len, res) =>
@@ -209,7 +215,7 @@ export default defineComponent({
required: true
}
},
setup (props) {
setup(props) {
const widget = computed(() => AppConfig<PriceCheckWidget>('price-check')!)
watch(() => props.item, (item) => {
@@ -220,10 +226,10 @@ export default defineComponent({
const showBrowser = inject<(url: string) => void>('builtin-browser')!
function makeTradeLink () {
function makeTradeLink() {
return (searchResult.value)
? `https://${getTradeEndpoint()}/trade/search/${props.filters.trade.league}/${searchResult.value.id}`
: `https://${getTradeEndpoint()}/trade/search/${props.filters.trade.league}?q=${JSON.stringify(createTradeRequest(props.filters, props.stats, props.item))}`
? `https://${getTradeEndpoint()}/trade2/search/poe2/${props.filters.trade.league}/${searchResult.value.id}`
: `https://${getTradeEndpoint()}/trade2/search/poe2/${props.filters.trade.league}?q=${JSON.stringify(createTradeRequest(props.filters, props.stats, props.item))}`
}
const { t } = useI18nNs('trade_result')
@@ -247,7 +253,7 @@ export default defineComponent({
error,
showSeller: computed(() => widget.value.showSeller),
makeTradeLink,
openTradeLink () {
openTradeLink() {
showBrowser(makeTradeLink())
}
}
@@ -261,7 +267,7 @@ export default defineComponent({
@apply bg-gray-800;
@apply p-0 m-0;
& > div {
&>div {
@apply border-b border-gray-700;
}
}
@@ -271,10 +277,14 @@ export default defineComponent({
height: 0.375rem;
border-radius: 100%;
&.online { /* */ }
&.online {
/* */
}
&.offline {
@apply bg-red-600;
}
&.afk {
@apply bg-orange-500;
}

View File

@@ -1,190 +1,225 @@
import { DateTime } from 'luxon'
import { Host } from '@/web/background/IPC'
import { TradeResponse, Account, getTradeEndpoint, RATE_LIMIT_RULES, adjustRateLimits, tradeTag, preventQueueCreation } from './common'
import { RateLimiter } from './RateLimiter'
import { ItemFilters } from '../filters/interfaces'
import { ParsedItem } from '@/parser'
import { Cache } from './Cache'
import { DateTime } from 'luxon';
import { Host } from '@/web/background/IPC';
import {
TradeResponse,
Account,
getTradeEndpoint,
RATE_LIMIT_RULES,
adjustRateLimits,
tradeTag,
preventQueueCreation,
} from './common';
import { RateLimiter } from './RateLimiter';
import { ItemFilters } from '../filters/interfaces';
import { ParsedItem } from '@/parser';
import { Cache } from './Cache';
interface TradeRequest { /* eslint-disable camelcase */
engine: 'new'
query: {
status: { option: 'online' | 'onlineleague' | 'any' }
have: string[]
want: string[]
minimum?: number
fulfillable?: null
}
sort: { have: 'asc' }
interface TradeRequest {
/* eslint-disable camelcase */ engine: 'new';
query: {
status: { option: 'online' | 'onlineleague' | 'any' };
have: string[];
want: string[];
minimum?: number;
fulfillable?: null;
};
sort: { have: 'asc' };
}
interface SearchResult {
id: string
result: Record<string, FetchResult>
total: number
id: string;
result: Record<string, FetchResult>;
total: number;
}
interface FetchResult {
id: string
listing: {
indexed: string
offers: Array<{
exchange: {
currency: string
amount: number
}
item: {
amount: number
stock: number
}
}>
account: Account
}
id: string;
listing: {
indexed: string;
offers: Array<{
exchange: {
currency: string;
amount: number;
};
item: {
amount: number;
stock: number;
};
}>;
account: Account;
};
}
export interface PricingResult {
id: string
relativeDate: string
exchangeAmount: number
itemAmount: number
stock: number
accountStatus: 'offline' | 'online' | 'afk'
isMine: boolean
accountName: string
ign: string
id: string;
relativeDate: string;
exchangeAmount: number;
itemAmount: number;
stock: number;
accountStatus: 'offline' | 'online' | 'afk';
isMine: boolean;
accountName: string;
ign: string;
}
const cache = new Cache()
const cache = new Cache();
async function requestTradeResultList (body: TradeRequest, leagueId: string): Promise<SearchResult> {
let data = cache.get<SearchResult>([body, leagueId])
async function requestTradeResultList(
body: TradeRequest,
leagueId: string
): Promise<SearchResult> {
let data = cache.get<SearchResult>([body, leagueId]);
if (!data) {
preventQueueCreation([
{ count: 1, limiters: RATE_LIMIT_RULES.EXCHANGE }
])
if (!data) {
preventQueueCreation([{ count: 1, limiters: RATE_LIMIT_RULES.EXCHANGE }]);
await RateLimiter.waitMulti(RATE_LIMIT_RULES.EXCHANGE)
await RateLimiter.waitMulti(RATE_LIMIT_RULES.EXCHANGE);
const response = await Host.proxy(`${getTradeEndpoint()}/api/trade/exchange/${leagueId}`, {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify(body)
})
adjustRateLimits(RATE_LIMIT_RULES.EXCHANGE, response.headers)
const response = await Host.proxy(
`${getTradeEndpoint()}/api/trade2/exchange/${leagueId}`,
{
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify(body),
}
);
adjustRateLimits(RATE_LIMIT_RULES.EXCHANGE, response.headers);
const _data = await response.json() as TradeResponse<SearchResult>
if (_data.error) {
throw new Error(_data.error.message)
} else {
data = _data
}
const _data = (await response.json()) as TradeResponse<SearchResult>;
if (_data.error) {
throw new Error(_data.error.message);
} else {
data = _data;
}
cache.set<SearchResult>([body, leagueId], data, Cache.deriveTtl(...RATE_LIMIT_RULES.EXCHANGE))
}
cache.set<SearchResult>(
[body, leagueId],
data,
Cache.deriveTtl(...RATE_LIMIT_RULES.EXCHANGE)
);
}
return data
return data;
}
function toPricingResult (
result: FetchResult,
opts: { accountName: string },
offer: number
function toPricingResult(
result: FetchResult,
opts: { accountName: string },
offer: number
): PricingResult {
return {
id: result.id,
relativeDate: DateTime.fromISO(result.listing.indexed).toRelative({ style: 'short' }) ?? '',
exchangeAmount: result.listing.offers[offer].exchange.amount,
itemAmount: result.listing.offers[offer].item.amount,
stock: result.listing.offers[offer].item.stock,
isMine: (result.listing.account.name === opts.accountName),
ign: result.listing.account.lastCharacterName,
accountName: result.listing.account.name,
accountStatus: result.listing.account.online
? (result.listing.account.online.status === 'afk' ? 'afk' : 'online')
: 'offline'
}
return {
id: result.id,
relativeDate:
DateTime.fromISO(result.listing.indexed).toRelative({ style: 'short' }) ??
'',
exchangeAmount: result.listing.offers[offer].exchange.amount,
itemAmount: result.listing.offers[offer].item.amount,
stock: result.listing.offers[offer].item.stock,
isMine: result.listing.account.name === opts.accountName,
ign: result.listing.account.lastCharacterName,
accountName: result.listing.account.name,
accountStatus: result.listing.account.online
? result.listing.account.online.status === 'afk'
? 'afk'
: 'online'
: 'offline',
};
}
export interface BulkSearch {
queryId: string
haveTag: string
total: number
listed: PricingResult[]
queryId: string;
haveTag: string;
total: number;
listed: PricingResult[];
}
export function createTradeRequest (filters: ItemFilters, item: ParsedItem, have: string[]): TradeRequest {
return {
engine: 'new',
query: {
have: have,
want: [tradeTag(item)!],
status: {
option: filters.trade.offline
? 'any'
: (filters.trade.onlineInLeague ? 'onlineleague' : 'online')
},
minimum: (filters.stackSize && !filters.stackSize.disabled) ? filters.stackSize.value : undefined
// fulfillable: null
},
sort: { have: 'asc' }
}
export function createTradeRequest(
filters: ItemFilters,
item: ParsedItem,
have: string[]
): TradeRequest {
return {
engine: 'new',
query: {
have: have,
want: [tradeTag(item)!],
status: {
option: filters.trade.offline
? 'any'
: filters.trade.onlineInLeague
? 'onlineleague'
: 'online',
},
minimum:
filters.stackSize && !filters.stackSize.disabled
? filters.stackSize.value
: undefined,
// fulfillable: null
},
sort: { have: 'asc' },
};
}
const SHOW_RESULTS = 20
const API_FETCH_LIMIT = 100
const SHOW_RESULTS = 20;
const API_FETCH_LIMIT = 100;
export async function execBulkSearch (
item: ParsedItem,
filters: ItemFilters,
have: string[],
opts: { accountName: string }
export async function execBulkSearch(
item: ParsedItem,
filters: ItemFilters,
have: string[],
opts: { accountName: string }
): Promise<Array<BulkSearch | null>> {
const query = await requestTradeResultList(
createTradeRequest(filters, item, have),
filters.trade.league
)
const query = await requestTradeResultList(
createTradeRequest(filters, item, have),
filters.trade.league
);
const offer = 0
const results = Object.values(query.result)
.filter(result => result.listing.offers.length === 1)
const offer = 0;
const results = Object.values(query.result).filter(
(result) => result.listing.offers.length === 1
);
const resultByHave = have.map(tradeTag => {
const resultsTag = results.filter(result => result.listing.offers[offer].exchange.currency === tradeTag)
const resultByHave = have.map((tradeTag) => {
const resultsTag = results.filter(
(result) => result.listing.offers[offer].exchange.currency === tradeTag
);
const loadedOnDemand = (
tradeTag === 'chaos' &&
resultsTag.length < SHOW_RESULTS &&
query.total > API_FETCH_LIMIT
)
if (loadedOnDemand) return null
const loadedOnDemand =
tradeTag === 'chaos' &&
resultsTag.length < SHOW_RESULTS &&
query.total > API_FETCH_LIMIT;
if (loadedOnDemand) return null;
const listed = resultsTag
.sort((a, b) =>
(a.listing.offers[offer].exchange.amount / a.listing.offers[offer].item.amount) -
(b.listing.offers[offer].exchange.amount / b.listing.offers[offer].item.amount))
.slice(0, SHOW_RESULTS)
.map(result => toPricingResult(result, opts, offer))
const listed = resultsTag
.sort(
(a, b) =>
a.listing.offers[offer].exchange.amount /
a.listing.offers[offer].item.amount -
b.listing.offers[offer].exchange.amount /
b.listing.offers[offer].item.amount
)
.slice(0, SHOW_RESULTS)
.map((result) => toPricingResult(result, opts, offer));
const chaosIsLoaded = (
tradeTag === 'divine' &&
resultsTag.length < results.length &&
((results.length - resultsTag.length) >= SHOW_RESULTS || query.total <= API_FETCH_LIMIT)
)
const chaosIsLoaded =
tradeTag === 'divine' &&
resultsTag.length < results.length &&
(results.length - resultsTag.length >= SHOW_RESULTS ||
query.total <= API_FETCH_LIMIT);
return {
queryId: query.id,
haveTag: tradeTag,
// this is a best guess when making request with multiple `have` currencies
total: (chaosIsLoaded)
? resultsTag.length
: (query.total - (results.length - resultsTag.length)),
listed: listed
}
})
return {
queryId: query.id,
haveTag: tradeTag,
// this is a best guess when making request with multiple `have` currencies
total: chaosIsLoaded
? resultsTag.length
: query.total - (results.length - resultsTag.length),
listed: listed,
};
});
return resultByHave
return resultByHave;
}

File diff suppressed because it is too large Load Diff

View File

@@ -28,7 +28,7 @@ export default defineComponent({
default: null
}
},
setup (props, ctx) {
setup(props, ctx) {
const identifiedVariants = computed(() => {
const baseType = props.item!.info.refName
const possible: BaseType[] = []
@@ -55,7 +55,7 @@ export default defineComponent({
!props.item.info.unique
})
function select (info: BaseType) {
function select(info: BaseType) {
const newItem: ParsedItem = {
...props.item!,
info: info

View File

@@ -1,51 +1,51 @@
<template>
<div>
<div :class="$style.podium" v-if="podiumVisible">
<div v-for="i in [2, 4, 5, 3, 1]">
<div v-for="patron in patrons[i - 1]" :key="patron.from"
:class="[$style.rating, $style[`rating-${patron.style}`]]"
>{{ patron.from }}{{ (patron.months > 1) ? ` x${patron.months}` : null }}</div>
</div>
</div>
<div :class="[$style.patronsHorizontal, { 'invisible': podiumVisible }]" :onMouseenter="showPodium">
<div class="bg-gray-800 rounded p-1 justify-center text-center w-44 shrink-0 flex items-center">
{{ t('settings.thank_you') }}
</div>
<div class="overflow-x-hidden whitespace-nowrap p-1 text-base">
<span :class="$style.patronsLine">{{ patronsString[0] }}</span><br>
<span :class="$style.patronsLine">{{ patronsString[1] }}</span>
</div>
</div>
<div :class="$style.window" class="grow layout-column" :onMouseenter="hidePodium">
<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">
<button v-if="item.type === 'menu-item'"
@click="item.select" :class="[$style['menu-item'], { [$style['active']]: item.isSelected }]">{{ item.name }}</button>
<div v-else
class="border-b mx-2 border-gray-800" />
</template>
<button v-if="menuItems.length >= 4"
:class="$style['quit-btn']" @click="quit">{{ t('app.quit') }}</button>
<div class="text-gray-400 text-center mt-auto pr-3 pt-4 pb-12" style="max-width: fit-content; min-width: 100%;">
<img class="mx-auto mb-1" src="/images/peepoLove2x.webp">
{{ t('Support development on') }}<br> <a href="https://patreon.com/awakened_poe_trade" class="inline-flex mt-1" target="_blank"><img class="inline h-5" src="/images/Patreon.svg"></a>
</div>
<div>
<div :class="$style.podium" v-if="podiumVisible">
<div v-for="i in [2, 4, 5, 3, 1]">
<div v-for="patron in patrons[i - 1]" :key="patron.from"
:class="[$style.rating, $style[`rating-${patron.style}`]]">{{ patron.from }}{{ (patron.months > 1) ? `
x${patron.months}` : null }}</div>
</div>
<div class="text-gray-100 grow layout-column bg-gray-900">
<div class="grow overflow-y-auto bg-gray-800 rounded-tl">
<component v-if="configClone"
:is="selectedComponent" :config="configClone" :configWidget="configWidget" />
</div>
<div :class="[$style.patronsHorizontal, { 'invisible': podiumVisible }]" :onMouseenter="showPodium">
<div class="bg-gray-800 rounded p-1 justify-center text-center w-44 shrink-0 flex items-center">
{{ t('settings.thank_you') }}
</div>
<div class="overflow-x-hidden whitespace-nowrap p-1 text-base">
<span :class="$style.patronsLine">{{ patronsString[0] }}</span><br>
<span :class="$style.patronsLine">{{ patronsString[1] }}</span>
</div>
</div>
<div :class="$style.window" class="grow layout-column" :onMouseenter="hidePodium">
<ConversionWarningBanner />
<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">
<button v-if="item.type === 'menu-item'" @click="item.select"
:class="[$style['menu-item'], { [$style['active']]: item.isSelected }]">{{ item.name }}</button>
<div v-else class="border-b mx-2 border-gray-800" />
</template>
<button v-if="menuItems.length >= 4" :class="$style['quit-btn']" @click="quit">{{ t('app.quit') }}</button>
<div class="text-gray-400 text-center mt-auto pr-3 pt-4 pb-12"
style="max-width: fit-content; min-width: 100%;">
<img class="mx-auto mb-1" src="/images/peepoLove2x.webp">
{{ t('Support development on') }}<br> <a href="https://patreon.com/awakened_poe_trade"
class="inline-flex mt-1" target="_blank"><img class="inline h-5" src="/images/Patreon.svg"></a>
</div>
</div>
<div class="border-t bg-gray-900 border-gray-600 p-2 flex justify-end gap-x-2">
<button @click="save" class="px-3 bg-gray-800 rounded">{{ t('Save') }}</button>
<button @click="cancel" class="px-3">{{ t('Cancel') }}</button>
<div class="text-gray-100 grow layout-column bg-gray-900">
<div class="grow overflow-y-auto bg-gray-800 rounded-tl">
<component v-if="configClone" :is="selectedComponent" :config="configClone" :configWidget="configWidget" />
</div>
<div class="border-t bg-gray-900 border-gray-600 p-2 flex justify-end gap-x-2">
<button @click="save" class="px-3 bg-gray-800 rounded">{{ t('Save') }}</button>
<button @click="cancel" class="px-3">{{ t('Cancel') }}</button>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script lang="ts">
@@ -67,8 +67,9 @@ import SettingsMaps from '../map-check/settings-maps.vue'
import SettingsStashSearch from '../stash-search/stash-search-editor.vue'
import SettingsStopwatch from './stopwatch.vue'
import SettingsItemSearch from '../item-search/settings-item-search.vue'
import ConversionWarningBanner from '../conversion-warn-banner/ConversionWarningBanner.vue'
function shuffle<T> (array: T[]): T[] {
function shuffle<T>(array: T[]): T[] {
let currentIndex = array.length
while (currentIndex !== 0) {
const randomIndex = Math.floor(Math.random() * currentIndex)
@@ -79,7 +80,7 @@ function shuffle<T> (array: T[]): T[] {
return array
}
function quit () {
function quit() {
Host.sendEvent({
name: 'CLIENT->MAIN::user-action',
payload: { action: 'quit' }
@@ -87,14 +88,14 @@ function quit () {
}
export default defineComponent({
components: { AppTitleBar },
components: { AppTitleBar, ConversionWarningBanner },
props: {
config: {
type: Object as PropType<Widget>,
required: true
}
},
setup (props) {
setup(props) {
const wm = inject<WidgetManager>('wm')!
const { t } = useI18n()
@@ -142,7 +143,7 @@ export default defineComponent({
menuByType(configWidget.value?.wmType)
.map(group => group.map(component => ({
name: t(component.name!),
select () { selectedComponent.value = component },
select() { selectedComponent.value = component },
isSelected: (selectedComponent.value === component),
type: 'menu-item' as const
}))),
@@ -151,14 +152,14 @@ export default defineComponent({
return {
t,
save () {
save() {
updateConfig(configClone.value!)
saveConfig()
pushHostConfig()
wm.hide(props.config.wmId)
},
cancel () {
cancel() {
wm.hide(props.config.wmId)
},
quit,
@@ -177,13 +178,13 @@ export default defineComponent({
})
}),
podiumVisible,
showPodium () { podiumVisible.value = true },
hidePodium () { podiumVisible.value = false }
showPodium() { podiumVisible.value = true },
hidePodium() { podiumVisible.value = false }
}
}
})
function menuByType (type?: string) {
function menuByType(type?: string) {
switch (type) {
case 'stash-search':
return [[SettingsStashSearch]]
@@ -205,7 +206,7 @@ function menuByType (type?: string) {
}
}
function flatJoin<T, J> (arr: T[][], joinEl: () => J) {
function flatJoin<T, J>(arr: T[][], joinEl: () => J) {
const out: Array<T | J> = []
for (const nested of arr) {
out.push(...nested)
@@ -218,13 +219,17 @@ function flatJoin<T, J> (arr: T[][], joinEl: () => J) {
<style lang="postcss" module>
.window {
position: absolute;
top: 0; bottom: 0; left: 0; right: 0;
top: 0;
bottom: 0;
left: 0;
right: 0;
margin: 0 auto;
max-width: 50rem;
max-height: 38rem;
overflow: hidden;
@apply bg-gray-800;
@apply rounded-b;
&:global {
animation-name: slideInDown;
animation-duration: 1s;
@@ -262,10 +267,13 @@ function flatJoin<T, J> (arr: T[][], joinEl: () => J) {
.patronsHorizontal {
@apply bg-gray-900 p-1 rounded gap-1;
position: absolute;
top: 40rem; left: 0; right: 0;
top: 40rem;
left: 0;
right: 0;
margin: 0 auto;
max-width: 50rem;
display: flex;
&:global {
animation-name: slideInDown;
animation-duration: 1s;
@@ -273,10 +281,19 @@ function flatJoin<T, J> (arr: T[][], joinEl: () => J) {
}
@keyframes slide {
0% { transform: translate(0%, 0); }
4% { transform: translate(0%, 0); }
100% { transform: translate(-99%, 0); }
0% {
transform: translate(0%, 0);
}
4% {
transform: translate(0%, 0);
}
100% {
transform: translate(-99%, 0);
}
}
.patronsLine {
display: inline-block;
animation: slide 64s linear infinite;
@@ -291,22 +308,40 @@ function flatJoin<T, J> (arr: T[][], joinEl: () => J) {
width: 100%;
justify-content: center;
@apply gap-4 p-4;
&:global {
animation-name: fadeIn;
animation-duration: 1.5s;
}
}
.podium > div {
.podium>div {
display: flex;
flex-wrap: wrap;
justify-content: center;
min-width: min-content;
}
.podium > div:nth-child(1) { max-width: 18rem; }
.podium > div:nth-child(2) { max-width: 16rem; }
.podium > div:nth-child(3) { flex-direction: column; align-items: center; }
.podium > div:nth-child(4) { max-width: 24rem; }
.podium > div:nth-child(5) { max-width: 18rem; }
.podium>div:nth-child(1) {
max-width: 18rem;
}
.podium>div:nth-child(2) {
max-width: 16rem;
}
.podium>div:nth-child(3) {
flex-direction: column;
align-items: center;
}
.podium>div:nth-child(4) {
max-width: 24rem;
}
.podium>div:nth-child(5) {
max-width: 18rem;
}
.rating {
min-width: 3rem;
@@ -314,30 +349,35 @@ function flatJoin<T, J> (arr: T[][], joinEl: () => J) {
white-space: nowrap;
@apply px-1 border;
}
.rating-1 {
background-color: rgb(0, 0, 0);
color: rgb(190, 178, 135);
border-color: currentColor;
@apply text-base;
}
.rating-2 {
background-color: rgb(210, 178, 135);
color: rgb(0, 0, 0);
border-color: currentColor;
@apply text-lg;
}
.rating-3 {
background-color: rgb(213, 159, 0);
color: rgb(0, 0, 0);
border-color: currentColor;
@apply text-lg;
}
.rating-4 {
background-color: rgb(240, 90, 35);
color: rgb(255, 255, 255);
border-color: currentColor;
@apply text-xl;
}
.rating-5 {
background-color: rgb(255, 255, 255);
color: rgb(255, 0, 0);

View File

@@ -2,24 +2,28 @@
<div class="p-2 flex flex-col h-full items-center">
<div class="flex flex-col items-center p-2 mb-4">
<img class="w-12 h-12" src="/images/TransferOrb.png">
<p class="text-base">Awakened PoE Trade</p>
<p class="text-base">Awakened PoE2 Trade2</p>
<p class="">{{ t('app.version', [version]) }}</p>
<div class="flex gap-2">
<a class="border-b" href="https://github.com/SnosMe/awakened-poe-trade/releases" target="_blank">{{ t('app.release_notes') }}</a>
<a class="border-b" href="https://github.com/SnosMe/awakened-poe-trade/issues" target="_blank">{{ t('app.report_bug') }}</a>
<a class="border-b" href="https://github.com/Kvan7/awakened-poe2-trade2/releases" target="_blank">{{
t('app.release_notes') }}</a>
<a class="border-b" href="https://github.com/Kvan7/awakened-poe2-trade2/issues" target="_blank">{{
t('app.report_bug') }}</a>
</div>
</div>
<div class="border border-gray-600 rounded p-2 whitespace-nowrap min-w-min w-72">
<p>{{ info.str1 }}</p>
<p>{{ info.str2 }}</p>
<button v-if="info.action" @click="info.action"
class="btn w-full mt-1">{{ info.actionText }}</button>
<button v-if="info.action" @click="info.action" class="btn w-full mt-1">{{ info.actionText }}</button>
</div>
<div class="text-center mt-auto py-8">
<p>{{ t('app.contact_me') }} <br><span class="font-sans text-gray-500 select-all">&lt;@295216259795124225&gt;</span></p>
<p>{{ t('app.contact_me') }} <br><span
class="font-sans text-gray-500 select-all">&lt;@295216259795124225&gt;</span></p>
<ul class="flex gap-4">
<li><img class="rounded inline" src="/images/dc_tft.gif"> <a class="border-b" href="https://discord.gg/tftrove" target="_blank">The Forbidden Trove</a></li>
<li><img class="rounded inline" src="/images/dc_reddit.png"> <a class="border-b" href="https://discord.gg/pathofexile" target="_blank">r/pathofexile</a></li>
<li><img class="rounded inline" src="/images/dc_tft.gif"> <a class="border-b" href="https://discord.gg/tftrove"
target="_blank">The Forbidden Trove</a></li>
<li><img class="rounded inline" src="/images/dc_reddit.png"> <a class="border-b"
href="https://discord.gg/pathofexile" target="_blank">r/pathofexile</a></li>
</ul>
</div>
</div>
@@ -31,32 +35,32 @@ import { useI18n } from 'vue-i18n'
import { Host } from '@/web/background/IPC'
import { DateTime } from 'luxon'
function checkForUpdates () {
function checkForUpdates() {
Host.sendEvent({
name: 'CLIENT->MAIN::user-action',
payload: { action: 'check-for-update' }
})
}
function openDownloadPage () {
function openDownloadPage() {
window.open('https://snosme.github.io/awakened-poe-trade/download')
}
function quitAndInstall () {
function quitAndInstall() {
Host.sendEvent({
name: 'CLIENT->MAIN::user-action',
payload: { action: 'update-and-restart' }
})
}
function fmtTime (millis: number) {
function fmtTime(millis: number) {
return DateTime.fromMillis(millis).toRelative({ style: 'long' }) ?? 'n/a'
}
export default defineComponent({
name: 'settings.about',
inheritAttrs: false,
setup () {
setup() {
const { t } = useI18n()
const info = computed(() => {

View File

@@ -28,12 +28,12 @@
<div class="mb-2">
<div class="flex-1 mb-1">{{ t(':poe_log_file') }}</div>
<input v-model.trim="clientLog"
class="rounded bg-gray-900 px-1 block w-full font-sans" placeholder="...?/Grinding Gear Games/Path of Exile/logs/Client.txt">
class="rounded bg-gray-900 px-1 block w-full font-sans" placeholder="...?/Grinding Gear Games/Path of Exile 2/logs/Client.txt">
</div>
<div class="mb-4">
<div class="flex-1 mb-1">{{ t(':poe_cfg_file') }}</div>
<input v-model.trim="gameConfig"
class="rounded bg-gray-900 px-1 block w-full font-sans" placeholder="...?/My Games/Path of Exile/production_Config.ini">
class="rounded bg-gray-900 px-1 block w-full font-sans" placeholder="...?/My Games/Path of Exile 2/production_Config.ini">
</div>
<hr class="mb-4 mx-8 border-gray-700">
<div class="mb-2">

3267
renderer/yarn.lock Normal file

File diff suppressed because it is too large Load Diff