mirror of
https://github.com/duckietm/Nitro-V3.git
synced 2026-06-19 15:06:20 +00:00
250 lines
9.8 KiB
TypeScript
250 lines
9.8 KiB
TypeScript
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 ?? '').replace(/[\t\r\n]/g, '');
|
|
|
|
if(normalizedValue.includes('=')) normalizedValue = normalizedValue.substring(0, normalizedValue.indexOf('=')).trim();
|
|
|
|
while(normalizedValue.startsWith('@') || normalizedValue.startsWith('~'))
|
|
{
|
|
normalizedValue = normalizedValue.substring(1);
|
|
}
|
|
|
|
normalizedValue = normalizedValue.replace(/\s+/g, '_');
|
|
normalizedValue = normalizedValue.replace(/[^A-Za-z0-9_]/g, '');
|
|
|
|
return normalizedValue.slice(0, MAX_NAME_LENGTH);
|
|
};
|
|
|
|
const handleVariableNameKeyDown = (event: React.KeyboardEvent<HTMLInputElement>, setValue: (value: string) => void) =>
|
|
{
|
|
if(event.key !== ' ') return;
|
|
|
|
event.preventDefault();
|
|
|
|
const input = event.currentTarget;
|
|
const start = (input.selectionStart ?? input.value.length);
|
|
const end = (input.selectionEnd ?? start);
|
|
const nextValue = `${ input.value.substring(0, start) }_${ input.value.substring(end) }`;
|
|
|
|
setValue(normalizeVariableName(nextValue));
|
|
|
|
window.requestAnimationFrame(() => input.setSelectionRange(Math.min(start + 1, input.value.length + 1), Math.min(start + 1, input.value.length + 1)));
|
|
};
|
|
|
|
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)) } onKeyDown={ event => handleVariableNameKeyDown(event, setVariableName) } />
|
|
</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>
|
|
);
|
|
};
|