Skip to content

Commit

Permalink
Palette changes (#13)
Browse files Browse the repository at this point in the history
* position and size

* working height animation

* animation

* overflow issue still on

* looking good

* visibility tweeks
  • Loading branch information
yhattav authored Nov 18, 2024
1 parent b5bc6cd commit 8d64f63
Show file tree
Hide file tree
Showing 4 changed files with 137 additions and 95 deletions.
55 changes: 5 additions & 50 deletions src/components/MassSlider/MassSlider.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React from "react";
import { motion } from "framer-motion";
import { StarRenderer } from "../StarRenderer/StarRenderer";
import { massToPercentage, percentageToMass } from "../../utils/mass/massUtils";

interface MassSliderProps {
value: number;
Expand All @@ -9,7 +10,6 @@ interface MassSliderProps {
orientation?: "vertical" | "horizontal";
onDragStart?: () => void;
onDragEnd?: () => void;
label?: string;
}

export const MassSlider: React.FC<MassSliderProps> = ({
Expand All @@ -19,78 +19,33 @@ export const MassSlider: React.FC<MassSliderProps> = ({
orientation = "horizontal",
onDragStart,
onDragEnd,
label,
}) => {
const sliderRef = React.useRef<HTMLDivElement>(null);
const padding = 20;
const innerLength = length - padding * 2;

// Constants for mass calculation
const MIN_MASS = 1;
const MAX_MASS = 2500000;
const EXPONENT = 4;

const percentageToMass = (percentage: number): number => {
const exponentialValue = Math.pow(percentage, EXPONENT);
return MIN_MASS + (MAX_MASS - MIN_MASS) * exponentialValue;
};

const massToPercentage = (mass: number): number => {
const normalizedMass = (mass - MIN_MASS) / (MAX_MASS - MIN_MASS);
return Math.pow(normalizedMass, 1 / EXPONENT);
};

// Calculate initial position based on current value
const initialPercentage = massToPercentage(value);
const isVertical = orientation === "vertical";

// Calculate the initial position relative to the center of the slider
const initialPosition = initialPercentage * innerLength - innerLength / 2;

const formatMass = (mass: number): string => {
if (mass >= 1000000) {
return `${(mass / 1000000).toFixed(1)}M`;
} else if (mass >= 1000) {
return `${(mass / 1000).toFixed(1)}K`;
}
return mass.toFixed(0);
};

// Add this function to determine star type based on mass
const getStarType = (mass: number): string => {
if (mass < 1000) return "Brown Dwarf";
if (mass < 20000) return "Red Dwarf";
if (mass < 200000) return "Main Sequence";
if (mass < 1000000) return "Red Giant";
return "Super Giant";
};

return (
<div
style={{
width: isVertical ? "30px" : `${length}px`,
height: isVertical ? `${length}px` : "30px",
width: isVertical ? "20px" : `${length}px`,
height: isVertical ? `${length}px` : "20px",
background: "rgba(255, 255, 255, 0.1)",
borderRadius: "30px",
borderRadius: "20px",
position: "relative",
display: "flex",
margin: "5px",
flexDirection: "column",
alignItems: "center",
justifyContent: "center",
}}
>
<div
style={{
position: "absolute",
fontSize: "12px",
color: "rgba(255, 255, 255, 0.8)",
left: "10px",
fontFamily: "monospace",
}}
>
{formatMass(value)}
<div style={{ textAlign: "center" }}>{label || getStarType(value)}</div>
</div>
<div
style={{
flex: 1,
Expand Down
141 changes: 99 additions & 42 deletions src/components/StarPalette/StarPalette.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import React, { useEffect, useState } from "react";
import React, { useState } from "react";
import { motion, AnimatePresence } from "framer-motion";
import { StarTemplate } from "../../types/star";
import { STAR_TEMPLATES } from "../../constants/physics";
import { StarRenderer } from "../StarRenderer/StarRenderer";
import { MassSlider } from "../MassSlider/MassSlider";
import { formatMass, getStarType } from "../../utils/mass/massUtils";

interface StarPaletteProps {
onStarDragStart: (template: StarTemplate) => void;
Expand All @@ -22,7 +23,7 @@ export const StarPalette: React.FC<StarPaletteProps> = ({
forceHover = false,
}) => {
const [starMasses, setStarMasses] = useState<{ [key: number]: number }>({});
const [isPaletteHovered, setIsPaletteHovered] = useState(forceHover);
const [isDragging, setIsDragging] = useState(false);

const handleStarMassChange = (index: number, mass: number) => {
setStarMasses((prev) => ({
Expand All @@ -32,64 +33,121 @@ export const StarPalette: React.FC<StarPaletteProps> = ({
};

return (
<div
<motion.div
onClick={(e) => e.stopPropagation()}
onMouseDown={(e) => e.stopPropagation()}
onTouchStart={(e) => e.stopPropagation()}
onMouseEnter={() => setIsPaletteHovered(true)}
onMouseLeave={() => setIsPaletteHovered(false)}
className="floating-panel star-palette"
whileHover={{
width: "400px",
height: "60px",
}}
transition={{ duration: 0.3, ease: "easeOut" }}
style={{
overflow: isDragging ? "visible" : "hidden",
width: isDragging ? "400px" : "40px",
height: isDragging ? "60px" : "40px",
}}
>
<div style={{ display: "flex", flexDirection: "column", gap: "20px" }}>
<div>
{STAR_TEMPLATES.map((template, index) => (
<div
key={index}
style={{
display: "flex",
flexDirection: "row",
alignItems: "center",
alignItems: "flex-start",
gap: "20px",
position: "relative",
}}
>
<motion.div
drag
dragSnapToOrigin
dragConstraints={containerRef}
whileDrag={{ scale: 1.1, zIndex: 1000 }}
onDragStart={() =>
onStarDragStart({
...template,
mass: starMasses[index] || template.mass,
})
}
onDragEnd={(e) =>
onStarDragEnd(
{
...template,
mass: starMasses[index] || template.mass,
},
e
)
}
<div
style={{
width: "40px",
height: "40px",
display: "flex",
flexDirection: "column",
alignItems: "center",
justifyContent: "center",
cursor: "grab",
position: "relative",
touchAction: "none",
transition: "height 0.3s ease-out",
}}
>
<StarRenderer mass={starMasses[index] || template.mass} />
</motion.div>
<motion.div
drag
dragSnapToOrigin
dragConstraints={containerRef}
whileDrag={{ scale: 1.1, zIndex: 1000 }}
onDragStart={() => {
setIsDragging(true);
onStarDragStart({
...template,
mass: starMasses[index] || template.mass,
});
}}
onDragEnd={(e) => {
setTimeout(() => {
setIsDragging(false);
}, 900);
onStarDragEnd(
{
...template,
mass: starMasses[index] || template.mass,
},
e
);
}}
style={{
width: "40px",
height: "40px",
display: "flex",
alignItems: "center",
justifyContent: "center",
cursor: "grab",
position: "relative",
touchAction: "none",
}}
>
<StarRenderer mass={starMasses[index] || template.mass} />
</motion.div>
<AnimatePresence>
{
<motion.div
initial={{ opacity: 0, height: 0 }}
animate={{ opacity: 1, height: "20px" }}
exit={{ opacity: 0, height: 0 }}
style={{
padding: "0px 10px",
fontSize: "0.8rem",
color: "rgba(255, 255, 255, 0.8)",
fontFamily:
"-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,'Helvetica Neue',Arial,'Noto Sans',sans-serif,'Apple Color Emoji','Segoe UI Emoji','Segoe UI Symbol','Noto Color Emoji'",
textAlign: "center",
overflow: "hidden",
whiteSpace: "nowrap",
position: "absolute",
top: "35px",
left: "0px",
display: "flex",
gap: "10px",
}}
>
<span
style={{
minWidth: "50px",
textAlign: "left",
}}
>
{formatMass(starMasses[index] || template.mass)}
</span>
<span>{getStarType(starMasses[index])}</span>
</motion.div>
}
</AnimatePresence>
</div>

<AnimatePresence>
{(forceHover || isPaletteHovered) && (
{
<motion.div
initial={{ opacity: 0, width: 0, marginLeft: 0 }}
animate={{ opacity: 1, width: "auto", marginLeft: "20px" }}
exit={{ opacity: 0, width: 0, marginLeft: 0 }}
initial={{ opacity: 0, width: 0 }}
animate={{ opacity: 1, width: "auto" }}
exit={{ opacity: 0, width: 0 }}
transition={{
type: "spring",
stiffness: 300,
Expand All @@ -100,7 +158,6 @@ export const StarPalette: React.FC<StarPaletteProps> = ({
height: "40px",
display: "flex",
alignItems: "center",
overflow: "hidden",
}}
>
<MassSlider
Expand All @@ -110,11 +167,11 @@ export const StarPalette: React.FC<StarPaletteProps> = ({
onChange={(value) => handleStarMassChange(index, value)}
/>
</motion.div>
)}
}
</AnimatePresence>
</div>
))}
</div>
</div>
</motion.div>
);
};
5 changes: 2 additions & 3 deletions src/styles/theme.scss
Original file line number Diff line number Diff line change
Expand Up @@ -40,12 +40,11 @@
.star-palette {
position: absolute;
left: 2%;
top: 50%;
transform: translateY(-50%);
top: 2%;
transform: none;
display: flex;
flex-direction: row;
gap: 20px;
padding: 5px;
}

.action-button {
Expand Down
31 changes: 31 additions & 0 deletions src/utils/mass/massUtils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// Constants for mass calculation
export const MIN_MASS = 1;
export const MAX_MASS = 2500000;
export const EXPONENT = 4;

export const percentageToMass = (percentage: number): number => {
const exponentialValue = Math.pow(percentage, EXPONENT);
return MIN_MASS + (MAX_MASS - MIN_MASS) * exponentialValue;
};

export const massToPercentage = (mass: number): number => {
const normalizedMass = (mass - MIN_MASS) / (MAX_MASS - MIN_MASS);
return Math.pow(normalizedMass, 1 / EXPONENT);
};

export const formatMass = (mass: number): string => {
if (mass >= 1000000) {
return `${(mass / 1000000).toFixed(1)}M`;
} else if (mass >= 1000) {
return `${(mass / 1000).toFixed(1)}K`;
}
return mass.toFixed(0);
};

export const getStarType = (mass: number): string => {
if (mass < 1000) return "Brown Dwarf";
if (mass < 20000) return "Red Dwarf";
if (mass < 200000) return "Main Sequence";
if (mass < 1000000) return "Red Giant";
return "Super Giant";
};

0 comments on commit 8d64f63

Please sign in to comment.