♻️ refactor(ui): Dependencies

This commit is contained in:
web@ppanel
2024-11-14 21:21:00 +07:00
parent 5af60aa4de
commit 727d779b84
187 changed files with 26448 additions and 14037 deletions
+277 -94
View File
@@ -1,113 +1,296 @@
// @ts-nocheck
'use client';
import { OrbitControls } from '@react-three/drei';
import { Canvas, extend, Object3DNode, useThree } from '@react-three/fiber';
import { useEffect, useRef, useState } from 'react';
import { Color, Fog, PerspectiveCamera, Scene, Vector3 } from 'three';
import ThreeGlobe from 'three-globe';
import countries from '../../data/globe.json';
declare module '@react-three/fiber' {
interface ThreeElements {
threeGlobe: Object3DNode<ThreeGlobe, typeof ThreeGlobe>;
}
}
import createGlobe, { COBEOptions } from 'cobe';
import { useCallback, useEffect, useRef, useState } from 'react';
extend({ ThreeGlobe });
import { cn } from '../../lib/utils';
const RING_PROPAGATION_SPEED = 3;
const aspect = 1.2;
const cameraZ = 300;
const GLOBE_CONFIG: COBEOptions = {
width: 800,
height: 800,
onRender: () => {},
devicePixelRatio: 2,
phi: 0,
theta: 0.3,
dark: 0,
diffuse: 0.4,
mapSamples: 16000,
mapBrightness: 1.2,
baseColor: [1, 1, 1],
markerColor: [251 / 255, 100 / 255, 21 / 255],
glowColor: [1, 1, 1],
markers: [
{ location: [14.5995, 120.9842], size: 0.03 },
{ location: [19.076, 72.8777], size: 0.1 },
{ location: [23.8103, 90.4125], size: 0.05 },
{ location: [30.0444, 31.2357], size: 0.07 },
{ location: [39.9042, 116.4074], size: 0.08 },
{ location: [-23.5505, -46.6333], size: 0.1 },
{ location: [19.4326, -99.1332], size: 0.1 },
{ location: [40.7128, -74.006], size: 0.1 },
{ location: [34.6937, 135.5022], size: 0.05 },
{ location: [41.0082, 28.9784], size: 0.06 },
],
type Position = {
order: number;
startLat: number;
startLng: number;
endLat: number;
endLng: number;
arcAlt: number;
color: string;
};
export default function Globe({
className,
config = GLOBE_CONFIG,
}: {
className?: string;
config?: COBEOptions;
}) {
let phi = 0;
let width = 0;
const canvasRef = useRef<HTMLCanvasElement>(null);
const pointerInteracting = useRef(null);
const pointerInteractionMovement = useRef(0);
const [r, setR] = useState(0);
const updatePointerInteraction = (value: any) => {
pointerInteracting.current = value;
if (canvasRef.current) {
canvasRef.current.style.cursor = value ? 'grabbing' : 'grab';
}
export type GlobeConfig = {
pointSize?: number;
globeColor?: string;
showAtmosphere?: boolean;
atmosphereColor?: string;
atmosphereAltitude?: number;
emissive?: string;
emissiveIntensity?: number;
shininess?: number;
polygonColor?: string;
ambientLight?: string;
directionalLeftLight?: string;
directionalTopLight?: string;
pointLight?: string;
arcTime?: number;
arcLength?: number;
rings?: number;
maxRings?: number;
initialPosition?: {
lat: number;
lng: number;
};
autoRotate?: boolean;
autoRotateSpeed?: number;
};
const updateMovement = (clientX: any) => {
if (pointerInteracting.current !== null) {
const delta = clientX - pointerInteracting.current;
pointerInteractionMovement.current = delta;
setR(delta / 200);
}
};
interface WorldProps {
globeConfig: GlobeConfig;
data: Position[];
}
const onRender = useCallback(
(state: Record<string, any>) => {
if (!pointerInteracting.current) phi += 0.005;
state.phi = phi + r;
state.width = width * 2;
state.height = width * 2;
},
[r],
);
let numbersOfRings = [0];
const onResize = () => {
if (canvasRef.current) {
width = canvasRef.current.offsetWidth;
}
export function Globe({ globeConfig, data }: WorldProps) {
const [globeData, setGlobeData] = useState<
| {
size: number;
order: number;
color: (t: number) => string;
lat: number;
lng: number;
}[]
| null
>(null);
const globeRef = useRef<ThreeGlobe | null>(null);
const defaultProps = {
pointSize: 1,
atmosphereColor: '#ffffff',
showAtmosphere: true,
atmosphereAltitude: 0.1,
polygonColor: 'rgba(255,255,255,0.7)',
globeColor: '#1d072e',
emissive: '#000000',
emissiveIntensity: 0.1,
shininess: 0.9,
arcTime: 2000,
arcLength: 0.9,
rings: 1,
maxRings: 3,
...globeConfig,
};
useEffect(() => {
window.addEventListener('resize', onResize);
onResize();
if (globeRef.current) {
_buildData();
_buildMaterial();
}
}, [globeRef.current]);
const globe = createGlobe(canvasRef.current!, {
...config,
width: width * 2,
height: width * 2,
onRender,
});
const _buildMaterial = () => {
if (!globeRef.current) return;
setTimeout(() => (canvasRef.current!.style.opacity = '1'));
return () => globe.destroy();
}, []);
const globeMaterial = globeRef.current.globeMaterial() as unknown as {
color: Color;
emissive: Color;
emissiveIntensity: number;
shininess: number;
};
globeMaterial.color = new Color(globeConfig.globeColor);
globeMaterial.emissive = new Color(globeConfig.emissive);
globeMaterial.emissiveIntensity = globeConfig.emissiveIntensity || 0.1;
globeMaterial.shininess = globeConfig.shininess || 0.9;
};
const _buildData = () => {
const arcs = data;
let points = [];
for (let i = 0; i < arcs.length; i++) {
const arc = arcs[i];
const rgb = hexToRgb(arc.color) as { r: number; g: number; b: number };
points.push({
size: defaultProps.pointSize,
order: arc.order,
color: (t: number) => `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, ${1 - t})`,
lat: arc.startLat,
lng: arc.startLng,
});
points.push({
size: defaultProps.pointSize,
order: arc.order,
color: (t: number) => `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, ${1 - t})`,
lat: arc.endLat,
lng: arc.endLng,
});
}
// remove duplicates for same lat and lng
const filteredPoints = points.filter(
(v, i, a) =>
a.findIndex((v2) =>
['lat', 'lng'].every((k) => v2[k as 'lat' | 'lng'] === v[k as 'lat' | 'lng']),
) === i,
);
setGlobeData(filteredPoints);
};
useEffect(() => {
if (globeRef.current && globeData) {
globeRef.current
.hexPolygonsData(countries.features)
.hexPolygonResolution(3)
.hexPolygonMargin(0.7)
.showAtmosphere(defaultProps.showAtmosphere)
.atmosphereColor(defaultProps.atmosphereColor)
.atmosphereAltitude(defaultProps.atmosphereAltitude)
.hexPolygonColor((e) => {
return defaultProps.polygonColor;
});
startAnimation();
}
}, [globeData]);
const startAnimation = () => {
if (!globeRef.current || !globeData) return;
globeRef.current
.arcsData(data)
.arcStartLat((d) => (d as { startLat: number }).startLat * 1)
.arcStartLng((d) => (d as { startLng: number }).startLng * 1)
.arcEndLat((d) => (d as { endLat: number }).endLat * 1)
.arcEndLng((d) => (d as { endLng: number }).endLng * 1)
.arcColor((e: any) => (e as { color: string }).color)
.arcAltitude((e) => {
return (e as { arcAlt: number }).arcAlt * 1;
})
.arcStroke((e) => {
return [0.32, 0.28, 0.3][Math.round(Math.random() * 2)];
})
.arcDashLength(defaultProps.arcLength)
.arcDashInitialGap((e) => (e as { order: number }).order * 1)
.arcDashGap(15)
.arcDashAnimateTime((e) => defaultProps.arcTime);
globeRef.current
.pointsData(data)
.pointColor((e) => (e as { color: string }).color)
.pointsMerge(true)
.pointAltitude(0.0)
.pointRadius(2);
globeRef.current
.ringsData([])
.ringColor((e: any) => (t: any) => e.color(t))
.ringMaxRadius(defaultProps.maxRings)
.ringPropagationSpeed(RING_PROPAGATION_SPEED)
.ringRepeatPeriod((defaultProps.arcTime * defaultProps.arcLength) / defaultProps.rings);
};
useEffect(() => {
if (!globeRef.current || !globeData) return;
const interval = setInterval(() => {
if (!globeRef.current || !globeData) return;
numbersOfRings = genRandomNumbers(0, data.length, Math.floor((data.length * 4) / 5));
globeRef.current.ringsData(globeData.filter((d, i) => numbersOfRings.includes(i)));
}, 2000);
return () => {
clearInterval(interval);
};
}, [globeRef.current, globeData]);
return (
<div className={cn('absolute inset-0 mx-auto aspect-[1/1] w-full max-w-[600px]', className)}>
<canvas
className={cn(
'size-full opacity-0 transition-opacity duration-500 [contain:layout_paint_size]',
)}
ref={canvasRef}
onPointerDown={(e) =>
updatePointerInteraction(e.clientX - pointerInteractionMovement.current)
}
onPointerUp={() => updatePointerInteraction(null)}
onPointerOut={() => updatePointerInteraction(null)}
onMouseMove={(e) => updateMovement(e.clientX)}
onTouchMove={(e) => e.touches[0] && updateMovement(e.touches[0].clientX)}
/>
</div>
<>
<threeGlobe ref={globeRef} />
</>
);
}
export function WebGLRendererConfig() {
const { gl, size } = useThree();
useEffect(() => {
gl.setPixelRatio(window.devicePixelRatio);
gl.setSize(size.width, size.height);
gl.setClearColor(0xffaaff, 0);
}, []);
return null;
}
export function World(props: WorldProps) {
const { globeConfig } = props;
const scene = new Scene();
scene.fog = new Fog(0xffffff, 400, 2000);
return (
<Canvas scene={scene} camera={new PerspectiveCamera(50, aspect, 180, 1800)}>
<WebGLRendererConfig />
<ambientLight color={globeConfig.ambientLight} intensity={0.6} />
<directionalLight
color={globeConfig.directionalLeftLight}
position={new Vector3(-400, 100, 400)}
/>
<directionalLight
color={globeConfig.directionalTopLight}
position={new Vector3(-200, 500, 200)}
/>
<pointLight
color={globeConfig.pointLight}
position={new Vector3(-200, 500, 200)}
intensity={0.8}
/>
<Globe {...props} />
<OrbitControls
enablePan={false}
enableZoom={false}
minDistance={cameraZ}
maxDistance={cameraZ}
autoRotateSpeed={1}
autoRotate={true}
minPolarAngle={Math.PI / 3.5}
maxPolarAngle={Math.PI - Math.PI / 3}
/>
</Canvas>
);
}
export function hexToRgb(hex: string) {
var shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i;
hex = hex.replace(shorthandRegex, function (m, r, g, b) {
return r + r + g + g + b + b;
});
var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
return result
? {
r: parseInt(result[1], 16),
g: parseInt(result[2], 16),
b: parseInt(result[3], 16),
}
: null;
}
export function genRandomNumbers(min: number, max: number, count: number) {
const arr = [];
while (arr.length < count) {
const r = Math.floor(Math.random() * (max - min)) + min;
if (arr.indexOf(r) === -1) arr.push(r);
}
return arr;
}