Files
Nitro-V3/src/components/wired/views/conditions/WiredConditionVariableAgeMatchView.tsx
T
2026-04-02 04:44:04 +02:00

336 lines
16 KiB
TypeScript

import { FC, useEffect, useMemo, useState } from 'react';
import { LocalizeText, WiredFurniType } from '../../../../api';
import contextVariableIcon from '../../../../assets/images/wired/var/icon_source_context_clean.png';
import furniVariableIcon from '../../../../assets/images/wired/var/icon_source_furni.png';
import globalVariableIcon from '../../../../assets/images/wired/var/icon_source_global.png';
import userVariableIcon from '../../../../assets/images/wired/var/icon_source_user.png';
import { Text } from '../../../../common';
import { useWired, useWiredTools } from '../../../../hooks';
import { NitroInput } from '../../../../layout';
import { WiredFurniSelectionSourceRow } from '../WiredFurniSelectionSourceRow';
import { WiredVariablePicker } from '../WiredVariablePicker';
import { createFallbackVariableEntry, flattenWiredVariablePickerEntries, IWiredVariablePickerEntry, normalizeVariableTokenFromWire } from '../WiredVariablePickerData';
import { FURNI_SOURCES, sortWiredSourceOptions, USER_SOURCES, useAvailableUserSources, WiredSourceOption } from '../WiredSourcesSelector';
import { WiredConditionBaseView } from './WiredConditionBaseView';
type VariableTargetType = 'user' | 'furni' | 'global' | 'context';
interface IVariableDefinition
{
availability: number;
hasValue: boolean;
itemId: number;
name: string;
}
const TARGET_USER = 0;
const TARGET_FURNI = 1;
const TARGET_CONTEXT = 2;
const TARGET_GLOBAL = 3;
const COMPARE_VALUE_CREATED = 0;
const COMPARE_VALUE_UPDATED = 1;
const COMPARISON_LOWER_THAN = 0;
const COMPARISON_HIGHER_THAN = 2;
const SOURCE_TRIGGER = 0;
const SOURCE_SELECTED = 100;
const QUANTIFIER_ALL = 0;
const QUANTIFIER_ANY = 1;
const TARGET_BUTTONS: Array<{ key: VariableTargetType; icon: string; disabled?: boolean; }> = [
{ key: 'furni', icon: furniVariableIcon },
{ key: 'user', icon: userVariableIcon },
{ key: 'global', icon: globalVariableIcon },
{ key: 'context', icon: contextVariableIcon }
];
const GLOBAL_SOURCE_OPTIONS: WiredSourceOption[] = [ { value: SOURCE_TRIGGER, label: 'wiredfurni.params.sources.global' } ];
const CONTEXT_SOURCE_OPTIONS: WiredSourceOption[] = [ { value: SOURCE_TRIGGER, label: 'Current execution' } ];
const COMPARE_VALUE_OPTIONS = [ COMPARE_VALUE_CREATED, COMPARE_VALUE_UPDATED ];
const COMPARISON_OPTIONS = [ COMPARISON_LOWER_THAN, COMPARISON_HIGHER_THAN ];
const DURATION_UNITS = [ 0, 1, 2, 3, 4, 5, 6, 7 ];
const getTargetValue = (value: VariableTargetType) =>
{
switch(value)
{
case 'furni': return TARGET_FURNI;
case 'global': return TARGET_GLOBAL;
case 'context': return TARGET_CONTEXT;
default: return TARGET_USER;
}
};
const normalizeTargetType = (value: number): VariableTargetType =>
{
switch(value)
{
case TARGET_FURNI: return 'furni';
case TARGET_GLOBAL: return 'global';
case TARGET_CONTEXT: return 'context';
default: return 'user';
}
};
const getTargetDefinitions = (targetType: VariableTargetType, userDefinitions: IVariableDefinition[], furniDefinitions: IVariableDefinition[], roomDefinitions: IVariableDefinition[], contextDefinitions: IVariableDefinition[]) =>
{
switch(targetType)
{
case 'furni': return furniDefinitions;
case 'global': return roomDefinitions;
case 'context': return contextDefinitions;
default: return userDefinitions;
}
};
const buildCustomVariableEntries = (target: VariableTargetType, definitions: IVariableDefinition[]): IWiredVariablePickerEntry[] =>
{
return [ ...definitions ]
.sort((left, right) => left.name.localeCompare(right.name, undefined, { sensitivity: 'base' }) || (left.itemId - right.itemId))
.map(definition => ({
id: `custom:${ definition.itemId }`,
token: `custom:${ definition.itemId }`,
label: definition.name,
displayLabel: definition.name,
searchableText: definition.name,
selectable: true,
hasValue: !!definition.hasValue,
kind: 'custom',
target
}));
};
const getSourceTitle = () => LocalizeText('wiredfurni.params.sources.merged.title.variables');
export const WiredConditionVariableAgeMatchView: FC<{}> = () =>
{
const { trigger = null, furniIds = [], setFurniIds = null, setIntParams = null, setStringParam = null } = useWired();
const { userVariableDefinitions = [], furniVariableDefinitions = [], roomVariableDefinitions = [], contextVariableDefinitions = [] } = useWiredTools();
const [ targetType, setTargetType ] = useState<VariableTargetType>('user');
const [ variableToken, setVariableToken ] = useState('');
const [ compareValue, setCompareValue ] = useState(COMPARE_VALUE_CREATED);
const [ comparison, setComparison ] = useState(COMPARISON_LOWER_THAN);
const [ durationInput, setDurationInput ] = useState('0');
const [ durationUnit, setDurationUnit ] = useState(1);
const [ userSource, setUserSource ] = useState(SOURCE_TRIGGER);
const [ furniSource, setFurniSource ] = useState(SOURCE_TRIGGER);
const [ quantifier, setQuantifier ] = useState(QUANTIFIER_ALL);
const availableUserSources = useAvailableUserSources(trigger, USER_SOURCES);
const orderedUserSources = useMemo(() => sortWiredSourceOptions(availableUserSources, 'users'), [ availableUserSources ]);
const orderedFurniSources = useMemo(() => sortWiredSourceOptions(FURNI_SOURCES, 'furni'), []);
const variableDefinitions = useMemo(() => getTargetDefinitions(targetType, userVariableDefinitions, furniVariableDefinitions, roomVariableDefinitions, contextVariableDefinitions), [ contextVariableDefinitions, furniVariableDefinitions, roomVariableDefinitions, targetType, userVariableDefinitions ]);
const variableEntries = useMemo(() => buildCustomVariableEntries(targetType, variableDefinitions), [ targetType, variableDefinitions ]);
const resolvedVariableEntries = useMemo(() =>
{
if(!variableToken) return variableEntries;
if(flattenWiredVariablePickerEntries(variableEntries).some(entry => (entry.token === variableToken))) return variableEntries;
const fallbackEntry = createFallbackVariableEntry(targetType, variableToken);
return fallbackEntry ? [ fallbackEntry, ...variableEntries ] : variableEntries;
}, [ targetType, variableEntries, variableToken ]);
const sourceOptions = useMemo(() =>
{
switch(targetType)
{
case 'furni': return orderedFurniSources;
case 'global': return GLOBAL_SOURCE_OPTIONS;
case 'context': return CONTEXT_SOURCE_OPTIONS;
default: return orderedUserSources;
}
}, [ orderedFurniSources, orderedUserSources, targetType ]);
const sourceValue = (targetType === 'furni') ? furniSource : ((targetType === 'user') ? userSource : SOURCE_TRIGGER);
const requiresFurni = ((targetType === 'furni') && (furniSource === SOURCE_SELECTED)) ? WiredFurniType.STUFF_SELECTION_OPTION_BY_ID : WiredFurniType.STUFF_SELECTION_OPTION_NONE;
const selectionLimit = trigger?.maximumItemSelectionCount ?? 0;
useEffect(() =>
{
if(!trigger) return;
const intData = trigger.intData || [];
const nextTargetType = normalizeTargetType((intData.length > 0) ? intData[0] : TARGET_USER);
setTargetType(nextTargetType);
setVariableToken(normalizeVariableTokenFromWire(trigger.stringData || ''));
setCompareValue(((intData.length > 1) && (intData[1] === COMPARE_VALUE_UPDATED)) ? COMPARE_VALUE_UPDATED : COMPARE_VALUE_CREATED);
setComparison(((intData.length > 2) && (intData[2] === COMPARISON_HIGHER_THAN)) ? COMPARISON_HIGHER_THAN : COMPARISON_LOWER_THAN);
setDurationInput(String((intData.length > 3) ? intData[3] : 0));
setDurationUnit((intData.length > 4) ? intData[4] : 1);
setUserSource((intData.length > 5) ? intData[5] : SOURCE_TRIGGER);
setFurniSource((intData.length > 6) ? intData[6] : (((trigger.selectedItems?.length ?? 0) > 0) ? SOURCE_SELECTED : SOURCE_TRIGGER));
setQuantifier(((intData.length > 7) && (intData[7] === QUANTIFIER_ANY)) ? QUANTIFIER_ANY : QUANTIFIER_ALL);
}, [ trigger ]);
useEffect(() =>
{
if(targetType !== 'user') return;
if(orderedUserSources.some(option => (option.value === userSource))) return;
setUserSource(SOURCE_TRIGGER);
}, [ orderedUserSources, targetType, userSource ]);
useEffect(() =>
{
if(targetType !== 'global') return;
if(compareValue === COMPARE_VALUE_UPDATED) return;
setCompareValue(COMPARE_VALUE_UPDATED);
}, [ targetType, compareValue ]);
const save = () =>
{
const parsedDuration = parseInt(durationInput.trim(), 10);
setStringParam(variableToken);
setIntParams([
getTargetValue(targetType),
compareValue,
comparison,
Number.isFinite(parsedDuration) ? Math.max(0, parsedDuration) : 0,
durationUnit,
userSource,
furniSource,
quantifier
]);
if(requiresFurni <= WiredFurniType.STUFF_SELECTION_OPTION_NONE) setFurniIds([]);
};
const validate = () =>
{
if(!variableToken.length) return false;
if((targetType === 'global') && (compareValue !== COMPARE_VALUE_UPDATED)) return false;
return true;
};
const handleTargetChange = (nextTargetType: VariableTargetType) =>
{
if(nextTargetType === targetType) return;
setTargetType(nextTargetType);
setVariableToken('');
};
return (
<WiredConditionBaseView
hasSpecialInput={ true }
requiresFurni={ requiresFurni }
save={ save }
validate={ validate }
cardStyle={ { width: 244 } }
footerCollapsible={ false }
footer={ (
<div className="flex flex-col gap-2">
<div className="flex flex-col gap-1">
<Text bold>{ LocalizeText('wiredfurni.params.quantifier_selection') }</Text>
{ [ QUANTIFIER_ALL, QUANTIFIER_ANY ].map(value => (
<label key={ value } className="flex items-center gap-1">
<input checked={ (quantifier === value) } className="form-check-input" name="wiredConditionVariableAgeQuantifier" type="radio" onChange={ () => setQuantifier(value) } />
<Text>{ LocalizeText(`wiredfurni.params.quantifier.variables.${ value }`) }</Text>
</label>
)) }
</div>
<WiredFurniSelectionSourceRow
title={ getSourceTitle() }
titleIsLiteral={ true }
options={ sourceOptions }
value={ sourceValue }
selectionKind="primary"
selectionActive={ requiresFurni > WiredFurniType.STUFF_SELECTION_OPTION_NONE }
selectionCount={ furniIds.length }
selectionLimit={ selectionLimit }
selectionEnabledValues={ [ SOURCE_SELECTED ] }
showSelectionToggle={ false }
onChange={ value =>
{
if(targetType === 'furni')
{
setFurniSource(value);
return;
}
if(targetType === 'user')
{
setUserSource(value);
}
} } />
</div>
) }>
<div className="nitro-wired__give-var">
<div className="nitro-wired__give-var-heading">
<Text>{ LocalizeText('wiredfurni.params.variables.variable_selection') }</Text>
<div className="nitro-wired__give-var-targets">
{ TARGET_BUTTONS.map(button => (
<button
key={ button.key }
type="button"
disabled={ button.disabled }
className={ `nitro-wired__give-var-target nitro-wired__give-var-target--${ button.key } ${ targetType === button.key ? 'is-active' : '' }` }
onClick={ () => handleTargetChange(button.key) }>
<img src={ button.icon } alt={ button.key } />
</button>
)) }
</div>
</div>
<WiredVariablePicker
entries={ resolvedVariableEntries }
recentScope="variable-conditions"
selectedToken={ variableToken }
onSelect={ entry => setVariableToken(entry.token) } />
<div className="nitro-wired__divider" />
<div className="nitro-wired__give-var-section">
<div className="nitro-wired__give-var-section-title">{ LocalizeText('wiredfurni.params.variables.compare_value') }</div>
{ COMPARE_VALUE_OPTIONS.map(value => (
<label key={ value } className="flex items-center gap-1">
<input
checked={ compareValue === value }
className="form-check-input"
disabled={ (targetType === 'global') && (value === COMPARE_VALUE_CREATED) }
name="wiredConditionVariableAgeCompareValue"
type="radio"
onChange={ () => setCompareValue(value) } />
<Text>{ LocalizeText(`wiredfurni.params.variables.compare_value.${ value }`) }</Text>
</label>
)) }
</div>
<div className="nitro-wired__divider" />
<div className="nitro-wired__give-var-section">
<div className="nitro-wired__give-var-section-title">{ LocalizeText('wiredfurni.params.choose_type') }</div>
{ COMPARISON_OPTIONS.map(value => (
<label key={ value } className="flex items-center gap-1">
<input checked={ comparison === value } className="form-check-input" name="wiredConditionVariableAgeComparison" type="radio" onChange={ () => setComparison(value) } />
<Text>{ LocalizeText(`wiredfurni.params.comparison.${ value }`) }</Text>
</label>
)) }
</div>
<div className="nitro-wired__divider" />
<div className="nitro-wired__give-var-section">
<div className="nitro-wired__give-var-section-title">{ LocalizeText('wiredfurni.params.variables.time_selection') }</div>
<div className="flex items-center gap-2">
<Text>{ LocalizeText('wiredfurni.params.variables.duration') }</Text>
<NitroInput className="nitro-wired__give-var-number" type="number" value={ durationInput } onChange={ event => setDurationInput(event.target.value) } />
<select
className="min-w-0 flex-1 rounded border border-[#b8b2a4] bg-white px-2 py-[3px] text-[12px]"
value={ durationUnit }
onChange={ event => setDurationUnit(Number(event.target.value)) }>
{ DURATION_UNITS.map(unit => (
<option key={ unit } value={ unit }>
{ LocalizeText(`wiredfurni.params.variables.duration.${ unit }`) }
</option>
)) }
</select>
</div>
</div>
</div>
</WiredConditionBaseView>
);
};