add planet name, upgrade level and 3d render of PI setup

This commit is contained in:
Calli
2023-07-02 22:34:00 +03:00
parent 04bdd3cc69
commit c89506420e
8 changed files with 384 additions and 33 deletions

View File

@@ -7,6 +7,7 @@ Any questions, feedback or suggestions are welcome at [EVE PI Discord](https://d
## [Avanto hosted PI tool](https://pi.avanto.tk) ## [Avanto hosted PI tool](https://pi.avanto.tk)
![Screenshot of PI tool](https://github.com/calli-eve/eve-pi/blob/main/images/eve-pi.png) ![Screenshot of PI tool](https://github.com/calli-eve/eve-pi/blob/main/images/eve-pi.png)
![3D render of a planet](https://github.com/calli-eve/eve-pi/blob/main/images/3dplanet.png)
Features: Features:
@@ -17,6 +18,7 @@ Features:
- Highlight the planet if extractor has stopped or has not been started. - Highlight the planet if extractor has stopped or has not been started.
- Backup to download characters to a file - Backup to download characters to a file
- Rstore from a file. Must be from the same instance! - Rstore from a file. Must be from the same instance!
- View the 3D render of the planet with your PI setup by clicking the planet
## Basic usage ## Basic usage
@@ -73,3 +75,12 @@ EVE_SSO_CALLBACK_URL=Callback URL (This should be the domain you are hosting at
## Hosting ## Hosting
Easiest way to host is deploy the app through Vercel https://vercel.com. Login with github, point to eve-pi repository, setup the env variables and the app should work out of the box. Easiest way to host is deploy the app through Vercel https://vercel.com. Login with github, point to eve-pi repository, setup the env variables and the app should work out of the box.
## Planetary coordinate system
ESI PI planet info endpoint returns pin coorinates. These coordinates seem to work like this:
Latitude starts from 0 at the north pole and ends at Pi (3.141...) at the south pole.
Longitude starts at some point from 0 and ends at Tau (6.283...) after going around the planet.
To translate the coordinates to 2D plane one could use Azimuthal equidistant projection. With this service we will just render a webgl sphere and place the pins directly on it. To access the render click the planet icon.

BIN
images/3dplanet.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 500 KiB

100
package-lock.json generated
View File

@@ -27,11 +27,13 @@
"react-countdown": "^2.3.5", "react-countdown": "^2.3.5",
"react-dom": "18.2.0", "react-dom": "18.2.0",
"sharp": "^0.32.1", "sharp": "^0.32.1",
"three": "^0.154.0",
"typescript": "5.1.3" "typescript": "5.1.3"
}, },
"devDependencies": { "devDependencies": {
"@types/crypto-js": "^4.1.1", "@types/crypto-js": "^4.1.1",
"@types/luxon": "^3.3.0" "@types/luxon": "^3.3.0",
"@types/three": "^0.152.1"
} }
}, },
"node_modules/@babel/code-frame": { "node_modules/@babel/code-frame": {
@@ -870,6 +872,12 @@
"tslib": "^2.4.0" "tslib": "^2.4.0"
} }
}, },
"node_modules/@tweenjs/tween.js": {
"version": "18.6.4",
"resolved": "https://registry.npmjs.org/@tweenjs/tween.js/-/tween.js-18.6.4.tgz",
"integrity": "sha512-lB9lMjuqjtuJrx7/kOkqQBtllspPIN+96OvTCeJ2j5FEzinoAXTdAMFnDAQT1KVPRlnYfBrqxtqP66vDM40xxQ==",
"dev": true
},
"node_modules/@types/crypto-js": { "node_modules/@types/crypto-js": {
"version": "4.1.1", "version": "4.1.1",
"resolved": "https://registry.npmjs.org/@types/crypto-js/-/crypto-js-4.1.1.tgz", "resolved": "https://registry.npmjs.org/@types/crypto-js/-/crypto-js-4.1.1.tgz",
@@ -941,6 +949,31 @@
"resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.3.tgz", "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.3.tgz",
"integrity": "sha512-5cJ8CB4yAx7BH1oMvdU0Jh9lrEXyPkar6F9G/ERswkCuvP4KQZfZkSjcMbAICCpQTN4OuZn8tz0HiKv9TGZgrQ==" "integrity": "sha512-5cJ8CB4yAx7BH1oMvdU0Jh9lrEXyPkar6F9G/ERswkCuvP4KQZfZkSjcMbAICCpQTN4OuZn8tz0HiKv9TGZgrQ=="
}, },
"node_modules/@types/stats.js": {
"version": "0.17.0",
"resolved": "https://registry.npmjs.org/@types/stats.js/-/stats.js-0.17.0.tgz",
"integrity": "sha512-9w+a7bR8PeB0dCT/HBULU2fMqf6BAzvKbxFboYhmDtDkKPiyXYbjoe2auwsXlEFI7CFNMF1dCv3dFH5Poy9R1w==",
"dev": true
},
"node_modules/@types/three": {
"version": "0.152.1",
"resolved": "https://registry.npmjs.org/@types/three/-/three-0.152.1.tgz",
"integrity": "sha512-PMOCQnx9JRmq+2OUGTPoY9h1hTWD2L7/nmuW/SyNq1Vbq3Lwt3MNdl3wYSa4DvLTGv62NmIXD9jYdAOwohwJyw==",
"dev": true,
"dependencies": {
"@tweenjs/tween.js": "~18.6.4",
"@types/stats.js": "*",
"@types/webxr": "*",
"fflate": "~0.6.9",
"lil-gui": "~0.17.0"
}
},
"node_modules/@types/webxr": {
"version": "0.5.2",
"resolved": "https://registry.npmjs.org/@types/webxr/-/webxr-0.5.2.tgz",
"integrity": "sha512-szL74BnIcok9m7QwYtVmQ+EdIKwbjPANudfuvDrAF8Cljg9MKUlIoc1w5tjj9PMpeSH3U1Xnx//czQybJ0EfSw==",
"dev": true
},
"node_modules/@typescript-eslint/parser": { "node_modules/@typescript-eslint/parser": {
"version": "5.59.11", "version": "5.59.11",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.59.11.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.59.11.tgz",
@@ -2377,6 +2410,12 @@
"reusify": "^1.0.4" "reusify": "^1.0.4"
} }
}, },
"node_modules/fflate": {
"version": "0.6.10",
"resolved": "https://registry.npmjs.org/fflate/-/fflate-0.6.10.tgz",
"integrity": "sha512-IQrh3lEPM93wVCEczc9SaAOvkmcoQn/G8Bo1e8ZPlY3X3bnAxWaBdvTdvM1hP62iZp0BXWDy4vTAy4fF0+Dlpg==",
"dev": true
},
"node_modules/file-entry-cache": { "node_modules/file-entry-cache": {
"version": "6.0.1", "version": "6.0.1",
"resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz",
@@ -3201,6 +3240,12 @@
"node": ">= 0.8.0" "node": ">= 0.8.0"
} }
}, },
"node_modules/lil-gui": {
"version": "0.17.0",
"resolved": "https://registry.npmjs.org/lil-gui/-/lil-gui-0.17.0.tgz",
"integrity": "sha512-MVBHmgY+uEbmJNApAaPbtvNh1RCAeMnKym82SBjtp5rODTYKWtM+MXHCifLe2H2Ti1HuBGBtK/5SyG4ShQ3pUQ==",
"dev": true
},
"node_modules/lines-and-columns": { "node_modules/lines-and-columns": {
"version": "1.2.4", "version": "1.2.4",
"resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
@@ -4556,6 +4601,11 @@
"resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
"integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==" "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw=="
}, },
"node_modules/three": {
"version": "0.154.0",
"resolved": "https://registry.npmjs.org/three/-/three-0.154.0.tgz",
"integrity": "sha512-Uzz8C/5GesJzv8i+Y2prEMYUwodwZySPcNhuJUdsVMH2Yn4Nm8qlbQe6qRN5fOhg55XB0WiLfTPBxVHxpE60ug=="
},
"node_modules/titleize": { "node_modules/titleize": {
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/titleize/-/titleize-3.0.0.tgz", "resolved": "https://registry.npmjs.org/titleize/-/titleize-3.0.0.tgz",
@@ -5371,6 +5421,12 @@
"tslib": "^2.4.0" "tslib": "^2.4.0"
} }
}, },
"@tweenjs/tween.js": {
"version": "18.6.4",
"resolved": "https://registry.npmjs.org/@tweenjs/tween.js/-/tween.js-18.6.4.tgz",
"integrity": "sha512-lB9lMjuqjtuJrx7/kOkqQBtllspPIN+96OvTCeJ2j5FEzinoAXTdAMFnDAQT1KVPRlnYfBrqxtqP66vDM40xxQ==",
"dev": true
},
"@types/crypto-js": { "@types/crypto-js": {
"version": "4.1.1", "version": "4.1.1",
"resolved": "https://registry.npmjs.org/@types/crypto-js/-/crypto-js-4.1.1.tgz", "resolved": "https://registry.npmjs.org/@types/crypto-js/-/crypto-js-4.1.1.tgz",
@@ -5442,6 +5498,31 @@
"resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.3.tgz", "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.3.tgz",
"integrity": "sha512-5cJ8CB4yAx7BH1oMvdU0Jh9lrEXyPkar6F9G/ERswkCuvP4KQZfZkSjcMbAICCpQTN4OuZn8tz0HiKv9TGZgrQ==" "integrity": "sha512-5cJ8CB4yAx7BH1oMvdU0Jh9lrEXyPkar6F9G/ERswkCuvP4KQZfZkSjcMbAICCpQTN4OuZn8tz0HiKv9TGZgrQ=="
}, },
"@types/stats.js": {
"version": "0.17.0",
"resolved": "https://registry.npmjs.org/@types/stats.js/-/stats.js-0.17.0.tgz",
"integrity": "sha512-9w+a7bR8PeB0dCT/HBULU2fMqf6BAzvKbxFboYhmDtDkKPiyXYbjoe2auwsXlEFI7CFNMF1dCv3dFH5Poy9R1w==",
"dev": true
},
"@types/three": {
"version": "0.152.1",
"resolved": "https://registry.npmjs.org/@types/three/-/three-0.152.1.tgz",
"integrity": "sha512-PMOCQnx9JRmq+2OUGTPoY9h1hTWD2L7/nmuW/SyNq1Vbq3Lwt3MNdl3wYSa4DvLTGv62NmIXD9jYdAOwohwJyw==",
"dev": true,
"requires": {
"@tweenjs/tween.js": "~18.6.4",
"@types/stats.js": "*",
"@types/webxr": "*",
"fflate": "~0.6.9",
"lil-gui": "~0.17.0"
}
},
"@types/webxr": {
"version": "0.5.2",
"resolved": "https://registry.npmjs.org/@types/webxr/-/webxr-0.5.2.tgz",
"integrity": "sha512-szL74BnIcok9m7QwYtVmQ+EdIKwbjPANudfuvDrAF8Cljg9MKUlIoc1w5tjj9PMpeSH3U1Xnx//czQybJ0EfSw==",
"dev": true
},
"@typescript-eslint/parser": { "@typescript-eslint/parser": {
"version": "5.59.11", "version": "5.59.11",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.59.11.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.59.11.tgz",
@@ -6443,6 +6524,12 @@
"reusify": "^1.0.4" "reusify": "^1.0.4"
} }
}, },
"fflate": {
"version": "0.6.10",
"resolved": "https://registry.npmjs.org/fflate/-/fflate-0.6.10.tgz",
"integrity": "sha512-IQrh3lEPM93wVCEczc9SaAOvkmcoQn/G8Bo1e8ZPlY3X3bnAxWaBdvTdvM1hP62iZp0BXWDy4vTAy4fF0+Dlpg==",
"dev": true
},
"file-entry-cache": { "file-entry-cache": {
"version": "6.0.1", "version": "6.0.1",
"resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz",
@@ -6999,6 +7086,12 @@
"type-check": "~0.4.0" "type-check": "~0.4.0"
} }
}, },
"lil-gui": {
"version": "0.17.0",
"resolved": "https://registry.npmjs.org/lil-gui/-/lil-gui-0.17.0.tgz",
"integrity": "sha512-MVBHmgY+uEbmJNApAaPbtvNh1RCAeMnKym82SBjtp5rODTYKWtM+MXHCifLe2H2Ti1HuBGBtK/5SyG4ShQ3pUQ==",
"dev": true
},
"lines-and-columns": { "lines-and-columns": {
"version": "1.2.4", "version": "1.2.4",
"resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
@@ -7878,6 +7971,11 @@
"resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
"integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==" "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw=="
}, },
"three": {
"version": "0.154.0",
"resolved": "https://registry.npmjs.org/three/-/three-0.154.0.tgz",
"integrity": "sha512-Uzz8C/5GesJzv8i+Y2prEMYUwodwZySPcNhuJUdsVMH2Yn4Nm8qlbQe6qRN5fOhg55XB0WiLfTPBxVHxpE60ug=="
},
"titleize": { "titleize": {
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/titleize/-/titleize-3.0.0.tgz", "resolved": "https://registry.npmjs.org/titleize/-/titleize-3.0.0.tgz",

View File

@@ -29,10 +29,12 @@
"react-countdown": "^2.3.5", "react-countdown": "^2.3.5",
"react-dom": "18.2.0", "react-dom": "18.2.0",
"sharp": "^0.32.1", "sharp": "^0.32.1",
"three": "^0.154.0",
"typescript": "5.1.3" "typescript": "5.1.3"
}, },
"devDependencies": { "devDependencies": {
"@types/crypto-js": "^4.1.1", "@types/crypto-js": "^4.1.1",
"@types/luxon": "^3.3.0" "@types/luxon": "^3.3.0",
"@types/three": "^0.152.1"
} }
} }

