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 needs: renderer
strategy: strategy:
matrix: 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 }} runs-on: ${{ matrix.os }}
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
@@ -43,11 +43,11 @@ jobs:
with: with:
name: renderer-dist name: renderer-dist
path: ./renderer/dist path: ./renderer/dist
- run: yarn --frozen-lockfile - run: npm ci
working-directory: ./main working-directory: ./main
- run: yarn build - run: npm run build
working-directory: ./main working-directory: ./main
- run: yarn package -p onTagOrDraft - run: npm run package "--" -p onTagOrDraft
working-directory: ./main working-directory: ./main
env: env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
@@ -59,4 +59,4 @@ jobs:
run: cat ./main/dist/latest-linux.yml run: cat ./main/dist/latest-linux.yml
- name: Hash - name: Hash
if: ${{ startsWith(matrix.os, 'macos') }} 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: 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. Here's what that looks like as of 2023-12-03.
```shell ```shell
cd renderer cd renderer
yarn install npm install
yarn make-index-files npm run make-index-files
yarn dev npm run dev
# In a second shell # In a second shell
cd main cd main
yarn install npm install
yarn dev npm run dev
``` ```
# How to build # How to build
```shell ```shell
cd renderer cd renderer
yarn install npm install
yarn make-index-files npm run make-index-files
yarn build npm run build
cd ../main cd ../main
yarn build npm run build
# We want to sign with a distribution certificate to ensure other users can # We want to sign with a distribution certificate to ensure other users can
# install without errors # install without errors
CSC_NAME="Certificate name in Keychain" yarn package 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) ## Moving from POE1/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)
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 ## 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) | | ![](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 ### Development
@@ -19,6 +30,7 @@ See [DEVELOPING.md](./DEVELOPING.md)
### Acknowledgments ### Acknowledgments
- [awakened-poe-trade](https://github.com/SnosMe/awakened-poe-trade)
- [libuiohook](https://github.com/kwhat/libuiohook) - [libuiohook](https://github.com/kwhat/libuiohook)
- [RePoE](https://github.com/brather1ng/RePoE) - [RePoE](https://github.com/brather1ng/RePoE)
- [poeprices.info](https://www.poeprices.info/) - [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' import { defineConfig } from 'vitepress'
const BASE = '/awakened-poe-trade/' const BASE = '/exiled-exchange-2/'
export default defineConfig({ export default defineConfig({
title: 'Awakened PoE Trade', title: 'Exiled Exchange 2',
description: 'App for price-checking items in Path of Exile', description: 'App for price-checking items in Path of Exile 2',
base: BASE, base: BASE,
mpa: true, mpa: true,
head: [ head: [
@@ -22,14 +22,9 @@ export default defineConfig({
// logo: 'TODO', https://github.com/vuejs/vitepress/issues/1401 // logo: 'TODO', https://github.com/vuejs/vitepress/issues/1401
appVersion: '3.25.101', appVersion: '3.25.101',
github: { github: {
releasesUrl: 'https://github.com/SnosMe/awakened-poe-trade/releases' releasesUrl: 'https://github.com/Kvan7/exiled-exchange-2/releases'
}, },
socialLinks: [ socialLinks: [
{
text: 'Discord',
color: '#7289DA',
link: 'https://github.com/SnosMe/awakened-poe-trade/issues/22'
},
{ {
text: 'Patreon', text: 'Patreon',
color: '#FF424D', color: '#FF424D',
@@ -38,7 +33,7 @@ export default defineConfig({
{ {
text: 'GitHub', text: 'GitHub',
color: '#181717', color: '#181717',
link: 'https://github.com/SnosMe/awakened-poe-trade' link: 'https://github.com/Kvan7/exiled-exchange-2'
} }
], ],
sidebar: [ sidebar: [

View File

@@ -8,15 +8,15 @@ import { useData } from 'vitepress'
const { theme } = useData() const { theme } = useData()
</script> </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. to the developer, downloading from them may be unsafe.
| Download link | Automatic updates | Startup time | | 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}/exiled-exchange-2-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}/exiled-exchange-2-${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}/exiled-exchange-2-${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 | | <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> 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\ No Administrator rights required, but\
⚠ **If you run PoE client as Admin, OS security boundaries take effect. ⚠ **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.** **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?** - **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) ![](https://i.imgur.com/81L9Cp0.png)
- **Where can I find the logs?** - **Where can I find the logs?**

View File

@@ -14,7 +14,7 @@ title: Common issues
If Awakened works for you with DirectX11/12 renderer, If Awakened works for you with DirectX11/12 renderer,
then problem is old Vulkan drivers for sure. 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. 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**. 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).* *(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;"} #### 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. When you press `Ctrl + C` Path of Exile 2 copies the item's text (under cursor, if any) to the clipboard.
All that remains is to parse text in Awakened PoE Trade and show to you in a fancy way. All that remains is to parse text in Exiled Exchange 2 and show to you in a fancy way.
### Usage ### 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: publish:
- "github" - "github"
productName: "Awakened PoE Trade" productName: "Exiled Exchange 2"
npmRebuild: false npmRebuild: false
files: files:
- "package.json" - "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", "name": "exiled-exchange-2",
"version": "3.25.102", "version": "0.0.11",
"private": true, "private": true,
"scripts": { "scripts": {
"dev": "node build/script.mjs", "dev": "node build/script.mjs",
"build": "tsc --noEmit && node build/script.mjs --prod", "build": "tsc --noEmit && node build/script.mjs --prod",
"package": "electron-builder build" "package": "electron-builder build",
"lint": "eslint src",
"fix": "eslint src --fix"
}, },
"author": { "author": {
"name": "Alexander Drozdov" "name": "Garrett Parker"
}, },
"repository": { "repository": {
"type": "git", "type": "git",
"url": "https://github.com/SnosMe/awakened-poe-trade.git" "url": "https://github.com/Kvan7/exiled-exchange-2.git"
}, },
"main": "dist/main.js", "main": "dist/main.js",
"dependencies": { "dependencies": {
@@ -25,15 +27,12 @@
"@types/ws": "^8.5.3", "@types/ws": "^8.5.3",
"@wokwi/bmp-ts": "^3.0.0", "@wokwi/bmp-ts": "^3.0.0",
"comlink": "^4.3.1", "comlink": "^4.3.1",
"electron": "31.3.1", "electron": "33.2.1",
"electron-builder": "24.13.3", "electron-builder": "25.1.8",
"electron-updater": "^6.1.0", "electron-updater": "^6.3.0",
"esbuild": "^0.23.0", "esbuild": "^0.24.0",
"ini": "^4.0.0", "ini": "^5.0.0",
"typescript": "5.5.x", "typescript": "5.6.x",
"ws": "^8.16.0" "ws": "^8.16.0"
},
"engines": {
"node": ">=16"
} }
} }

View File

@@ -1,70 +1,70 @@
import path from 'path' import path from "path";
import { app, Tray, Menu, shell, nativeImage, dialog } from 'electron' import { app, Tray, Menu, shell, nativeImage, dialog } from "electron";
import type { ServerEvents } from './server' import type { ServerEvents } from "./server";
export class AppTray { export class AppTray {
public overlayKey = 'Shift + Space' public overlayKey = "Shift + Space";
private tray: Tray private tray: Tray;
serverPort = 0 serverPort = 0;
constructor (server: ServerEvents) { constructor(server: ServerEvents) {
let trayImage = nativeImage.createFromPath( let trayImage = nativeImage.createFromPath(
path.join( path.join(
__dirname, __dirname,
process.env.STATIC!, process.env.STATIC!,
process.platform === "win32" ? "icon.ico" : "icon.png" 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 // Mac image size needs to be smaller, or else it looks huge. Size
// guideline is from https://iconhandbook.co.uk/reference/chart/osx/ // 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 = new Tray(trayImage);
this.tray.setToolTip(`Awakened PoE Trade v${app.getVersion()}`) this.tray.setToolTip(`Exiled Exchange 2 v${app.getVersion()}`);
this.rebuildMenu() this.rebuildMenu();
server.onEventAnyClient('CLIENT->MAIN::user-action', ({ action }) => { server.onEventAnyClient("CLIENT->MAIN::user-action", ({ action }) => {
if (action === 'quit') { if (action === "quit") {
app.quit() app.quit();
} }
}) });
} }
rebuildMenu () { rebuildMenu() {
const contextMenu = Menu.buildFromTemplate([ const contextMenu = Menu.buildFromTemplate([
{ {
label: 'Settings/League', label: "Settings/League",
click: () => { click: () => {
dialog.showMessageBox({ dialog.showMessageBox({
title: 'Settings', title: "Settings",
message: `Open Path of Exile and press "${this.overlayKey}". Click on the button with cog icon there.` 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: () => { 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: () => { click: () => {
shell.openPath(path.join(app.getPath('userData'), 'apt-data')) shell.openPath(path.join(app.getPath("userData"), "apt-data"));
} },
}, },
{ {
label: 'Quit', label: "Quit",
click: () => { 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 fs from "fs/promises";
import path from 'path' import path from "path";
import ini from 'ini' import ini from "ini";
import { app } from 'electron' import { app } from "electron";
import { hotkeyToString, CodeToKey } from '../../../ipc/KeyToCode' import { hotkeyToString, CodeToKey } from "../../../ipc/KeyToCode";
import { guessFileLocation } from './utils' import { guessFileLocation } from "./utils";
import type { Logger } from '../RemoteLogger' import type { Logger } from "../RemoteLogger";
import type { ServerEvents } from '../server' import type { ServerEvents } from "../server";
const POSSIBLE_PATH = const POSSIBLE_PATH =
(process.platform === 'win32') ? [ process.platform === "win32"
path.join(app.getPath('documents'), 'My Games\\Path of Exile\\production_Config.ini') ? [
] : (process.platform === 'linux') ? [ path.join(
path.join(app.getPath('documents'), 'My Games/Path of Exile/production_Config.ini'), app.getPath("documents"),
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') "My Games\\Path of Exile 2\\poe2_production_Config.ini"
] : (process.platform === 'darwin') ? [ ),
path.join(app.getPath('appData'), 'Path of Exile/Preferences/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 { export class GameConfig {
private _wantedPath: string | null = null private _wantedPath: string | null = null;
private _actualPath: string | null = null private _actualPath: string | null = null;
get actualPath () { return this._actualPath } get actualPath() {
return this._actualPath;
}
private _showModsKey: string | null = null private _showModsKey: string | null = null;
get showModsKeyNullable () { return this._showModsKey } get showModsKeyNullable() {
get showModsKey () { return this._showModsKey ?? 'Alt' } return this._showModsKey;
}
get showModsKey() {
return this._showModsKey ?? "Alt";
}
constructor ( constructor(private server: ServerEvents, private logger: Logger) {}
private server: ServerEvents,
private logger: Logger
) {}
async readConfig (filePath: string) { async readConfig(filePath: string) {
if (this._wantedPath !== filePath) { if (this._wantedPath !== filePath) {
this._wantedPath = filePath this._wantedPath = filePath;
this._actualPath = null this._actualPath = null;
} else { } else {
return return;
} }
if (!filePath.length) { if (!filePath.length) {
const guessedPath = await guessFileLocation(POSSIBLE_PATH) const guessedPath = await guessFileLocation(POSSIBLE_PATH);
if (guessedPath != null) { if (guessedPath != null) {
filePath = guessedPath filePath = guessedPath;
} else { } else {
this.logger.write('error [GameConfig] Failed to find game configuration file in the default location.') this.logger.write(
return "error [GameConfig] Failed to find game configuration file in the default location."
);
return;
} }
} }
try { try {
let contents = await fs.readFile(filePath, { encoding: 'utf-8', flag: 'r' }) let contents = await fs.readFile(filePath, {
contents = contents.trimStart() // remove BOM encoding: "utf-8",
const parsed = ini.parse(contents) flag: "r",
});
contents = contents.trimStart(); // remove BOM
const parsed = ini.parse(contents);
this._showModsKey = this.parseConfigHotkey( 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 { } 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 { private parseConfigHotkey(cfgKey?: string): string | null {
if (!cfgKey) return null if (!cfgKey) return null;
const [keyMain, keyMod] = cfgKey.split(' ') const [keyMain, keyMod] = cfgKey.split(" ");
let key1: string let key1: string;
if (CodeToKey[keyMain]) { if (CodeToKey[keyMain]) {
key1 = CodeToKey[keyMain] key1 = CodeToKey[keyMain];
} else { } else {
this.logger.write(`error [GameConfig] Failed to read key: ${cfgKey}.`) this.logger.write(`error [GameConfig] Failed to read key: ${cfgKey}.`);
return null return null;
} }
let key2: string | undefined let key2: string | undefined;
if (keyMod) { if (keyMod) {
if (keyMod === '1') { if (keyMod === "1") {
key2 = 'Shift' key2 = "Shift";
} else if (keyMod === '2') { } else if (keyMod === "2") {
key2 = 'Ctrl' key2 = "Ctrl";
} else if (keyMod === '3') { } else if (keyMod === "3") {
key2 = 'Alt' key2 = "Alt";
} else { } else {
this.logger.write(`error [GameConfig] Failed to read modifier key: ${cfgKey}.`) this.logger.write(
return null `error [GameConfig] Failed to read modifier key: ${cfgKey}.`
);
return null;
} }
} }
return hotkeyToString( return hotkeyToString(
[key1], [key1],
key2 === 'Ctrl', key2 === "Ctrl",
key2 === 'Shift', key2 === "Shift",
key2 === 'Alt' key2 === "Alt"
) );
} }
} }

View File

@@ -1,101 +1,118 @@
import { promises as fs, watchFile, unwatchFile } from 'fs' import { promises as fs, watchFile, unwatchFile } from "fs";
import path from 'path' import path from "path";
import { app } from 'electron' import { app } from "electron";
import { guessFileLocation } from './utils' import { guessFileLocation } from "./utils";
import { ServerEvents } from '../server' import { ServerEvents } from "../server";
import { Logger } from '../RemoteLogger' import { Logger } from "../RemoteLogger";
const POSSIBLE_PATH = const POSSIBLE_PATH =
(process.platform === 'win32') ? [ 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' "C:\\Program Files (x86)\\Grinding Gear Games\\Path of Exile 2\\logs\\Client.txt",
] : (process.platform === 'linux') ? [ "C:\\Program Files (x86)\\Steam\\steamapps\\common\\Path of Exile 2\\logs\\Client.txt",
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 === "linux"
] : (process.platform === 'darwin') ? [ ? [
path.join(app.getPath('home'), 'Library/Caches/com.GGG.PathOfExile/Logs/Client.txt') 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 { export class GameLogWatcher {
private _wantedPath: string | null = null private _wantedPath: string | null = null;
get actualPath () { return this._state?.path ?? null } get actualPath() {
return this._state?.path ?? null;
}
private _state: { private _state: {
offset: number offset: number;
path: string path: string;
file: fs.FileHandle file: fs.FileHandle;
isReading: boolean isReading: boolean;
readBuff: Buffer readBuff: Buffer;
} | null = null } | null = null;
constructor ( constructor(private server: ServerEvents, private logger: Logger) {}
private server: ServerEvents,
private logger: Logger,
) {}
async restart (logFile: string) { async restart(logFile: string) {
if (this._wantedPath !== logFile) { if (this._wantedPath !== logFile) {
this._wantedPath = logFile this._wantedPath = logFile;
if (this._state) { if (this._state) {
unwatchFile(this._state.path) unwatchFile(this._state.path);
await this._state.file.close() await this._state.file.close();
this._state = null this._state = null;
} }
} else { } else {
return return;
} }
if (!logFile.length) { if (!logFile.length) {
const guessedPath = await guessFileLocation(POSSIBLE_PATH) const guessedPath = await guessFileLocation(POSSIBLE_PATH);
if (guessedPath != null) { if (guessedPath != null) {
logFile = guessedPath logFile = guessedPath;
} else { } else {
return return;
} }
} }
try { try {
const file = await fs.open(logFile, 'r') const file = await fs.open(logFile, "r");
const stats = await file.stat() const stats = await file.stat();
watchFile(logFile, { interval: 450 }, this.handleFileChange.bind(this)) watchFile(logFile, { interval: 450 }, this.handleFileChange.bind(this));
this._state = { this._state = {
path: logFile, path: logFile,
file: file, file: file,
offset: stats.size, offset: stats.size,
isReading: false, isReading: false,
readBuff: Buffer.allocUnsafe(64 * 1024), readBuff: Buffer.allocUnsafe(64 * 1024),
} };
} catch { } 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) { if (this._state && !this._state.isReading) {
this._state.isReading = true this._state.isReading = true;
this.readToEOF() this.readToEOF();
} }
} }
private async readToEOF () { private async readToEOF() {
if (!this._state) return if (!this._state) return;
const { file, readBuff, offset } = this._state const { file, readBuff, offset } = this._state;
const { bytesRead } = await file.read(readBuff, 0, readBuff.length, offset) const { bytesRead } = await file.read(readBuff, 0, readBuff.length, offset);
if (bytesRead) { if (bytesRead) {
const str = readBuff.toString('utf8', 0, bytesRead) const str = readBuff.toString("utf8", 0, bytesRead);
const lines = str.split('\n').map(line => line.trim()).filter(line => line.length) const lines = str
this.server.sendEventTo('broadcast', { .split("\n")
name: 'MAIN->CLIENT::game-log', .map((line) => line.trim())
payload: { lines } .filter((line) => line.length);
}) this.server.sendEventTo("broadcast", {
name: "MAIN->CLIENT::game-log",
payload: { lines },
});
} }
if (bytesRead) { if (bytesRead) {
this._state.offset += bytesRead this._state.offset += bytesRead;
this.readToEOF() this.readToEOF();
} else { } 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 const official = PROXY_HOSTS.find(entry => entry.host === host)?.official
if (official === undefined) return req.destroy() if (official === undefined) return req.destroy()
for (const key in req.headers) { for (const key in req.headers) {
if (key.startsWith('sec-') || key === 'host' || key === 'origin' || key === 'content-length') { if (key.startsWith('sec-') || key === 'host' || key === 'origin' || key === 'content-length') {
delete req.headers[key] delete req.headers[key]
@@ -40,7 +40,6 @@ export class HttpProxy {
}) })
proxyReq.addListener('response', (proxyRes) => { proxyReq.addListener('response', (proxyRes) => {
const resHeaders = { ...proxyRes.headers } const resHeaders = { ...proxyRes.headers }
// `net.request` returns an already decoded body
delete resHeaders['content-encoding'] delete resHeaders['content-encoding']
res.writeHead(proxyRes.statusCode, proxyRes.statusMessage, resHeaders) res.writeHead(proxyRes.statusCode, proxyRes.statusMessage, resHeaders)
;(proxyRes as unknown as NodeJS.ReadableStream).pipe(res) ;(proxyRes as unknown as NodeJS.ReadableStream).pipe(res)
@@ -49,6 +48,7 @@ export class HttpProxy {
logger.write(`error [cors-proxy] ${err.message} (${host})`) logger.write(`error [cors-proxy] ${err.message} (${host})`)
res.destroy(err) res.destroy(err)
}) })
req.pipe(proxyReq as unknown as NodeJS.WritableStream) req.pipe(proxyReq as unknown as NodeJS.WritableStream)
}) })
} }

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

@@ -1,15 +1,18 @@
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<head>
<meta charset="utf-8"> <head>
<meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="color-scheme" content="dark"> <meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="icon" href="/icon.ico"> <meta name="color-scheme" content="dark">
<title>Awakened PoE Trade</title> <link rel="icon" href="/icon.ico">
</head> <title>Exiled Exchange 2</title>
<body> </head>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script> <body>
</body> <div id="app"></div>
</html> <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", "name": "exiled-exchange-2",
"version": "0.0.0", "version": "0.0.0",
"private": true, "private": true,
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",
"lint": "eslint --ext .ts,.vue src", "lint": "eslint --ext .ts,.vue src",
"build": "vue-tsc --noEmit && vite build", "lint-fix": "eslint --ext .ts,.vue src --fix",
"make-index-files": "node src/assets/make-index-files.mjs" "build": "vue-tsc --noEmit && vite build",
}, "make-index-files": "node src/assets/make-index-files.mjs"
"dependencies": { },
"@fortawesome/fontawesome-free": "6.x.x", "dependencies": {
"@sindresorhus/fnv1a": "^3.0.0", "@fortawesome/fontawesome-free": "6.x.x",
"@vueuse/core": "^11.0.0", "@sindresorhus/fnv1a": "^3.0.0",
"animate.css": "^4.1.1", "@vueuse/core": "^11.0.0",
"apexcharts": "^4.0.0", "animate.css": "^4.1.1",
"dot-prop": "9.x.x", "apexcharts": "^4.0.0",
"fast-deep-equal": "^3.1.3", "dot-prop": "9.x.x",
"fastest-levenshtein": "^1.0.16", "fast-deep-equal": "^3.1.3",
"luxon": "3.x.x", "fastest-levenshtein": "^1.0.16",
"neverthrow": "^8.0.0", "luxon": "3.x.x",
"object-hash": "^3.0.0", "neverthrow": "^8.0.0",
"sockette": "^2.0.6", "object-hash": "^3.0.0",
"tailwindcss": "3.x.x", "sockette": "^2.0.6",
"tippy.js": "^6.2.7", "tailwindcss": "3.x.x",
"vue": "3.2.37", "tippy.js": "^6.2.7",
"vue-i18n": "^10.0.0", "vue": "3.2.37",
"vue3-apexcharts": "^1.1.1", "vue-i18n": "^10.0.0",
"vuedraggable": "4.1.0" "vue3-apexcharts": "^1.1.1",
}, "vuedraggable": "4.1.0"
"devDependencies": { },
"@types/luxon": "^3.0.0", "devDependencies": {
"@types/node": "^20.0.0", "@types/luxon": "^3.0.0",
"@types/object-hash": "^3.0.0", "@types/node": "^20.0.0",
"@vitejs/plugin-vue": "^4.0.0", "@types/object-hash": "^3.0.0",
"autoprefixer": "^10.0.2", "@vitejs/plugin-vue": "^4.0.0",
"postcss": "^8.2.14", "autoprefixer": "^10.0.2",
"typescript": "5.6.x", "postcss": "^8.2.14",
"vite": "^5.0.0", "typescript": "5.6.x",
"vue-tsc": "^2.0.0" "vite": "^5.0.0",
}, "vue-tsc": "^2.0.0"
"optionalDependencies": { },
"@typescript-eslint/eslint-plugin": "^5.0.0", "optionalDependencies": {
"@typescript-eslint/parser": "^5.0.0", "@typescript-eslint/eslint-plugin": "^5.0.0",
"eslint": "^8.21.0", "@typescript-eslint/parser": "^5.0.0",
"eslint-config-standard-with-typescript": "^31.0.0", "eslint": "^8.21.0",
"eslint-plugin-import": "^2.20.2", "eslint-config-standard-with-typescript": "^31.0.0",
"eslint-plugin-n": "^15.0.0", "eslint-plugin-import": "^2.20.2",
"eslint-plugin-promise": "^6.0.0", "eslint-plugin-n": "^15.0.0",
"eslint-plugin-vue": "^9.1.1" "eslint-plugin-promise": "^6.0.0",
}, "eslint-plugin-vue": "^9.1.1"
"postcss": { },
"plugins": { "postcss": {
"tailwindcss/nesting": {}, "plugins": {
"tailwindcss": {}, "tailwindcss/nesting": {},
"autoprefixer": {} "tailwindcss": {},
} "autoprefixer": {}
}, }
"browserslist": [ },
"chrome >= 101" "browserslist": [
] "chrome >= 101"
} ]
}

View File

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

View File

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

View File

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

View File

@@ -47,6 +47,7 @@ export enum ItemCategory {
SanctumRelic = 'Sanctum Relic', SanctumRelic = 'Sanctum Relic',
Tincture = 'Tincture', Tincture = 'Tincture',
Charm = 'Charm', Charm = 'Charm',
Crossbow = 'Crossbow',
} }
export const WEAPON_ONE_HANDED_MELEE = new Set([ 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) calc.sources.some(s => s.stat.stat.ref === calc.stat.ref && s.stat.roll!.dp)
: undefined : undefined
return { string: translation.string, negate: translation.negate || false, dp: dp } return { string: translation.string, negate: translation.negate || false, dp }
} }
export enum ModifierType { export enum ModifierType {

View File

@@ -16,7 +16,7 @@ export function AppConfig (type?: string) {
if (!type) { if (!type) {
return _config.value! return _config.value!
} else { } 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 }) { export function saveConfig (opts?: { isTemporary: boolean }) {
const rawConfig = toRaw(_config.value!) 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 return
} }
@@ -72,9 +76,9 @@ export async function initConfig () {
// TODO // TODO
// dialog.showErrorBox( // 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.' // 'You need to install the latest version to continue using it.'
// ) // )
} }
@@ -85,12 +89,14 @@ export async function initConfig () {
export function poeWebApi () { export function poeWebApi () {
const { language, realm } = AppConfig() const { language, realm } = AppConfig()
switch (language) { switch (language) {
case 'en': return 'www.pathofexile.com' case 'en':
case 'ru': return 'ru.pathofexile.com' return 'www.pathofexile.com'
case 'cmn-Hant': return (realm === 'pc-garena') case 'ru':
? 'pathofexile.tw' return 'ru.pathofexile.com'
: 'www.pathofexile.com' case 'cmn-Hant':
case 'ko': return 'poe.game.daum.net' 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, overlayBackgroundClose: true,
restoreClipboard: false, restoreClipboard: false,
showAttachNotification: true, showAttachNotification: true,
commands: [{ commands: [
text: '/hideout', {
hotkey: 'F5', text: '/hideout',
send: true hotkey: 'F5',
}, { send: true
text: '/exit', },
hotkey: 'F9', {
send: true text: '/exit',
}, { hotkey: 'F9',
text: '@last ty', send: true
hotkey: null, },
send: true {
}, { text: '@last ty',
text: '/invite @last', hotkey: null,
hotkey: null, send: true
send: true },
}, { {
text: '/tradewith @last', text: '/invite @last',
hotkey: null, hotkey: null,
send: true send: true
}, { },
text: '/hideout @last', {
hotkey: null, text: '/tradewith @last',
send: true hotkey: null,
}], send: true
},
{
text: '/hideout @last',
hotkey: null,
send: true
}
],
clientLog: null, clientLog: null,
gameConfig: null, gameConfig: null,
windowTitle: 'Path of Exile', windowTitle: 'Path of Exile 2',
logKeys: false, logKeys: false,
accountName: '', accountName: '',
stashScroll: true, stashScroll: true,
@@ -301,7 +314,12 @@ export const defaultConfig = (): Config => ({
{ id: 2, name: '', text: '"Divination Card"', hotkey: null }, { id: 2, name: '', text: '"Divination Card"', hotkey: null },
{ id: 3, name: '', text: 'Fossil', hotkey: null }, { id: 3, name: '', text: 'Fossil', hotkey: null },
{ id: 4, name: '', text: '"Map Tier"', 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 } { id: 6, name: '', text: 'Tane Laboratory', hotkey: null }
] ]
} as StashSearchWidget, } as StashSearchWidget,
@@ -317,47 +335,47 @@ export const defaultConfig = (): Config => ({
x: 50, x: 50,
y: 10 y: 10
}, },
images: [ images: [{ id: 1, url: 'syndicate.jpg' }]
{ id: 1, url: 'syndicate.jpg' }
]
} as widget.ImageStripWidget } as widget.ImageStripWidget
] ]
}) })
function upgradeConfig (_config: Config): Config { 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) { if (config.configVersion < 3) {
config.widgets.push({ config.widgets.push({
...defaultConfig().widgets.find(w => w.wmType === 'image-strip')!, ...defaultConfig().widgets.find((w) => w.wmType === 'image-strip')!,
wmId: Math.max(0, ...config.widgets.map(_ => _.wmId)) + 1, wmId: Math.max(0, ...config.widgets.map((_) => _.wmId)) + 1,
wmZorder: null wmZorder: null
}) })
config.widgets.push({ config.widgets.push({
...defaultConfig().widgets.find(w => w.wmType === 'delve-grid')!, ...defaultConfig().widgets.find((w) => w.wmType === 'delve-grid')!,
wmId: Math.max(0, ...config.widgets.map(_ => _.wmId)) + 1, wmId: Math.max(0, ...config.widgets.map((_) => _.wmId)) + 1,
wmZorder: null wmZorder: null
}) })
config.widgets.find(w => w.wmType === 'menu')! config.widgets.find((w) => w.wmType === 'menu')!.alwaysShow = false
.alwaysShow = false
config.configVersion = 3 config.configVersion = 3
} }
if (config.configVersion < 4) { if (config.configVersion < 4) {
config.widgets.find(w => w.wmType === 'price-check')! config.widgets.find(
.chaosPriceThreshold = 0.05 (w) => w.wmType === 'price-check'
)!.chaosPriceThreshold = 0.05
const mapCheck = config.widgets.find(w => w.wmType === 'map-check')! const mapCheck = config.widgets.find((w) => w.wmType === 'map-check')!;
;(mapCheck as any).selectedStats.forEach((e: any) => { (mapCheck as any).selectedStats.forEach((e: any) => {
e.matcher = e.matchRef e.matcher = e.matchRef
e.matchRef = undefined 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) => { widgets.forEach((imgStrip: any) => {
imgStrip.images.forEach((e: any, idx: number) => { imgStrip.images.forEach((e: any, idx: number) => {
e.id = idx e.id = idx
@@ -369,7 +387,7 @@ function upgradeConfig (_config: Config): Config {
} }
if (config.configVersion < 5) { if (config.configVersion < 5) {
config.commands.forEach(cmd => { config.commands.forEach((cmd) => {
cmd.send = true cmd.send = true
}) })
@@ -377,63 +395,68 @@ function upgradeConfig (_config: Config): Config {
} }
if (config.configVersion < 6) { if (config.configVersion < 6) {
config.widgets.find(w => w.wmType === 'price-check')! config.widgets.find((w) => w.wmType === 'price-check')!.showRateLimitState =
.showRateLimitState = ((config as any).logLevel === 'debug') (config as any).logLevel === 'debug'
config.widgets.find(w => w.wmType === 'price-check')! config.widgets.find((w) => w.wmType === 'price-check')!.apiLatencySeconds =
.apiLatencySeconds = 2 2
config.configVersion = 6 config.configVersion = 6
} }
if (config.configVersion < 7) { 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.wmType = 'item-check'
mapCheck.maps = { selectedStats: mapCheck.selectedStats } mapCheck.maps = { selectedStats: mapCheck.selectedStats }
mapCheck.selectedStats = undefined mapCheck.selectedStats = undefined;
(config as any).itemCheckKey = (config as any).mapCheckKey || null;
;(config as any).itemCheckKey = (config as any).mapCheckKey || null (config as any).mapCheckKey = undefined
;(config as any).mapCheckKey = undefined
config.configVersion = 7 config.configVersion = 7
} }
if (config.configVersion < 8) { if (config.configVersion < 8) {
const itemCheck = config.widgets.find(w => w.wmType === 'item-check')! const itemCheck = config.widgets.find((w) => w.wmType === 'item-check')!;
;(itemCheck as ItemCheckWidget).maps.showNewStats = false (itemCheck as ItemCheckWidget).maps.showNewStats = false
itemCheck.maps.selectedStats = (itemCheck as ItemCheckWidget).maps.selectedStats.map(entry => ({ itemCheck.maps.selectedStats = (
itemCheck as ItemCheckWidget
).maps.selectedStats.map((entry) => ({
matcher: entry.matcher, matcher: entry.matcher,
decision: decision: (entry as any).valueDanger
(entry as any).valueDanger ? 'danger' ? 'danger'
: (entry as any).valueWarning ? 'warning' : (entry as any).valueWarning
: (entry as any).valueDesirable ? 'desirable' ? 'warning'
: 'seen' : (entry as any).valueDesirable
? 'desirable'
: 'seen'
})) }))
config.configVersion = 8 config.configVersion = 8
} }
if (config.configVersion < 9) { if (config.configVersion < 9) {
config.widgets.find(w => w.wmType === 'price-check')! config.widgets.find((w) => w.wmType === 'price-check')!.collapseListings =
.collapseListings = 'api' 'api'
config.widgets.find(w => w.wmType === 'price-check')! config.widgets.find((w) => w.wmType === 'price-check')!.smartInitialSearch =
.smartInitialSearch = true true
config.widgets.find(w => w.wmType === 'price-check')! config.widgets.find(
.lockedInitialSearch = true (w) => w.wmType === 'price-check'
)!.lockedInitialSearch = true
config.widgets.find(w => w.wmType === 'price-check')! config.widgets.find(
.activateStockFilter = false (w) => w.wmType === 'price-check'
)!.activateStockFilter = false
config.configVersion = 9 config.configVersion = 9
} }
if (config.configVersion < 10) { if (config.configVersion < 10) {
config.widgets.push({ config.widgets.push({
...defaultConfig().widgets.find(w => w.wmType === 'settings')!, ...defaultConfig().widgets.find((w) => w.wmType === 'settings')!,
wmId: Math.max(0, ...config.widgets.map(_ => _.wmId)) + 1 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.hotkey = (config as any).priceCheckKey
priceCheck.hotkeyHold = (config as any).priceCheckKeyHold priceCheck.hotkeyHold = (config as any).priceCheckKeyHold
priceCheck.hotkeyLocked = (config as any).priceCheckLocked priceCheck.hotkeyLocked = (config as any).priceCheckLocked
@@ -449,22 +472,25 @@ function upgradeConfig (_config: Config): Config {
} }
if (config.configVersion < 11) { if (config.configVersion < 11) {
config.widgets.find(w => w.wmType === 'price-check')! config.widgets.find(
.requestPricePrediction = false (w) => w.wmType === 'price-check'
)!.requestPricePrediction = false
config.configVersion = 11 config.configVersion = 11
} }
if (config.configVersion < 12) { 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, { config.widgets.splice(afterSettings + 1, 0, {
...defaultConfig().widgets.find(w => w.wmType === 'item-search')!, ...defaultConfig().widgets.find((w) => w.wmType === 'item-search')!,
wmWants: 'show', wmWants: 'show',
wmId: Math.max(0, ...config.widgets.map(_ => _.wmId)) + 1 wmId: Math.max(0, ...config.widgets.map((_) => _.wmId)) + 1
}) })
config.realm = 'pc-ggg' config.realm = 'pc-ggg'
if (config.language === 'zh_TW' as string) { if (config.language === ('zh_TW' as string)) {
config.language = 'cmn-Hant' config.language = 'cmn-Hant'
} }
@@ -478,7 +504,9 @@ function upgradeConfig (_config: Config): Config {
} }
if (config.configVersion < 14) { 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) => { imgWidgets.forEach((imgStrip) => {
imgStrip.images.forEach((e) => { imgStrip.images.forEach((e) => {
e.url = e.url.startsWith('app-file://') 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.wikiKey = (config as any).wikiKey
itemCheck.poedbKey = null itemCheck.poedbKey = null
itemCheck.craftOfExileKey = (config as any).craftOfExileKey itemCheck.craftOfExileKey = (config as any).craftOfExileKey
@@ -497,19 +527,29 @@ function upgradeConfig (_config: Config): Config {
} }
if (config.configVersion < 15) { 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 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 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 itemCheck.maps.profile = 1
for (const stat of itemCheck.maps.selectedStats) { for (const stat of itemCheck.maps.selectedStats) {
const p1decision = const p1decision =
(stat.decision === 'danger') ? 'd' stat.decision === 'danger'
: (stat.decision === 'warning') ? 'w' ? 'd'
: (stat.decision === 'desirable') ? 'g' : 's' : stat.decision === 'warning'
? 'w'
: stat.decision === 'desirable'
? 'g'
: 's'
stat.decision = `${p1decision}--` stat.decision = `${p1decision}--`
} }
@@ -518,10 +558,14 @@ function upgradeConfig (_config: Config): Config {
} }
if (config.configVersion < 16) { 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 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 itemCheck.hotkey = (config as any).itemCheckKey
if (itemCheck.maps.profile === undefined) { if (itemCheck.maps.profile === undefined) {
@@ -536,7 +580,9 @@ function upgradeConfig (_config: Config): Config {
config.logKeys = false 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) { if (priceCheck.rememberCurrency === undefined) {
priceCheck.rememberCurrency = false priceCheck.rememberCurrency = false
} }
@@ -616,7 +662,11 @@ function getConfigForHost (): HostConfig {
if (command.hotkey) { if (command.hotkey) {
actions.push({ actions.push({
shortcut: command.hotkey, 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 { computed, shallowRef, readonly } from 'vue'
import { createGlobalState } from '@vueuse/core' import { createGlobalState } from '@vueuse/core'
import { AppConfig, poeWebApi } from '@/web/Config' import { AppConfig } from '@/web/Config'
import { Host } from './IPC'
// pc-ggg, pc-garena // pc-ggg, pc-garena
// const PERMANENT_SC = ['Standard', '標準模式'] // const PERMANENT_SC = ['Standard', '標準模式']
@@ -23,11 +22,21 @@ export const useLeagues = createGlobalState(() => {
const error = shallowRef<string | null>(null) const error = shallowRef<string | null>(null)
const tradeLeagues = shallowRef<League[]>([]) const tradeLeagues = shallowRef<League[]>([])
const DEFAULT_POE2_LEAGUES: ApiLeague[] = [
{ id: 'Standard', rules: [] },
{
id: 'Hardcore',
rules: [
{
id: 'Hardcore'
}
]
}
]
const selectedId = computed<string | undefined>({ const selectedId = computed<string | undefined>({
get () { get () {
return (tradeLeagues.value.length) return tradeLeagues.value.length ? AppConfig().leagueId : undefined
? AppConfig().leagueId
: undefined
}, },
set (id) { set (id) {
AppConfig().leagueId = id AppConfig().leagueId = id
@@ -37,7 +46,7 @@ export const useLeagues = createGlobalState(() => {
const selected = computed(() => { const selected = computed(() => {
const { leagueId } = AppConfig() const { leagueId } = AppConfig()
if (!tradeLeagues.value || !leagueId) return undefined 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 { return {
id: leagueId, id: leagueId,
realm: AppConfig().realm, realm: AppConfig().realm,
@@ -50,19 +59,30 @@ export const useLeagues = createGlobalState(() => {
error.value = null error.value = null
try { try {
const response = await Host.proxy(`${poeWebApi()}/api/leagues?type=main&realm=pc`) // const response = await Host.proxy(
if (!response.ok) throw new Error(JSON.stringify(Object.fromEntries(response.headers))) // `${poeWebApi()}/api/leagues?type=main&realm=pc`
const leagues: ApiLeague[] = await response.json() // );
// 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 tradeLeagues.value = leagues
.filter(league => .filter(
!PERMANENT_HC.includes(league.id) && (league) =>
!league.rules.some(rule => rule.id === 'NoParties' || !PERMANENT_HC.includes(league.id) &&
(rule.id === 'HardMode' && !league.event))) !league.rules.some(
.map(league => { (rule) =>
rule.id === 'NoParties' ||
(rule.id === 'HardMode' && !league.event)
)
)
.map((league) => {
return { id: league.id, isPopular: true } 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 (!leagueIsAlive && !isPrivateLeague(selectedId.value ?? '')) {
if (tradeLeagues.value.length > 1) { if (tradeLeagues.value.length > 1) {
const TMP_CHALLENGE = 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> <template>
<Widget :config="{ ...config, anchor }" move-handles="none" :removable="false" :inline-edit="false"> <Widget :config="{ ...config, anchor }" move-handles="none" :removable="false" :inline-edit="false">
<template v-if="item"> <template v-if="item">
<MapCheck v-if="isMapLike" <ConversionWarningBanner />
:item="item" :config="config.maps" /> <MapCheck v-if="isMapLike" :item="item" :config="config.maps" />
<ItemInfo v-else <ItemInfo v-else :item="item" />
:item="item" />
</template> </template>
</Widget> </Widget>
</template> </template>
@@ -20,6 +19,7 @@ import type { ItemCheckWidget } from './widget.js'
import Widget from '../overlay/Widget.vue' import Widget from '../overlay/Widget.vue'
import MapCheck from '../map-check/MapCheck.vue' import MapCheck from '../map-check/MapCheck.vue'
import ItemInfo from './ItemInfo.vue' import ItemInfo from './ItemInfo.vue'
import ConversionWarningBanner from '../conversion-warn-banner/ConversionWarningBanner.vue'
const props = defineProps<{ const props = defineProps<{
config: ItemCheckWidget config: ItemCheckWidget

View File

@@ -6,7 +6,14 @@ const POEDB_LANGS = { 'en': 'us', 'ru': 'ru', 'cmn-Hant': 'tw', 'ko': 'kr' }
export function registerActions () { export function registerActions () {
Host.onEvent('MAIN->CLIENT::item-text', (e) => { 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) const parsed = parseClipboard(e.clipboard)
if (!parsed.isOk()) return if (!parsed.isOk()) return
@@ -23,14 +30,18 @@ export function registerActions () {
} }
export function openWiki (item: ParsedItem) { 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) { 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) { export function openCoE (item: ParsedItem) {
const encodedClipboard = encodeURIComponent(item.rawText) 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) { export function findSimilarItems (item: ParsedItem) {

View File

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

View File

@@ -1,11 +1,10 @@
<template> <template>
<transition <transition enter-active-class="animate__animated animate__fadeIn"
enter-active-class="animate__animated animate__fadeIn"
leave-active-class="animate__animated animate__backOutDown"> leave-active-class="animate__animated animate__backOutDown">
<div :class="$style.widget" v-if="show"> <div :class="$style.widget" v-if="show">
<div :class="$style.box"> <div :class="$style.box">
<div class="py-2 px-4"> <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> <p>{{ t('app_is_ready') }}</p>
</div> </div>
</div> </div>
@@ -26,7 +25,9 @@ const show = shallowRef(false)
Host.onEvent('MAIN->OVERLAY::overlay-attached', () => { Host.onEvent('MAIN->OVERLAY::overlay-attached', () => {
if (!show.value && AppConfig().showAttachNotification) { if (!show.value && AppConfig().showAttachNotification) {
show.value = true show.value = true
setTimeout(() => { show.value = false }, 2500) setTimeout(() => {
show.value = false
}, 2500)
} }
}) })
</script> </script>
@@ -52,7 +53,7 @@ Host.onEvent('MAIN->OVERLAY::overlay-attached', () => {
.box::before { .box::before {
position: absolute; position: absolute;
content: ''; content: '';
background: url('/images/TransferOrb.png') no-repeat top right/contain; background: url('/images/exa.png') no-repeat top right/contain;
right: 100%; right: 100%;
width: 100%; width: 100%;
height: 100%; height: 100%;

View File

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

View File

@@ -1,42 +1,24 @@
<template> <template>
<div v-if="show" class="p-4 layout-column min-h-0"> <div v-if="show" class="p-4 layout-column min-h-0">
<filter-name <filter-name :filters="itemFilters" :item="item" />
:filters="itemFilters" <price-prediction v-if="showPredictedPrice" class="mb-4" :item="item" />
:item="item" /> <price-trend v-else :item="item" :filters="itemFilters" />
<price-prediction v-if="showPredictedPrice" class="mb-4" <filters-block ref="filtersComponent" :filters="itemFilters" :stats="itemStats" :item="item" :presets="presets"
:item="item" /> @preset="selectPreset" @submit="doSearch = true" />
<price-trend v-else <trade-listing v-if="tradeAPI === 'trade' && doSearch" ref="tradeService" :filters="itemFilters" :stats="itemStats"
: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" /> :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 v-if="!doSearch" class="flex justify-between items-center">
<div class="flex w-40" @mouseenter="handleSearchMouseenter"> <div class="flex w-40" @mouseenter="handleSearchMouseenter">
<button class="btn" @click="doSearch = true" style="min-width: 5rem;">{{ t('Search') }}</button> <button class="btn" @click="doSearch = true" style="min-width: 5rem;">{{ t('Search') }}</button>
</div> </div>
<trade-links v-if="tradeAPI === 'trade'" <trade-links v-if="tradeAPI === 'trade'" :get-link="makeTradeLink" />
:get-link="makeTradeLink" />
</div> </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 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"> <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://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> <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) || if ((!props.advancedCheck && !widget.value.smartInitialSearch) ||
(props.advancedCheck && !widget.value.lockedInitialSearch)) { (props.advancedCheck && !widget.value.lockedInitialSearch)) {
doSearch.value = false doSearch.value = false
} else { } else {
doSearch.value = Boolean( doSearch.value = Boolean(
@@ -174,8 +156,8 @@ export default defineComponent({
const showPredictedPrice = computed(() => { const showPredictedPrice = computed(() => {
if (!widget.value.requestPricePrediction || if (!widget.value.requestPricePrediction ||
AppConfig().language !== 'en' || AppConfig().language !== 'en' ||
!leagues.selected.value!.isPopular) return false !leagues.selected.value!.isPopular) return false
if (presets.value.active === 'filters.preset_base_item') return false if (presets.value.active === 'filters.preset_base_item') return false
@@ -238,7 +220,7 @@ export default defineComponent({
presets.value.active = id presets.value.active = id
}, },
makeTradeLink () { 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> <template>
<div <div
style="top: 0; left: 0; height: 100%; width: 100%; position: absolute;" style="top: 0; left: 0; height: 100%; width: 100%; position: absolute"
class="flex grow h-full pointer-events-none" :class="{ class="flex grow h-full pointer-events-none"
'flex-row': clickPosition === 'stash', :class="{
'flex-row-reverse': clickPosition === 'inventory', '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
<div id="price-window" class="layout-column shrink-0 text-gray-200 pointer-events-auto" style="width: 28.75rem;"> v-if="!isBrowserShown"
<AppTitleBar @close="closePriceCheck" @click="openLeagueSelection" :title="title"> class="layout-column shrink-0"
<ui-popover v-if="stableOrbCost" trigger="click" boundary="#price-window"> 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> <template #target>
<button><i class="fas fa-exchange-alt" /> {{ stableOrbCost }}</button> <button>
<i class="fas fa-exchange-alt" /> {{ stableOrbCost }}
</button>
</template> </template>
<template #content> <template #content>
<item-quick-price class="text-base" <item-quick-price
:price="{ min: stableOrbCost, max: stableOrbCost, currency: 'chaos' }" class="text-base"
:price="{
min: stableOrbCost,
max: stableOrbCost,
currency: 'chaos',
}"
item-img="/images/divine.png" item-img="/images/divine.png"
/> />
<div v-for="i in 9" :key="i"> <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> </div>
</template> </template>
</ui-popover> </ui-popover>
@@ -29,37 +55,61 @@
</AppTitleBar> </AppTitleBar>
<div class="grow layout-column min-h-0 bg-gray-800"> <div class="grow layout-column min-h-0 bg-gray-800">
<background-info /> <background-info />
<check-position-circle v-if="showCheckPos" <check-position-circle
:position="checkPosition" style="z-index: -1;" /> v-if="showCheckPos"
:position="checkPosition"
style="z-index: -1"
/>
<template v-if="item?.isErr()"> <template v-if="item?.isErr()">
<ui-error-box class="m-4"> <ui-error-box class="m-4">
<template #name>{{ t(item.error.name) }}</template> <template #name>{{ t(item.error.name) }}</template>
<p>{{ t(item.error.message) }}</p> <p>{{ t(item.error.message) }}</p>
</ui-error-box> </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>
<template v-else-if="item?.isOk()"> <template v-else-if="item?.isOk()">
<unidentified-resolver :item="item.value" @identify="handleIdentification($event)" /> <unidentified-resolver
<checked-item v-if="isLeagueSelected" :item="item.value"
:item="item.value" :advanced-check="advancedCheck" /> @identify="handleIdentification($event)"
/>
<checked-item
v-if="isLeagueSelected"
:item="item.value"
:advanced-check="advancedCheck"
/>
</template> </template>
<div v-if="isBrowserShown" class="bg-gray-900 px-6 py-2 truncate"> <div v-if="isBrowserShown" class="bg-gray-900 px-6 py-2 truncate">
<i18n-t keypath="app.toggle_browser_hint" tag="div"> <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> </i18n-t>
</div> </div>
</div> </div>
</div> </div>
<webview v-if="isBrowserShown" ref="iframeEl" <webview
v-if="isBrowserShown"
ref="iframeEl"
class="pointer-events-auto flex-1" 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 v-else class="layout-column flex-1 min-w-0">
<div class="flex" :class="{ <div
'flex-row': clickPosition === 'stash', class="flex"
'flex-row-reverse': clickPosition === 'inventory' :class="{
}"> 'flex-row': clickPosition === 'stash',
<related-items v-if="item?.isOk()" class="pointer-events-auto" 'flex-row-reverse': clickPosition === 'inventory',
:item="item.value" :click-position="clickPosition" /> }"
>
<related-items
v-if="item?.isOk()"
class="pointer-events-auto"
:item="item.value"
:click-position="clickPosition"
/>
<rate-limiter-state class="pointer-events-auto" /> <rate-limiter-state class="pointer-events-auto" />
</div> </div>
</div> </div>
@@ -86,8 +136,13 @@ import CheckPositionCircle from './CheckPositionCircle.vue'
import AppTitleBar from '@/web/ui/AppTitlebar.vue' import AppTitleBar from '@/web/ui/AppTitlebar.vue'
import ItemQuickPrice from '@/web/ui/ItemQuickPrice.vue' import ItemQuickPrice from '@/web/ui/ItemQuickPrice.vue'
import { PriceCheckWidget, WidgetManager } from '../overlay/interfaces' 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({ export default defineComponent({
components: { components: {
@@ -100,7 +155,8 @@ export default defineComponent({
CheckPositionCircle, CheckPositionCircle,
ItemQuickPrice, ItemQuickPrice,
UiErrorBox, UiErrorBox,
UiPopover UiPopover,
ConversionWarningBanner
}, },
props: { props: {
config: { config: {
@@ -127,9 +183,13 @@ export default defineComponent({
if (Host.isElectron && !e.focusOverlay) { if (Host.isElectron && !e.focusOverlay) {
// everything in CSS pixels // everything in CSS pixels
const width = 28.75 * AppConfig().fontSize const width = 28.75 * AppConfig().fontSize
const screenX = ((e.position.x - window.screenX) > window.innerWidth / 2) const screenX =
? (window.screenX + window.innerWidth) - wm.poePanelWidth.value - width e.position.x - window.screenX > window.innerWidth / 2
: window.screenX + wm.poePanelWidth.value ? window.screenX +
window.innerWidth -
wm.poePanelWidth.value -
width
: window.screenX + wm.poePanelWidth.value
MainProcess.sendEvent({ MainProcess.sendEvent({
name: 'OVERLAY->MAIN::track-area', name: 'OVERLAY->MAIN::track-area',
payload: { payload: {
@@ -151,13 +211,18 @@ export default defineComponent({
checkPosition.value = e.position checkPosition.value = e.position
advancedCheck.value = e.focusOverlay advancedCheck.value = e.focusOverlay
item.value = (e.item ? ok(e.item as ParsedItem) : parseClipboard(e.clipboard)) item.value = (
.andThen(item => ( e.item ? ok(e.item as ParsedItem) : parseClipboard(e.clipboard)
(item.category === ItemCategory.HeistContract && item.rarity !== ItemRarity.Unique) || )
(item.category === ItemCategory.Sentinel && item.rarity !== ItemRarity.Unique)) .andThen((item) =>
? err('item.unknown') (item.category === ItemCategory.HeistContract &&
: ok(item)) item.rarity !== ItemRarity.Unique) ||
.mapErr(err => ({ (item.category === ItemCategory.Sentinel &&
item.rarity !== ItemRarity.Unique)
? err('item.unknown')
: ok(item)
)
.mapErr((err) => ({
name: `${err}`, name: `${err}`,
message: `${err}_help`, message: `${err}_help`,
rawText: e.clipboard rawText: e.clipboard
@@ -176,14 +241,17 @@ export default defineComponent({
wm.hide(props.config.wmId) wm.hide(props.config.wmId)
}) })
watch(() => props.config.wmWants, (state) => { watch(
if (state === 'hide') { () => props.config.wmWants,
closeBrowser() (state) => {
if (state === 'hide') {
closeBrowser()
}
} }
}) )
const leagues = useLeagues() 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 stableOrbCost = computed(() => (xchgRate.value) ? Math.round(xchgRate.value) : null)
const isBrowserShown = computed(() => props.config.wmFlags.includes('has-browser')) const isBrowserShown = computed(() => props.config.wmFlags.includes('has-browser'))
const overlayKey = computed(() => AppConfig().overlayKey) const overlayKey = computed(() => AppConfig().overlayKey)
@@ -196,7 +264,7 @@ export default defineComponent({
return checkPosition.value.x > (window.screenX + window.innerWidth / 2) return checkPosition.value.x > (window.screenX + window.innerWidth / 2)
? 'inventory' ? 'inventory'
: 'stash' : '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) { if (!parsed.roll) {
return { return {
text: parsed.translation.string, text: parsed.translation.string,
contribution: contribution, contribution,
contributes: true contributes: true
} }
} }

View File

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

View File

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

View File

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

View File

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

View File

@@ -6,8 +6,8 @@ import { usePoeninja } from '@/web/background/Prices'
const cache = new Cache() const cache = new Cache()
interface PoepricesApiResponse { /* eslint-disable camelcase */ interface PoepricesApiResponse {
currency: 'chaos' | 'divine' | 'exalt' /* eslint-disable camelcase */ currency: 'chaos' | 'divine' | 'exalt'
error: number error: number
error_msg: string error_msg: string
warning_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({ const query = querystring({
i: utf8ToBase64(transformItemText(item.rawText)), i: utf8ToBase64(transformItemText(item.rawText)),
l: useLeagues().selectedId.value, l: useLeagues().selectedId.value,
s: 'awakened-poe-trade' s: 'awakened-poe-trade' // might be required name here
}) })
let data = cache.get<PoepricesApiResponse>(query) let data = cache.get<PoepricesApiResponse>(query)
if (!data) { if (!data) {
const response = await Host.proxy(`www.poeprices.info/api?${query}`) const response = await Host.proxy(`www.poeprices.info/api?${query}`)
try { try {
data = await response.json() as PoepricesApiResponse data = (await response.json()) as PoepricesApiResponse
} catch (e) { } 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) { if (data.error !== 0) {
@@ -53,24 +57,31 @@ export async function requestPoeprices (item: ParsedItem): Promise<RareItemPrice
if (data.currency === 'exalt') { if (data.currency === 'exalt') {
const { findPriceByQuery, autoCurrency } = usePoeninja() 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) { if (!xchgExalted) {
throw new Error('poeprices.info gave the price in Exalted Orbs.') 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.min = converted.min
data.max = converted.max 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') { } else if (data.currency !== 'divine' && data.currency !== 'chaos') {
throw new Error('poeprices.info gave the price in unknown currency.') throw new Error('poeprices.info gave the price in unknown currency.')
} }
return { return {
currency: (data.currency === 'divine') ? 'div' : 'chaos', currency: data.currency === 'divine' ? 'div' : 'chaos',
min: data.min, min: data.min,
max: data.max, max: data.max,
confidence: Math.round(data.pred_confidence_score), confidence: Math.round(data.pred_confidence_score),
explanation: data.pred_explanation.map(expl => ({ explanation: data.pred_explanation.map((expl) => ({
name: expl[0], name: expl[0],
contrib: Math.round(expl[1] * 100) contrib: Math.round(expl[1] * 100)
})) }))
@@ -118,7 +129,7 @@ function utf8ToBase64 (value: string) {
function querystring (q: Record<string, any>) { function querystring (q: Record<string, any>) {
return Object.entries(q) return Object.entries(q)
.map(pair => pair.map(encodeURIComponent).join('=')) .map((pair) => pair.map(encodeURIComponent).join('='))
.join('&') .join('&')
} }

View File

@@ -6,12 +6,14 @@
<span class="mr-1">{{ t(':matched') }}</span> <span class="mr-1">{{ t(':matched') }}</span>
<span v-if="!result" class="text-gray-600">...</span> <span v-if="!result" class="text-gray-600">...</span>
<div v-else class="flex items-center"> <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'"> @click="selectedCurr = 'xchgChaos'">
<img src="/images/chaos.png" class="trade-bulk-currency-icon"> <img src="/images/chaos.png" class="trade-bulk-currency-icon">
<span>{{ result.xchgChaos.listed.value?.total ?? '?' }}</span> <span>{{ result.xchgChaos.listed.value?.total ?? '?' }}</span>
</button> </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'"> @click="selectedCurr = 'xchgStable'">
<img src="/images/divine.png" class="trade-bulk-currency-icon"> <img src="/images/divine.png" class="trade-bulk-currency-icon">
<span>{{ result.xchgStable.listed.value?.total ?? '?' }}</span> <span>{{ result.xchgStable.listed.value?.total ?? '?' }}</span>
@@ -19,8 +21,7 @@
<span class="ml-1"><online-filter :filters="filters" /></span> <span class="ml-1"><online-filter :filters="filters" /></span>
</div> </div>
</div> </div>
<trade-links v-if="result" <trade-links v-if="result" :get-link="makeTradeLink" />
:get-link="makeTradeLink" />
</div> </div>
<div class="layout-column overflow-y-auto overflow-x-hidden"> <div class="layout-column overflow-y-auto overflow-x-hidden">
<table class="table-stripped w-full"> <table class="table-stripped w-full">
@@ -30,7 +31,10 @@
<div class="px-2">{{ t(':price') }}</div> <div class="px-2">{{ t(':price') }}</div>
</th> </th>
<th class="trade-table-heading"> <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>
<th class="trade-table-heading"> <th class="trade-table-heading">
<div class="px-1">{{ t(':stock') }}</div> <div class="px-1">{{ t(':stock') }}</div>
@@ -55,19 +59,25 @@
</tr> </tr>
<tr v-else :key="result.id"> <tr v-else :key="result.id">
<td class="px-2">{{ Number((result.exchangeAmount / result.itemAmount).toFixed(4)) }}</td> <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">{{ 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"> <td class="pr-2 pl-4 whitespace-nowrap">
<div class="inline-flex items-center"> <div class="inline-flex items-center">
<div class="account-status" :class="result.accountStatus"></div> <div class="account-status" :class="result.accountStatus"></div>
<div class="ml-1 font-sans text-xs">{{ result.relativeDate }}</div> <div class="ml-1 font-sans text-xs">{{ result.relativeDate }}</div>
</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>
<td v-if="showSeller" class="px-2 whitespace-nowrap"> <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-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> </td>
</tr> </tr>
</template> </template>
@@ -154,7 +164,7 @@ function useBulkApi () {
const listedLazy = computed(() => { const listedLazy = computed(() => {
if (!requested) { if (!requested) {
;(async function () { ; (async function () {
try { try {
requested = true requested = true
_result.value = shallowReactive((await execBulkSearch( _result.value = shallowReactive((await execBulkSearch(
@@ -230,7 +240,7 @@ export default defineComponent({
const have = _have ?? ((selectedCurr.value === 'xchgStable') ? ['divine'] : ['chaos']) const have = _have ?? ((selectedCurr.value === 'xchgStable') ? ['divine'] : ['chaos'])
const httpPostBody = createTradeRequest(props.filters, props.item, have) const httpPostBody = createTradeRequest(props.filters, props.item, have)
const httpGetQuery = { exchange: httpPostBody.query } 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') const { t } = useI18nNs('trade_result')

View File

@@ -8,8 +8,7 @@
</div> </div>
<online-filter v-if="list" :by-time="true" :filters="filters" /> <online-filter v-if="list" :by-time="true" :filters="filters" />
<div class="flex-1"></div> <div class="flex-1"></div>
<trade-links v-if="list" <trade-links v-if="list" :get-link="makeTradeLink" />
:get-link="makeTradeLink" />
</div> </div>
<div class="layout-column overflow-y-auto overflow-x-hidden"> <div class="layout-column overflow-y-auto overflow-x-hidden">
<table class="table-stripped w-full"> <table class="table-stripped w-full">
@@ -46,21 +45,28 @@
<td colspan="100" class="text-transparent">***</td> <td colspan="100" class="text-transparent">***</td>
</tr> </tr>
<tr v-else :key="result.id"> <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="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="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="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"> <td class="pr-2 pl-4 whitespace-nowrap">
<div class="inline-flex items-center"> <div class="inline-flex items-center">
<div class="account-status" :class="result.accountStatus"></div> <div class="account-status" :class="result.accountStatus"></div>
<div class="ml-1 font-sans text-xs">{{ result.relativeDate }}</div> <div class="ml-1 font-sans text-xs">{{ result.relativeDate }}</div>
</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>
<td v-if="showSeller" class="px-2 whitespace-nowrap"> <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-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> </td>
</tr> </tr>
</template> </template>
@@ -222,8 +228,8 @@ export default defineComponent({
function makeTradeLink () { function makeTradeLink () {
return (searchResult.value) return (searchResult.value)
? `https://${getTradeEndpoint()}/trade/search/${props.filters.trade.league}/${searchResult.value.id}` ? `https://${getTradeEndpoint()}/trade2/search/poe2/${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}?q=${JSON.stringify(createTradeRequest(props.filters, props.stats, props.item))}`
} }
const { t } = useI18nNs('trade_result') const { t } = useI18nNs('trade_result')
@@ -261,7 +267,7 @@ export default defineComponent({
@apply bg-gray-800; @apply bg-gray-800;
@apply p-0 m-0; @apply p-0 m-0;
& > div { &>div {
@apply border-b border-gray-700; @apply border-b border-gray-700;
} }
} }
@@ -271,10 +277,14 @@ export default defineComponent({
height: 0.375rem; height: 0.375rem;
border-radius: 100%; border-radius: 100%;
&.online { /* */ } &.online {
/* */
}
&.offline { &.offline {
@apply bg-red-600; @apply bg-red-600;
} }
&.afk { &.afk {
@apply bg-orange-500; @apply bg-orange-500;
} }

View File

@@ -1,13 +1,21 @@
import { DateTime } from 'luxon' import { DateTime } from 'luxon'
import { Host } from '@/web/background/IPC' 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 { RateLimiter } from './RateLimiter'
import { ItemFilters } from '../filters/interfaces' import { ItemFilters } from '../filters/interfaces'
import { ParsedItem } from '@/parser' import { ParsedItem } from '@/parser'
import { Cache } from './Cache' import { Cache } from './Cache'
interface TradeRequest { /* eslint-disable camelcase */ interface TradeRequest {
engine: 'new' /* eslint-disable camelcase */ engine: 'new'
query: { query: {
status: { option: 'online' | 'onlineleague' | 'any' } status: { option: 'online' | 'onlineleague' | 'any' }
have: string[] have: string[]
@@ -56,34 +64,42 @@ export interface PricingResult {
const cache = new Cache() 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]) let data = cache.get<SearchResult>([body, leagueId])
if (!data) { if (!data) {
preventQueueCreation([ preventQueueCreation([{ count: 1, limiters: RATE_LIMIT_RULES.EXCHANGE }])
{ count: 1, limiters: RATE_LIMIT_RULES.EXCHANGE }
])
await RateLimiter.waitMulti(RATE_LIMIT_RULES.EXCHANGE) await RateLimiter.waitMulti(RATE_LIMIT_RULES.EXCHANGE)
const response = await Host.proxy(`${getTradeEndpoint()}/api/trade/exchange/${leagueId}`, { const response = await Host.proxy(
method: 'POST', `${getTradeEndpoint()}/api/trade2/exchange/${leagueId}`,
headers: { {
'Accept': 'application/json', method: 'POST',
'Content-Type': 'application/json' headers: {
}, 'Accept': 'application/json',
body: JSON.stringify(body) 'Content-Type': 'application/json'
}) },
body: JSON.stringify(body)
}
)
adjustRateLimits(RATE_LIMIT_RULES.EXCHANGE, response.headers) 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) { if (_data.error) {
throw new Error(_data.error.message) throw new Error(_data.error.message)
} else { } else {
data = _data data = _data
} }
cache.set<SearchResult>([body, leagueId], data, Cache.deriveTtl(...RATE_LIMIT_RULES.EXCHANGE)) cache.set<SearchResult>(
[body, leagueId],
data,
Cache.deriveTtl(...RATE_LIMIT_RULES.EXCHANGE)
)
} }
return data return data
@@ -96,15 +112,19 @@ function toPricingResult (
): PricingResult { ): PricingResult {
return { return {
id: result.id, 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, exchangeAmount: result.listing.offers[offer].exchange.amount,
itemAmount: result.listing.offers[offer].item.amount, itemAmount: result.listing.offers[offer].item.amount,
stock: result.listing.offers[offer].item.stock, 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, ign: result.listing.account.lastCharacterName,
accountName: result.listing.account.name, accountName: result.listing.account.name,
accountStatus: result.listing.account.online accountStatus: result.listing.account.online
? (result.listing.account.online.status === 'afk' ? 'afk' : 'online') ? result.listing.account.online.status === 'afk'
? 'afk'
: 'online'
: 'offline' : 'offline'
} }
} }
@@ -116,18 +136,27 @@ export interface BulkSearch {
listed: PricingResult[] listed: PricingResult[]
} }
export function createTradeRequest (filters: ItemFilters, item: ParsedItem, have: string[]): TradeRequest { export function createTradeRequest (
filters: ItemFilters,
item: ParsedItem,
have: string[]
): TradeRequest {
return { return {
engine: 'new', engine: 'new',
query: { query: {
have: have, have,
want: [tradeTag(item)!], want: [tradeTag(item)!],
status: { status: {
option: filters.trade.offline option: filters.trade.offline
? 'any' ? '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 // fulfillable: null
}, },
sort: { have: 'asc' } sort: { have: 'asc' }
@@ -149,40 +178,46 @@ export async function execBulkSearch (
) )
const offer = 0 const offer = 0
const results = Object.values(query.result) const results = Object.values(query.result).filter(
.filter(result => result.listing.offers.length === 1) (result) => result.listing.offers.length === 1
)
const resultByHave = have.map(tradeTag => { const resultByHave = have.map((tradeTag) => {
const resultsTag = results.filter(result => result.listing.offers[offer].exchange.currency === tradeTag) const resultsTag = results.filter(
(result) => result.listing.offers[offer].exchange.currency === tradeTag
)
const loadedOnDemand = ( const loadedOnDemand =
tradeTag === 'chaos' && tradeTag === 'chaos' &&
resultsTag.length < SHOW_RESULTS && resultsTag.length < SHOW_RESULTS &&
query.total > API_FETCH_LIMIT query.total > API_FETCH_LIMIT
)
if (loadedOnDemand) return null if (loadedOnDemand) return null
const listed = resultsTag const listed = resultsTag
.sort((a, b) => .sort(
(a.listing.offers[offer].exchange.amount / a.listing.offers[offer].item.amount) - (a, b) =>
(b.listing.offers[offer].exchange.amount / b.listing.offers[offer].item.amount)) 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) .slice(0, SHOW_RESULTS)
.map(result => toPricingResult(result, opts, offer)) .map((result) => toPricingResult(result, opts, offer))
const chaosIsLoaded = ( const chaosIsLoaded =
tradeTag === 'divine' && tradeTag === 'divine' &&
resultsTag.length < results.length && 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 { return {
queryId: query.id, queryId: query.id,
haveTag: tradeTag, haveTag: tradeTag,
// this is a best guess when making request with multiple `have` currencies // this is a best guess when making request with multiple `have` currencies
total: (chaosIsLoaded) total: chaosIsLoaded
? resultsTag.length ? resultsTag.length
: (query.total - (results.length - resultsTag.length)), : query.total - (results.length - resultsTag.length),
listed: listed listed
} }
}) })

View File

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

View File

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

View File

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

View File

@@ -1,51 +1,51 @@
<template> <template>
<div> <div>
<div :class="$style.podium" v-if="podiumVisible"> <div :class="$style.podium" v-if="podiumVisible">
<div v-for="i in [2, 4, 5, 3, 1]"> <div v-for="i in [2, 4, 5, 3, 1]">
<div v-for="patron in patrons[i - 1]" :key="patron.from" <div v-for="patron in patrons[i - 1]" :key="patron.from"
:class="[$style.rating, $style[`rating-${patron.style}`]]" :class="[$style.rating, $style[`rating-${patron.style}`]]">{{ patron.from }}{{ (patron.months > 1) ? `
>{{ patron.from }}{{ (patron.months > 1) ? ` x${patron.months}` : null }}</div> 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>
<div class="text-gray-100 grow layout-column bg-gray-900"> </div>
<div class="grow overflow-y-auto bg-gray-800 rounded-tl"> <div :class="[$style.patronsHorizontal, { 'invisible': podiumVisible }]" :onMouseenter="showPodium">
<component v-if="configClone" <div class="bg-gray-800 rounded p-1 justify-center text-center w-44 shrink-0 flex items-center">
:is="selectedComponent" :config="configClone" :configWidget="configWidget" /> {{ 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>
<div class="border-t bg-gray-900 border-gray-600 p-2 flex justify-end gap-x-2"> <div class="text-gray-100 grow layout-column bg-gray-900">
<button @click="save" class="px-3 bg-gray-800 rounded">{{ t('Save') }}</button> <div class="grow overflow-y-auto bg-gray-800 rounded-tl">
<button @click="cancel" class="px-3">{{ t('Cancel') }}</button> <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> </div>
</div> </div>
</div>
</template> </template>
<script lang="ts"> <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 SettingsStashSearch from '../stash-search/stash-search-editor.vue'
import SettingsStopwatch from './stopwatch.vue' import SettingsStopwatch from './stopwatch.vue'
import SettingsItemSearch from '../item-search/settings-item-search.vue' import SettingsItemSearch from '../item-search/settings-item-search.vue'
import ConversionWarningBanner from '../conversion-warn-banner/ConversionWarningBanner.vue'
function shuffle<T> (array: T[]): T[] { function shuffle<T> (array: T[]): T[] {
let currentIndex = array.length let currentIndex = array.length
@@ -87,7 +88,7 @@ function quit () {
} }
export default defineComponent({ export default defineComponent({
components: { AppTitleBar }, components: { AppTitleBar, ConversionWarningBanner },
props: { props: {
config: { config: {
type: Object as PropType<Widget>, type: Object as PropType<Widget>,
@@ -218,13 +219,17 @@ function flatJoin<T, J> (arr: T[][], joinEl: () => J) {
<style lang="postcss" module> <style lang="postcss" module>
.window { .window {
position: absolute; position: absolute;
top: 0; bottom: 0; left: 0; right: 0; top: 0;
bottom: 0;
left: 0;
right: 0;
margin: 0 auto; margin: 0 auto;
max-width: 50rem; max-width: 50rem;
max-height: 38rem; max-height: 38rem;
overflow: hidden; overflow: hidden;
@apply bg-gray-800; @apply bg-gray-800;
@apply rounded-b; @apply rounded-b;
&:global { &:global {
animation-name: slideInDown; animation-name: slideInDown;
animation-duration: 1s; animation-duration: 1s;
@@ -262,10 +267,13 @@ function flatJoin<T, J> (arr: T[][], joinEl: () => J) {
.patronsHorizontal { .patronsHorizontal {
@apply bg-gray-900 p-1 rounded gap-1; @apply bg-gray-900 p-1 rounded gap-1;
position: absolute; position: absolute;
top: 40rem; left: 0; right: 0; top: 40rem;
left: 0;
right: 0;
margin: 0 auto; margin: 0 auto;
max-width: 50rem; max-width: 50rem;
display: flex; display: flex;
&:global { &:global {
animation-name: slideInDown; animation-name: slideInDown;
animation-duration: 1s; animation-duration: 1s;
@@ -273,10 +281,19 @@ function flatJoin<T, J> (arr: T[][], joinEl: () => J) {
} }
@keyframes slide { @keyframes slide {
0% { transform: translate(0%, 0); } 0% {
4% { transform: translate(0%, 0); } transform: translate(0%, 0);
100% { transform: translate(-99%, 0); } }
4% {
transform: translate(0%, 0);
}
100% {
transform: translate(-99%, 0);
}
} }
.patronsLine { .patronsLine {
display: inline-block; display: inline-block;
animation: slide 64s linear infinite; animation: slide 64s linear infinite;
@@ -291,22 +308,40 @@ function flatJoin<T, J> (arr: T[][], joinEl: () => J) {
width: 100%; width: 100%;
justify-content: center; justify-content: center;
@apply gap-4 p-4; @apply gap-4 p-4;
&:global { &:global {
animation-name: fadeIn; animation-name: fadeIn;
animation-duration: 1.5s; animation-duration: 1.5s;
} }
} }
.podium > div {
.podium>div {
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
justify-content: center; justify-content: center;
min-width: min-content; min-width: min-content;
} }
.podium > div:nth-child(1) { max-width: 18rem; }
.podium > div:nth-child(2) { max-width: 16rem; } .podium>div:nth-child(1) {
.podium > div:nth-child(3) { flex-direction: column; align-items: center; } max-width: 18rem;
.podium > div:nth-child(4) { max-width: 24rem; } }
.podium > div:nth-child(5) { 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 { .rating {
min-width: 3rem; min-width: 3rem;
@@ -314,30 +349,35 @@ function flatJoin<T, J> (arr: T[][], joinEl: () => J) {
white-space: nowrap; white-space: nowrap;
@apply px-1 border; @apply px-1 border;
} }
.rating-1 { .rating-1 {
background-color: rgb(0, 0, 0); background-color: rgb(0, 0, 0);
color: rgb(190, 178, 135); color: rgb(190, 178, 135);
border-color: currentColor; border-color: currentColor;
@apply text-base; @apply text-base;
} }
.rating-2 { .rating-2 {
background-color: rgb(210, 178, 135); background-color: rgb(210, 178, 135);
color: rgb(0, 0, 0); color: rgb(0, 0, 0);
border-color: currentColor; border-color: currentColor;
@apply text-lg; @apply text-lg;
} }
.rating-3 { .rating-3 {
background-color: rgb(213, 159, 0); background-color: rgb(213, 159, 0);
color: rgb(0, 0, 0); color: rgb(0, 0, 0);
border-color: currentColor; border-color: currentColor;
@apply text-lg; @apply text-lg;
} }
.rating-4 { .rating-4 {
background-color: rgb(240, 90, 35); background-color: rgb(240, 90, 35);
color: rgb(255, 255, 255); color: rgb(255, 255, 255);
border-color: currentColor; border-color: currentColor;
@apply text-xl; @apply text-xl;
} }
.rating-5 { .rating-5 {
background-color: rgb(255, 255, 255); background-color: rgb(255, 255, 255);
color: rgb(255, 0, 0); color: rgb(255, 0, 0);

View File

@@ -2,24 +2,28 @@
<div class="p-2 flex flex-col h-full items-center"> <div class="p-2 flex flex-col h-full items-center">
<div class="flex flex-col items-center p-2 mb-4"> <div class="flex flex-col items-center p-2 mb-4">
<img class="w-12 h-12" src="/images/TransferOrb.png"> <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> <p class="">{{ t('app.version', [version]) }}</p>
<div class="flex gap-2"> <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/Kvan7/exiled-exchange-2/releases" target="_blank">{{
<a class="border-b" href="https://github.com/SnosMe/awakened-poe-trade/issues" target="_blank">{{ t('app.report_bug') }}</a> 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> </div>
<div class="border border-gray-600 rounded p-2 whitespace-nowrap min-w-min w-72"> <div class="border border-gray-600 rounded p-2 whitespace-nowrap min-w-min w-72">
<p>{{ info.str1 }}</p> <p>{{ info.str1 }}</p>
<p>{{ info.str2 }}</p> <p>{{ info.str2 }}</p>
<button v-if="info.action" @click="info.action" <button v-if="info.action" @click="info.action" class="btn w-full mt-1">{{ info.actionText }}</button>
class="btn w-full mt-1">{{ info.actionText }}</button>
</div> </div>
<div class="text-center mt-auto py-8"> <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"> <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_tft.gif"> <a class="border-b" href="https://discord.gg/tftrove"
<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> 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> </ul>
</div> </div>
</div> </div>
@@ -39,7 +43,7 @@ function checkForUpdates () {
} }
function openDownloadPage () { function openDownloadPage () {
window.open('https://snosme.github.io/awakened-poe-trade/download') window.open('https://github.com/Kvan7/exiled-exchange-2/releases')
} }
function quitAndInstall () { function quitAndInstall () {
@@ -63,19 +67,50 @@ export default defineComponent({
const rawInfo = Host.updateInfo.value const rawInfo = Host.updateInfo.value
switch (rawInfo.state) { switch (rawInfo.state) {
case 'initial': 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': case 'checking-for-update':
return { str1: t('updates.checking'), str2: t('please_wait') } return { str1: t('updates.checking'), str2: t('please_wait') }
case 'update-not-available': 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': 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': 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': case 'update-available':
return (rawInfo.noDownloadReason) 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') } 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="mb-2">
<div class="flex-1 mb-1">{{ t(':poe_log_file') }}</div> <div class="flex-1 mb-1">{{ t(':poe_log_file') }}</div>
<input v-model.trim="clientLog" <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>
<div class="mb-4"> <div class="mb-4">
<div class="flex-1 mb-1">{{ t(':poe_cfg_file') }}</div> <div class="flex-1 mb-1">{{ t(':poe_cfg_file') }}</div>
<input v-model.trim="gameConfig" <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> </div>
<hr class="mb-4 mx-8 border-gray-700"> <hr class="mb-4 mx-8 border-gray-700">
<div class="mb-2"> <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