Initial Menu System Commit.

This commit is contained in:
Michael Mainguy 2023-06-27 16:58:19 -05:00
parent ed9bf36007
commit a0aeb13fe1
20 changed files with 3476 additions and 65 deletions

View File

@ -2,10 +2,30 @@
<html> <html>
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<title>Title of Your Project</title> <title>Immersive Idea</title>
<style>
.loader {
position: fixed;
left: 0px;
top: 0px;
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-content: center;
flex-direction: column;
z-index: -1;
background: url("/loading-loading-forever.gif");
background-position: center;
background-repeat: no-repeat;
text-align: center;
}
</style>
</head> </head>
<body> <body>
<div class="loader">Loading...</div>
<script type="module" src="./src/app.ts"></script> <script type="module" src="./src/app.ts"></script>
</body> </body>
</html> </html>

3034
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -6,7 +6,10 @@
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",
"build": "tsc && vite build", "build": "tsc && vite build",
"preview": "vite preview" "preview": "vite preview",
"havok": "node ./copyHavok.cjs",
"serve": "",
"postinstall": "cp ./node_modules/@babylonjs/havok/lib/esm/HavokPhysics.wasm ./node_modules/.vite/deps"
}, },
"dependencies": { "dependencies": {
"@babylonjs/core": "^6.8.0", "@babylonjs/core": "^6.8.0",
@ -14,12 +17,17 @@
"@babylonjs/havok": "^1.0.1", "@babylonjs/havok": "^1.0.1",
"@babylonjs/inspector": "^6.8.0", "@babylonjs/inspector": "^6.8.0",
"express": "^4.18.2", "express": "^4.18.2",
"@auth0/auth0-spa-js": "^2.0.8" "@auth0/auth0-spa-js": "^2.0.8",
"ring-client-api": "^11.8.0",
"@maptiler/client": "^1.5.0",
"axios": "^1.4.0",
"google-static-maps-tile": "1.0.0",
"query-string": "^8.1.0"
}, },
"devDependencies": { "devDependencies": {
"typescript": "^5.0.2", "typescript": "^5.0.2",
"vite": "^4.3.9", "vite": "^4.3.9",
"vite-plugin-api": "^0.1.11" "vite-plugin-api": "^0.1.11",
"vite-plugin-cp": "^1.0.0"
} }
} }

11
pages/about.html Normal file
View File

@ -0,0 +1,11 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Immersive Idea</title>
</head>
<body>
<h1>About</h1>
</body>
</html>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 MiB

19
src/api/cameras.ts Normal file
View File

@ -0,0 +1,19 @@
import {RingCamera} from "../server/ring/ringCamera";
export const GET = async (req, res) => {
const cams = new RingCamera();
const list = await cams.getCameras();
if (req.query.id) {
const photoItem = list.filter(camera => camera.id == req.query.id);
const photo = await photoItem[0].getSnapshot();
res.contentType('image/jpg');
res.send(photo);
} else {
const data = list.map((value) => {return {id: value.id, data: value.data}});
res.contentType('application/json');
res.send(data);
}
}

6
src/api/map/location.ts Normal file
View File

@ -0,0 +1,6 @@
export const GET = async (req, res, next) => {
res.contentType('application/json');
res.send('{"status": "OK"}');
}

View File