10
public/circle.svg Normal file
View File

@@ -0,0 +1,10 @@
<svg xmlns="http://www.w3.org/2000/svg" width="2000" height="2000">
<circle r="1" cx="1044.9600749327133" cy="74.18930645014314" stroke="#e0ac2b"></circle>
<circle r="1" cx="1058.4117550745584" cy="58.19017363712419" stroke="#e0ac2b"></circle>
<circle r="1" cx="1049.8380060337904" cy="60.72804832023394" stroke="#e0ac2b"></circle>
<circle r="1" cx="1047.4748578968981" cy="67.31575878445028" stroke="#e0ac2b"></circle>
<circle r="1" cx="1044.7022970518328" cy="56.933346735981104" stroke="#e0ac2b"></circle>
<circle r="1" cx="1041.9031078728167" cy="64.15153707237278" stroke="#e0ac2b"></circle>
<circle r="1" cx="1039.0654570407075" cy="70.63510529411178" stroke="#e0ac2b"></circle>
<circle r="1" cx="1055.2421718655278" cy="64.3413157302308" stroke="#e0ac2b"></circle>
</svg>

After

Width:  |  Height:  |  Size: 810 B

BIN
public/factory.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -0,0 +1,140 @@
import React, { useEffect } from "react";
import { PlanetInfo, PlanetInfoUniverse } from "./PlanetCard";
import * as THREE from "three";
import * as BufferGeometryUtils from "three/examples/jsm/utils/BufferGeometryUtils.js";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls";
const commandCenterIds = [2254, 2524, 2525, 2533, 2534, 2549, 2550, 2551];
const PinsCanvas3D = ({
planetInfo,
}: {
planetInfo: PlanetInfo | undefined;
}) => {
useEffect(() => {
if (!planetInfo) return;
const pinsWithoutCommandCentes =
planetInfo?.pins.filter(
(p) => !commandCenterIds.some((c) => c === p.type_id)
) ?? [];
const CANVAS = document.querySelector("#canvas") as HTMLCanvasElement;
if (!CANVAS) return;
const SCENE_ANTIALIAS = true;
const SCENE_ALPHA = true;
const SCENE_BACKGROUND_COLOR = 0x000000;
const CAMERA_FOV = 20;
const CAMERA_NEAR = 100;
const CAMERA_FAR = 500;
const CAMERA_X = 0;
const CAMERA_Y = 0;
const CAMERA_Z = 220;
const SPHERE_RADIUS = 30;
const LATITUDE_COUNT = 40;
const LONGITUDE_COUNT = 80;
const DOT_SIZE = 0.2;
const DOT_COLOR = 0x36454f;
const renderScene = () => {
const renderer = new THREE.WebGLRenderer({
canvas: CANVAS as HTMLCanvasElement,
antialias: SCENE_ANTIALIAS,
alpha: SCENE_ALPHA,
});
const camera = new THREE.PerspectiveCamera(
CAMERA_FOV,
CANVAS.width / CANVAS.height,
CAMERA_NEAR,
CAMERA_FAR
);
const controls = new OrbitControls(camera, renderer.domElement);
camera.position.set(CAMERA_X, CAMERA_Y, CAMERA_Z);
controls.update();
const scene = new THREE.Scene();
scene.background = new THREE.Color(SCENE_BACKGROUND_COLOR);
const dotGeometries: THREE.CircleGeometry[] = [];
const dotGeometriesPI: THREE.CircleGeometry[] = [];
const vector = new THREE.Vector3();
const vectorPI = new THREE.Vector3();
pinsWithoutCommandCentes.forEach((p) => {
const dotGeometryPI = new THREE.CircleGeometry(DOT_SIZE, 9);
const phi = p.latitude;
const theta = p.longitude;
vectorPI.setFromSphericalCoords(SPHERE_RADIUS, phi, theta);
dotGeometryPI.lookAt(vectorPI);
dotGeometryPI.translate(vectorPI.x, vectorPI.y, vectorPI.z);
dotGeometriesPI.push(dotGeometryPI);
});
for (let lat = 0; lat < LATITUDE_COUNT; lat += 1) {
for (let lng = 0; lng < LONGITUDE_COUNT; lng += 1) {
const dotGeometry = new THREE.CircleGeometry(DOT_SIZE, 5);
const phi = (Math.PI / LATITUDE_COUNT) * lat;
const theta = ((2 * Math.PI) / LONGITUDE_COUNT) * lng;
vector.setFromSphericalCoords(SPHERE_RADIUS, phi, theta);
dotGeometry.lookAt(vector);
dotGeometry.translate(vector.x, vector.y, vector.z);
dotGeometries.push(dotGeometry);
}
}
const mergedDotGeometries =
BufferGeometryUtils.mergeBufferGeometries(dotGeometries);
const mergedDotGeometriesPI =
BufferGeometryUtils.mergeBufferGeometries(dotGeometriesPI);
const dotMaterial = new THREE.MeshBasicMaterial({
color: DOT_COLOR,
side: THREE.DoubleSide,
});
const dotMaterialPI = new THREE.MeshBasicMaterial({
color: 0xfdda0d,
side: THREE.DoubleSide,
});
const dotMesh = new THREE.Mesh(mergedDotGeometries, dotMaterial);
const dotMeshPI = new THREE.Mesh(mergedDotGeometriesPI, dotMaterialPI);
scene.add(dotMesh);
scene.add(dotMeshPI);
const animate = (time: number) => {
time *= 0.001;
controls.update();
renderer.render(scene, camera);
requestAnimationFrame(animate);
};
requestAnimationFrame(animate);
};
const setCanvasSize = () => {
CANVAS.width = window.innerWidth;
CANVAS.height = window.innerHeight;
renderScene();
};
setCanvasSize();
// When the window isresized, redraw the scene.
window.addEventListener("resize", setCanvasSize);
}, [planetInfo]);
return <canvas id="canvas"></canvas>;
};
export default PinsCanvas3D;

