mirror of
https://github.com/duckietm/Nitro-V3.git
synced 2026-06-19 23:16:21 +00:00
feat: add advanced wired variable tools UI
This commit is contained in:
@@ -0,0 +1,232 @@
|
||||
import { FC, useEffect, useMemo, useState } from 'react';
|
||||
import { LocalizeText, WiredFurniType } from '../../../../api';
|
||||
import { Text } from '../../../../common';
|
||||
import { useWired } from '../../../../hooks';
|
||||
import { NitroInput } from '../../../../layout';
|
||||
import { WiredExtraBaseView } from './WiredExtraBaseView';
|
||||
|
||||
const TARGET_USER = 0;
|
||||
const TARGET_ROOM = 3;
|
||||
const MAX_NAME_LENGTH = 40;
|
||||
|
||||
interface IVariableReferenceEditorVariable
|
||||
{
|
||||
hasValue: boolean;
|
||||
itemId: number;
|
||||
name: string;
|
||||
targetType: number;
|
||||
}
|
||||
|
||||
interface IVariableReferenceEditorRoom
|
||||
{
|
||||
roomId: number;
|
||||
roomName: string;
|
||||
variables: IVariableReferenceEditorVariable[];
|
||||
}
|
||||
|
||||
interface IVariableReferenceEditorData
|
||||
{
|
||||
readOnly?: boolean;
|
||||
rooms?: IVariableReferenceEditorRoom[];
|
||||
sourceRoomId?: number;
|
||||
sourceRoomName?: string;
|
||||
sourceTargetType?: number;
|
||||
sourceVariableItemId?: number;
|
||||
sourceVariableName?: string;
|
||||
variableName?: string;
|
||||
}
|
||||
|
||||
const normalizeVariableName = (value: string) =>
|
||||
{
|
||||
let normalizedValue = (value ?? '').trim().replace(/[\t\r\n]/g, '');
|
||||
|
||||
if(normalizedValue.includes('=')) normalizedValue = normalizedValue.substring(0, normalizedValue.indexOf('=')).trim();
|
||||
|
||||
while(normalizedValue.startsWith('@') || normalizedValue.startsWith('~'))
|
||||
{
|
||||
normalizedValue = normalizedValue.substring(1).trim();
|
||||
}
|
||||
|
||||
normalizedValue = normalizedValue.replace(/[^A-Za-z0-9_]/g, '');
|
||||
|
||||
return normalizedValue.slice(0, MAX_NAME_LENGTH);
|
||||
};
|
||||
|
||||
const parseEditorData = (value: string): IVariableReferenceEditorData =>
|
||||
{
|
||||
if(!value?.trim().startsWith('{')) return {};
|
||||
|
||||
try
|
||||
{
|
||||
return (JSON.parse(value) as IVariableReferenceEditorData) || {};
|
||||
}
|
||||
catch
|
||||
{
|
||||
return {};
|
||||
}
|
||||
};
|
||||
|
||||
export const WiredExtraVariableReferenceView: FC<{}> = () =>
|
||||
{
|
||||
const { trigger = null, setIntParams = null, setStringParam = null } = useWired();
|
||||
const [ variableName, setVariableName ] = useState('');
|
||||
const [ sourceRoomId, setSourceRoomId ] = useState(0);
|
||||
const [ sourceVariableItemId, setSourceVariableItemId ] = useState(0);
|
||||
const [ sourceTargetType, setSourceTargetType ] = useState(TARGET_USER);
|
||||
const [ readOnly, setReadOnly ] = useState(true);
|
||||
const [ roomOptions, setRoomOptions ] = useState<IVariableReferenceEditorRoom[]>([]);
|
||||
const [ fallbackRoomName, setFallbackRoomName ] = useState('');
|
||||
const [ fallbackVariableName, setFallbackVariableName ] = useState('');
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
if(!trigger)
|
||||
{
|
||||
setVariableName('');
|
||||
setSourceRoomId(0);
|
||||
setSourceVariableItemId(0);
|
||||
setSourceTargetType(TARGET_USER);
|
||||
setReadOnly(true);
|
||||
setRoomOptions([]);
|
||||
setFallbackRoomName('');
|
||||
setFallbackVariableName('');
|
||||
return;
|
||||
}
|
||||
|
||||
const editorData = parseEditorData(trigger.stringData);
|
||||
|
||||
setVariableName(normalizeVariableName(editorData.variableName || ''));
|
||||
setSourceRoomId(editorData.sourceRoomId || 0);
|
||||
setSourceVariableItemId(editorData.sourceVariableItemId || 0);
|
||||
setSourceTargetType((editorData.sourceTargetType === TARGET_ROOM) ? TARGET_ROOM : TARGET_USER);
|
||||
setReadOnly(editorData.readOnly !== false);
|
||||
setRoomOptions([ ...(editorData.rooms || []) ]);
|
||||
setFallbackRoomName((editorData.sourceRoomName || '').trim());
|
||||
setFallbackVariableName((editorData.sourceVariableName || '').trim());
|
||||
}, [ trigger ]);
|
||||
|
||||
const mergedRoomOptions = useMemo(() =>
|
||||
{
|
||||
const nextValue = [ ...roomOptions ];
|
||||
|
||||
if(sourceRoomId <= 0) return nextValue;
|
||||
if(nextValue.some(room => (room.roomId === sourceRoomId))) return nextValue;
|
||||
|
||||
nextValue.push({
|
||||
roomId: sourceRoomId,
|
||||
roomName: (fallbackRoomName || `#${ sourceRoomId }`),
|
||||
variables: sourceVariableItemId > 0
|
||||
? [ {
|
||||
itemId: sourceVariableItemId,
|
||||
name: (fallbackVariableName || `#${ sourceVariableItemId }`),
|
||||
targetType: sourceTargetType,
|
||||
hasValue: true
|
||||
} ]
|
||||
: []
|
||||
});
|
||||
|
||||
return nextValue;
|
||||
}, [ fallbackRoomName, fallbackVariableName, roomOptions, sourceRoomId, sourceTargetType, sourceVariableItemId ]);
|
||||
|
||||
const selectedRoom = useMemo(() => mergedRoomOptions.find(option => (option.roomId === sourceRoomId)) ?? null, [ mergedRoomOptions, sourceRoomId ]);
|
||||
const selectedRoomVariables = (selectedRoom?.variables || []);
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
if(!selectedRoom)
|
||||
{
|
||||
if(!sourceRoomId && mergedRoomOptions.length) setSourceRoomId(mergedRoomOptions[0].roomId);
|
||||
return;
|
||||
}
|
||||
|
||||
const hasSelectedVariable = selectedRoomVariables.some(variable => (variable.itemId === sourceVariableItemId) && (variable.targetType === sourceTargetType));
|
||||
|
||||
if(hasSelectedVariable) return;
|
||||
|
||||
const fallbackVariable = selectedRoomVariables[0];
|
||||
|
||||
if(!fallbackVariable)
|
||||
{
|
||||
setSourceVariableItemId(0);
|
||||
setSourceTargetType(TARGET_USER);
|
||||
return;
|
||||
}
|
||||
|
||||
setSourceVariableItemId(fallbackVariable.itemId);
|
||||
setSourceTargetType(fallbackVariable.targetType);
|
||||
}, [ mergedRoomOptions, selectedRoom, selectedRoomVariables, sourceRoomId, sourceTargetType, sourceVariableItemId ]);
|
||||
|
||||
const save = () =>
|
||||
{
|
||||
setIntParams([]);
|
||||
setStringParam(JSON.stringify({
|
||||
variableName: normalizeVariableName(variableName),
|
||||
sourceRoomId,
|
||||
sourceVariableItemId,
|
||||
sourceTargetType,
|
||||
readOnly
|
||||
}));
|
||||
};
|
||||
|
||||
const validate = () => !!normalizeVariableName(variableName).length && (sourceRoomId > 0) && (sourceVariableItemId > 0);
|
||||
|
||||
const getTargetLabel = (targetType: number) =>
|
||||
{
|
||||
if(targetType === TARGET_ROOM)
|
||||
{
|
||||
const globalLabel = LocalizeText('wiredfurni.params.sources.global');
|
||||
|
||||
return ((globalLabel && (globalLabel !== 'wiredfurni.params.sources.global')) ? globalLabel : 'Global');
|
||||
}
|
||||
|
||||
return 'User';
|
||||
};
|
||||
|
||||
return (
|
||||
<WiredExtraBaseView hasSpecialInput={ true } requiresFurni={ WiredFurniType.STUFF_SELECTION_OPTION_NONE } save={ save } validate={ validate } cardStyle={ { width: 400 } }>
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="flex flex-col gap-1">
|
||||
<Text>{ LocalizeText('wiredfurni.params.variables.variable_name') }</Text>
|
||||
<NitroInput maxLength={ MAX_NAME_LENGTH } type="text" value={ variableName } onChange={ event => setVariableName(normalizeVariableName(event.target.value)) } />
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-1">
|
||||
<Text>{ LocalizeText('wiredfurni.params.variables.room_selection') }</Text>
|
||||
<select className="form-select form-select-sm" value={ sourceRoomId } onChange={ event => setSourceRoomId(parseInt(event.target.value, 10) || 0) }>
|
||||
<option value={ 0 }>{ LocalizeText('wiredfurni.variable_picker.search') }</option>
|
||||
{ mergedRoomOptions.map(option => <option key={ option.roomId } value={ option.roomId }>{ option.roomName }</option>) }
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-1">
|
||||
<Text>{ LocalizeText('wiredfurni.params.variables.variable_ref_selection') }</Text>
|
||||
<select
|
||||
className="form-select form-select-sm"
|
||||
value={ `${ sourceVariableItemId }:${ sourceTargetType }` }
|
||||
onChange={ event =>
|
||||
{
|
||||
const [ nextItemId, nextTargetType ] = event.target.value.split(':').map(value => parseInt(value, 10) || 0);
|
||||
|
||||
setSourceVariableItemId(nextItemId);
|
||||
setSourceTargetType((nextTargetType === TARGET_ROOM) ? TARGET_ROOM : TARGET_USER);
|
||||
} }>
|
||||
<option value="0:0">{ LocalizeText('wiredfurni.variable_picker.search') }</option>
|
||||
{ selectedRoomVariables.map(variable => (
|
||||
<option key={ `${ variable.itemId }:${ variable.targetType }` } value={ `${ variable.itemId }:${ variable.targetType }` }>
|
||||
{ `${ variable.name } (${ getTargetLabel(variable.targetType) })` }
|
||||
</option>
|
||||
)) }
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-1">
|
||||
<Text>{ LocalizeText('wiredfurni.params.variables.settings') }</Text>
|
||||
<label className="flex items-center gap-1 cursor-pointer">
|
||||
<input checked={ readOnly } className="form-check-input" type="checkbox" onChange={ event => setReadOnly(event.target.checked) } />
|
||||
<Text>{ LocalizeText('wiredfurni.params.variables.settings.read_only') }</Text>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</WiredExtraBaseView>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user