@ -1,31 +1,44 @@
import "@babylonjs/core/Debug/debugLayer"; import "@babylonjs/core/Debug/debugLayer";
import "@babylonjs/inspector"; import "@babylonjs/inspector";
import {Auth0Client, createAuth0Client} from '@auth0/auth0-spa-js';
import { import {
ArcRotateCamera, Color3, ActionManager, Angle,
ArcRotateCamera, Color3, CubeTexture,
Engine, Engine,
HavokPlugin, HavokPlugin,
HemisphericLight, HemisphericLight, InterpolateValueAction,
Mesh, Mesh,
MeshBuilder, PBRMetallicRoughnessMaterial, MeshBuilder, PBRMetallicRoughnessMaterial,
PhotoDome, PhotoDome,
PhysicsAggregate, PhysicsAggregate,
PhysicsShapeType, Quaternion, PhysicsShapeType, Plane, Quaternion,
Scene, StandardMaterial, Texture, Scene, StandardMaterial, Texture,
Vector3, Vector3,
WebXRDefaultExperience WebXRDefaultExperience
} from "@babylonjs/core"; } from "@babylonjs/core";
import {Right} from "./controllers/right"; import {Right} from "./controllers/right";
import {Left} from "./controllers/left"; import {Left} from "./controllers/left";
import {havokModule} from "./util/havok"; ///import {havokModule} from "./util/havok";
import {Bmenu} from "./menus/bmenu"; import {Bmenu} from "./menus/bmenu";
import HavokPhysics from "@babylonjs/havok";
import {Rigplatform} from "./controllers/rigplatform"; import {Rigplatform} from "./controllers/rigplatform";
import {ObjectEditor} from "./menus/objectEditor";
import {RingCamera} from "./server/ring/ringCamera";
import {AdvancedDynamicTexture, Image} from "@babylonjs/gui";
import {Cameras} from "./integration/ring/cameras";
import {Gmap} from "./util/gmap";
import {Mapt} from "./util/mapt";
class App { class App {
preTasks = [havokModule]; //preTasks = [havokModule];
private auth0: Auth0Client;
constructor(auth0: Auth0Client) {
this.auth0 = auth0;
constructor() {
const canvas = document.createElement("canvas"); const canvas = document.createElement("canvas");
canvas.style.width = "100%"; canvas.style.width = "100%";
canvas.style.height = "100%"; canvas.style.height = "100%";
@ -35,24 +48,48 @@ class App {
} }
async initialize(canvas) { async initialize(canvas) {
console.log(await this.auth0.getUser());
const engine = new Engine(canvas, true); const engine = new Engine(canvas, true);
const scene = new Scene(engine); const scene = new Scene(engine);
const hk = new HavokPlugin(true, await havokModule); const havokInstance = await HavokPhysics();
scene.enablePhysics(new Vector3(0 , -9.8, 0), hk); const havokPlugin = new HavokPlugin(true, havokInstance);
scene.enablePhysics(new Vector3(0 , -9.8, 0), havokPlugin);
const camera: ArcRotateCamera = new ArcRotateCamera("Camera", Math.PI / 2, Math.PI / 2, 2, const camera: ArcRotateCamera = new ArcRotateCamera("Camera", Math.PI / 2, Math.PI / 2, 2,
new Vector3(0, 1.6, 0), scene); new Vector3(0, 1.6, 0), scene);
camera.attachControl(canvas, true); camera.attachControl(canvas, true);
new HemisphericLight("light1", new Vector3(1, 1, 0), scene); new HemisphericLight("light1", new Vector3(1, 1, 0), scene);
const rig = new Rigplatform(scene); const rig = new Rigplatform(scene);
//const envTexture = new CubeTexture("/assets/textures/SpecularHDR.dds", scene);
//scene.createDefaultSkybox(envTexture, true, 1000);
const photoDome = new PhotoDome('sky', /*const photoDome = new PhotoDome('sky',
'./outdoor_field.jpeg', {}, './outdoor_field.jpeg', {},
scene); scene);
*/
const xr = await WebXRDefaultExperience.CreateAsync(scene, {floorMeshes: [this.createGround(scene)], const xr = await WebXRDefaultExperience.CreateAsync(scene, {floorMeshes: [this.createGround(scene)],
optionalFeatures: true}); optionalFeatures: true});
xr.baseExperience.camera.parent = rig.rigMesh; xr.baseExperience.camera.parent = rig.rigMesh;
const b = new Bmenu(scene, rig, xr.baseExperience);
//const box = MeshBuilder.CreateBox("box", {size: 1}, scene);
//box.position.z = -4;
/*box.actionManager.registerAction(
new InterpolateValueAction(
ActionManager.OnPointerOverTrigger,box, 'visibility', 0.2, 500
)
);
box.actionManager.registerAction(
new InterpolateValueAction(
ActionManager.OnPointerOutTrigger,box, 'visibility', 1, 200
)
);*/
//const edit = new ObjectEditor(scene, box);
//const edit = new ObjectEditor(scene, box);
const ring = new Cameras(scene);
ring.getCameras().then(()=> ring.createCameras());
const stickVector = Vector3.Zero(); const stickVector = Vector3.Zero();
xr.input.onControllerAddedObservable.add((source, state) => { xr.input.onControllerAddedObservable.add((source, state) => {
@ -60,9 +97,12 @@ class App {
switch (source.inputSource.handedness) { switch (source.inputSource.handedness) {
case "right": case "right":
controller = new Right(source); controller = new Right(source);
rig.right = controller;
controller.setBMenu(b);
break; break;
case "left": case "left":
controller = new Left(source); controller = new Left(source);
rig.left = controller;
break; break;
} }
@ -89,7 +129,10 @@ class App {
} }
} }
}); });
const b = new Bmenu(scene, rig); const map = new Mapt(scene);
map.buildMapImage();
//map.createMapTiles(26.443397,-82.111512);
// run the main render loop // run the main render loop
engine.runRenderLoop(() => { engine.runRenderLoop(() => {
@ -113,4 +156,32 @@ class App {
} }
} }
new App(); createAuth0Client({
domain: import.meta.env.VITE_AUTH0_DOMAIN,
clientId: import.meta.env.VITE_AUTH0_CLIENTID,
authorizationParams: {
redirect_uri: 'https://cameras.immersiveidea.com/'
}
}).then(async (auth0)=> {
try {
const query = window.location.search;
if (query.includes("code=") && query.includes("state=")) {
await auth0.handleRedirectCallback();
window.location.href = 'https://cameras.immersiveidea.com';
}
const isAuthentic = await auth0.isAuthenticated();
if (!isAuthentic) {
await auth0.loginWithRedirect();
} else {
const token = await auth0.getTokenSilently();
new App(auth0);
}
} catch (error) {
console.log(error);
}
});

View File

@ -5,17 +5,30 @@ export class Base {
protected stickVector: Vector3; protected stickVector: Vector3;
protected body: PhysicsBody; protected body: PhysicsBody;
protected camera: WebXRCamera; protected camera: WebXRCamera;
protected speedFactor = 2; protected speedFactor = 4;
constructor(controller: constructor(controller:
WebXRInputSource) { WebXRInputSource) {
this.controller = controller; this.controller = controller;
this.controller.onMotionControllerInitObservable.add((init) => {
if (init.components['xr-standard-trigger']) {
init.components['xr-standard-trigger'].onButtonStateChangedObservable.add((value) => {
if (value.value == 1) {
console.log(value);
} }
});
}
});
}
setRig(body: PhysicsBody) { setRig(body: PhysicsBody) {
this.body = body; this.body = body;
} }
setCamera(camera: WebXRCamera) { setCamera(camera: WebXRCamera) {
this.camera = camera; this.camera = camera;
} }
setStickVector(vector: Vector3) { setStickVector(vector: Vector3) {
this.stickVector = vector; this.stickVector = vector;
} }

View File

@ -1,8 +1,10 @@
import {Base} from "./base"; import {Base} from "./base";
import {Vector3, WebXRInputSource} from "@babylonjs/core"; import {Mesh, Vector3, WebXRInputSource} from "@babylonjs/core";
import {Bmenu} from "../menus/bmenu";
export class Right extends Base { export class Right extends Base {
private bmenu: any; private bmenu: Bmenu;
private addMesh: Mesh;
constructor(controller: constructor(controller:
WebXRInputSource) { WebXRInputSource) {
super(controller); super(controller);
@ -11,10 +13,11 @@ export class Right extends Base {
if (init.components['b-button']) { if (init.components['b-button']) {
init.components['b-button'].onButtonStateChangedObservable.add((value)=>{ init.components['b-button'].onButtonStateChangedObservable.add((value)=>{
if (value.pressed) { if (value.pressed) {
this.bmenu.toggle();
} }
}); });
} }
if (init.components['xr-standard-thumbstick']) { if (init.components['xr-standard-thumbstick']) {
init.components['xr-standard-thumbstick'] init.components['xr-standard-thumbstick']
.onAxisValueChangedObservable.add((value) => { .onAxisValueChangedObservable.add((value) => {
@ -38,7 +41,7 @@ export class Right extends Base {
}); });
} }
public setBMenu(menu: any) { public setBMenu(menu: Bmenu) {
this.bmenu = menu; this.bmenu = menu;
} }

View File

@ -8,9 +8,13 @@ import {
StandardMaterial, StandardMaterial,
Vector3 Vector3
} from "@babylonjs/core"; } from "@babylonjs/core";
import {Right} from "./right";
import {Left} from "./left";
export class Rigplatform { export class Rigplatform {
private scene: Scene; private scene: Scene;
public right: Right;
public left: Left;
public body: PhysicsBody; public body: PhysicsBody;
public rigMesh: Mesh; public rigMesh: Mesh;
constructor(scene: Scene) { constructor(scene: Scene) {
@ -20,6 +24,7 @@ export class Rigplatform {
myMaterial.diffuseColor = Color3.Blue(); myMaterial.diffuseColor = Color3.Blue();
this.rigMesh.material = myMaterial; this.rigMesh.material = myMaterial;
this.rigMesh.setAbsolutePosition(new Vector3(0, .1, -3)); this.rigMesh.setAbsolutePosition(new Vector3(0, .1, -3));
this.rigMesh.visibility=0;
const rigAggregate = const rigAggregate =
new PhysicsAggregate( new PhysicsAggregate(
this.rigMesh, this.rigMesh,
@ -31,6 +36,7 @@ export class Rigplatform {
this.#fixRotation(); this.#fixRotation();
this.body = rigAggregate.body; this.body = rigAggregate.body;
} }
#fixRotation() { #fixRotation() {
this.scene.registerBeforeRender(() => { this.scene.registerBeforeRender(() => {
const q = this.rigMesh.rotationQuaternion; const q = this.rigMesh.rotationQuaternion;

View File

@ -0,0 +1,38 @@
import {Angle, Color3, MeshBuilder, Scene, StandardMaterial, Texture} from "@babylonjs/core";
import axios from "axios";
export class Cameras {
private scene: Scene;
private cameras;
constructor(scene: Scene) {
this.scene = scene;
}
public async getCameras() {
const cameras = await axios.get('/api/cameras',{});
this.cameras = cameras;
console.log(cameras);
}
public createCameras() {
this.createCamera(12333524, 0);
this.createCamera( 115860395, 1);
this.createCamera( 115855810, 2);
this.createCamera( 99677736, 3);
this.createCamera( 48497021, 4);
this.createCamera( 55870327, 5);
}
public createCamera(id, index) {
const plane = MeshBuilder.CreatePlane("plane", {width: 1.6, height:.9}, this.scene);
const materialPlane = new StandardMaterial("texturePlane", this.scene);
materialPlane.diffuseTexture = new Texture("/api/cameras?id=" + id, this.scene);
materialPlane.specularColor = new Color3(0, 0, 0);
materialPlane.backFaceCulling = false;//Allways show the front and the back of an element
plane.material = materialPlane;
plane.rotation.y = Angle.FromDegrees(180).radians();
plane.position.y = 1.5;
plane.position.z = -5;
plane.position.x = (1.6*3) - (index * 1.6);
}
}

View File

@ -1,36 +1,63 @@
import {Angle, Scene, TransformNode, Vector3} from "@babylonjs/core"; import {Angle, FadeInOutBehavior, Scene, TransformNode, Vector3, WebXRExperienceHelper} from "@babylonjs/core";
import {Container3D, CylinderPanel, GUI3DManager, HolographicButton, SpherePanel} from "@babylonjs/gui"; import {
Container3D,
CylinderPanel,
GUI3DManager,
HandMenu,
HolographicButton, HolographicSlate,
NearMenu, PlanePanel,
SpherePanel
} from "@babylonjs/gui";
import {Rigplatform} from "../controllers/rigplatform"; import {Rigplatform} from "../controllers/rigplatform";
export class Bmenu { export class Bmenu {
private scene; private scene;
constructor(scene: Scene, rig: Rigplatform) { private rig;
const anchor = new TransformNode("bMenuAnchor"); private xr;
anchor.rotation.y = Angle.FromDegrees(180).radians(); private manager;
//anchor.position = rig.rigMesh.position; private panel;
//anchor.rotation = new Vector3(0 , Angle.FromDegrees(180).radians(), 0); constructor(scene: Scene, rig: Rigplatform, xr: WebXRExperienceHelper) {
this.scene = scene; this.scene = scene;
const manager = new GUI3DManager(scene); this.rig = rig;
const panel = new CylinderPanel(); this.manager = new GUI3DManager(scene);
panel.margin=.6; this.xr = xr;
panel.scaling.y=.5;
//panel.orientation = Container3D.FACEFORWARDREVERSED_ORIENTATION;
panel.radius = 2;
panel.columns = 8;
manager.addControl(panel);
panel.linkToTransformNode(anchor);
panel.position.z = 2;
panel.position.y = 4;
for (var i = 0; i < 10; i++) {
panel.addControl(this.makeButton("Button " + i));
} }
makeButton(name: string, id: string) {
}
makeButton(name: string) {
const button = new HolographicButton(name); const button = new HolographicButton(name);
button.text = name; button.text = name;
button.name = id;
button.onPointerClickObservable.add(this.#clickhandler, -1, false, this);
return button; return button;
} }
#clickhandler(_info, state) {
console.log(state.currentTarget.name);
}
toggle() {
if (this.panel) {
this.panel.dispose();
this.panel = null;
} else {
const anchor = new TransformNode("bMenuAnchor");
anchor.rotation.y = Angle.FromDegrees(180).radians();
const cam = this.xr.camera.getFrontPosition(2);
anchor.position = cam;
const panel = new PlanePanel();
panel.margin=.6;
//panel.scaling.y=.5;
//panel.orientation = Container3D.FACEFORWARDREVERSED_ORIENTATION;
panel.columns = 5;
this.manager.addControl(panel);
panel.linkToTransformNode(anchor);
//panel.position.z = 2;
//panel.position.y = 4;
panel.addControl(this.makeButton("Add Box", "addBox"));
panel.addControl(this.makeButton("Add Sphere", "addSphere"));
panel.addControl(this.makeButton("Add Cylinder", "addCylinder"));
this.panel = panel;
}
}
} }

68
src/menus/objectEditor.ts Normal file
View File

@ -0,0 +1,68 @@
import {AdvancedDynamicTexture, Button, Control, Slider, StackPanel} from "@babylonjs/gui";
import {Angle, Mesh} from "@babylonjs/core";
export class ObjectEditor {
private scene;
private editor: Mesh;
private mesh;
constructor(scene, mesh) {
this.scene=scene;
this.mesh = mesh;
this.edit();
}
public edit() {
this.editor = Mesh.CreatePlane("editor", 2, this.scene);
this.editor.position.z = -2;
this.editor.position.y = 2;
this.editor.rotation.y = Angle.FromDegrees(180).radians();
const texture = AdvancedDynamicTexture.CreateForMesh(this.editor);
const panel = new StackPanel();
panel.horizontalAlignment = Control.HORIZONTAL_ALIGNMENT_RIGHT;
panel.verticalAlignment = Control.VERTICAL_ALIGNMENT_CENTER;
panel.width='100%';
panel.height='100%';
texture.addControl(panel);
const x = this.createControl();
const y = this.createControl();
const z = this.createControl()
const myMesh = this.mesh;
z.value = myMesh.scaling.z;
z.onValueChangedObservable.add((value)=> {
myMesh.scaling.z = value;
});
y.onValueChangedObservable.add((value)=> {
myMesh.scaling.y = value;
});
y.value = myMesh.scaling.x;
x.onValueChangedObservable.add((value)=> {
myMesh.scaling.x = value;
});
x.value = myMesh.scaling.x;
panel.addControl(x);
panel.addControl(y);
panel.addControl(z);
const button1 = Button.CreateSimpleButton("close-editor", "Close");
button1.height = '20px';
button1.background = "#FFF";
panel.addControl(button1);
button1.onPointerClickObservable.add(()=> {
this.close();
}, -1, false, this);
}
createControl(): Slider {
const slider = new Slider();
slider.minimum = .1
slider.maximum = 10;
slider.height = '40px';
slider.step =.1
return slider;
}
close() {
this.editor.dispose();
this.mesh=null;
this.scene=null;
}
}

View File

@ -0,0 +1,22 @@
import {RingApi} from "ring-client-api";
export class RingCamera {
private ringApi: RingApi;
constructor() {
const ringApi = new RingApi({
refreshToken: process.env.RING_TOKEN,
cameraStatusPollingSeconds: 30,
debug: true
});
this.ringApi = ringApi;
}
public async getCameras() {
const cams = await this.ringApi.getCameras();
console.log(cams[0]);
const camid = cams.map((value ) => value.id);
console.log(camid);
return cams;
}
}

51
src/util/gmap.ts Normal file
View File

@ -0,0 +1,51 @@
import {Angle, Color3, MeshBuilder, Scene, StandardMaterial, Texture} from "@babylonjs/core";
import googleStaticMapsTile from "google-static-maps-tile";
export class Gmap {
private scene: Scene;
constructor(scene: Scene) {
this.scene = scene;
}
public async createMapTiles(lat, lon) {
googleStaticMapsTile({
areaSize: '2560x2560',
center: '26.443397,-82.111512',
zoom: 12,
imagePerLoad: 50,
durationBetweenLoads: 60*1000+100,
key: 'AIzaSyD4jJCYcIvHDEiOkVxC2c4zNYRqZKYHMMk',
maptype: 'satellite'
})
.on('progress', function(info) {
console.log(info.count);
console.log(info.total);
const image = info.image;
image.style.position = 'absolute';
image.style.left = info.data.x + 'px';
image.style.top = info.data.y + 'px';
document.body.appendChild(image);
});
}
public createMap(lat, lon) {
//const lat = 42.3369513;
//const lon = -88.8707076;
const plane = MeshBuilder.CreatePlane("plane", {width: 1, height:1}, this.scene);
const materialPlane = new StandardMaterial("texturePlane", this.scene);
const zoom = 10;
materialPlane.diffuseTexture =
new
Texture(`https://maps.googleapis.com/maps/api/staticmap?center=${lat},${lon}&zoom=${zoom}&size=640x640&key=AIzaSyD4jJCYcIvHDEiOkVxC2c4zNYRqZKYHMMk`,
this.scene);
materialPlane.specularColor = new Color3(0, 0, 0);
materialPlane.backFaceCulling = false;//Allways show the front and the back of an element
plane.material = materialPlane;
plane.rotation.x = Angle.FromDegrees(90).radians();
plane.rotation.y = Angle.FromDegrees(180).radians();
plane.position.y = 0.1;
plane.position.z = -5;
}
}

View File

@ -1,3 +0,0 @@
import HavokPlugin from "@babylonjs/havok";
export const havokModule = HavokPlugin();

43
src/util/mapt.ts Normal file
View File

@ -0,0 +1,43 @@
import * as maptilerClient from '@maptiler/client';
import {Angle, Color3, MeshBuilder, Scene, StandardMaterial, Texture} from "@babylonjs/core";
export class Mapt {
private scene: Scene;
constructor(scene: Scene) {
this.scene = scene;
}
buildMapImage() {
const apiKey = '073I3Pfe4lzoSf8tNriR';
maptilerClient.config.apiKey = apiKey;
const link = maptilerClient.staticMaps.centered(
[-88.8711198, 42.3370588],
14,
{width:2048, height:2048, style: 'streets-v2'}
);
const plane = MeshBuilder.CreatePlane("plane", {width: 10, height:10}, this.scene);
const materialPlane = new StandardMaterial("texturePlane", this.scene);
const zoom = 10;
const sphere = MeshBuilder.CreateSphere("cams", {diameter: .1}, this.scene);
sphere.position.y = 0.2;
sphere.position.z = -5;
const sphereMaterial = new StandardMaterial("sphere", this.scene);
sphereMaterial.diffuseColor = Color3.Blue();
sphereMaterial.ambientColor = Color3.Blue();
sphere.visibility = 0.8;
materialPlane.diffuseTexture =
new
Texture(link,
this.scene);
materialPlane.specularColor = new Color3(0, 0, 0);
materialPlane.backFaceCulling = false;//Allways show the front and the back of an element
plane.material = materialPlane;
plane.rotation.x = Angle.FromDegrees(90).radians();
plane.rotation.y = Angle.FromDegrees(180).radians();
plane.position.y = 0.1;
plane.position.z = -5;
}
}

View File

@ -1,9 +1,11 @@
import { defineConfig } from "vite"; import { defineConfig } from "vite";
import { pluginAPI } from "vite-plugin-api"; import { pluginAPI } from "vite-plugin-api";
export default defineConfig({ export default defineConfig({
server: { server: {
port: 3000 port: 3001
}, },
plugins: [ plugins: [
pluginAPI({ pluginAPI({