@poe2-toolkit/tree-react 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +165 -0
- package/dist/TreeView.d.ts +101 -0
- package/dist/TreeView.d.ts.map +1 -0
- package/dist/TreeView.js +756 -0
- package/dist/TreeView.js.map +1 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +10 -0
- package/dist/index.js.map +1 -0
- package/dist/resources.d.ts +13 -0
- package/dist/resources.d.ts.map +1 -0
- package/dist/resources.js +2 -0
- package/dist/resources.js.map +1 -0
- package/dist/spriteKeys.d.ts +35 -0
- package/dist/spriteKeys.d.ts.map +1 -0
- package/dist/spriteKeys.js +75 -0
- package/dist/spriteKeys.js.map +1 -0
- package/package.json +52 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Vladislav Rajtmajer
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
# @poe2-toolkit/tree-react
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/@poe2-toolkit/tree-react)
|
|
4
|
+
[](src/index.ts)
|
|
5
|
+
[](https://react.dev)
|
|
6
|
+
[](LICENSE)
|
|
7
|
+
|
|
8
|
+
React renderer for the Path of Exile 2 passive tree. It's a thin view layer on
|
|
9
|
+
top of [`@poe2-toolkit/tree-core`](../poe2-tree-core): the core works out where
|
|
10
|
+
everything goes, and this package draws it and runs the canvas, panning,
|
|
11
|
+
zooming, hovering, and clicking.
|
|
12
|
+
|
|
13
|
+
It does no geometry of its own. Positions, sizes, rotations, the hub layout, and
|
|
14
|
+
hit-testing all come from the core. If you ever catch this package computing a
|
|
15
|
+
coordinate, that's a bug.
|
|
16
|
+
|
|
17
|
+
The geometry lives in a framework-agnostic core, so the same `Scene` can just as
|
|
18
|
+
easily be drawn from Vue, Svelte, or anything else. React is what this project
|
|
19
|
+
happens to use.
|
|
20
|
+
|
|
21
|
+
> **Live demo:** see this renderer in a real app at
|
|
22
|
+
> [poe.rajtik.com/tree](https://poe.rajtik.com/tree).
|
|
23
|
+
|
|
24
|
+
## Who does what
|
|
25
|
+
|
|
26
|
+
| Concern | Owner |
|
|
27
|
+
|---|---|
|
|
28
|
+
| node positions, sizes, hub geometry, arcs, hit-test math | core |
|
|
29
|
+
| canvas, device-pixel sizing, draw loop, layer order | this package |
|
|
30
|
+
| pan, zoom, wheel, fullscreen, pointer hover and click | this package |
|
|
31
|
+
| loading atlas bitmaps, colors, tooltips, surrounding UI | you |
|
|
32
|
+
|
|
33
|
+
## Install
|
|
34
|
+
|
|
35
|
+
```sh
|
|
36
|
+
npm install @poe2-toolkit/tree-react @poe2-toolkit/tree-core
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
React 18 or newer is a peer dependency.
|
|
40
|
+
|
|
41
|
+
## Usage
|
|
42
|
+
|
|
43
|
+
The tree data comes from GGG's official skill-tree export. Run it through the
|
|
44
|
+
core's GGG adapter to get a `TreeData`, build a `Scene` from it, and hand that to
|
|
45
|
+
`TreeView`.
|
|
46
|
+
|
|
47
|
+
```tsx
|
|
48
|
+
import { buildScene } from '@poe2-toolkit/tree-core';
|
|
49
|
+
import { normalizeGggTree } from '@poe2-toolkit/tree-core/ggg';
|
|
50
|
+
import { TreeView } from '@poe2-toolkit/tree-react';
|
|
51
|
+
|
|
52
|
+
// Normalize GGG's data.json into the engine's TreeData (once per tree).
|
|
53
|
+
const data = normalizeGggTree(rawGggExport, '0_5');
|
|
54
|
+
|
|
55
|
+
// Build a render-ready scene for the current build (rebuild it on edits).
|
|
56
|
+
const scene = buildScene(data, { allocation });
|
|
57
|
+
|
|
58
|
+
// Draw it. `resources` (atlas bitmaps + manifest) is optional; leave it out
|
|
59
|
+
// and you get the vector debug render.
|
|
60
|
+
<TreeView
|
|
61
|
+
scene={scene}
|
|
62
|
+
resources={{ manifest, atlases }}
|
|
63
|
+
activeClassId={allocation.classId}
|
|
64
|
+
activeAscendancy={allocation.ascendId}
|
|
65
|
+
onNodeClick={(skill) => toggle(skill)}
|
|
66
|
+
/>;
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
The pattern is state in, intent out. The `scene` already holds everything
|
|
70
|
+
visual, so the component just reports what the user did (`onNodeClick`,
|
|
71
|
+
`onNodeHover`, and the rest) and never touches the build itself.
|
|
72
|
+
|
|
73
|
+
## Graphics
|
|
74
|
+
|
|
75
|
+
`TreeView` doesn't load any images. You give it a `RenderResources`:
|
|
76
|
+
|
|
77
|
+
```ts
|
|
78
|
+
interface RenderResources {
|
|
79
|
+
manifest: SpriteManifest; // sprite key -> native atlas rect
|
|
80
|
+
atlases: Record<string, CanvasImageSource>; // atlas id -> bitmap
|
|
81
|
+
}
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
To draw a node, the renderer turns it into a sprite key with the helpers in
|
|
85
|
+
[`spriteKeys`](src/spriteKeys.ts) (`iconKeyFor`, `frameKeyFor`, `effectKeyFor`,
|
|
86
|
+
and friends), looks that key up in your `manifest`, and blits the rect from the
|
|
87
|
+
matching atlas. The keys follow GGG's atlas naming, so pointing the renderer at a
|
|
88
|
+
different atlas set comes down to swapping that one file. Leave `resources` out
|
|
89
|
+
and you get a plain vector render of discs and rails, which is handy for
|
|
90
|
+
debugging without art.
|
|
91
|
+
|
|
92
|
+
The hub artwork (class portrait and ornate ring) comes in through the optional
|
|
93
|
+
`centreSprites` prop. Skip it and the hub falls back to a vector placeholder.
|
|
94
|
+
|
|
95
|
+
## Component props
|
|
96
|
+
|
|
97
|
+
```tsx
|
|
98
|
+
<TreeView
|
|
99
|
+
scene={scene} // required: core.buildScene output
|
|
100
|
+
resources={resources} // atlas bitmaps + manifest (omit for vector)
|
|
101
|
+
activeClassId={classId} // rotates the active ring onto the class
|
|
102
|
+
activeAscendancy={ascId} // relocates that ascendancy disc into the hub
|
|
103
|
+
centreSprites={centreSprites} // optional portrait + ring artwork
|
|
104
|
+
preview={preview} // hover highlight: pending add (gold) / remove (red)
|
|
105
|
+
focus={worldRect} // pass a fresh rect to pan + zoom-fit to it
|
|
106
|
+
wheelZoom // turn on wheel zoom (off by default)
|
|
107
|
+
controls={controlsRef} // imperative zoomIn() / zoomOut()
|
|
108
|
+
onNodeClick={(skill, screen) => …}
|
|
109
|
+
onNodeDoubleClick={(skill) => …}
|
|
110
|
+
onNodeHover={(skill, screen) => …}
|
|
111
|
+
onInteractStart={() => …} // a press started on the canvas (e.g. close popovers)
|
|
112
|
+
/>
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
Exported types: `TreeViewProps`, `TreeViewControls`, `AllocationPreview`,
|
|
116
|
+
`CentreSprite`, `RenderResources`.
|
|
117
|
+
|
|
118
|
+
For external +/- buttons, reach for the imperative handle on `controls`:
|
|
119
|
+
|
|
120
|
+
```tsx
|
|
121
|
+
const controls = useRef<TreeViewControls>(null);
|
|
122
|
+
// …
|
|
123
|
+
<button onClick={() => controls.current?.zoomIn()}>+</button>
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
## Non-goals
|
|
127
|
+
|
|
128
|
+
This package won't:
|
|
129
|
+
|
|
130
|
+
- compute or adjust any position, size, rotation, or hub placement;
|
|
131
|
+
- carry magic numbers for node, icon, or effect sizing;
|
|
132
|
+
- lock itself to one data source (it only knows `Scene` and `SpriteManifest`);
|
|
133
|
+
- claim to be the only frontend. A Vue, Svelte, or Livewire renderer on the same
|
|
134
|
+
contract is every bit as valid.
|
|
135
|
+
|
|
136
|
+
## Local development
|
|
137
|
+
|
|
138
|
+
In-repo, this package finds `@poe2-toolkit/tree-core` two ways:
|
|
139
|
+
|
|
140
|
+
- typecheck and build read it through a `tsconfig` `paths` entry that points at
|
|
141
|
+
the sibling source, so there's no build or link step;
|
|
142
|
+
- `npm install` links the sibling through the `file:../poe2-tree-core`
|
|
143
|
+
dependency.
|
|
144
|
+
|
|
145
|
+
When you split this out into its own repo, change two things: drop the `paths`
|
|
146
|
+
block in `tsconfig.json`, and swap the `@poe2-toolkit/tree-core` dependency from
|
|
147
|
+
`file:../poe2-tree-core` to a published version like `^0.1.0`.
|
|
148
|
+
|
|
149
|
+
```sh
|
|
150
|
+
npm install
|
|
151
|
+
npm run typecheck
|
|
152
|
+
npm run build
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
## Attributions and legal
|
|
156
|
+
|
|
157
|
+
This is an unofficial, fan-made project, **not** affiliated with, endorsed by, or
|
|
158
|
+
sponsored by Grinding Gear Games. "Path of Exile 2" is a trademark of Grinding
|
|
159
|
+
Gear Games, and all game content, data, and art are their property. This package
|
|
160
|
+
ships code only and stores nothing derived from the game. Thank you to Grinding
|
|
161
|
+
Gear Games for making Path of Exile 2. See the repository [NOTICE](../../NOTICE.md).
|
|
162
|
+
|
|
163
|
+
## License
|
|
164
|
+
|
|
165
|
+
MIT — see [LICENSE](./LICENSE).
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import type { Scene, WorldRect } from '@poe2-toolkit/tree-core';
|
|
2
|
+
import type { CSSProperties, Ref } from 'react';
|
|
3
|
+
import type { RenderResources } from './resources.js';
|
|
4
|
+
export interface TreeViewProps {
|
|
5
|
+
/** Computed geometry from `@poe2-toolkit/tree-core`'s `buildScene`. */
|
|
6
|
+
scene: Scene;
|
|
7
|
+
/** Atlas bitmaps + manifest. Omit for the vector debug render (no GGG art). */
|
|
8
|
+
resources?: RenderResources;
|
|
9
|
+
/** Active class id — rotates the active ring onto that class. */
|
|
10
|
+
activeClassId?: number;
|
|
11
|
+
/** Active ascendancy id — its disc is relocated into the hub. */
|
|
12
|
+
activeAscendancy?: string;
|
|
13
|
+
/**
|
|
14
|
+
* Optional centre artwork. Each sprite is a URL + source sub-rect, drawn at
|
|
15
|
+
* the hub sized to the matching core radius:
|
|
16
|
+
* - `portrait` -> `ring.artRadius` (the inner class illustration)
|
|
17
|
+
* - `ringStatic` -> `ring.frameRadius` (the ornate ring)
|
|
18
|
+
* - `ringActive` -> `ring.activeRadius`, rotated by the active class's
|
|
19
|
+
* `ringRotation` (the gold band pointing at the class)
|
|
20
|
+
* When absent, the vector hub stand-in is drawn instead.
|
|
21
|
+
*/
|
|
22
|
+
centreSprites?: {
|
|
23
|
+
portrait?: CentreSprite;
|
|
24
|
+
ringStatic?: CentreSprite;
|
|
25
|
+
ringActive?: CentreSprite;
|
|
26
|
+
};
|
|
27
|
+
/** Single-click intent out: the skill id and the node's centre in canvas px. */
|
|
28
|
+
onNodeClick?: (skill: number, screen: {
|
|
29
|
+
x: number;
|
|
30
|
+
y: number;
|
|
31
|
+
}) => void;
|
|
32
|
+
/** Double-click intent out (skill id). */
|
|
33
|
+
onNodeDoubleClick?: (skill: number) => void;
|
|
34
|
+
/** Fires when a press starts on the canvas (pan/empty click) — e.g. to dismiss popovers. */
|
|
35
|
+
onInteractStart?: () => void;
|
|
36
|
+
/**
|
|
37
|
+
* Hover feedback out: the skill id (or null when leaving all nodes) and, when
|
|
38
|
+
* hovering a node, its centre in canvas pixels — so callers can anchor UI
|
|
39
|
+
* (e.g. an attribute picker) to the node.
|
|
40
|
+
*/
|
|
41
|
+
onNodeHover?: (skill: number | null, screen?: {
|
|
42
|
+
x: number;
|
|
43
|
+
y: number;
|
|
44
|
+
}) => void;
|
|
45
|
+
/**
|
|
46
|
+
* Hover preview to highlight: the nodes/edges that a click would allocate
|
|
47
|
+
* (`add`) or remove (`remove`). Drawn on top of the base render.
|
|
48
|
+
*/
|
|
49
|
+
preview?: AllocationPreview | null;
|
|
50
|
+
/** Imperative zoom controls, for external +/- buttons. */
|
|
51
|
+
controls?: Ref<TreeViewControls>;
|
|
52
|
+
/**
|
|
53
|
+
* Enable mouse-wheel zoom. Off by default so the page can scroll over an
|
|
54
|
+
* embedded canvas; turn on in fullscreen, where there's nothing to scroll.
|
|
55
|
+
*/
|
|
56
|
+
wheelZoom?: boolean;
|
|
57
|
+
/**
|
|
58
|
+
* World rect to frame (pan + zoom to fit). Whenever the object reference
|
|
59
|
+
* changes the view re-frames it — pass a fresh object to trigger, `null` to
|
|
60
|
+
* keep the current/default view (the hub).
|
|
61
|
+
*/
|
|
62
|
+
focus?: WorldRect | null;
|
|
63
|
+
/**
|
|
64
|
+
* Skill ids to emphasise with a standing teal ring (e.g. name-search hits).
|
|
65
|
+
* Unlike `onNodeHover`, this is a persistent set drawn until it changes.
|
|
66
|
+
*/
|
|
67
|
+
highlight?: Set<number> | null;
|
|
68
|
+
className?: string;
|
|
69
|
+
style?: CSSProperties;
|
|
70
|
+
}
|
|
71
|
+
/** Imperative handle exposed via `controls` for external zoom buttons. */
|
|
72
|
+
export interface TreeViewControls {
|
|
73
|
+
zoomIn: () => void;
|
|
74
|
+
zoomOut: () => void;
|
|
75
|
+
}
|
|
76
|
+
/** Hover preview of a pending allocate/remove: node ids + edge keys to glow. */
|
|
77
|
+
export interface AllocationPreview {
|
|
78
|
+
kind: 'add' | 'remove';
|
|
79
|
+
nodes: Set<number>;
|
|
80
|
+
/** Edge keys as `min-max` of the two node ids. */
|
|
81
|
+
edges: Set<string>;
|
|
82
|
+
}
|
|
83
|
+
/** A sprite to draw at the hub: source image URL + the sub-rect to crop. */
|
|
84
|
+
export interface CentreSprite {
|
|
85
|
+
url: string;
|
|
86
|
+
sx: number;
|
|
87
|
+
sy: number;
|
|
88
|
+
sw: number;
|
|
89
|
+
sh: number;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Thin canvas view over a core `Scene`. It owns nothing geometric — pan, zoom,
|
|
93
|
+
* device-pixel sizing, the draw loop, and hover hit-testing only. Positions,
|
|
94
|
+
* sizes, projection and hit-testing all come from `@poe2-toolkit/tree-core`.
|
|
95
|
+
*
|
|
96
|
+
* Without `resources` it renders a vector debug view (nodes as discs, edges as
|
|
97
|
+
* lines/arcs, the hub opening as a ring) — enough to see the geometry before any
|
|
98
|
+
* GGG atlas art exists.
|
|
99
|
+
*/
|
|
100
|
+
export declare function TreeView({ scene, resources, activeClassId, activeAscendancy, centreSprites, onNodeClick, onNodeDoubleClick, onInteractStart, onNodeHover, preview, controls, wheelZoom, focus, highlight, className, style, }: TreeViewProps): React.JSX.Element;
|
|
101
|
+
//# sourceMappingURL=TreeView.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TreeView.d.ts","sourceRoot":"","sources":["../src/TreeView.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAY,KAAK,EAAqC,SAAS,EAAE,MAAM,yBAAyB,CAAC;AAG7G,OAAO,KAAK,EAAE,aAAa,EAAoE,GAAG,EAAE,MAAM,OAAO,CAAC;AAClH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAGtD,MAAM,WAAW,aAAa;IAC5B,uEAAuE;IACvE,KAAK,EAAE,KAAK,CAAC;IACb,+EAA+E;IAC/E,SAAS,CAAC,EAAE,eAAe,CAAC;IAC5B,iEAAiE;IACjE,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,iEAAiE;IACjE,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B;;;;;;;;OAQG;IACH,aAAa,CAAC,EAAE;QACd,QAAQ,CAAC,EAAE,YAAY,CAAC;QACxB,UAAU,CAAC,EAAE,YAAY,CAAC;QAC1B,UAAU,CAAC,EAAE,YAAY,CAAC;KAC3B,CAAC;IACF,gFAAgF;IAChF,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC;IACxE,0CAA0C;IAC1C,iBAAiB,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAC5C,4FAA4F;IAC5F,eAAe,CAAC,EAAE,MAAM,IAAI,CAAC;IAC7B;;;;OAIG;IACH,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,EAAE,MAAM,CAAC,EAAE;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC;IAChF;;;OAGG;IACH,OAAO,CAAC,EAAE,iBAAiB,GAAG,IAAI,CAAC;IACnC,0DAA0D;IAC1D,QAAQ,CAAC,EAAE,GAAG,CAAC,gBAAgB,CAAC,CAAC;IACjC;;;OAGG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB;;;;OAIG;IACH,KAAK,CAAC,EAAE,SAAS,GAAG,IAAI,CAAC;IACzB;;;OAGG;IACH,SAAS,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,IAAI,CAAC;IAC/B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,aAAa,CAAC;CACvB;AAED,0EAA0E;AAC1E,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,MAAM,IAAI,CAAC;IACnB,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB;AAED,gFAAgF;AAChF,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,KAAK,GAAG,QAAQ,CAAC;IACvB,KAAK,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IACnB,kDAAkD;IAClD,KAAK,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;CACpB;AAED,4EAA4E;AAC5E,MAAM,WAAW,YAAY;IAC3B,GAAG,EAAE,MAAM,CAAC;IACZ,EAAE,EAAE,MAAM,CAAC;IACX,EAAE,EAAE,MAAM,CAAC;IACX,EAAE,EAAE,MAAM,CAAC;IACX,EAAE,EAAE,MAAM,CAAC;CACZ;AA0ED;;;;;;;;GAQG;AACH,wBAAgB,QAAQ,CAAC,EACvB,KAAK,EACL,SAAS,EACT,aAAa,EACb,gBAAgB,EAChB,aAAa,EACb,WAAW,EACX,iBAAiB,EACjB,eAAe,EACf,WAAW,EACX,OAAO,EACP,QAAQ,EACR,SAAS,EACT,KAAK,EACL,SAAS,EACT,SAAS,EACT,KAAK,GACN,EAAE,aAAa,GAAG,KAAK,CAAC,GAAG,CAAC,OAAO,CAkWnC"}
|