add Chinese translate
This commit is contained in:
+47
-45
@@ -9,6 +9,7 @@ import PlayArrowIcon from '@mui/icons-material/PlayArrow';
|
||||
import StopIcon from '@mui/icons-material/Stop'; // Add this import for the stop button
|
||||
import MusicNoteIcon from '@mui/icons-material/MusicNote';
|
||||
import AM32_Rtttl from './am32_rtttl'; // Updated import to match class name
|
||||
import { useTranslation } from './i18n/LanguageContext';
|
||||
|
||||
const EditParamModal = ({ open, onClose, nodeId, paramIndex }) => {
|
||||
// Add a new state for tracking whether a tune is currently playing
|
||||
@@ -21,6 +22,7 @@ const EditParamModal = ({ open, onClose, nodeId, paramIndex }) => {
|
||||
const [errorMessage, setErrorMessage] = useState(''); // For validation error messages
|
||||
const [isValid, setIsValid] = useState(true); // Add a new state variable to track validation status
|
||||
const [paramName, setParamName] = useState(""); // Add paramName to the component state
|
||||
const { t } = useTranslation();
|
||||
|
||||
useEffect(() => {
|
||||
const localNode = window.localNode;
|
||||
@@ -84,7 +86,7 @@ const EditParamModal = ({ open, onClose, nodeId, paramIndex }) => {
|
||||
|
||||
if (!isValidFormat) {
|
||||
// Warn user but continue with a default tune
|
||||
setErrorMessage('Warning: Invalid RTTTL format! Using a default empty tune instead.');
|
||||
setErrorMessage(t('edit.rtttl_warning'));
|
||||
// Continue with a minimal valid RTTTL string
|
||||
const tuneToParse = "Empty:d=4,o=5,b=120:";
|
||||
result = AM32_Rtttl.to_am32_startup_melody(tuneToParse);
|
||||
@@ -102,7 +104,7 @@ const EditParamModal = ({ open, onClose, nodeId, paramIndex }) => {
|
||||
console.log("Binary array values:", Array.from(result.data).slice(0, 30));
|
||||
} catch (err) {
|
||||
console.error("Error converting RTTTL to binary:", err);
|
||||
setErrorMessage(`Error saving tune: ${err.message || 'Unknown error'}`);
|
||||
setErrorMessage(t('edit.error_saving', { error: err.message || t('edit.unknown') }));
|
||||
// Provide an empty binary string (all zeros) as fallback
|
||||
const emptyArray = new Uint8Array(128);
|
||||
valueToSave = String.fromCharCode.apply(null, emptyArray);
|
||||
@@ -126,30 +128,30 @@ const EditParamModal = ({ open, onClose, nodeId, paramIndex }) => {
|
||||
try {
|
||||
// First validate that the tune has the basic RTTTL format (name:defaults:notes)
|
||||
if (!tuneToPlay || !tuneToPlay.includes(':') || tuneToPlay.split(':').length !== 3) {
|
||||
setErrorMessage('Invalid RTTTL format! Format should be: name:defaults:notes');
|
||||
setErrorMessage(t('edit.rtttl_invalid'));
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// Clear any previous error when successful
|
||||
setErrorMessage('');
|
||||
|
||||
|
||||
// Stop any currently playing tune before starting a new one
|
||||
AM32_Rtttl.stopMelody();
|
||||
|
||||
|
||||
// Play the new tune
|
||||
AM32_Rtttl.playMelody(tuneToPlay);
|
||||
setPreviewTune(tuneToPlay);
|
||||
setIsPlaying(true);
|
||||
|
||||
|
||||
// Set up an event listener to detect when audio context is closed or ends
|
||||
const estimatedDuration = estimateTuneDuration(tuneToPlay);
|
||||
setTimeout(() => {
|
||||
setIsPlaying(false);
|
||||
}, estimatedDuration + 500); // Add a small buffer
|
||||
|
||||
|
||||
} catch (err) {
|
||||
console.error("Error playing tune:", err);
|
||||
setErrorMessage(`Error playing tune: ${err.message || 'Unknown error'}`);
|
||||
setErrorMessage(t('edit.error_playing', { error: err.message || t('edit.unknown') }));
|
||||
setIsPlaying(false);
|
||||
}
|
||||
};
|
||||
@@ -224,7 +226,7 @@ const EditParamModal = ({ open, onClose, nodeId, paramIndex }) => {
|
||||
const isValidFormat = stringValue.includes(':') && stringValue.split(':').length === 3;
|
||||
|
||||
if (!isValidFormat) {
|
||||
setErrorMessage('Invalid RTTTL format! Format should be: name:defaults:notes');
|
||||
setErrorMessage(t('edit.rtttl_invalid'));
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -275,13 +277,13 @@ const EditParamModal = ({ open, onClose, nodeId, paramIndex }) => {
|
||||
// Validate against min/max if they exist
|
||||
if ((min !== null && numericValue < min) || (max !== null && numericValue > max)) {
|
||||
setIsValid(false);
|
||||
setErrorMessage(`Value must be between ${min !== null ? min : '-∞'} and ${max !== null ? max : '∞'}`);
|
||||
setErrorMessage(t('edit.value_range', { min: min !== null ? min : '-∞', max: max !== null ? max : '∞' }));
|
||||
} else {
|
||||
setIsValid(true);
|
||||
setErrorMessage('');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
setValue(newValue);
|
||||
};
|
||||
|
||||
@@ -322,7 +324,7 @@ const EditParamModal = ({ open, onClose, nodeId, paramIndex }) => {
|
||||
// Validate against min/max if they exist
|
||||
if ((min !== null && numericValue < min) || (max !== null && numericValue > max)) {
|
||||
setIsValid(false);
|
||||
setErrorMessage(`Value must be between ${min !== null ? min : '-∞'} and ${max !== null ? max : '∞'}`);
|
||||
setErrorMessage(t('edit.value_range', { min: min !== null ? min : '-∞', max: max !== null ? max : '∞' }));
|
||||
} else {
|
||||
setIsValid(true);
|
||||
setErrorMessage('');
|
||||
@@ -340,14 +342,14 @@ const EditParamModal = ({ open, onClose, nodeId, paramIndex }) => {
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 2, mt: 2 }}>
|
||||
<Box sx={{ display: 'flex', flexDirection: 'row', gap: 1, alignItems: 'flex-end' }}>
|
||||
<FormControl fullWidth margin="dense">
|
||||
<InputLabel id="rtttl-preset-label">Select Preset Tune</InputLabel>
|
||||
<InputLabel id="rtttl-preset-label">{t('edit.select_preset')}</InputLabel>
|
||||
<Select
|
||||
labelId="rtttl-preset-label"
|
||||
value={selectedPreset}
|
||||
onChange={(e) => setSelectedPreset(e.target.value)}
|
||||
>
|
||||
<MenuItem value="" disabled>
|
||||
<em>Choose a preset tune</em>
|
||||
<em>{t('edit.choose_preset')}</em>
|
||||
</MenuItem>
|
||||
{Object.entries(rtttlPresets).map(([name, tune]) => (
|
||||
<MenuItem key={name} value={tune}>
|
||||
@@ -363,24 +365,24 @@ const EditParamModal = ({ open, onClose, nodeId, paramIndex }) => {
|
||||
disabled={!selectedPreset}
|
||||
sx={{ mb: 1 }}
|
||||
>
|
||||
Apply
|
||||
{t('edit.apply')}
|
||||
</Button>
|
||||
</Box>
|
||||
|
||||
<Box sx={{ position: 'relative' }}>
|
||||
<TextField
|
||||
label="RTTTL Tune"
|
||||
label={t('edit.rtttl_tune')}
|
||||
value={value || ""}
|
||||
onChange={(e) => handleValueChange(e.target.value)}
|
||||
fullWidth
|
||||
margin="dense"
|
||||
multiline
|
||||
rows={3}
|
||||
placeholder="Format: name:d=duration,o=octave,b=bpm:notes"
|
||||
placeholder={t('edit.rtttl_placeholder')}
|
||||
error={!isValid && value !== ''}
|
||||
// Remove helperText to avoid layout issues
|
||||
/>
|
||||
<Tooltip title={isPlaying ? "Stop tune" : "Play tune"}>
|
||||
<Tooltip title={isPlaying ? t('edit.stop_tune') : t('edit.play_tune')}>
|
||||
<IconButton
|
||||
size="small"
|
||||
color={isPlaying ? "secondary" : "primary"}
|
||||
@@ -401,21 +403,21 @@ const EditParamModal = ({ open, onClose, nodeId, paramIndex }) => {
|
||||
|
||||
{/* Add a simple instruction text below the field */}
|
||||
<Typography variant="caption" color="text.secondary" sx={{ ml: 1 }}>
|
||||
Enter RTTTL format tune or select a preset
|
||||
{t('edit.rtttl_instruction')}
|
||||
</Typography>
|
||||
|
||||
<Divider />
|
||||
|
||||
<Box sx={{ bgcolor: 'action.hover', p: 1, borderRadius: 1 }}>
|
||||
<Typography variant="caption" color="text.secondary" sx={{ fontWeight: 'bold' }}>
|
||||
RTTTL Format Guide
|
||||
{t('edit.rtttl_guide_title')}
|
||||
</Typography>
|
||||
<Box sx={{ mt: 0.5 }}>
|
||||
<Typography variant="caption" display="block">• d=duration (1=whole, 2=half, 4=quarter, 8=eighth, 16=16th note)</Typography>
|
||||
<Typography variant="caption" display="block">• o=octave (4-7 where 5 is default)</Typography>
|
||||
<Typography variant="caption" display="block">• b=tempo (beats per minute)</Typography>
|
||||
<Typography variant="caption" display="block">• Notes are: c, c#, d, d#, e, f, f#, g, g#, a, a#, b or h</Typography>
|
||||
<Typography variant="caption" display="block">• Example: Beep:d=4,o=5,b=120:c</Typography>
|
||||
<Typography variant="caption" display="block">• {t('edit.rtttl_guide_duration')}</Typography>
|
||||
<Typography variant="caption" display="block">• {t('edit.rtttl_guide_octave')}</Typography>
|
||||
<Typography variant="caption" display="block">• {t('edit.rtttl_guide_tempo')}</Typography>
|
||||
<Typography variant="caption" display="block">• {t('edit.rtttl_guide_notes')}</Typography>
|
||||
<Typography variant="caption" display="block">• {t('edit.rtttl_guide_example')}</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
@@ -431,7 +433,7 @@ const EditParamModal = ({ open, onClose, nodeId, paramIndex }) => {
|
||||
if (param.fields.value.msg.fields.boolean_value) {
|
||||
return (
|
||||
<Box sx={{ display: 'flex', alignItems: 'center'}}>
|
||||
<Typography variant="body2" sx={{ mr: 2 }}>Enable/Disable:</Typography>
|
||||
<Typography variant="body2" sx={{ mr: 2 }}>{t('edit.enable_disable')}</Typography>
|
||||
<Checkbox
|
||||
checked={value === 1 || value === true}
|
||||
onChange={(e) => setValue(e.target.checked ? 1 : 0)}
|
||||
@@ -462,7 +464,7 @@ const EditParamModal = ({ open, onClose, nodeId, paramIndex }) => {
|
||||
|
||||
return (
|
||||
<TextField
|
||||
label="New Value"
|
||||
label={t('edit.new_value')}
|
||||
value={value}
|
||||
type="number"
|
||||
inputProps={{
|
||||
@@ -472,8 +474,8 @@ const EditParamModal = ({ open, onClose, nodeId, paramIndex }) => {
|
||||
onChange={(e) => handleValueChange(e.target.value)}
|
||||
fullWidth
|
||||
margin="dense"
|
||||
helperText={isOutOfBounds ?
|
||||
`Value must be between ${min !== "" ? min : '-∞'} and ${max !== "" ? max : '∞'}` :
|
||||
helperText={isOutOfBounds ?
|
||||
t('edit.value_range', { min: min !== "" ? min : '-∞', max: max !== "" ? max : '∞' }) :
|
||||
null
|
||||
}
|
||||
/>
|
||||
@@ -506,8 +508,8 @@ const EditParamModal = ({ open, onClose, nodeId, paramIndex }) => {
|
||||
|
||||
const renderParamNameField = (name) => (
|
||||
<TextField
|
||||
label="Parameter Name"
|
||||
value={name || "Unknown"}
|
||||
label={t('edit.param_name')}
|
||||
value={name || t('edit.unknown')}
|
||||
InputProps={{
|
||||
readOnly: true,
|
||||
}}
|
||||
@@ -542,7 +544,7 @@ const EditParamModal = ({ open, onClose, nodeId, paramIndex }) => {
|
||||
/>
|
||||
) : (
|
||||
<Typography variant="body2">
|
||||
{value !== undefined && value !== "" && value !== null ? value : "Unknown"}
|
||||
{value !== undefined && value !== "" && value !== null ? value : t('edit.unknown')}
|
||||
</Typography>
|
||||
)}
|
||||
</Box>
|
||||
@@ -554,7 +556,7 @@ const EditParamModal = ({ open, onClose, nodeId, paramIndex }) => {
|
||||
onClose={onClose}
|
||||
sx={{ '& .MuiDialog-paper': { minWidth: isRTTTLEditor ? '600px' : '400px' } }}
|
||||
>
|
||||
<DialogTitle>Edit Parameter</DialogTitle>
|
||||
<DialogTitle>{t('edit.title')}</DialogTitle>
|
||||
<DialogContent>
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1, mt: 1 }}>
|
||||
<Box sx={{ display: 'flex', flexDirection: 'row', gap: 1, alignItems: 'flex-end' }}>
|
||||
@@ -565,7 +567,7 @@ const EditParamModal = ({ open, onClose, nodeId, paramIndex }) => {
|
||||
{isString && !isRTTTLEditor && (
|
||||
<Box sx={{ display: 'flex', flexDirection: 'row', gap: 1 }}>
|
||||
<TextField
|
||||
label="String Value"
|
||||
label={t('edit.string_value')}
|
||||
value={value || ""}
|
||||
onChange={(e) => setValue(e.target.value)}
|
||||
fullWidth
|
||||
@@ -580,7 +582,7 @@ const EditParamModal = ({ open, onClose, nodeId, paramIndex }) => {
|
||||
|
||||
<Box sx={{ display: 'flex', flexDirection: 'row', gap: 2 }}>
|
||||
{paramName === "STARTUP_TUNE" && isString ? (
|
||||
renderInfoField("Current RTTTL", (() => {
|
||||
renderInfoField(t('edit.current_rtttl'), (() => {
|
||||
try {
|
||||
// Get the binary string value
|
||||
const binaryString = paramValueField.toString();
|
||||
@@ -595,27 +597,27 @@ const EditParamModal = ({ open, onClose, nodeId, paramIndex }) => {
|
||||
return AM32_Rtttl.from_am32_startup_melody(binaryData, "Tune");
|
||||
} catch (err) {
|
||||
console.error("Error converting binary data to RTTTL:", err);
|
||||
return "Error parsing melody data";
|
||||
return t('edit.error_parsing_melody');
|
||||
}
|
||||
})(), true) // Pass true to indicate this is an RTTTL value
|
||||
) : (
|
||||
renderInfoField(
|
||||
"Current Value",
|
||||
isBoolean
|
||||
? (paramValueField.value ? "True" : "False")
|
||||
t('edit.current_value'),
|
||||
isBoolean
|
||||
? (paramValueField.value ? t('edit.true') : t('edit.false'))
|
||||
: isString
|
||||
? paramValueField.toString()
|
||||
: paramValueField.value
|
||||
)
|
||||
)}
|
||||
{/* Only show default value when not STARTUP_TUNE */}
|
||||
{paramName !== "STARTUP_TUNE" && renderInfoField("Default Value", isBoolean ? (paramDefaultValue ? "True" : "False") : paramDefaultValue)}
|
||||
{paramName !== "STARTUP_TUNE" && renderInfoField(t('edit.default_value'), isBoolean ? (paramDefaultValue ? t('param.true') : t('param.false')) : paramDefaultValue)}
|
||||
</Box>
|
||||
|
||||
{!isBoolean && !isString && !isRTTTLEditor && (
|
||||
<Box sx={{ display: 'flex', flexDirection: 'row', gap: 2 }}>
|
||||
{renderInfoField("Min Value", paramMinValue)}
|
||||
{renderInfoField("Max Value", paramMaxValue)}
|
||||
{renderInfoField(t('edit.min_value'), paramMinValue)}
|
||||
{renderInfoField(t('edit.max_value'), paramMaxValue)}
|
||||
</Box>
|
||||
)}
|
||||
{errorMessage && (
|
||||
@@ -629,14 +631,14 @@ const EditParamModal = ({ open, onClose, nodeId, paramIndex }) => {
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={onClose} color="secondary">
|
||||
Cancel
|
||||
{t('edit.cancel')}
|
||||
</Button>
|
||||
<Button
|
||||
onClick={handleSave}
|
||||
color="primary"
|
||||
disabled={!isValid}
|
||||
>
|
||||
Save
|
||||
{t('edit.save')}
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
|
||||
Reference in New Issue
Block a user