mirror of
https://github.com/ad044/lainTSX.git
synced 2024-10-22 23:19:06 +00:00
added second ring, tweaked code, camera functions properly
This commit is contained in:
parent
25f60c8d13
commit
af26c3563c
9 changed files with 225 additions and 135 deletions
BIN
public/models/ring1.glb
Normal file
BIN
public/models/ring1.glb
Normal file
Binary file not shown.
|
@ -2,6 +2,7 @@ import React, { useEffect, useState } from "react";
|
||||||
import Intro from "./components/Intro";
|
import Intro from "./components/Intro";
|
||||||
import Game from "./components/Game";
|
import Game from "./components/Game";
|
||||||
import "./static/css/main.css";
|
import "./static/css/main.css";
|
||||||
|
import "./static/css/hub.css";
|
||||||
|
|
||||||
const App = () => {
|
const App = () => {
|
||||||
const [moveToGame, setMoveToGame] = useState(false);
|
const [moveToGame, setMoveToGame] = useState(false);
|
||||||
|
|
|
@ -10,7 +10,8 @@ import Lain, {
|
||||||
} from "./Lain";
|
} from "./Lain";
|
||||||
import Hub from "./Hub";
|
import Hub from "./Hub";
|
||||||
//import Orb from "./Orb";
|
//import Orb from "./Orb";
|
||||||
import { OrbitControls } from "drei";
|
import { OrbitControls, PerspectiveCamera } from "drei";
|
||||||
|
import Lights from "./Lights";
|
||||||
|
|
||||||
type KeyCodeAssociations = {
|
type KeyCodeAssociations = {
|
||||||
[keyCode: number]: string;
|
[keyCode: number]: string;
|
||||||
|
@ -20,17 +21,34 @@ type FrameCounts = {
|
||||||
[animation: string]: number;
|
[animation: string]: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
// value by which to rotate/move the ring on the y axis
|
|
||||||
type LowerRingValues = {
|
|
||||||
[direction: string]: number;
|
|
||||||
};
|
|
||||||
|
|
||||||
const Game = () => {
|
const Game = () => {
|
||||||
const [isLainMoving, setLainMoving] = useState(false);
|
const [isLainMoving, setLainMoving] = useState(false);
|
||||||
const [lainMoveState, setLainMoveState] = useState(<LainStanding />);
|
const [lainMoveState, setLainMoveState] = useState(<LainStanding />);
|
||||||
|
const [lainPosY, setLainPosY] = useState(-0.2);
|
||||||
|
|
||||||
const [lowerRingRotationY, setLowerRingRotationY] = useState(5);
|
const [cameraPosY, setCameraPosY] = useState(0);
|
||||||
const [lowerRingPositionY, setLowerRingPositionY] = useState(-0.31);
|
const [cameraRotationY, setCameraRotationY] = useState(0);
|
||||||
|
|
||||||
|
const moveCamera = (value: number, duration: number) => {
|
||||||
|
const moveInterval = setInterval(() => {
|
||||||
|
setCameraPosY((prev: number) => prev + value);
|
||||||
|
setLainPosY((prev: number) => prev - value);
|
||||||
|
});
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
clearInterval(moveInterval);
|
||||||
|
}, duration);
|
||||||
|
};
|
||||||
|
|
||||||
|
const rotateCamera = (value: number, duration: number) => {
|
||||||
|
const rotationInterval = setInterval(() => {
|
||||||
|
setCameraRotationY((prev: number) => prev + value);
|
||||||
|
});
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
clearInterval(rotationInterval);
|
||||||
|
}, duration);
|
||||||
|
};
|
||||||
|
|
||||||
const getKeyValue = <U extends keyof T, T extends object>(key: U) => (
|
const getKeyValue = <U extends keyof T, T extends object>(key: U) => (
|
||||||
obj: T
|
obj: T
|
||||||
|
@ -93,35 +111,6 @@ const Game = () => {
|
||||||
}, getAnimationDuration(key));
|
}, getAnimationDuration(key));
|
||||||
};
|
};
|
||||||
|
|
||||||
const getLowerRingValue = (direction: string) => {
|
|
||||||
return getKeyValue<keyof LowerRingValues, LowerRingValues>(direction)({
|
|
||||||
left: 0.002,
|
|
||||||
right: -0.002,
|
|
||||||
up: -0.005,
|
|
||||||
down: 0.005,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const rotateLowerRing = (key: string, duration: number) => {
|
|
||||||
const rotationInterval = setInterval(() => {
|
|
||||||
setLowerRingRotationY((prev) => prev + getLowerRingValue(key));
|
|
||||||
}, 10);
|
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
clearInterval(rotationInterval);
|
|
||||||
}, duration);
|
|
||||||
};
|
|
||||||
|
|
||||||
const moveLowerRing = (key: string, duration: number) => {
|
|
||||||
const moveInterval = setInterval(() => {
|
|
||||||
setLowerRingPositionY((prev) => prev + getLowerRingValue(key));
|
|
||||||
});
|
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
clearInterval(moveInterval);
|
|
||||||
}, duration);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleUserKeyPress = useCallback(
|
const handleUserKeyPress = useCallback(
|
||||||
(event) => {
|
(event) => {
|
||||||
const { _, keyCode } = event;
|
const { _, keyCode } = event;
|
||||||
|
@ -129,22 +118,33 @@ const Game = () => {
|
||||||
const key = getKeyCodeAssociation(keyCode);
|
const key = getKeyCodeAssociation(keyCode);
|
||||||
|
|
||||||
console.log(key);
|
console.log(key);
|
||||||
|
|
||||||
if (!isLainMoving) {
|
if (!isLainMoving) {
|
||||||
setAnimationState(key);
|
setAnimationState(key);
|
||||||
setTimeout(() => {
|
|
||||||
switch (key) {
|
switch (key) {
|
||||||
case "left":
|
case "left":
|
||||||
|
setTimeout(() => {
|
||||||
|
rotateCamera(0.001, 1600);
|
||||||
|
}, 1100);
|
||||||
|
break;
|
||||||
case "right":
|
case "right":
|
||||||
rotateLowerRing(key, 1000);
|
setTimeout(() => {
|
||||||
|
rotateCamera(-0.001, 1600);
|
||||||
|
}, 1100);
|
||||||
break;
|
break;
|
||||||
case "up":
|
case "up":
|
||||||
|
setTimeout(() => {
|
||||||
|
moveCamera(-0.005, 1200);
|
||||||
|
}, 1300);
|
||||||
|
break;
|
||||||
case "down":
|
case "down":
|
||||||
moveLowerRing(key, 1000);
|
setTimeout(() => {
|
||||||
|
moveCamera(0.005, 1200);
|
||||||
|
}, 1300);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}, 1200);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[isLainMoving]
|
[isLainMoving]
|
||||||
|
@ -153,23 +153,31 @@ const Game = () => {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
window.addEventListener("keydown", handleUserKeyPress);
|
window.addEventListener("keydown", handleUserKeyPress);
|
||||||
|
|
||||||
|
document.getElementsByTagName("canvas")[0].className = "hub-background";
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
window.removeEventListener("keydown", handleUserKeyPress);
|
window.removeEventListener("keydown", handleUserKeyPress);
|
||||||
|
document.getElementsByTagName("body")[0].className = "";
|
||||||
};
|
};
|
||||||
}, [handleUserKeyPress]);
|
}, [handleUserKeyPress]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Canvas shadowMap concurrent camera={{ position: [0, -0.1, -2] }}>
|
{/* <Canvas camera={{ position: [0, 0, -2] }}> */}
|
||||||
<Lain isLainMoving={isLainMoving} lainMoveState={lainMoveState} />
|
<Canvas>
|
||||||
<Hub
|
<PerspectiveCamera
|
||||||
lowerRingRotationY={lowerRingRotationY}
|
position={[0, cameraPosY, 3]}
|
||||||
lowerRingPositionY={lowerRingPositionY}
|
rotation={[0, cameraRotationY, 0]}
|
||||||
|
>
|
||||||
|
<OrbitControls />
|
||||||
|
<Lain
|
||||||
|
isLainMoving={isLainMoving}
|
||||||
|
lainMoveState={lainMoveState}
|
||||||
|
lainPosY={lainPosY}
|
||||||
/>
|
/>
|
||||||
<ambientLight color={0x808080} />
|
<Hub />
|
||||||
<pointLight color={0xffffff} position={[0, 0, 700]} intensity={0.5} />
|
<Lights />
|
||||||
<pointLight color={0x7f7f7f} position={[0, 1000, 0]} intensity={1} />
|
</PerspectiveCamera>
|
||||||
<pointLight color={0xffffff} position={[0, 0, 0]} intensity={0.1} />
|
|
||||||
</Canvas>
|
</Canvas>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,19 +1,13 @@
|
||||||
import React, { Suspense } from "react";
|
import React, { Suspense } from "react";
|
||||||
import Ring0 from "./Ring0";
|
import Ring0 from "./Ring0";
|
||||||
|
import Ring1 from "./Ring1";
|
||||||
|
|
||||||
type HubProps = {
|
const Hub = (props: any) => {
|
||||||
lowerRingPositionY: number;
|
|
||||||
lowerRingRotationY: number;
|
|
||||||
};
|
|
||||||
|
|
||||||
const Hub = (props: HubProps) => {
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Suspense fallback={<React.Fragment>loading...</React.Fragment>}>
|
<Suspense fallback={<>loading...</>}>
|
||||||
<Ring0
|
<Ring1 />
|
||||||
lowerRingPositionY={props.lowerRingPositionY}
|
<Ring0 />
|
||||||
lowerRingRotationY={props.lowerRingRotationY}
|
|
||||||
/>
|
|
||||||
</Suspense>
|
</Suspense>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
|
@ -4,7 +4,7 @@ import * as THREE from "three";
|
||||||
import introSpriteSheet from "../static/sprites/intro.png";
|
import introSpriteSheet from "../static/sprites/intro.png";
|
||||||
import moveDownSpriteSheet from "../static/sprites/jump_down.png";
|
import moveDownSpriteSheet from "../static/sprites/jump_down.png";
|
||||||
import standingSpriteSheet from "../static/sprites/standing.png";
|
import standingSpriteSheet from "../static/sprites/standing.png";
|
||||||
import moveLeftSpriteSheet from "../static/sprites/move_left1.png";
|
import moveLeftSpriteSheet from "../static/sprites/move_left.png";
|
||||||
import moveRightSpriteSheet from "../static/sprites/move_right.png";
|
import moveRightSpriteSheet from "../static/sprites/move_right.png";
|
||||||
import moveUpSpriteSheet from "../static/sprites/jump_up.png";
|
import moveUpSpriteSheet from "../static/sprites/jump_up.png";
|
||||||
import { PlainSingularAnimator } from "three-plain-animator/lib/plain-singular-animator";
|
import { PlainSingularAnimator } from "three-plain-animator/lib/plain-singular-animator";
|
||||||
|
@ -12,26 +12,26 @@ import { PlainSingularAnimator } from "three-plain-animator/lib/plain-singular-a
|
||||||
type LainProps = {
|
type LainProps = {
|
||||||
isLainMoving: boolean;
|
isLainMoving: boolean;
|
||||||
lainMoveState: JSX.Element;
|
lainMoveState: JSX.Element;
|
||||||
|
lainPosY: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
type LainConstructorProps = {
|
type LainConstructorProps = {
|
||||||
sprite: string;
|
sprite: string;
|
||||||
frameCount: number;
|
frameCount: number;
|
||||||
|
framesVertical: number;
|
||||||
|
framesHorizontal: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
const LainConstructor = (props: LainConstructorProps) => {
|
const LainConstructor = (props: LainConstructorProps) => {
|
||||||
// any here temporarily
|
// any here temporarily
|
||||||
const spriteTexture: any = useLoader(
|
const spriteTexture: any = useLoader(THREE.TextureLoader, props.sprite);
|
||||||
THREE.TextureLoader,
|
|
||||||
props.sprite
|
|
||||||
);
|
|
||||||
|
|
||||||
const [animator] = useState(
|
const [animator] = useState(
|
||||||
() =>
|
() =>
|
||||||
new PlainSingularAnimator(
|
new PlainSingularAnimator(
|
||||||
spriteTexture,
|
spriteTexture,
|
||||||
props.frameCount,
|
props.framesHorizontal,
|
||||||
1,
|
props.framesVertical,
|
||||||
props.frameCount,
|
props.frameCount,
|
||||||
props.frameCount * 0.27
|
props.frameCount * 0.27
|
||||||
)
|
)
|
||||||
|
@ -42,57 +42,84 @@ const LainConstructor = (props: LainConstructorProps) => {
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Suspense fallback={null}>
|
|
||||||
<sprite position={[0.1, -0.7, 0]} scale={[3.3, 3.3, 3.3]}>
|
|
||||||
<spriteMaterial attach="material" map={spriteTexture}></spriteMaterial>
|
<spriteMaterial attach="material" map={spriteTexture}></spriteMaterial>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const LainIntro = () => {
|
||||||
|
return (
|
||||||
|
<LainConstructor
|
||||||
|
sprite={standingSpriteSheet}
|
||||||
|
frameCount={1}
|
||||||
|
framesHorizontal={8}
|
||||||
|
framesVertical={6}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const LainStanding = () => {
|
||||||
|
return (
|
||||||
|
<LainConstructor
|
||||||
|
sprite={standingSpriteSheet}
|
||||||
|
frameCount={3}
|
||||||
|
framesHorizontal={3}
|
||||||
|
framesVertical={1}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const LainMoveDown = () => {
|
||||||
|
return (
|
||||||
|
<LainConstructor
|
||||||
|
sprite={moveDownSpriteSheet}
|
||||||
|
frameCount={36}
|
||||||
|
framesHorizontal={6}
|
||||||
|
framesVertical={6}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const LainMoveLeft = () => {
|
||||||
|
return (
|
||||||
|
<LainConstructor
|
||||||
|
sprite={moveLeftSpriteSheet}
|
||||||
|
frameCount={47}
|
||||||
|
framesVertical={6}
|
||||||
|
framesHorizontal={8}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const LainMoveRight = () => {
|
||||||
|
return (
|
||||||
|
<LainConstructor
|
||||||
|
sprite={moveRightSpriteSheet}
|
||||||
|
frameCount={47}
|
||||||
|
framesHorizontal={8}
|
||||||
|
framesVertical={6}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const LainMoveUp = () => {
|
||||||
|
return (
|
||||||
|
<LainConstructor
|
||||||
|
sprite={moveUpSpriteSheet}
|
||||||
|
frameCount={36}
|
||||||
|
framesHorizontal={6}
|
||||||
|
framesVertical={6}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const Lain = (props: LainProps) => {
|
||||||
|
return (
|
||||||
|
<Suspense fallback={<>loading...</>}>
|
||||||
|
<sprite position={[0.1, props.lainPosY, 0]} scale={[4.3, 4.3, 4.3]}>
|
||||||
|
{props.isLainMoving ? props.lainMoveState : <LainStanding />}
|
||||||
</sprite>
|
</sprite>
|
||||||
</Suspense>
|
</Suspense>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const LainIntro = () => {
|
|
||||||
return <LainConstructor sprite={introSpriteSheet} frameCount={51} />;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const LainStanding = () => {
|
|
||||||
return <LainConstructor sprite={standingSpriteSheet} frameCount={1} />;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const LainMoveDown = () => {
|
|
||||||
return <LainConstructor sprite={moveDownSpriteSheet} frameCount={36} />;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const LainMoveLeft = () => {
|
|
||||||
return <LainConstructor sprite={moveLeftSpriteSheet} frameCount={47} />;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const LainMoveRight = () => {
|
|
||||||
return <LainConstructor sprite={moveRightSpriteSheet} frameCount={47} />;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const LainMoveUp = () => {
|
|
||||||
return <LainConstructor sprite={moveUpSpriteSheet} frameCount={36} />;
|
|
||||||
};
|
|
||||||
|
|
||||||
const Lain = (props: LainProps) => {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{/* without a suspense the sprites wont load, and the suspense loading
|
|
||||||
animation takes about .3 seconds to finish for each sprite resulting in
|
|
||||||
blinks between each spritesheet. with a nested suspense we can have
|
|
||||||
LainStanding as the suspense fallback with a fallback of its own (the
|
|
||||||
loading message) which will only be shown once, when the game loads. */}
|
|
||||||
<Suspense
|
|
||||||
fallback={
|
|
||||||
<Suspense fallback={<React.Fragment>loading...</React.Fragment>}>
|
|
||||||
<LainStanding />
|
|
||||||
</Suspense>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{props.isLainMoving ? props.lainMoveState : <LainStanding />}
|
|
||||||
</Suspense>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default Lain;
|
export default Lain;
|
||||||
|
|
14
src/components/Lights.tsx
Normal file
14
src/components/Lights.tsx
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
const Lights = () => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<ambientLight color={0x808080} />
|
||||||
|
<pointLight color={0xffffff} position={[0, 0, 700]} intensity={0.5} />
|
||||||
|
<pointLight color={0x7f7f7f} position={[0, 1000, 0]} intensity={1} />
|
||||||
|
<pointLight color={0xffffff} position={[0, 0, 0]} intensity={0.1} />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Lights;
|
|
@ -24,7 +24,7 @@ type GLTFResult = GLTF & {
|
||||||
materials: {};
|
materials: {};
|
||||||
};
|
};
|
||||||
|
|
||||||
const Ring0 = (props: Ring0Props) => {
|
const Ring0 = (props: any) => {
|
||||||
const { nodes, materials } = useLoader<GLTFResult>(
|
const { nodes, materials } = useLoader<GLTFResult>(
|
||||||
GLTFLoader,
|
GLTFLoader,
|
||||||
"/models/ring0.glb",
|
"/models/ring0.glb",
|
||||||
|
@ -33,14 +33,14 @@ const Ring0 = (props: Ring0Props) => {
|
||||||
return (
|
return (
|
||||||
<group
|
<group
|
||||||
scale={[1.3, 1.3, 1.3]}
|
scale={[1.3, 1.3, 1.3]}
|
||||||
position={[0, props.lowerRingPositionY, 0]}
|
position={[0, -0.27, 0]}
|
||||||
rotation={[0, props.lowerRingRotationY, 0]}
|
rotation={[0, 0.26, 0]}
|
||||||
dispose={null}
|
dispose={null}
|
||||||
>
|
>
|
||||||
<mesh geometry={nodes.Circle.geometry} rotation={[0, Math.PI / 4, 0]}>
|
<mesh geometry={nodes.Circle.geometry} rotation={[0, Math.PI / 4, 0]}>
|
||||||
<meshLambertMaterial
|
<meshLambertMaterial
|
||||||
attach="material"
|
attach="material"
|
||||||
transparent={true}
|
color={0x636363}
|
||||||
side={THREE.DoubleSide}
|
side={THREE.DoubleSide}
|
||||||
/>
|
/>
|
||||||
</mesh>
|
</mesh>
|
||||||
|
@ -48,4 +48,4 @@ const Ring0 = (props: Ring0Props) => {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Ring0
|
export default Ring0;
|
||||||
|
|
49
src/components/Ring1.tsx
Normal file
49
src/components/Ring1.tsx
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
import * as THREE from "three";
|
||||||
|
import React, { useRef, useState, useEffect } from "react";
|
||||||
|
import { useLoader } from "react-three-fiber";
|
||||||
|
import { GLTFLoader, GLTF } from "three/examples/jsm/loaders/GLTFLoader";
|
||||||
|
import { draco } from "drei";
|
||||||
|
|
||||||
|
type GLTFResult = GLTF & {
|
||||||
|
nodes: {
|
||||||
|
Circle002: THREE.Mesh;
|
||||||
|
};
|
||||||
|
materials: {};
|
||||||
|
};
|
||||||
|
|
||||||
|
const Ring1 = (props: JSX.IntrinsicElements["group"]) => {
|
||||||
|
const [higherRingRotation, setHigherRingRotation] = useState(0);
|
||||||
|
|
||||||
|
const { nodes, materials } = useLoader<GLTFResult>(
|
||||||
|
GLTFLoader,
|
||||||
|
"/models/ring1.glb",
|
||||||
|
draco("/draco-gltf/")
|
||||||
|
);
|
||||||
|
|
||||||
|
const ring1PermaRotation = () => {
|
||||||
|
setHigherRingRotation((prev) => prev + 0.002);
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setInterval(ring1PermaRotation, 1);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<group
|
||||||
|
position={[0, 0.4, 0]}
|
||||||
|
rotation={[0, higherRingRotation, 0]}
|
||||||
|
scale={[1.3, 1.3, 1.3]}
|
||||||
|
dispose={null}
|
||||||
|
>
|
||||||
|
<mesh geometry={nodes.Circle002.geometry} rotation={[0, Math.PI / 4, 0]}>
|
||||||
|
<meshLambertMaterial
|
||||||
|
attach="material"
|
||||||
|
color={0x8b6ff7}
|
||||||
|
side={THREE.DoubleSide}
|
||||||
|
/>
|
||||||
|
</mesh>
|
||||||
|
</group>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Ring1;
|
|
@ -1,3 +0,0 @@
|
||||||
export const getKeyValue = <U extends keyof T, T extends object>(key: U) => (
|
|
||||||
obj: T
|
|
||||||
) => obj[key];
|
|
Loading…
Reference in a new issue