View File

@@ -1,11 +1,20 @@
import { Stack, Typography, styled } from "@mui/material"; import { Stack, Tooltip, Typography, styled } from "@mui/material";
import Image from "next/image"; import Image from "next/image";
import { AccessToken, Planet } from "@/types"; import { AccessToken, Planet } from "@/types";
import { Api } from "@/esi-api"; import { Api } from "@/esi-api";
import { useEffect, useState } from "react"; import { forwardRef, useEffect, useState } from "react";
import { DateTime } from "luxon"; import { DateTime } from "luxon";
import { EXTRACTOR_TYPE_IDS } from "@/const"; import { EXTRACTOR_TYPE_IDS } from "@/const";
import Countdown from "react-countdown"; import Countdown from "react-countdown";
import PinsCanvas3D from "./PinsCanvas3D";
import Slide from "@mui/material/Slide";
import { TransitionProps } from "@mui/material/transitions";
import Dialog from "@mui/material/Dialog";
import AppBar from "@mui/material/AppBar";
import Toolbar from "@mui/material/Toolbar";
import IconButton from "@mui/material/IconButton";
import CloseIcon from "@mui/icons-material/Close";
import Button from "@mui/material/Button";
const StackItem = styled(Stack)(({ theme }) => ({ const StackItem = styled(Stack)(({ theme }) => ({
...theme.typography.body2, ...theme.typography.body2,
@@ -16,40 +25,42 @@ const StackItem = styled(Stack)(({ theme }) => ({
alignItems: "center", alignItems: "center",
})); }));
export interface Pin {
contents?: {
amount: number;
type_id: number;
}[];
expiry_time?: string;
extractor_details?: {
cycle_time?: number;
head_radius?: number;
heads: {
head_id: number;
latitude: number;
longitude: number;
}[];
product_type_id?: number;
qty_per_cycle?: number;
};
factory_details?: {
schematic_id: number;
};
install_time?: string;
last_cycle_start?: string;
latitude: number;
longitude: number;
pin_id: number;
schematic_id?: number;
type_id: number;
}
export interface PlanetInfo { export interface PlanetInfo {
links: { links: {
destination_pin_id: number; destination_pin_id: number;
link_level: number; link_level: number;
source_pin_id: number; source_pin_id: number;
}[]; }[];
pins: { pins: Pin[];
contents?: {
amount: number;
type_id: number;
}[];
expiry_time?: string;
extractor_details?: {
cycle_time?: number;
head_radius?: number;
heads: {
head_id: number;
latitude: number;
longitude: number;
}[];
product_type_id?: number;
qty_per_cycle?: number;
};
factory_details?: {
schematic_id: number;
};
install_time?: string;
last_cycle_start?: string;
latitude: number;
longitude: number;
pin_id: number;
schematic_id?: number;
type_id: number;
}[];
routes: { routes: {
content_type_id: number; content_type_id: number;
destination_pin_id: number; destination_pin_id: number;
@@ -60,6 +71,27 @@ export interface PlanetInfo {
}[]; }[];
} }
export interface PlanetInfoUniverse {
name: string;
planet_id: number;
position: {
x: number;
y: number;
z: number;
};
system_id: number;
type_id: number;
}
const Transition = forwardRef(function Transition(
props: TransitionProps & {
children: React.ReactElement;
},
ref: React.Ref<unknown>
) {
return <Slide direction="up" ref={ref} {...props} />;
});
export const PlanetCard = ({ export const PlanetCard = ({
planet, planet,
character, character,
@@ -70,6 +102,21 @@ export const PlanetCard = ({
const [planetInfo, setPlanetInfo] = useState<PlanetInfo | undefined>( const [planetInfo, setPlanetInfo] = useState<PlanetInfo | undefined>(
undefined undefined
); );
const [planetInfoUniverse, setPlanetInfoUniverse] = useState<
PlanetInfoUniverse | undefined
>(undefined);
const [planetRenderOpen, setPlanetRenderOpen] = useState(false);
const handle3DrenderOpen = () => {
setPlanetRenderOpen(true);
};
const handle3DrenderClose = () => {
setPlanetRenderOpen(false);
};
const extractors = const extractors =
(planetInfo && (planetInfo &&
planetInfo.pins planetInfo.pins
@@ -92,17 +139,29 @@ export const PlanetCard = ({
).data; ).data;
return planetInfo; return planetInfo;
}; };
const getPlanetUniverse = async (
planet: Planet
): Promise<PlanetInfoUniverse> => {
const api = new Api();
const planetInfo = (
await api.universe.getUniversePlanetsPlanetId(planet.planet_id)
).data;
return planetInfo;
};
useEffect(() => { useEffect(() => {
getPlanet(character, planet).then(setPlanetInfo); getPlanet(character, planet).then(setPlanetInfo);
getPlanetUniverse(planet).then(setPlanetInfoUniverse);
}, [planet, character]); }, [planet, character]);
return ( return (
<StackItem alignItems="flex-start" height="100%"> <StackItem alignItems="flex-start" height="100%" position="relative">
<Image <Image
src={`/${planet.planet_type}.png`} src={`/${planet.planet_type}.png`}
alt="" alt=""
width={120} width={120}
height={120} height={120}
style={{ borderRadius: 8, marginRight: 4 }} style={{ borderRadius: 8, marginRight: 4 }}
onClick={handle3DrenderOpen}
/> />
{extractors.some((e) => { {extractors.some((e) => {
if (!e) return true; if (!e) return true;
@@ -118,6 +177,11 @@ export const PlanetCard = ({
style={{ position: "absolute" }} style={{ position: "absolute" }}
/> />
)} )}
<div style={{ position: "absolute", top: 5, left: 10 }}>
<Typography fontSize="0.8rem">{planetInfoUniverse?.name}</Typography>
<Typography fontSize="0.8rem">L{planet.upgrade_level}</Typography>
</div>
{extractors.map((e, idx) => { {extractors.map((e, idx) => {
const inPast = () => { const inPast = () => {
if (!e) return true; if (!e) return true;
@@ -142,6 +206,32 @@ export const PlanetCard = ({
</Typography> </Typography>
); );
})} })}
<Dialog
fullScreen
open={planetRenderOpen}
onClose={handle3DrenderClose}
TransitionComponent={Transition}
>
<AppBar sx={{ position: "relative" }}>
<Toolbar>
<IconButton
edge="start"
color="inherit"
onClick={handle3DrenderClose}
aria-label="close"
>
<CloseIcon />
</IconButton>
<Typography sx={{ ml: 2, flex: 1 }} variant="h6" component="div">
{planetInfoUniverse?.name}
</Typography>
<Button autoFocus color="inherit" onClick={handle3DrenderClose}>
Close
</Button>
</Toolbar>
</AppBar>
<PinsCanvas3D planetInfo={planetInfo} />
</Dialog>
</StackItem> </StackItem>
); );
}; };