411 lines
8.8 KiB
JavaScript
411 lines
8.8 KiB
JavaScript
/**
|
|
* Modern Status Indicator Component
|
|
* Enhanced status indicators with true color and Unicode support
|
|
* Requirements: 12.1, 12.2, 12.3
|
|
*/
|
|
|
|
const React = require("react");
|
|
const { Box, Text } = require("ink");
|
|
const useModernTerminal = require("../../hooks/useModernTerminal.js");
|
|
|
|
/**
|
|
* Modern status indicator with enhanced visuals
|
|
*/
|
|
const ModernStatusIndicator = ({
|
|
status = "idle",
|
|
label = "",
|
|
showLabel = true,
|
|
size = "medium",
|
|
animated = false,
|
|
customColor,
|
|
customIcon,
|
|
...props
|
|
}) => {
|
|
const { colors, unicode, utils, capabilities } = useModernTerminal();
|
|
const [animationFrame, setAnimationFrame] = React.useState(0);
|
|
|
|
// Status configurations
|
|
const statusConfig = {
|
|
success: {
|
|
color: "#00FF00",
|
|
icon: "checkMark",
|
|
fallback: "✓",
|
|
label: "Success",
|
|
},
|
|
error: {
|
|
color: "#FF0000",
|
|
icon: "crossMark",
|
|
fallback: "✗",
|
|
label: "Error",
|
|
},
|
|
warning: {
|
|
color: "#FFFF00",
|
|
icon: "warning",
|
|
fallback: "!",
|
|
label: "Warning",
|
|
},
|
|
info: {
|
|
color: "#00FFFF",
|
|
icon: "info",
|
|
fallback: "i",
|
|
label: "Info",
|
|
},
|
|
loading: {
|
|
color: "#0080FF",
|
|
icon: "spinner",
|
|
fallback: "...",
|
|
label: "Loading",
|
|
animated: true,
|
|
},
|
|
idle: {
|
|
color: "#808080",
|
|
icon: "circle",
|
|
fallback: "○",
|
|
label: "Idle",
|
|
},
|
|
connected: {
|
|
color: "#00FF00",
|
|
icon: "filledCircle",
|
|
fallback: "●",
|
|
label: "Connected",
|
|
},
|
|
disconnected: {
|
|
color: "#FF0000",
|
|
icon: "circle",
|
|
fallback: "○",
|
|
label: "Disconnected",
|
|
},
|
|
processing: {
|
|
color: "#FF8000",
|
|
icon: "spinner",
|
|
fallback: "⟳",
|
|
label: "Processing",
|
|
animated: true,
|
|
},
|
|
};
|
|
|
|
const config = statusConfig[status] || statusConfig.idle;
|
|
const shouldAnimate = animated || config.animated;
|
|
|
|
// Animation effect
|
|
React.useEffect(() => {
|
|
if (!shouldAnimate) return;
|
|
|
|
const interval = setInterval(() => {
|
|
setAnimationFrame((frame) => (frame + 1) % 8);
|
|
}, 200);
|
|
|
|
return () => clearInterval(interval);
|
|
}, [shouldAnimate]);
|
|
|
|
// Generate status icon
|
|
const generateIcon = () => {
|
|
let icon;
|
|
|
|
if (customIcon) {
|
|
icon = customIcon;
|
|
} else if (config.icon === "spinner" && shouldAnimate) {
|
|
icon = utils.createSpinner(animationFrame);
|
|
} else {
|
|
icon = unicode.getChar("symbols", config.icon, config.fallback);
|
|
}
|
|
|
|
const iconColor =
|
|
customColor ||
|
|
(capabilities.trueColor
|
|
? colors.getInkColor(config.color)
|
|
: config.color.toLowerCase().replace("#", ""));
|
|
|
|
const sizeStyle = {
|
|
small: {},
|
|
medium: { bold: true },
|
|
large: { bold: true },
|
|
};
|
|
|
|
return (
|
|
<Text color={iconColor} {...sizeStyle[size]}>
|
|
{icon}
|
|
</Text>
|
|
);
|
|
};
|
|
|
|
// Generate status label
|
|
const generateLabel = () => {
|
|
if (!showLabel) return null;
|
|
|
|
const labelText = label || config.label;
|
|
const labelColor = capabilities.trueColor
|
|
? colors.getInkColor("#FFFFFF")
|
|
: "white";
|
|
|
|
return (
|
|
<Text color={labelColor} marginLeft={1}>
|
|
{labelText}
|
|
</Text>
|
|
);
|
|
};
|
|
|
|
return (
|
|
<Box flexDirection="row" alignItems="center" {...props}>
|
|
{generateIcon()}
|
|
{generateLabel()}
|
|
</Box>
|
|
);
|
|
};
|
|
|
|
/**
|
|
* Connection status indicator with pulse animation
|
|
*/
|
|
const ModernConnectionStatus = ({
|
|
isConnected = false,
|
|
label = "",
|
|
showDetails = false,
|
|
details = {},
|
|
...props
|
|
}) => {
|
|
const { colors, unicode, capabilities } = useModernTerminal();
|
|
const [pulseFrame, setPulseFrame] = React.useState(0);
|
|
|
|
// Pulse animation for connected state
|
|
React.useEffect(() => {
|
|
if (!isConnected) return;
|
|
|
|
const interval = setInterval(() => {
|
|
setPulseFrame((frame) => (frame + 1) % 6);
|
|
}, 300);
|
|
|
|
return () => clearInterval(interval);
|
|
}, [isConnected]);
|
|
|
|
const generateConnectionIcon = () => {
|
|
if (isConnected) {
|
|
// Pulsing connected indicator
|
|
const intensity = Math.sin((pulseFrame / 6) * Math.PI * 2) * 0.3 + 0.7;
|
|
const baseColor = capabilities.trueColor ? "#00FF00" : "green";
|
|
|
|
// For true color terminals, we could adjust brightness
|
|
// For now, just use the base color
|
|
const icon = unicode.getChar("symbols", "filledCircle", "●");
|
|
|
|
return (
|
|
<Text color={colors.getInkColor(baseColor)} bold>
|
|
{icon}
|
|
</Text>
|
|
);
|
|
} else {
|
|
// Disconnected indicator
|
|
const icon = unicode.getChar("symbols", "circle", "○");
|
|
const color = capabilities.trueColor
|
|
? colors.getInkColor("#FF0000")
|
|
: "red";
|
|
|
|
return <Text color={color}>{icon}</Text>;
|
|
}
|
|
};
|
|
|
|
const generateDetails = () => {
|
|
if (!showDetails || !details) return null;
|
|
|
|
return (
|
|
<Box flexDirection="column" marginLeft={2}>
|
|
{Object.entries(details).map(([key, value]) => (
|
|
<Text key={key} color="gray">
|
|
{key}: {value}
|
|
</Text>
|
|
))}
|
|
</Box>
|
|
);
|
|
};
|
|
|
|
return (
|
|
<Box flexDirection="column" {...props}>
|
|
<Box flexDirection="row" alignItems="center">
|
|
{generateConnectionIcon()}
|
|
<Text marginLeft={1}>
|
|
{label || (isConnected ? "Connected" : "Disconnected")}
|
|
</Text>
|
|
</Box>
|
|
{generateDetails()}
|
|
</Box>
|
|
);
|
|
};
|
|
|
|
/**
|
|
* Multi-state status indicator
|
|
*/
|
|
const ModernMultiStateIndicator = ({
|
|
states = [],
|
|
currentState = 0,
|
|
showProgress = false,
|
|
orientation = "horizontal",
|
|
...props
|
|
}) => {
|
|
const { colors, unicode, capabilities } = useModernTerminal();
|
|
|
|
const generateStateIndicators = () => {
|
|
return states.map((state, index) => {
|
|
const isActive = index === currentState;
|
|
const isCompleted = index < currentState;
|
|
const isPending = index > currentState;
|
|
|
|
let icon, color;
|
|
|
|
if (isCompleted) {
|
|
icon = unicode.getChar("symbols", "checkMark", "✓");
|
|
color = capabilities.trueColor
|
|
? colors.getInkColor("#00FF00")
|
|
: "green";
|
|
} else if (isActive) {
|
|
icon = unicode.getChar("symbols", "pointer", "►");
|
|
color = capabilities.trueColor ? colors.getInkColor("#0080FF") : "blue";
|
|
} else {
|
|
icon = unicode.getChar("symbols", "circle", "○");
|
|
color = capabilities.trueColor ? colors.getInkColor("#808080") : "gray";
|
|
}
|
|
|
|
const connector =
|
|
index < states.length - 1 ? (
|
|
<Text color="gray" marginX={1}>
|
|
{orientation === "horizontal" ? "─" : "│"}
|
|
</Text>
|
|
) : null;
|
|
|
|
return (
|
|
<React.Fragment key={index}>
|
|
<Box
|
|
flexDirection={orientation === "horizontal" ? "row" : "column"}
|
|
alignItems="center"
|
|
>
|
|
<Text color={color}>{icon}</Text>
|
|
<Text marginLeft={1} color={isActive ? "white" : "gray"}>
|
|
{state.label || `State ${index + 1}`}
|
|
</Text>
|
|
</Box>
|
|
{connector}
|
|
</React.Fragment>
|
|
);
|
|
});
|
|
};
|
|
|
|
const generateProgress = () => {
|
|
if (!showProgress) return null;
|
|
|
|
const progress = ((currentState + 1) / states.length) * 100;
|
|
|
|
return (
|
|
<Box marginTop={1}>
|
|
<Text color="gray">
|
|
Progress: {Math.round(progress)}% ({currentState + 1}/{states.length})
|
|
</Text>
|
|
</Box>
|
|
);
|
|
};
|
|
|
|
return (
|
|
<Box flexDirection="column" {...props}>
|
|
<Box flexDirection={orientation === "horizontal" ? "row" : "column"}>
|
|
{generateStateIndicators()}
|
|
</Box>
|
|
{generateProgress()}
|
|
</Box>
|
|
);
|
|
};
|
|
|
|
/**
|
|
* Health status indicator with metrics
|
|
*/
|
|
const ModernHealthIndicator = ({
|
|
health = "unknown",
|
|
metrics = {},
|
|
showMetrics = false,
|
|
thresholds = {},
|
|
...props
|
|
}) => {
|
|
const { colors, unicode, utils, capabilities } = useModernTerminal();
|
|
|
|
// Health status configurations
|
|
const healthConfig = {
|
|
healthy: {
|
|
color: "#00FF00",
|
|
icon: "checkMark",
|
|
label: "Healthy",
|
|
},
|
|
degraded: {
|
|
color: "#FFFF00",
|
|
icon: "warning",
|
|
label: "Degraded",
|
|
},
|
|
unhealthy: {
|
|
color: "#FF0000",
|
|
icon: "crossMark",
|
|
label: "Unhealthy",
|
|
},
|
|
unknown: {
|
|
color: "#808080",
|
|
icon: "info",
|
|
label: "Unknown",
|
|
},
|
|
};
|
|
|
|
const config = healthConfig[health] || healthConfig.unknown;
|
|
|
|
const generateHealthIcon = () => {
|
|
const icon = unicode.getChar("symbols", config.icon, "?");
|
|
const color = capabilities.trueColor
|
|
? colors.getInkColor(config.color)
|
|
: config.color.toLowerCase().replace("#", "");
|
|
|
|
return (
|
|
<Text color={color} bold>
|
|
{icon}
|
|
</Text>
|
|
);
|
|
};
|
|
|
|
const generateMetrics = () => {
|
|
if (!showMetrics || !metrics) return null;
|
|
|
|
return (
|
|
<Box flexDirection="column" marginLeft={2} marginTop={1}>
|
|
{Object.entries(metrics).map(([key, value]) => {
|
|
const threshold = thresholds[key];
|
|
let metricColor = "white";
|
|
|
|
if (threshold) {
|
|
if (value > threshold.critical) {
|
|
metricColor = "red";
|
|
} else if (value > threshold.warning) {
|
|
metricColor = "yellow";
|
|
} else {
|
|
metricColor = "green";
|
|
}
|
|
}
|
|
|
|
return (
|
|
<Text key={key} color={metricColor}>
|
|
{key}: {value}
|
|
</Text>
|
|
);
|
|
})}
|
|
</Box>
|
|
);
|
|
};
|
|
|
|
return (
|
|
<Box flexDirection="column" {...props}>
|
|
<Box flexDirection="row" alignItems="center">
|
|
{generateHealthIcon()}
|
|
<Text marginLeft={1}>{config.label}</Text>
|
|
</Box>
|
|
{generateMetrics()}
|
|
</Box>
|
|
);
|
|
};
|
|
|
|
module.exports = {
|
|
ModernStatusIndicator,
|
|
ModernConnectionStatus,
|
|
ModernMultiStateIndicator,
|
|
ModernHealthIndicator,
|
|
};
|