Compare commits

...

35 Commits

Author SHA1 Message Date
kvan7
4b8e2ae8cf bump to prove im not (completely) insane 2024-12-12 06:20:22 -06:00
Kvan7
3450f39381 Update README.md 2024-12-11 21:08:34 -06:00
Kvan7
854887056e Finish updating branding (#26)
* remove debugging from proxy

* Removes leftover, fixed title oops

* bump for updating branding
2024-12-11 20:56:00 -06:00
kvan7
292e2de2dd fix lint 2024-12-11 17:23:42 -06:00
kvan7
3cda3a74b8 Add lots more testing code 2024-12-11 17:21:18 -06:00
kvan7
32215e5c03 fix name 2024-12-11 17:21:18 -06:00
kvan7
fd4917ca7f style(Update to 2): Update branding
Renamed to Exalted PoE2 Trade
2024-12-11 17:21:18 -06:00
kvan7
c3d6c69d9d change to node 18 2024-12-11 17:21:18 -06:00
Kvan7
0774814912 Update issue templates 2024-12-11 06:34:12 -06:00
kvan7
20b695b1b3 Fixes Config file name updated #14 2024-12-11 06:25:11 -06:00
kvan7
e0a3b34c22 Update Names 2024-12-10 23:46:20 -06:00
kvan7
ed2a93a82f add a lot more logging 2024-12-10 18:28:47 -06:00
kvan7
14ecde0c71 test(Update to 2): Test for more informative logs 2024-12-10 18:16:11 -06:00
Kvan7
dd91dea7b1 Merge pull request #11 from Kvan7:dev
Attempt at bugfix for leagues missing
2024-12-10 17:51:48 -06:00
kvan7
a6ace8e234 build(Update to 2): Version bump 2024-12-10 17:51:10 -06:00
kvan7
68ee7016d1 fix(Update to 2): Remove unused ref
removes unused imports that were added back for some reason
2024-12-10 17:42:43 -06:00
kvan7
46e70400b6 fix(Update to 2): Solving league bug
solve bug by commenting ocut problem code

uhhhh some other stuff may break
2024-12-10 17:42:43 -06:00
kvan7
4a37528500 Merge branch 'dev' of github.com:Kvan7/awakened-poe-trade into dev 2024-12-10 17:16:53 -06:00
kvan7
b36954d0a9 fix(Update to 2): Fixes linting yay
linters, am i right
2024-12-10 17:16:47 -06:00
Alexander Drozdov
932ebd3b44 switch main to npm too 2024-12-10 17:08:03 -06:00
Kvan7
e18ddda81a Update README.md 2024-12-09 22:33:09 -06:00
Kvan7
ef592e0cf4 Update README.md 2024-12-09 22:31:57 -06:00
kvan7
de5444fa04 docs: Update readme 2024-12-09 22:30:02 -06:00
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
71 changed files with 13682 additions and 9340 deletions

View File

@@ -0,0 +1,24 @@
---
name: Something Broken in PoE2
about: Use this for things that worked in PoE 1 and do not in PoE 2
title: "[PoE2]"
labels: bug
assignees: Kvan7
---
**Describe the problem**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem. This is very helpful for comparing the PoE1 vs 2 problems

View File

@@ -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
@@ -43,11 +43,11 @@ jobs:
with:
name: renderer-dist
path: ./renderer/dist
- run: yarn --frozen-lockfile
- run: npm ci
working-directory: ./main
- run: yarn build
- run: npm run build
working-directory: ./main
- run: yarn package -p onTagOrDraft
- run: npm run package "--" -p onTagOrDraft
working-directory: ./main
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
@@ -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,32 +11,32 @@ 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/exiled-exchange-2/blob/master/.github/workflows/main.yml)
Here's what that looks like as of 2023-12-03.
```shell
cd renderer
yarn install
yarn make-index-files
yarn dev
npm install
npm run make-index-files
npm run dev
# In a second shell
cd main
yarn install
yarn dev
npm install
npm run dev
```
# How to build
```shell
cd renderer
yarn install
yarn make-index-files
yarn build
npm install
npm run make-index-files
npm run build
cd ../main
yarn build
npm run build
# We want to sign with a distribution certificate to ensure other users can
# install without errors
CSC_NAME="Certificate name in Keychain" yarn package

View File

@@ -1,16 +1,27 @@
# ![Awakener's Orb](https://web.poecdn.com/image/Art/2DItems/Currency/TransferOrb.png) Awakened PoE Trade
# ![Exalted Orb](./renderer/public/images/exa.png) Exiled Exchange 2
[![](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)
## Moving from POE1/Awakened PoE Trade
1. Download latest release from [releases](https://github.com/Kvan7/exiled-exchange-2/releases)
- Currently only Windows is supported
- Only available as pre-release right now
2. Run installer
3. Run Exiled Exchange 2
4. Launch PoE2 to generate correct files
5. Copy `apt-data` from `%APPDATA%\awakened-poe-trade` to `%APPDATA%\exiled-exchange-2` to copy your previous settings
- Resulting directory structure should look like this:
- `%APPDATA%\exiled-exchange-2\apt-data\`
- `config.json`
6. Restart Exiled Exchange 2
➡ [Download for Windows & Linux](https://snosme.github.io/awakened-poe-trade/download) ⬅
#### Updating from 0.0.1 -> 0.0.10
Follow same steps as tranfering from PoE1, instead of copying from `%APPDATA%\awakened-poe-trade` to `%APPDATA%\exiled-exchange-2` instead copy from `%APPDATA%\awakened-poe2-trade` or `%APPDATA%\awakened-poe2-trade2` to `%APPDATA%\exiled-exchange-2`. After copying files feel free to run the uninstaller for `awakened-poe2-trade` to remove old executables, or delete `%APPDATA%\awakened-poe2-trade` and `Local\Programs\awakened-poe2-trade`
## 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
@@ -19,6 +30,7 @@ See [DEVELOPING.md](./DEVELOPING.md)
### Acknowledgments
- [awakened-poe-trade](https://github.com/SnosMe/awakened-poe-trade)
- [libuiohook](https://github.com/kwhat/libuiohook)
- [RePoE](https://github.com/brather1ng/RePoE)
- [poeprices.info](https://www.poeprices.info/)

View File

@@ -1,28 +0,0 @@
{
"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
}
}
}

View File

@@ -1,10 +1,10 @@
import { defineConfig } from 'vitepress'
const BASE = '/awakened-poe-trade/'
const BASE = '/exiled-exchange-2/'
export default defineConfig({
title: 'Awakened PoE Trade',
description: 'App for price-checking items in Path of Exile',
title: 'Exiled Exchange 2',
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/exiled-exchange-2/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/exiled-exchange-2'
}
],
sidebar: [

View File

@@ -8,15 +8,15 @@ import { useData } from 'vitepress'
const { theme } = useData()
</script>
You can download Awakened Poe Trade here. Any other mirrors are not known
You can download Exalted 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}/exiled-exchange-2-Setup-${theme.appVersion}.exe`">Windows 10+ (installer)</a> | ✔ | Fast |
| <a :href="`${theme.github.releasesUrl}/download/v${theme.appVersion}/exiled-exchange-2-${theme.appVersion}.exe`">Windows 10+ (portable)</a> | ❌ | Slower |
| <a :href="`${theme.github.releasesUrl}/download/v${theme.appVersion}/exiled-exchange-2-${theme.appVersion}.AppImage`">Linux (AppImage)</a> | ✔ | n/a |
| <a :href="`${theme.github.releasesUrl}/download/v${theme.appVersion}/exiled-exchange-2-${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 Exiled Exchange 2 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%\exiled-exchange-2`
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 Exiled Exchange 2.
*(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 Exiled Exchange 2 and show to you in a fancy way.
### Usage

View File

@@ -0,0 +1,33 @@
{
"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,
"editor.insertSpaces": true,
"conventionalCommits.scopes": [
"Update to 2"
]
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 841 KiB

After

Width:  |  Height:  |  Size: 712 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 791 B

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 91 KiB

After

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.1 KiB

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 280 KiB

After

Width:  |  Height:  |  Size: 240 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.6 KiB

After

Width:  |  Height:  |  Size: 9.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 353 KiB

After

Width:  |  Height:  |  Size: 66 KiB

BIN
main/build/icons/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

View File

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

5656
main/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,18 +1,20 @@
{
"name": "awakened-poe-trade",
"version": "3.25.102",
"name": "exiled-exchange-2",
"version": "0.0.11",
"private": true,
"scripts": {
"dev": "node build/script.mjs",
"build": "tsc --noEmit && node build/script.mjs --prod",
"package": "electron-builder build"
"package": "electron-builder build",
"lint": "eslint src",
"fix": "eslint src --fix"
},
"author": {
"name": "Alexander Drozdov"
"name": "Garrett Parker"
},
"repository": {
"type": "git",
"url": "https://github.com/SnosMe/awakened-poe-trade.git"
"url": "https://github.com/Kvan7/exiled-exchange-2.git"
},
"main": "dist/main.js",
"dependencies": {
@@ -25,15 +27,12 @@
"@types/ws": "^8.5.3",
"@wokwi/bmp-ts": "^3.0.0",
"comlink": "^4.3.1",
"electron": "31.3.1",
"electron-builder": "24.13.3",
"electron-updater": "^6.1.0",
"esbuild": "^0.23.0",
"ini": "^4.0.0",
"typescript": "5.5.x",
"electron": "33.2.1",
"electron-builder": "25.1.8",
"electron-updater": "^6.3.0",
"esbuild": "^0.24.0",
"ini": "^5.0.0",
"typescript": "5.6.x",
"ws": "^8.16.0"
},
"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(`Exiled Exchange 2 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\\poe2_production_Config.ini"
),
]
: process.platform === "linux"
? [
path.join(
app.getPath("documents"),
"My Games/Path of Exile 2/poe2_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/poe2_production_Config.ini"
),
]
: process.platform === "darwin"
? [
path.join(
app.getPath("appData"),
"Path of Exile 2/Preferences/poe2_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

@@ -22,7 +22,7 @@ export class HttpProxy {
const official = PROXY_HOSTS.find(entry => entry.host === host)?.official
if (official === undefined) return req.destroy()
for (const key in req.headers) {
if (key.startsWith('sec-') || key === 'host' || key === 'origin' || key === 'content-length') {
delete req.headers[key]
@@ -40,7 +40,6 @@ export class HttpProxy {
})
proxyReq.addListener('response', (proxyRes) => {
const resHeaders = { ...proxyRes.headers }
// `net.request` returns an already decoded body
delete resHeaders['content-encoding']
res.writeHead(proxyRes.statusCode, proxyRes.statusMessage, resHeaders)
;(proxyRes as unknown as NodeJS.ReadableStream).pipe(res)
@@ -49,6 +48,7 @@ export class HttpProxy {
logger.write(`error [cors-proxy] ${err.message} (${host})`)
res.destroy(err)
})
req.pipe(proxyReq as unknown as NodeJS.WritableStream)
})
}

View File

@@ -93,7 +93,7 @@ export async function startServer (
socket.on('close', () => {
const clients = websocketServer.clients
if (clients.size === 1) {
lastActiveClient = clients.values().next().value
lastActiveClient = clients.values().next().value!
evBus.emit('CLIENT->MAIN::used-recently', { isOverlay: true })
}
})

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 Exiled Exchange 2 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;
};
}

File diff suppressed because it is too large Load Diff

View File

@@ -4,12 +4,12 @@ module.exports = {
node: true
},
plugins: [
'@typescript-eslint'
'@typescript-eslint',
// 'only-warn'
],
extends: [
'plugin:vue/base',
'standard-with-typescript'
'standard-with-typescript',
],
rules: {
'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
@@ -36,7 +36,7 @@ module.exports = {
'import/no-duplicates': 'off',
'func-call-spacing': 'off',
// TODO: refactor IPC and enable
'@typescript-eslint/consistent-type-assertions': 'off'
'@typescript-eslint/consistent-type-assertions': 'off',
},
overrides: [{
files: ['src/main/**/*'],

View File

@@ -1,15 +1,18 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<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>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<meta name="color-scheme" content="dark">
<link rel="icon" href="/icon.ico">
<title>Exiled Exchange 2</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>

12786
renderer/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,62 +1,63 @@
{
"name": "awakened-poe-trade",
"version": "0.0.0",
"private": true,
"scripts": {
"dev": "vite",
"lint": "eslint --ext .ts,.vue src",
"build": "vue-tsc --noEmit && vite build",
"make-index-files": "node src/assets/make-index-files.mjs"
},
"dependencies": {
"@fortawesome/fontawesome-free": "6.x.x",
"@sindresorhus/fnv1a": "^3.0.0",
"@vueuse/core": "^11.0.0",
"animate.css": "^4.1.1",
"apexcharts": "^4.0.0",
"dot-prop": "9.x.x",
"fast-deep-equal": "^3.1.3",
"fastest-levenshtein": "^1.0.16",
"luxon": "3.x.x",
"neverthrow": "^8.0.0",
"object-hash": "^3.0.0",
"sockette": "^2.0.6",
"tailwindcss": "3.x.x",
"tippy.js": "^6.2.7",
"vue": "3.2.37",
"vue-i18n": "^10.0.0",
"vue3-apexcharts": "^1.1.1",
"vuedraggable": "4.1.0"
},
"devDependencies": {
"@types/luxon": "^3.0.0",
"@types/node": "^20.0.0",
"@types/object-hash": "^3.0.0",
"@vitejs/plugin-vue": "^4.0.0",
"autoprefixer": "^10.0.2",
"postcss": "^8.2.14",
"typescript": "5.6.x",
"vite": "^5.0.0",
"vue-tsc": "^2.0.0"
},
"optionalDependencies": {
"@typescript-eslint/eslint-plugin": "^5.0.0",
"@typescript-eslint/parser": "^5.0.0",
"eslint": "^8.21.0",
"eslint-config-standard-with-typescript": "^31.0.0",
"eslint-plugin-import": "^2.20.2",
"eslint-plugin-n": "^15.0.0",
"eslint-plugin-promise": "^6.0.0",
"eslint-plugin-vue": "^9.1.1"
},
"postcss": {
"plugins": {
"tailwindcss/nesting": {},
"tailwindcss": {},
"autoprefixer": {}
}
},
"browserslist": [
"chrome >= 101"
]
}
"name": "exiled-exchange-2",
"version": "0.0.0",
"private": true,
"scripts": {
"dev": "vite",
"lint": "eslint --ext .ts,.vue src",
"lint-fix": "eslint --ext .ts,.vue src --fix",
"build": "vue-tsc --noEmit && vite build",
"make-index-files": "node src/assets/make-index-files.mjs"
},
"dependencies": {
"@fortawesome/fontawesome-free": "6.x.x",
"@sindresorhus/fnv1a": "^3.0.0",
"@vueuse/core": "^11.0.0",
"animate.css": "^4.1.1",
"apexcharts": "^4.0.0",
"dot-prop": "9.x.x",
"fast-deep-equal": "^3.1.3",
"fastest-levenshtein": "^1.0.16",
"luxon": "3.x.x",
"neverthrow": "^8.0.0",
"object-hash": "^3.0.0",
"sockette": "^2.0.6",
"tailwindcss": "3.x.x",
"tippy.js": "^6.2.7",
"vue": "3.2.37",
"vue-i18n": "^10.0.0",
"vue3-apexcharts": "^1.1.1",
"vuedraggable": "4.1.0"
},
"devDependencies": {
"@types/luxon": "^3.0.0",
"@types/node": "^20.0.0",
"@types/object-hash": "^3.0.0",
"@vitejs/plugin-vue": "^4.0.0",
"autoprefixer": "^10.0.2",
"postcss": "^8.2.14",
"typescript": "5.6.x",
"vite": "^5.0.0",
"vue-tsc": "^2.0.0"
},
"optionalDependencies": {
"@typescript-eslint/eslint-plugin": "^5.0.0",
"@typescript-eslint/parser": "^5.0.0",
"eslint": "^8.21.0",
"eslint-config-standard-with-typescript": "^31.0.0",
"eslint-plugin-import": "^2.20.2",
"eslint-plugin-n": "^15.0.0",
"eslint-plugin-promise": "^6.0.0",
"eslint-plugin-vue": "^9.1.1"
},
"postcss": {
"plugins": {
"tailwindcss/nesting": {},
"tailwindcss": {},
"autoprefixer": {}
}
},
"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 - Exiled Exchange 2",
"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": "세팅 - Exiled Exchange 2",
"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": "Настройки - Exiled Exchange 2",
"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": "Запоминать фильтр \"Валюты выкупа\""
}
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 353 KiB

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.4 KiB

After

Width:  |  Height:  |  Size: 14 KiB

View File

@@ -299,8 +299,8 @@ function parseNamePlate (section: string[]) {
const item: ParserState = {
rarity: undefined,
category: undefined,
name: name,
baseType: baseType,
name,
baseType,
isUnidentified: false,
isCorrupted: false,
newMods: [],

View File

@@ -47,6 +47,7 @@ export enum ItemCategory {
SanctumRelic = 'Sanctum Relic',
Tincture = 'Tincture',
Charm = 'Charm',
Crossbow = 'Crossbow',
}
export const WEAPON_ONE_HANDED_MELEE = new Set([

View File

@@ -109,7 +109,7 @@ export function translateStatWithRoll (
calc.sources.some(s => s.stat.stat.ref === calc.stat.ref && s.stat.roll!.dp)
: undefined
return { string: translation.string, negate: translation.negate || false, dp: dp }
return { string: translation.string, negate: translation.negate || false, dp }
}
export enum ModifierType {

View File

@@ -16,7 +16,7 @@ export function AppConfig (type?: string) {
if (!type) {
return _config.value!
} else {
return _config.value!.widgets.find(w => w.wmType === type)
return _config.value!.widgets.find((w) => w.wmType === type)
}
}
@@ -27,7 +27,11 @@ export function updateConfig (updates: Config) {
export function saveConfig (opts?: { isTemporary: boolean }) {
const rawConfig = toRaw(_config.value!)
if (rawConfig.widgets.some(w => w.wmZorder === 'exclusive' && w.wmWants === 'show')) {
if (
rawConfig.widgets.some(
(w) => w.wmZorder === 'exclusive' && w.wmWants === 'show'
)
) {
return
}
@@ -72,9 +76,9 @@ export async function initConfig () {
// TODO
// dialog.showErrorBox(
// 'Awakened PoE Trade - Incompatible configuration',
// 'Exiled Exchange 2 - Incompatible configuration',
// // ----------------------
// 'You are trying to use an older version of Awakened PoE Trade with a newer incompatible configuration file.\n' +
// 'You are trying to use an older version of Exiled Exchange 2 with a newer incompatible configuration file.\n' +
// 'You need to install the latest version to continue using it.'
// )
}
@@ -85,12 +89,14 @@ export async function initConfig () {
export function poeWebApi () {
const { language, realm } = AppConfig()
switch (language) {
case 'en': return 'www.pathofexile.com'
case 'ru': return 'ru.pathofexile.com'
case 'cmn-Hant': return (realm === 'pc-garena')
? 'pathofexile.tw'
: 'www.pathofexile.com'
case 'ko': return 'poe.game.daum.net'
case 'en':
return 'www.pathofexile.com'
case 'ru':
return 'ru.pathofexile.com'
case 'cmn-Hant':
return realm === 'pc-garena' ? 'pathofexile.tw' : 'www.pathofexile.com'
case 'ko':
return 'poe.game.daum.net'
}
}
@@ -126,34 +132,41 @@ export const defaultConfig = (): Config => ({
overlayBackgroundClose: true,
restoreClipboard: false,
showAttachNotification: true,
commands: [{
text: '/hideout',
hotkey: 'F5',
send: true
}, {
text: '/exit',
hotkey: 'F9',
send: true
}, {
text: '@last ty',
hotkey: null,
send: true
}, {
text: '/invite @last',
hotkey: null,
send: true
}, {
text: '/tradewith @last',
hotkey: null,
send: true
}, {
text: '/hideout @last',
hotkey: null,
send: true
}],
commands: [
{
text: '/hideout',
hotkey: 'F5',
send: true
},
{
text: '/exit',
hotkey: 'F9',
send: true
},
{
text: '@last ty',
hotkey: null,
send: true
},
{
text: '/invite @last',
hotkey: null,
send: true
},
{
text: '/tradewith @last',
hotkey: null,
send: true
},
{
text: '/hideout @last',
hotkey: null,
send: true
}
],
clientLog: null,
gameConfig: null,
windowTitle: 'Path of Exile',
windowTitle: 'Path of Exile 2',
logKeys: false,
accountName: '',
stashScroll: true,
@@ -301,7 +314,12 @@ export const defaultConfig = (): Config => ({
{ id: 2, name: '', text: '"Divination Card"', hotkey: null },
{ id: 3, name: '', text: 'Fossil', hotkey: null },
{ id: 4, name: '', text: '"Map Tier"', hotkey: null },
{ id: 5, name: '', text: '"Map Device" "Rarity: Normal"', hotkey: null },
{
id: 5,
name: '',
text: '"Map Device" "Rarity: Normal"',
hotkey: null
},
{ id: 6, name: '', text: 'Tane Laboratory', hotkey: null }
]
} as StashSearchWidget,
@@ -317,47 +335,47 @@ export const defaultConfig = (): Config => ({
x: 50,
y: 10
},
images: [
{ id: 1, url: 'syndicate.jpg' }
]
images: [{ id: 1, url: 'syndicate.jpg' }]
} as widget.ImageStripWidget
]
})
function upgradeConfig (_config: Config): Config {
const config = _config as Omit<Config, 'widgets'> & { widgets: Array<Record<string, any>> }
const config = _config as Omit<Config, 'widgets'> & {
widgets: Array<Record<string, any>>
}
if (config.configVersion < 3) {
config.widgets.push({
...defaultConfig().widgets.find(w => w.wmType === 'image-strip')!,
wmId: Math.max(0, ...config.widgets.map(_ => _.wmId)) + 1,
...defaultConfig().widgets.find((w) => w.wmType === 'image-strip')!,
wmId: Math.max(0, ...config.widgets.map((_) => _.wmId)) + 1,
wmZorder: null
})
config.widgets.push({
...defaultConfig().widgets.find(w => w.wmType === 'delve-grid')!,
wmId: Math.max(0, ...config.widgets.map(_ => _.wmId)) + 1,
...defaultConfig().widgets.find((w) => w.wmType === 'delve-grid')!,
wmId: Math.max(0, ...config.widgets.map((_) => _.wmId)) + 1,
wmZorder: null
})
config.widgets.find(w => w.wmType === 'menu')!
.alwaysShow = false
config.widgets.find((w) => w.wmType === 'menu')!.alwaysShow = false
config.configVersion = 3
}
if (config.configVersion < 4) {
config.widgets.find(w => w.wmType === 'price-check')!
.chaosPriceThreshold = 0.05
config.widgets.find(
(w) => w.wmType === 'price-check'
)!.chaosPriceThreshold = 0.05
const mapCheck = config.widgets.find(w => w.wmType === 'map-check')!
;(mapCheck as any).selectedStats.forEach((e: any) => {
const mapCheck = config.widgets.find((w) => w.wmType === 'map-check')!;
(mapCheck as any).selectedStats.forEach((e: any) => {
e.matcher = e.matchRef
e.matchRef = undefined
})
{
const widgets = config.widgets.filter(w => w.wmType === 'image-strip')!
const widgets = config.widgets.filter((w) => w.wmType === 'image-strip')!
widgets.forEach((imgStrip: any) => {
imgStrip.images.forEach((e: any, idx: number) => {
e.id = idx
@@ -369,7 +387,7 @@ function upgradeConfig (_config: Config): Config {
}
if (config.configVersion < 5) {
config.commands.forEach(cmd => {
config.commands.forEach((cmd) => {
cmd.send = true
})
@@ -377,63 +395,68 @@ function upgradeConfig (_config: Config): Config {
}
if (config.configVersion < 6) {
config.widgets.find(w => w.wmType === 'price-check')!
.showRateLimitState = ((config as any).logLevel === 'debug')
config.widgets.find(w => w.wmType === 'price-check')!
.apiLatencySeconds = 2
config.widgets.find((w) => w.wmType === 'price-check')!.showRateLimitState =
(config as any).logLevel === 'debug'
config.widgets.find((w) => w.wmType === 'price-check')!.apiLatencySeconds =
2
config.configVersion = 6
}
if (config.configVersion < 7) {
const mapCheck = config.widgets.find(w => w.wmType === 'map-check')!
const mapCheck = config.widgets.find((w) => w.wmType === 'map-check')!
mapCheck.wmType = 'item-check'
mapCheck.maps = { selectedStats: mapCheck.selectedStats }
mapCheck.selectedStats = undefined
;(config as any).itemCheckKey = (config as any).mapCheckKey || null
;(config as any).mapCheckKey = undefined
mapCheck.selectedStats = undefined;
(config as any).itemCheckKey = (config as any).mapCheckKey || null;
(config as any).mapCheckKey = undefined
config.configVersion = 7
}
if (config.configVersion < 8) {
const itemCheck = config.widgets.find(w => w.wmType === 'item-check')!
;(itemCheck as ItemCheckWidget).maps.showNewStats = false
itemCheck.maps.selectedStats = (itemCheck as ItemCheckWidget).maps.selectedStats.map(entry => ({
const itemCheck = config.widgets.find((w) => w.wmType === 'item-check')!;
(itemCheck as ItemCheckWidget).maps.showNewStats = false
itemCheck.maps.selectedStats = (
itemCheck as ItemCheckWidget
).maps.selectedStats.map((entry) => ({
matcher: entry.matcher,
decision:
(entry as any).valueDanger ? 'danger'
: (entry as any).valueWarning ? 'warning'
: (entry as any).valueDesirable ? 'desirable'
: 'seen'
decision: (entry as any).valueDanger
? 'danger'
: (entry as any).valueWarning
? 'warning'
: (entry as any).valueDesirable
? 'desirable'
: 'seen'
}))
config.configVersion = 8
}
if (config.configVersion < 9) {
config.widgets.find(w => w.wmType === 'price-check')!
.collapseListings = 'api'
config.widgets.find((w) => w.wmType === 'price-check')!.collapseListings =
'api'
config.widgets.find(w => w.wmType === 'price-check')!
.smartInitialSearch = true
config.widgets.find(w => w.wmType === 'price-check')!
.lockedInitialSearch = true
config.widgets.find((w) => w.wmType === 'price-check')!.smartInitialSearch =
true
config.widgets.find(
(w) => w.wmType === 'price-check'
)!.lockedInitialSearch = true
config.widgets.find(w => w.wmType === 'price-check')!
.activateStockFilter = false
config.widgets.find(
(w) => w.wmType === 'price-check'
)!.activateStockFilter = false
config.configVersion = 9
}
if (config.configVersion < 10) {
config.widgets.push({
...defaultConfig().widgets.find(w => w.wmType === 'settings')!,
wmId: Math.max(0, ...config.widgets.map(_ => _.wmId)) + 1
...defaultConfig().widgets.find((w) => w.wmType === 'settings')!,
wmId: Math.max(0, ...config.widgets.map((_) => _.wmId)) + 1
})
const priceCheck = config.widgets.find(w => w.wmType === 'price-check')!
const priceCheck = config.widgets.find((w) => w.wmType === 'price-check')!
priceCheck.hotkey = (config as any).priceCheckKey
priceCheck.hotkeyHold = (config as any).priceCheckKeyHold
priceCheck.hotkeyLocked = (config as any).priceCheckLocked
@@ -449,22 +472,25 @@ function upgradeConfig (_config: Config): Config {
}
if (config.configVersion < 11) {
config.widgets.find(w => w.wmType === 'price-check')!
.requestPricePrediction = false
config.widgets.find(
(w) => w.wmType === 'price-check'
)!.requestPricePrediction = false
config.configVersion = 11
}
if (config.configVersion < 12) {
const afterSettings = config.widgets.findIndex(w => w.wmType === 'settings')
const afterSettings = config.widgets.findIndex(
(w) => w.wmType === 'settings'
)
config.widgets.splice(afterSettings + 1, 0, {
...defaultConfig().widgets.find(w => w.wmType === 'item-search')!,
...defaultConfig().widgets.find((w) => w.wmType === 'item-search')!,
wmWants: 'show',
wmId: Math.max(0, ...config.widgets.map(_ => _.wmId)) + 1
wmId: Math.max(0, ...config.widgets.map((_) => _.wmId)) + 1
})
config.realm = 'pc-ggg'
if (config.language === 'zh_TW' as string) {
if (config.language === ('zh_TW' as string)) {
config.language = 'cmn-Hant'
}
@@ -478,7 +504,9 @@ function upgradeConfig (_config: Config): Config {
}
if (config.configVersion < 14) {
const imgWidgets = config.widgets.filter(w => w.wmType === 'image-strip') as widget.ImageStripWidget[]
const imgWidgets = config.widgets.filter(
(w) => w.wmType === 'image-strip'
) as widget.ImageStripWidget[]
imgWidgets.forEach((imgStrip) => {
imgStrip.images.forEach((e) => {
e.url = e.url.startsWith('app-file://')
@@ -487,7 +515,9 @@ function upgradeConfig (_config: Config): Config {
})
})
const itemCheck = config.widgets.find(w => w.wmType === 'item-check') as ItemCheckWidget
const itemCheck = config.widgets.find(
(w) => w.wmType === 'item-check'
) as ItemCheckWidget
itemCheck.wikiKey = (config as any).wikiKey
itemCheck.poedbKey = null
itemCheck.craftOfExileKey = (config as any).craftOfExileKey
@@ -497,19 +527,29 @@ function upgradeConfig (_config: Config): Config {
}
if (config.configVersion < 15) {
const priceCheck = config.widgets.find(w => w.wmType === 'price-check') as widget.PriceCheckWidget
const priceCheck = config.widgets.find(
(w) => w.wmType === 'price-check'
) as widget.PriceCheckWidget
priceCheck.builtinBrowser = false
const itemSearch = config.widgets.find(w => w.wmType === 'item-search') as ItemSearchWidget
const itemSearch = config.widgets.find(
(w) => w.wmType === 'item-search'
) as ItemSearchWidget
itemSearch.ocrGemsKey = null
const itemCheck = config.widgets.find(w => w.wmType === 'item-check') as ItemCheckWidget
const itemCheck = config.widgets.find(
(w) => w.wmType === 'item-check'
) as ItemCheckWidget
itemCheck.maps.profile = 1
for (const stat of itemCheck.maps.selectedStats) {
const p1decision =
(stat.decision === 'danger') ? 'd'
: (stat.decision === 'warning') ? 'w'
: (stat.decision === 'desirable') ? 'g' : 's'
stat.decision === 'danger'
? 'd'
: stat.decision === 'warning'
? 'w'
: stat.decision === 'desirable'
? 'g'
: 's'
stat.decision = `${p1decision}--`
}
@@ -518,10 +558,14 @@ function upgradeConfig (_config: Config): Config {
}
if (config.configVersion < 16) {
const delve = config.widgets.find(w => w.wmType === 'delve-grid') as widget.DelveGridWidget
const delve = config.widgets.find(
(w) => w.wmType === 'delve-grid'
) as widget.DelveGridWidget
delve.toggleKey = (config as any).delveGridKey
const itemCheck = config.widgets.find(w => w.wmType === 'item-check') as ItemCheckWidget
const itemCheck = config.widgets.find(
(w) => w.wmType === 'item-check'
) as ItemCheckWidget
itemCheck.hotkey = (config as any).itemCheckKey
if (itemCheck.maps.profile === undefined) {
@@ -536,7 +580,9 @@ function upgradeConfig (_config: Config): Config {
config.logKeys = false
}
const priceCheck = config.widgets.find(w => w.wmType === 'price-check') as widget.PriceCheckWidget
const priceCheck = config.widgets.find(
(w) => w.wmType === 'price-check'
) as widget.PriceCheckWidget
if (priceCheck.rememberCurrency === undefined) {
priceCheck.rememberCurrency = false
}
@@ -616,7 +662,11 @@ function getConfigForHost (): HostConfig {
if (command.hotkey) {
actions.push({
shortcut: command.hotkey,
action: { type: 'paste-in-chat', text: command.text, send: command.send }
action: {
type: 'paste-in-chat',
text: command.text,
send: command.send
}
})
}
}

View File

@@ -1,7 +1,6 @@
import { computed, shallowRef, readonly } from 'vue'
import { createGlobalState } from '@vueuse/core'
import { AppConfig, poeWebApi } from '@/web/Config'
import { Host } from './IPC'
import { AppConfig } from '@/web/Config'
// pc-ggg, pc-garena
// const PERMANENT_SC = ['Standard', '標準模式']
@@ -23,11 +22,21 @@ export const useLeagues = createGlobalState(() => {
const error = shallowRef<string | null>(null)
const tradeLeagues = shallowRef<League[]>([])
const DEFAULT_POE2_LEAGUES: ApiLeague[] = [
{ id: 'Standard', rules: [] },
{
id: 'Hardcore',
rules: [
{
id: 'Hardcore'
}
]
}
]
const selectedId = computed<string | undefined>({
get () {
return (tradeLeagues.value.length)
? AppConfig().leagueId
: undefined
return tradeLeagues.value.length ? AppConfig().leagueId : undefined
},
set (id) {
AppConfig().leagueId = id
@@ -37,7 +46,7 @@ export const useLeagues = createGlobalState(() => {
const selected = computed(() => {
const { leagueId } = AppConfig()
if (!tradeLeagues.value || !leagueId) return undefined
const listed = tradeLeagues.value.find(league => league.id === leagueId)
const listed = tradeLeagues.value.find((league) => league.id === leagueId)
return {
id: leagueId,
realm: AppConfig().realm,
@@ -50,19 +59,30 @@ export const useLeagues = createGlobalState(() => {
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()
// 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 => {
.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)
const leagueIsAlive = tradeLeagues.value.some(
(league) => league.id === selectedId.value
)
if (!leagueIsAlive && !isPrivateLeague(selectedId.value ?? '')) {
if (tradeLeagues.value.length > 1) {
const TMP_CHALLENGE = 1

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

@@ -6,7 +6,14 @@ 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
if (
![
'open-wiki',
'open-craft-of-exile',
'open-poedb',
'search-similar'
].includes(e.target)
) { return }
const parsed = parseClipboard(e.clipboard)
if (!parsed.isOk()) return
@@ -23,14 +30,18 @@ export function registerActions () {
}
export function openWiki (item: ParsedItem) {
window.open(`https://www.poewiki.net/wiki/${item.info.refName}`)
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}`)
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}`)
window.open(
`https://craftofexile.com/?game=poe2&eimport=${encodedClipboard}`
)
}
export function findSimilarItems (item: ParsedItem) {

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">Exiled Exchange 2</div>
<p>{{ t('app_is_ready') }}</p>
</div>
</div>
@@ -26,7 +25,9 @@ const show = shallowRef(false)
Host.onEvent('MAIN->OVERLAY::overlay-attached', () => {
if (!show.value && AppConfig().showAttachNotification) {
show.value = true
setTimeout(() => { show.value = false }, 2500)
setTimeout(() => {
show.value = false
}, 2500)
}
})
</script>
@@ -52,7 +53,7 @@ Host.onEvent('MAIN->OVERLAY::overlay-attached', () => {
.box::before {
position: absolute;
content: '';
background: url('/images/TransferOrb.png') no-repeat top right/contain;
background: url('/images/exa.png') no-repeat top right/contain;
right: 100%;
width: 100%;
height: 100%;

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,9 +51,10 @@ 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>,

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>
@@ -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
@@ -238,7 +220,7 @@ export default defineComponent({
presets.value.active = id
},
makeTradeLink () {
return `https://${getTradeEndpoint()}/trade/search/${itemFilters.value.trade.league}?q=${JSON.stringify(createTradeRequest(itemFilters.value, itemStats.value, props.item))}`
return `https://${getTradeEndpoint()}/trade2/search/poe2/${itemFilters.value.trade.league}?q=${JSON.stringify(createTradeRequest(itemFilters.value, itemStats.value, props.item))}`
}
}
}

View File

@@ -1,26 +1,52 @@
<template>
<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);">
</div>
<div id="price-window" class="layout-column shrink-0 text-gray-200 pointer-events-auto" style="width: 28.75rem;">
<AppTitleBar @close="closePriceCheck" @click="openLeagueSelection" :title="title">
<ui-popover v-if="stableOrbCost" trigger="click" boundary="#price-window">
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)"
></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>
<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-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 class="pl-1">
{{ i / 10 }} div {{ Math.round((stableOrbCost * i) / 10) }} c
</div>
</div>
</template>
</ui-popover>
@@ -29,37 +55,61 @@
</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>
<p>{{ t(item.error.message) }}</p>
</ui-error-box>
<pre class="bg-gray-900 rounded m-4 overflow-x-hidden p-2">{{ item.error.rawText }}</pre>
<pre class="bg-gray-900 rounded m-4 overflow-x-hidden p-2">{{
item.error.rawText
}}</pre>
</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" />
<unidentified-resolver
:item="item.value"
@identify="handleIdentification($event)"
/>
<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">
<span class="bg-gray-400 text-gray-900 rounded px-1">{{ overlayKey }}</span>
<span class="bg-gray-400 text-gray-900 rounded px-1">{{
overlayKey
}}</span>
</i18n-t>
</div>
</div>
</div>
<webview v-if="isBrowserShown" ref="iframeEl"
<webview
v-if="isBrowserShown"
ref="iframeEl"
class="pointer-events-auto flex-1"
width="100%" height="100%" />
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" />
<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"
/>
<rate-limiter-state class="pointer-events-auto" />
</div>
</div>
@@ -86,8 +136,13 @@ 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'] }
type ParseError = {
name: string;
message: string;
rawText: ParsedItem['rawText'];
};
export default defineComponent({
components: {
@@ -100,7 +155,8 @@ export default defineComponent({
CheckPositionCircle,
ItemQuickPrice,
UiErrorBox,
UiPopover
UiPopover,
ConversionWarningBanner
},
props: {
config: {
@@ -127,9 +183,13 @@ export default defineComponent({
if (Host.isElectron && !e.focusOverlay) {
// everything in CSS pixels
const width = 28.75 * AppConfig().fontSize
const screenX = ((e.position.x - window.screenX) > window.innerWidth / 2)
? (window.screenX + window.innerWidth) - wm.poePanelWidth.value - width
: window.screenX + wm.poePanelWidth.value
const screenX =
e.position.x - window.screenX > window.innerWidth / 2
? window.screenX +
window.innerWidth -
wm.poePanelWidth.value -
width
: window.screenX + wm.poePanelWidth.value
MainProcess.sendEvent({
name: 'OVERLAY->MAIN::track-area',
payload: {
@@ -151,13 +211,18 @@ export default defineComponent({
checkPosition.value = e.position
advancedCheck.value = e.focusOverlay
item.value = (e.item ? ok(e.item as ParsedItem) : parseClipboard(e.clipboard))
.andThen(item => (
(item.category === ItemCategory.HeistContract && item.rarity !== ItemRarity.Unique) ||
(item.category === ItemCategory.Sentinel && item.rarity !== ItemRarity.Unique))
? err('item.unknown')
: ok(item))
.mapErr(err => ({
item.value = (
e.item ? ok(e.item as ParsedItem) : parseClipboard(e.clipboard)
)
.andThen((item) =>
(item.category === ItemCategory.HeistContract &&
item.rarity !== ItemRarity.Unique) ||
(item.category === ItemCategory.Sentinel &&
item.rarity !== ItemRarity.Unique)
? err('item.unknown')
: ok(item)
)
.mapErr((err) => ({
name: `${err}`,
message: `${err}_help`,
rawText: e.clipboard
@@ -176,14 +241,17 @@ export default defineComponent({
wm.hide(props.config.wmId)
})
watch(() => props.config.wmWants, (state) => {
if (state === 'hide') {
closeBrowser()
watch(
() => props.config.wmWants,
(state) => {
if (state === 'hide') {
closeBrowser()
}
}
})
)
const leagues = useLeagues()
const title = computed(() => leagues.selectedId.value || 'Awakened PoE Trade')
const title = computed(() => leagues.selectedId.value || 'Exiled Exchange 2')
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 +264,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}
}
})

View File

@@ -63,7 +63,7 @@ const stats = computed(() => {
if (!parsed.roll) {
return {
text: parsed.translation.string,
contribution: contribution,
contribution,
contributes: true
}
}

View File

@@ -176,7 +176,7 @@ export function createFilters (
}
filters.searchRelaxed = {
category: item.category,
disabled: disabled
disabled
}
}
}

View File

@@ -177,7 +177,7 @@ export function calculatedStatToFilter (
? FilterTag.Enchant
: FilterTag.Variant,
oils: decodeOils(calc),
sources: sources,
sources,
option: {
value: sources[0].contributes!.value
},
@@ -197,7 +197,7 @@ export function calculatedStatToFilter (
text: translation.string,
tag: (type as unknown) as FilterTag,
oils: decodeOils(calc),
sources: sources,
sources,
roll: undefined,
disabled: true
}
@@ -288,7 +288,7 @@ export function calculatedStatToFilter (
bounds: (item.rarity === ItemRarity.Unique && roll.min !== roll.max && calc.stat.better !== StatBetter.NotComparable)
? filterBounds
: undefined,
dp: dp,
dp,
isNegated: false,
tradeInvert: calc.stat.trade.inverted
}
@@ -427,10 +427,10 @@ function applyClusterJewelRules (filters: StatFilter[]) {
// 4 is [_, 5]
if (filter.roll!.value === 4) {
filter.roll!.max = 5
// 5 is [5, 5]
// 5 is [5, 5]
} else if (filter.roll!.value === 5) {
filter.roll!.min = filter.roll!.default.min
// 3, 6, 10, 11, 12 are [n, _]
// 3, 6, 10, 11, 12 are [n, _]
} else if (
filter.roll!.value === 3 ||
filter.roll!.value === 6 ||

View File

@@ -335,7 +335,7 @@ export function filterPseudo (ctx: FiltersCreationContext) {
const filter = calculatedStatToFilter({
stat: STAT_BY_REF(rule.pseudo)!,
type: ModifierType.Pseudo,
sources: sources
sources
}, ctx.searchInRange, ctx.item)
filter.disabled = rule.disabled ?? true

View File

@@ -257,7 +257,7 @@ function propToFilter (opts: {
better: StatBetter.PositiveRoll
}
const filter = calculatedStatToFilter({
stat: stat,
stat,
type: ModifierType.Pseudo,
sources: [{
modifier: {
@@ -265,7 +265,7 @@ function propToFilter (opts: {
stats: []
},
stat: {
stat: stat,
stat,
translation: stat.matchers[0],
roll: {
dp: opts.dp ?? false,

View File

@@ -6,8 +6,8 @@ import { usePoeninja } from '@/web/background/Prices'
const cache = new Cache()
interface PoepricesApiResponse { /* eslint-disable camelcase */
currency: 'chaos' | 'divine' | 'exalt'
interface PoepricesApiResponse {
/* eslint-disable camelcase */ currency: 'chaos' | 'divine' | 'exalt'
error: number
error_msg: string
warning_msg: string
@@ -28,20 +28,24 @@ export interface RareItemPrice {
}>
}
export async function requestPoeprices (item: ParsedItem): Promise<RareItemPrice> {
export async function requestPoeprices (
item: ParsedItem
): Promise<RareItemPrice> {
const query = querystring({
i: utf8ToBase64(transformItemText(item.rawText)),
l: useLeagues().selectedId.value,
s: 'awakened-poe-trade'
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
data = (await response.json()) as PoepricesApiResponse
} catch (e) {
throw new Error(`${response.status}, poeprices.info API is under load or down.`)
throw new Error(
`${response.status}, poeprices.info API is under load or down.`
)
}
if (data.error !== 0) {
@@ -53,24 +57,31 @@ export async function requestPoeprices (item: ParsedItem): Promise<RareItemPrice
if (data.currency === 'exalt') {
const { findPriceByQuery, autoCurrency } = usePoeninja()
const xchgExalted = findPriceByQuery({ ns: 'ITEM', name: 'Exalted Orb', variant: undefined })
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])
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'
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',
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 => ({
explanation: data.pred_explanation.map((expl) => ({
name: expl[0],
contrib: Math.round(expl[1] * 100)
}))
@@ -118,7 +129,7 @@ function utf8ToBase64 (value: string) {
function querystring (q: Record<string, any>) {
return Object.entries(q)
.map(pair => pair.map(encodeURIComponent).join('='))
.map((pair) => pair.map(encodeURIComponent).join('='))
.join('&')
}

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>
@@ -154,7 +164,7 @@ function useBulkApi () {
const listedLazy = computed(() => {
if (!requested) {
;(async function () {
; (async function () {
try {
requested = true
_result.value = shallowReactive((await execBulkSearch(
@@ -230,7 +240,7 @@ export default defineComponent({
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')

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>
@@ -222,8 +228,8 @@ export default defineComponent({
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')
@@ -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,13 +1,21 @@
import { DateTime } from 'luxon'
import { Host } from '@/web/background/IPC'
import { TradeResponse, Account, getTradeEndpoint, RATE_LIMIT_RULES, adjustRateLimits, tradeTag, preventQueueCreation } from './common'
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'
interface TradeRequest {
/* eslint-disable camelcase */ engine: 'new'
query: {
status: { option: 'online' | 'onlineleague' | 'any' }
have: string[]
@@ -56,34 +64,42 @@ export interface PricingResult {
const cache = new Cache()
async function requestTradeResultList (body: TradeRequest, leagueId: string): Promise<SearchResult> {
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 }
])
preventQueueCreation([{ count: 1, limiters: 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)
})
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>
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
@@ -96,15 +112,19 @@ function toPricingResult (
): PricingResult {
return {
id: result.id,
relativeDate: DateTime.fromISO(result.listing.indexed).toRelative({ style: 'short' }) ?? '',
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),
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')
? result.listing.account.online.status === 'afk'
? 'afk'
: 'online'
: 'offline'
}
}
@@ -116,18 +136,27 @@ export interface BulkSearch {
listed: PricingResult[]
}
export function createTradeRequest (filters: ItemFilters, item: ParsedItem, have: string[]): TradeRequest {
export function createTradeRequest (
filters: ItemFilters,
item: ParsedItem,
have: string[]
): TradeRequest {
return {
engine: 'new',
query: {
have: have,
have,
want: [tradeTag(item)!],
status: {
option: filters.trade.offline
? 'any'
: (filters.trade.onlineInLeague ? 'onlineleague' : 'online')
: filters.trade.onlineInLeague
? 'onlineleague'
: 'online'
},
minimum: (filters.stackSize && !filters.stackSize.disabled) ? filters.stackSize.value : undefined
minimum:
filters.stackSize && !filters.stackSize.disabled
? filters.stackSize.value
: undefined
// fulfillable: null
},
sort: { have: 'asc' }
@@ -149,40 +178,46 @@ export async function execBulkSearch (
)
const offer = 0
const results = Object.values(query.result)
.filter(result => result.listing.offers.length === 1)
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 = (
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))
.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))
.map((result) => toPricingResult(result, opts, offer))
const chaosIsLoaded = (
const chaosIsLoaded =
tradeTag === 'divine' &&
resultsTag.length < results.length &&
((results.length - resultsTag.length) >= SHOW_RESULTS || query.total <= API_FETCH_LIMIT)
)
(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)
total: chaosIsLoaded
? resultsTag.length
: (query.total - (results.length - resultsTag.length)),
listed: listed
: query.total - (results.length - resultsTag.length),
listed
}
})

View File

@@ -1,9 +1,21 @@
import { ItemInfluence, ItemCategory, ParsedItem, ItemRarity } from '@/parser'
import { ItemFilters, StatFilter, INTERNAL_TRADE_IDS, InternalTradeId } from '../filters/interfaces'
import {
ItemFilters,
StatFilter,
INTERNAL_TRADE_IDS,
InternalTradeId
} from '../filters/interfaces'
import { setProperty as propSet } from 'dot-prop'
import { DateTime } from 'luxon'
import { Host } from '@/web/background/IPC'
import { TradeResponse, Account, getTradeEndpoint, adjustRateLimits, RATE_LIMIT_RULES, preventQueueCreation } from './common'
import {
TradeResponse,
Account,
getTradeEndpoint,
adjustRateLimits,
RATE_LIMIT_RULES,
preventQueueCreation
} from './common'
import { STAT_BY_REF } from '@/assets/data'
import { RateLimiter } from './RateLimiter'
import { ModifierType } from '@/parser/modifiers'
@@ -62,11 +74,7 @@ const TOTAL_MODS_TEXT = {
'# Empty Prefix Modifiers',
'# Empty Suffix Modifiers'
],
TOTAL_MODIFIERS: [
'# Modifiers',
'# Prefix Modifiers',
'# Suffix Modifiers'
]
TOTAL_MODIFIERS: ['# Modifiers', '# Prefix Modifiers', '# Suffix Modifiers']
}
const INFLUENCE_PSEUDO_TEXT = {
@@ -78,10 +86,16 @@ const INFLUENCE_PSEUDO_TEXT = {
[ItemInfluence.Warlord]: 'Has Warlord Influence'
}
interface FilterBoolean { option?: 'true' | 'false' }
interface FilterRange { min?: number, max?: number }
interface FilterBoolean {
option?: 'true' | 'false'
}
interface FilterRange {
min?: number
max?: number
}
interface TradeRequest { /* eslint-disable camelcase */
interface TradeRequest {
/* eslint-disable camelcase */
query: {
status: { option: 'online' | 'onlineleague' | 'any' }
name?: string | { discriminator: string, option: string }
@@ -207,10 +221,10 @@ interface FetchResult {
properties?: Array<{
values: [[string, number]]
type:
78 | // Corpse Level (Filled Coffin)
30 | // Spawns a Level %0 Monster when Harvested
6 | // Quality
5 // Level
| 78 // Corpse Level (Filled Coffin)
| 30 // Spawns a Level %0 Monster when Harvested
| 6 // Quality
| 5 // Level
}>
note?: string
}
@@ -242,17 +256,21 @@ export interface PricingResult {
ign: string
}
export function createTradeRequest (filters: ItemFilters, stats: StatFilter[], item: ParsedItem) {
export function createTradeRequest (
filters: ItemFilters,
stats: StatFilter[],
item: ParsedItem
) {
const body: TradeRequest = {
query: {
status: {
option: filters.trade.offline
? 'any'
: (filters.trade.onlineInLeague ? 'onlineleague' : 'online')
: filters.trade.onlineInLeague
? 'onlineleague'
: 'online'
},
stats: [
{ type: 'and', filters: [] }
],
stats: [{ type: 'and', filters: [] }],
filters: {}
},
sort: {
@@ -262,20 +280,33 @@ export function createTradeRequest (filters: ItemFilters, stats: StatFilter[], i
const { query } = body
if (filters.trade.currency) {
propSet(query.filters, 'trade_filters.filters.price.option', filters.trade.currency)
propSet(
query.filters,
'trade_filters.filters.price.option',
filters.trade.currency
)
}
if (filters.trade.collapseListings === 'api') {
propSet(query.filters, 'trade_filters.filters.collapse.option', String(true))
propSet(
query.filters,
'trade_filters.filters.collapse.option',
String(true)
)
}
if (filters.trade.listed) {
propSet(query.filters, 'trade_filters.filters.indexed.option', filters.trade.listed)
propSet(
query.filters,
'trade_filters.filters.indexed.option',
filters.trade.listed
)
}
const activeSearch = (filters.searchRelaxed && !filters.searchRelaxed.disabled)
? filters.searchRelaxed
: filters.searchExact
const activeSearch =
filters.searchRelaxed && !filters.searchRelaxed.disabled
? filters.searchRelaxed
: filters.searchExact
if (activeSearch.nameTrade) {
query.name = nameToQuery(activeSearch.nameTrade, filters)
@@ -292,7 +323,11 @@ export function createTradeRequest (filters: ItemFilters, stats: StatFilter[], i
if (filters.foil && !filters.foil.disabled) {
propSet(query.filters, 'type_filters.filters.rarity.option', 'uniquefoil')
} else if (filters.rarity) {
propSet(query.filters, 'type_filters.filters.rarity.option', filters.rarity.value)
propSet(
query.filters,
'type_filters.filters.rarity.option',
filters.rarity.value
)
}
if (activeSearch.category) {
@@ -305,85 +340,166 @@ export function createTradeRequest (filters: ItemFilters, stats: StatFilter[], i
}
if (filters.corrupted?.value === false || filters.corrupted?.exact) {
propSet(query.filters, 'misc_filters.filters.corrupted.option', String(filters.corrupted.value))
propSet(
query.filters,
'misc_filters.filters.corrupted.option',
String(filters.corrupted.value)
)
}
if (filters.fractured?.value === false) {
propSet(query.filters, 'misc_filters.filters.fractured_item.option', String(false))
propSet(
query.filters,
'misc_filters.filters.fractured_item.option',
String(false)
)
}
if (filters.mirrored) {
if (filters.mirrored.disabled) {
propSet(query.filters, 'misc_filters.filters.mirrored.option', String(false))
propSet(
query.filters,
'misc_filters.filters.mirrored.option',
String(false)
)
}
} else if (
item.rarity === ItemRarity.Normal ||
item.rarity === ItemRarity.Magic ||
item.rarity === ItemRarity.Rare
) {
propSet(query.filters, 'misc_filters.filters.mirrored.option', String(false))
propSet(
query.filters,
'misc_filters.filters.mirrored.option',
String(false)
)
}
if (filters.gemLevel && !filters.gemLevel.disabled) {
propSet(query.filters, 'misc_filters.filters.gem_level.min', filters.gemLevel.value)
propSet(
query.filters,
'misc_filters.filters.gem_level.min',
filters.gemLevel.value
)
}
if (filters.quality && !filters.quality.disabled) {
propSet(query.filters, 'misc_filters.filters.quality.min', filters.quality.value)
propSet(
query.filters,
'misc_filters.filters.quality.min',
filters.quality.value
)
}
if (filters.itemLevel && !filters.itemLevel.disabled) {
propSet(query.filters, 'misc_filters.filters.ilvl.min', filters.itemLevel.value)
propSet(
query.filters,
'misc_filters.filters.ilvl.min',
filters.itemLevel.value
)
if (filters.itemLevel.max) {
propSet(query.filters, 'misc_filters.filters.ilvl.max', filters.itemLevel.max)
propSet(
query.filters,
'misc_filters.filters.ilvl.max',
filters.itemLevel.max
)
}
}
if (filters.stackSize && !filters.stackSize.disabled) {
propSet(query.filters, 'misc_filters.filters.stack_size.min', filters.stackSize.value)
propSet(
query.filters,
'misc_filters.filters.stack_size.min',
filters.stackSize.value
)
}
if (filters.linkedSockets && !filters.linkedSockets.disabled) {
propSet(query.filters, 'socket_filters.filters.links.min', filters.linkedSockets.value)
propSet(
query.filters,
'socket_filters.filters.links.min',
filters.linkedSockets.value
)
}
if (filters.whiteSockets && !filters.whiteSockets.disabled) {
propSet(query.filters, 'socket_filters.filters.sockets.w', filters.whiteSockets.value)
propSet(
query.filters,
'socket_filters.filters.sockets.w',
filters.whiteSockets.value
)
}
if (filters.mapTier && !filters.mapTier.disabled) {
propSet(query.filters, 'map_filters.filters.map_tier.min', filters.mapTier.value)
propSet(query.filters, 'map_filters.filters.map_tier.max', filters.mapTier.value)
propSet(
query.filters,
'map_filters.filters.map_tier.min',
filters.mapTier.value
)
propSet(
query.filters,
'map_filters.filters.map_tier.max',
filters.mapTier.value
)
}
if (filters.mapBlighted) {
if (filters.mapBlighted.value === 'Blighted') {
propSet(query.filters, 'map_filters.filters.map_blighted.option', String(true))
propSet(
query.filters,
'map_filters.filters.map_blighted.option',
String(true)
)
} else if (filters.mapBlighted.value === 'Blight-ravaged') {
propSet(query.filters, 'map_filters.filters.map_uberblighted.option', String(true))
propSet(
query.filters,
'map_filters.filters.map_uberblighted.option',
String(true)
)
}
}
if (filters.unidentified && !filters.unidentified.disabled) {
propSet(query.filters, 'misc_filters.filters.identified.option', String(false))
propSet(
query.filters,
'misc_filters.filters.identified.option',
String(false)
)
}
if (filters.areaLevel && !filters.areaLevel.disabled) {
propSet(query.filters, 'map_filters.filters.area_level.min', filters.areaLevel.value)
propSet(
query.filters,
'map_filters.filters.area_level.min',
filters.areaLevel.value
)
}
if (filters.heistWingsRevealed && !filters.heistWingsRevealed.disabled) {
propSet(query.filters, 'heist_filters.filters.heist_wings.min', filters.heistWingsRevealed.value)
propSet(
query.filters,
'heist_filters.filters.heist_wings.min',
filters.heistWingsRevealed.value
)
}
if (filters.sentinelCharge && !filters.sentinelCharge.disabled) {
propSet(query.filters, 'sentinel_filters.filters.sentinel_durability.min', filters.sentinelCharge.value)
propSet(
query.filters,
'sentinel_filters.filters.sentinel_durability.min',
filters.sentinelCharge.value
)
}
for (const stat of stats) {
if (stat.tradeId[0] === 'item.has_empty_modifier') {
const TARGET_ID = {
CRAFTED_MODIFIERS: STAT_BY_REF(TOTAL_MODS_TEXT.CRAFTED_MODIFIERS[stat.option!.value])!.trade.ids[ModifierType.Pseudo][0],
EMPTY_MODIFIERS: STAT_BY_REF(TOTAL_MODS_TEXT.EMPTY_MODIFIERS[stat.option!.value])!.trade.ids[ModifierType.Pseudo][0],
TOTAL_MODIFIERS: STAT_BY_REF(TOTAL_MODS_TEXT.TOTAL_MODIFIERS[0])!.trade.ids[ModifierType.Pseudo][0]
CRAFTED_MODIFIERS: STAT_BY_REF(
TOTAL_MODS_TEXT.CRAFTED_MODIFIERS[stat.option!.value]
)!.trade.ids[ModifierType.Pseudo][0],
EMPTY_MODIFIERS: STAT_BY_REF(
TOTAL_MODS_TEXT.EMPTY_MODIFIERS[stat.option!.value]
)!.trade.ids[ModifierType.Pseudo][0],
TOTAL_MODIFIERS: STAT_BY_REF(TOTAL_MODS_TEXT.TOTAL_MODIFIERS[0])!.trade
.ids[ModifierType.Pseudo][0]
}
query.stats.push({
@@ -391,8 +507,16 @@ export function createTradeRequest (filters: ItemFilters, stats: StatFilter[], i
value: { min: 1, max: 1 },
disabled: stat.disabled,
filters: [
{ id: TARGET_ID.EMPTY_MODIFIERS, value: { min: 1, max: 1 }, disabled: stat.disabled },
{ id: TARGET_ID.CRAFTED_MODIFIERS, value: { min: 1, max: undefined }, disabled: stat.disabled }
{
id: TARGET_ID.EMPTY_MODIFIERS,
value: { min: 1, max: 1 },
disabled: stat.disabled
},
{
id: TARGET_ID.CRAFTED_MODIFIERS,
value: { min: 1, max: undefined },
disabled: stat.disabled
}
]
})
@@ -401,22 +525,31 @@ export function createTradeRequest (filters: ItemFilters, stats: StatFilter[], i
value: { min: 1, max: 1 },
disabled: stat.disabled,
filters: [
{ id: TARGET_ID.EMPTY_MODIFIERS, value: { min: 1, max: 1 }, disabled: stat.disabled },
{ id: TARGET_ID.TOTAL_MODIFIERS, value: { min: 6, max: undefined }, disabled: stat.disabled }
{
id: TARGET_ID.EMPTY_MODIFIERS,
value: { min: 1, max: 1 },
disabled: stat.disabled
},
{
id: TARGET_ID.TOTAL_MODIFIERS,
value: { min: 6, max: undefined },
disabled: stat.disabled
}
]
})
} else if ( // https://github.com/SnosMe/awakened-poe-trade/issues/758
} else if (
// https://github.com/SnosMe/awakened-poe-trade/issues/758
item.category === ItemCategory.Flask &&
stat.statRef === '#% increased Charge Recovery' &&
!stats.some(s => s.statRef === '#% increased effect')
!stats.some((s) => s.statRef === '#% increased effect')
) {
const reducedEffectId = STAT_BY_REF('#% increased effect')!.trade.ids[ModifierType.Explicit][0]
const reducedEffectId = STAT_BY_REF('#% increased effect')!.trade.ids[
ModifierType.Explicit
][0]
query.stats.push({
type: 'not',
disabled: stat.disabled,
filters: [
{ id: reducedEffectId, disabled: stat.disabled }
]
filters: [{ id: reducedEffectId, disabled: stat.disabled }]
})
}
@@ -425,53 +558,143 @@ export function createTradeRequest (filters: ItemFilters, stats: StatFilter[], i
const input = stat.roll!
switch (stat.tradeId[0] as InternalTradeId) {
case 'item.base_percentile':
propSet(query.filters, 'armour_filters.filters.base_defence_percentile.min', typeof input.min === 'number' ? input.min : undefined)
propSet(query.filters, 'armour_filters.filters.base_defence_percentile.max', typeof input.max === 'number' ? input.max : undefined)
propSet(
query.filters,
'armour_filters.filters.base_defence_percentile.min',
typeof input.min === 'number' ? input.min : undefined
)
propSet(
query.filters,
'armour_filters.filters.base_defence_percentile.max',
typeof input.max === 'number' ? input.max : undefined
)
break
case 'item.armour':
propSet(query.filters, 'armour_filters.filters.ar.min', typeof input.min === 'number' ? input.min : undefined)
propSet(query.filters, 'armour_filters.filters.ar.max', typeof input.max === 'number' ? input.max : undefined)
propSet(
query.filters,
'armour_filters.filters.ar.min',
typeof input.min === 'number' ? input.min : undefined
)
propSet(
query.filters,
'armour_filters.filters.ar.max',
typeof input.max === 'number' ? input.max : undefined
)
break
case 'item.evasion_rating':
propSet(query.filters, 'armour_filters.filters.ev.min', typeof input.min === 'number' ? input.min : undefined)
propSet(query.filters, 'armour_filters.filters.ev.max', typeof input.max === 'number' ? input.max : undefined)
propSet(
query.filters,
'armour_filters.filters.ev.min',
typeof input.min === 'number' ? input.min : undefined
)
propSet(
query.filters,
'armour_filters.filters.ev.max',
typeof input.max === 'number' ? input.max : undefined
)
break
case 'item.energy_shield':
propSet(query.filters, 'armour_filters.filters.es.min', typeof input.min === 'number' ? input.min : undefined)
propSet(query.filters, 'armour_filters.filters.es.max', typeof input.max === 'number' ? input.max : undefined)
propSet(
query.filters,
'armour_filters.filters.es.min',
typeof input.min === 'number' ? input.min : undefined
)
propSet(
query.filters,
'armour_filters.filters.es.max',
typeof input.max === 'number' ? input.max : undefined
)
break
case 'item.ward':
propSet(query.filters, 'armour_filters.filters.ward.min', typeof input.min === 'number' ? input.min : undefined)
propSet(query.filters, 'armour_filters.filters.ward.max', typeof input.max === 'number' ? input.max : undefined)
propSet(
query.filters,
'armour_filters.filters.ward.min',
typeof input.min === 'number' ? input.min : undefined
)
propSet(
query.filters,
'armour_filters.filters.ward.max',
typeof input.max === 'number' ? input.max : undefined
)
break
case 'item.block':
propSet(query.filters, 'armour_filters.filters.block.min', typeof input.min === 'number' ? input.min : undefined)
propSet(query.filters, 'armour_filters.filters.block.max', typeof input.max === 'number' ? input.max : undefined)
propSet(
query.filters,
'armour_filters.filters.block.min',
typeof input.min === 'number' ? input.min : undefined
)
propSet(
query.filters,
'armour_filters.filters.block.max',
typeof input.max === 'number' ? input.max : undefined
)
break
case 'item.total_dps':
propSet(query.filters, 'weapon_filters.filters.dps.min', typeof input.min === 'number' ? input.min : undefined)
propSet(query.filters, 'weapon_filters.filters.dps.max', typeof input.max === 'number' ? input.max : undefined)
propSet(
query.filters,
'weapon_filters.filters.dps.min',
typeof input.min === 'number' ? input.min : undefined
)
propSet(
query.filters,
'weapon_filters.filters.dps.max',
typeof input.max === 'number' ? input.max : undefined
)
break
case 'item.physical_dps':
propSet(query.filters, 'weapon_filters.filters.pdps.min', typeof input.min === 'number' ? input.min : undefined)
propSet(query.filters, 'weapon_filters.filters.pdps.max', typeof input.max === 'number' ? input.max : undefined)
propSet(
query.filters,
'weapon_filters.filters.pdps.min',
typeof input.min === 'number' ? input.min : undefined
)
propSet(
query.filters,
'weapon_filters.filters.pdps.max',
typeof input.max === 'number' ? input.max : undefined
)
break
case 'item.elemental_dps':
propSet(query.filters, 'weapon_filters.filters.edps.min', typeof input.min === 'number' ? input.min : undefined)
propSet(query.filters, 'weapon_filters.filters.edps.max', typeof input.max === 'number' ? input.max : undefined)
propSet(
query.filters,
'weapon_filters.filters.edps.min',
typeof input.min === 'number' ? input.min : undefined
)
propSet(
query.filters,
'weapon_filters.filters.edps.max',
typeof input.max === 'number' ? input.max : undefined
)
break
case 'item.crit':
propSet(query.filters, 'weapon_filters.filters.crit.min', typeof input.min === 'number' ? input.min : undefined)
propSet(query.filters, 'weapon_filters.filters.crit.max', typeof input.max === 'number' ? input.max : undefined)
propSet(
query.filters,
'weapon_filters.filters.crit.min',
typeof input.min === 'number' ? input.min : undefined
)
propSet(
query.filters,
'weapon_filters.filters.crit.max',
typeof input.max === 'number' ? input.max : undefined
)
break
case 'item.aps':
propSet(query.filters, 'weapon_filters.filters.aps.min', typeof input.min === 'number' ? input.min : undefined)
propSet(query.filters, 'weapon_filters.filters.aps.max', typeof input.max === 'number' ? input.max : undefined)
propSet(
query.filters,
'weapon_filters.filters.aps.min',
typeof input.min === 'number' ? input.min : undefined
)
propSet(
query.filters,
'weapon_filters.filters.aps.max',
typeof input.max === 'number' ? input.max : undefined
)
break
}
}
stats = stats.filter(stat => !INTERNAL_TRADE_IDS.includes(stat.tradeId[0] as any))
stats = stats.filter(
(stat) => !INTERNAL_TRADE_IDS.includes(stat.tradeId[0] as any)
)
if (filters.veiled) {
for (const statRef of filters.veiled.statRefs) {
stats.push({
@@ -493,7 +716,9 @@ export function createTradeRequest (filters: ItemFilters, stats: StatFilter[], i
text: undefined!,
tag: undefined!,
sources: undefined!,
tradeId: STAT_BY_REF(INFLUENCE_PSEUDO_TEXT[influence.value])!.trade.ids[ModifierType.Pseudo]
tradeId: STAT_BY_REF(INFLUENCE_PSEUDO_TEXT[influence.value])!.trade.ids[
ModifierType.Pseudo
]
})
}
}
@@ -507,7 +732,7 @@ export function createTradeRequest (filters: ItemFilters, stats: StatFilter[], i
type: 'count',
value: { min: 1 },
disabled: stat.disabled,
filters: stat.tradeId.map(id => tradeIdToQuery(id, stat))
filters: stat.tradeId.map((id) => tradeIdToQuery(id, stat))
})
}
}
@@ -517,7 +742,10 @@ export function createTradeRequest (filters: ItemFilters, stats: StatFilter[], i
const cache = new Cache()
export async function requestTradeResultList (body: TradeRequest, leagueId: string): Promise<SearchResult> {
export async function requestTradeResultList (
body: TradeRequest,
leagueId: string
): Promise<SearchResult> {
let data = cache.get<SearchResult>([body, leagueId])
if (!data) {
@@ -528,24 +756,31 @@ export async function requestTradeResultList (body: TradeRequest, leagueId: stri
await RateLimiter.waitMulti(RATE_LIMIT_RULES.SEARCH)
const response = await Host.proxy(`${getTradeEndpoint()}/api/trade/search/${leagueId}`, {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify(body)
})
const response = await Host.proxy(
`${getTradeEndpoint()}/api/trade2/search/${leagueId}`,
{
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify(body)
}
)
adjustRateLimits(RATE_LIMIT_RULES.SEARCH, response.headers)
const _data = await response.json() as TradeResponse<SearchResult>
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.SEARCH, ...RATE_LIMIT_RULES.FETCH))
cache.set<SearchResult>(
[body, leagueId],
data,
Cache.deriveTtl(...RATE_LIMIT_RULES.SEARCH, ...RATE_LIMIT_RULES.FETCH)
)
}
return data
@@ -561,36 +796,53 @@ export async function requestResults (
if (!data) {
await RateLimiter.waitMulti(RATE_LIMIT_RULES.FETCH)
const response = await Host.proxy(`${getTradeEndpoint()}/api/trade/fetch/${resultIds.join(',')}?query=${queryId}`)
const response = await Host.proxy(
`${getTradeEndpoint()}/api/trade2/fetch/${resultIds.join(',')}?query=${queryId}`
)
adjustRateLimits(RATE_LIMIT_RULES.FETCH, response.headers)
const _data = await response.json() as TradeResponse<{ result: Array<FetchResult | null> }>
const _data = (await response.json()) as TradeResponse<{
result: Array<FetchResult | null>
}>
if (_data.error) {
throw new Error(_data.error.message)
} else {
data = _data.result.filter(res => res != null)
data = _data.result.filter((res) => res != null)
}
cache.set<FetchResult[]>(resultIds, data, Cache.deriveTtl(...RATE_LIMIT_RULES.SEARCH, ...RATE_LIMIT_RULES.FETCH))
cache.set<FetchResult[]>(
resultIds,
data,
Cache.deriveTtl(...RATE_LIMIT_RULES.SEARCH, ...RATE_LIMIT_RULES.FETCH)
)
}
return data.map<PricingResult>(result => {
return data.map<PricingResult>((result) => {
return {
id: result.id,
itemLevel: result.item.properties?.find(prop => prop.type === 78)?.values[0][0] ?? String(result.item.ilvl),
itemLevel:
result.item.properties?.find((prop) => prop.type === 78)
?.values[0][0] ?? String(result.item.ilvl),
stackSize: result.item.stackSize,
corrupted: result.item.corrupted,
quality: result.item.properties?.find(prop => prop.type === 6)?.values[0][0],
level: result.item.properties?.find(prop => prop.type === 5)?.values[0][0],
relativeDate: DateTime.fromISO(result.listing.indexed).toRelative({ style: 'short' }) ?? '',
quality: result.item.properties?.find((prop) => prop.type === 6)
?.values[0][0],
level: result.item.properties?.find((prop) => prop.type === 5)
?.values[0][0],
relativeDate:
DateTime.fromISO(result.listing.indexed).toRelative({
style: 'short'
}) ?? '',
priceAmount: result.listing.price?.amount ?? 0,
priceCurrency: result.listing.price?.currency ?? 'no price',
hasNote: result.item.note != null,
isMine: (result.listing.account.name === opts.accountName),
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')
? result.listing.account.online.status === 'afk'
? 'afk'
: 'online'
: 'offline'
}
})
@@ -619,28 +871,26 @@ function tradeIdToQuery (id: string, stat: StatFilter) {
if (stat.roll?.value === 100) {
roll = undefined // stat semantic type is flag
}
// fixes "Cannot be Poisoned" from Essence
// fixes "Cannot be Poisoned" from Essence
} else if (id === 'explicit.stat_3835551335') {
if (stat.roll?.value === 100) {
roll = undefined // stat semantic type is flag
}
// fixes "Instant Recovery" on Flasks
// fixes "Instant Recovery" on Flasks
} else if (id.endsWith('stat_1526933524')) {
if (stat.roll?.value === 100) {
roll = undefined // stat semantic type is flag
}
// fixes Delve "Reservation Efficiency of Skills"
// fixes Delve "Reservation Efficiency of Skills"
} else if (id.endsWith('stat_1269219558')) {
roll = { ...roll!, tradeInvert: !(roll!.tradeInvert) }
roll = { ...roll!, tradeInvert: !roll!.tradeInvert }
}
return {
id,
value: {
...getMinMax(roll),
option: stat.option != null
? stat.option.value
: undefined
option: stat.option != null ? stat.option.value : undefined
},
disabled: stat.disabled
}

View File

@@ -104,7 +104,7 @@ export default defineComponent({
: autoCurrency(trend.chaos)
return {
price: price,
price,
change: deltaFromGraph(trend.graph),
url: trend.url
}

View File

@@ -58,7 +58,7 @@ export default defineComponent({
function select (info: BaseType) {
const newItem: ParsedItem = {
...props.item!,
info: info
info
}
ctx.emit('identify', newItem)
}

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,6 +67,7 @@ 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[] {
let currentIndex = array.length
@@ -87,7 +88,7 @@ function quit () {
}
export default defineComponent({
components: { AppTitleBar },
components: { AppTitleBar, ConversionWarningBanner },
props: {
config: {
type: Object as PropType<Widget>,
@@ -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">Exiled Exchange 2</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/exiled-exchange-2/releases" target="_blank">{{
t('app.release_notes') }}</a>
<a class="border-b" href="https://github.com/Kvan7/exiled-exchange-2/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>
@@ -39,7 +43,7 @@ function checkForUpdates () {
}
function openDownloadPage () {
window.open('https://snosme.github.io/awakened-poe-trade/download')
window.open('https://github.com/Kvan7/exiled-exchange-2/releases')
}
function quitAndInstall () {
@@ -63,19 +67,50 @@ export default defineComponent({
const rawInfo = Host.updateInfo.value
switch (rawInfo.state) {
case 'initial':
return { str1: t('updates.maybe_outdated'), str2: t('updates.never_checked'), action: checkForUpdates, actionText: t('updates.check_now') }
return {
str1: t('updates.maybe_outdated'),
str2: t('updates.never_checked'),
action: checkForUpdates,
actionText: t('updates.check_now')
}
case 'checking-for-update':
return { str1: t('updates.checking'), str2: t('please_wait') }
case 'update-not-available':
return { str1: t('updates.latest'), str2: t('updates.last_checked', [fmtTime(rawInfo.checkedAt)]), action: checkForUpdates, actionText: t('updates.check_now') }
return {
str1: t('updates.latest'),
str2: t('updates.last_checked', [fmtTime(rawInfo.checkedAt)]),
action: checkForUpdates,
actionText: t('updates.check_now')
}
case 'error':
return { str1: t('updates.maybe_outdated'), str2: t('updates.error'), action: openDownloadPage, actionText: t('updates.downloads_page') }
return {
str1: t('updates.maybe_outdated'),
str2: t('updates.error'),
action: openDownloadPage,
actionText: t('updates.downloads_page')
}
case 'update-downloaded':
return { str1: t('updates.available', [rawInfo.version]), str2: t('updates.installed_on_exit'), action: quitAndInstall, actionText: t('updates.install_now') }
return {
str1: t('updates.available', [rawInfo.version]),
str2: t('updates.installed_on_exit'),
action: quitAndInstall,
actionText: t('updates.install_now')
}
case 'update-available':
return (rawInfo.noDownloadReason)
? { str1: t('updates.available', [rawInfo.version]), str2: (rawInfo.noDownloadReason === 'not-supported') ? t('updates.download_manually') : t('updates.download_disabled'), action: openDownloadPage, actionText: t('updates.downloads_page') }
: { str1: t('updates.available', [rawInfo.version]), str2: t('updates.downloading') }
return rawInfo.noDownloadReason
? {
str1: t('updates.available', [rawInfo.version]),
str2:
rawInfo.noDownloadReason === 'not-supported'
? t('updates.download_manually')
: t('updates.download_disabled'),
action: openDownloadPage,
actionText: t('updates.downloads_page')
}
: {
str1: t('updates.available', [rawInfo.version]),
str2: t('updates.downloading')
}
}
})

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/poe2_production_Config.ini">
</div>
<hr class="mb-4 mx-8 border-gray-700">
<div class="mb-2">

22
testUpdate.sh Normal file
View File

@@ -0,0 +1,22 @@
#!/bin/bash
# Remove ./renderer/dist if it exists
rm -rf ./renderer/dist
# Remove ./main/dist if it exists
rm -rf ./main/dist
cd ./renderer
npm install
npm run make-index-files
npm run build
cd ..
cd ./main
npm install
npm run build
npm run package