5180 fichiers 38.5 Mo 0 téléchargements 355 vues mis à jour il y a 7 heures ui
nitro-v36-with-renderer-v36 / src / components / wired / views / actions / WiredActionSendLinkDataframeView.tsx
WiredActionSendLinkDataframeView.tsx 18.6 Ko Télécharger le fichier
import { FC, useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { localizeWithFallback, WiredFurniType, WiredSelectionVisualizer } from '../../../../api';
import {
    emptySendRow,
    ILinkSendRow,
    LINK_DATAFRAME_MAX_KEY_LENGTH,
    LINK_DATAFRAME_MAX_MESSAGE_BYTES,
    LINK_DATAFRAME_MAX_ROWS,
    LINK_SEND_MESSAGE,
    LINK_SEND_NUMBER,
    LINK_SEND_VARIABLE_TEXT,
    LINK_SEND_VARIABLE_VALUE,
    LINK_TARGET_CONTEXT,
    LINK_TARGET_FURNI,
    LINK_TARGET_GLOBAL,
    LINK_TARGET_USER,
    linkSendError,
    readLinkSend,
    writeLinkSend
} from '../../../../api/wired/WiredLinkDataframe';
import { Text } from '../../../../common';
import { useNotification, useWired, useWiredTools } from '../../../../hooks';
import { WiredFurniSelectionSourceRow } from '../WiredFurniSelectionSourceRow';
import { WiredSourcesSelector } from '../WiredSourcesSelector';
import { WiredVariablePicker } from '../WiredVariablePicker';
import { buildWiredVariablePickerEntries, IWiredVariablePickerEntry, normalizeVariableTokenFromWire, WiredVariablePickerTarget } from '../WiredVariablePickerData';
import { WiredActionBaseView } from './WiredActionBaseView';
import { Checkbox } from '../../../../common/Checkbox';
import { NumberInput } from '../../../../common/NumberInput';
import { Select } from '../../../../common/Select';
import { NativeTextTree } from '../../../../common/native-text/NativeTextTree';

const LINK_SOURCES = [
    { value: 100, label: 'wiredfurni.params.sources.furni.100' },
    { value: 200, label: 'wiredfurni.params.sources.furni.200' }
];
const ROW_FURNI_SOURCES = [
    { value: 0, label: 'wiredfurni.params.sources.furni.0' },
    { value: 101, label: 'wiredfurni.params.sources.furni.101' },
    { value: 200, label: 'wiredfurni.params.sources.furni.200' },
    { value: 201, label: 'wiredfurni.params.sources.furni.201' }
];
const ROW_USER_SOURCES = [
    { value: 0, label: 'wiredfurni.params.sources.users.0' },
    { value: 200, label: 'wiredfurni.params.sources.users.200' },
    { value: 201, label: 'wiredfurni.params.sources.users.201' }
];
const TARGETS: { value: number; picker: WiredVariablePickerTarget; key: string }[] = [
    { value: LINK_TARGET_USER, picker: 'user', key: 'wiredfurni.params.variables.target.user' },
    { value: LINK_TARGET_FURNI, picker: 'furni', key: 'wiredfurni.params.variables.target.furni' },
    { value: LINK_TARGET_GLOBAL, picker: 'global', key: 'wiredfurni.params.variables.target.global' },
    { value: LINK_TARGET_CONTEXT, picker: 'context', key: 'wiredfurni.params.variables.target.context' }
];

/** Row furni source 101: the second pick list (stuffIds2), as AIR's "other picked furni". */
const SOURCE_OTHER_PICKS = 101;

type PickList = 'endpoints' | 'other';

interface IRowState extends ILinkSendRow
{
    furniSource: number;
    userSource: number;
}

/**
 * Official send link dataframe (64, AIR SendLinkDataframe / LinkDataframeSendPreset): up to 12 rows, each a key and a
 * value: a number (typed, or a variable's value), a variable's text, or a custom message. Variable rows read their
 * holders from their own furni and user source (AIR mergedSelections: furni slot i + 1, user slot i).
 */
export const WiredActionSendLinkDataframeView: FC<{}> = () =>
{
    const { trigger = null, furniIds = [], setFurniIds = null, setFurniIds2 = null, setIntParams = null, setStringParam = null, setVariableIds = null, setSourceTypes = null } = useWired();
    const { userVariableDefinitions = [], furniVariableDefinitions = [], roomVariableDefinitions = [], contextVariableDefinitions = [] } = useWiredTools();
    const { simpleAlert = null } = useNotification();
    const [ endpointSource, setEndpointSource ] = useState(100);
    const [ rows, setRows ] = useState<IRowState[]>([]);
    const [ invalid, setInvalid ] = useState(false);
    const [ endpointIds, setEndpointIds ] = useState<number[]>([]);
    const [ otherIds, setOtherIds ] = useState<number[]>([]);
    const [ pickList, setPickList ] = useState<PickList>('endpoints');
    const highlightedIds = useRef<number[]>([]);

    useEffect(() =>
    {
        if(!trigger) return;

        const read = readLinkSend(trigger.intData ?? [], trigger.stringData ?? '', trigger.variableIds ?? []);

        setInvalid(read === null);
        setEndpointSource((trigger.furniSourceTypes?.[0] === 200) ? 200 : 100);
        setRows((read ?? []).map((row, index) => ({
            ...row,
            variableId: normalizeVariableTokenFromWire(row.variableId),
            furniSource: trigger.furniSourceTypes?.[index + 1] ?? 0,
            userSource: trigger.userSourceTypes?.[index] ?? 0
        })));
        setEndpointIds(trigger.selectedItems ?? []);
        setOtherIds(trigger.selectedItems2 ? [ ...trigger.selectedItems2 ] : []);
        setPickList('endpoints');
    }, [ trigger ]);

    // A furni variable row on "other picked furni" reads the second pick list (AIR's dual furni picking).
    const usesOtherPicks = rows.some(row => (row.target === LINK_TARGET_FURNI) && (row.mode === LINK_SEND_VARIABLE_VALUE || row.mode === LINK_SEND_VARIABLE_TEXT) && (row.furniSource === SOURCE_OTHER_PICKS));

    // The picking in the room edits the active list.
    useEffect(() =>
    {
        if(pickList === 'endpoints') setEndpointIds(furniIds);
        else setOtherIds(furniIds);
    }, [ furniIds, pickList ]);

    useEffect(() =>
    {
        highlightedIds.current = WiredSelectionVisualizer.showPickLists(highlightedIds.current, endpointIds, usesOtherPicks ? otherIds : []);
    }, [ endpointIds, otherIds, usesOtherPicks ]);

    useEffect(() =>
    {
        WiredSelectionVisualizer.setEditingPickList((pickList === 'other') ? 2 : 1);
    }, [ pickList ]);

    useEffect(() => () =>
    {
        WiredSelectionVisualizer.setEditingPickList(1);

        if(!highlightedIds.current.length) return;

        WiredSelectionVisualizer.clearSelectionShaderFromFurni(highlightedIds.current);
        WiredSelectionVisualizer.clearSecondarySelectionShaderFromFurni(highlightedIds.current);
        highlightedIds.current = [];
    }, []);

    const switchPickList = useCallback((next: PickList) =>
    {
        if(next === pickList) return;
        if(next === 'other' && !usesOtherPicks) return;

        const nextEndpointIds = (pickList === 'endpoints') ? [ ...furniIds ] : [ ...endpointIds ];
        const nextOtherIds = (pickList === 'other') ? [ ...furniIds ] : [ ...otherIds ];

        setEndpointIds(nextEndpointIds);
        setOtherIds(nextOtherIds);
        setPickList(next);
        setFurniIds?.([ ...((next === 'endpoints') ? nextEndpointIds : nextOtherIds) ]);
    }, [ pickList, usesOtherPicks, furniIds, endpointIds, otherIds, setFurniIds ]);

    // No row reads the second list any more: go back to picking the endpoints.
    useEffect(() =>
    {
        if(!usesOtherPicks && pickList === 'other') switchPickList('endpoints');
    }, [ usesOtherPicks, pickList, switchPickList ]);

    const entriesByTarget = useMemo(() => ({
        user: buildWiredVariablePickerEntries('user', 'change-reference', userVariableDefinitions),
        furni: buildWiredVariablePickerEntries('furni', 'change-reference', furniVariableDefinitions),
        global: buildWiredVariablePickerEntries('global', 'change-reference', roomVariableDefinitions),
        context: buildWiredVariablePickerEntries('context', 'change-reference', contextVariableDefinitions)
    }), [ userVariableDefinitions, furniVariableDefinitions, roomVariableDefinitions, contextVariableDefinitions ]);

    const setCount = (count: number) => setRows(current =>
    {
        const next = current.slice(0, count);

        while(next.length < count) next.push({ ...emptySendRow(), furniSource: 0, userSource: 0 });

        return next;
    });

    const updateRow = (index: number, patch: Partial<IRowState>) => setRows(current => current.map((row, i) => ((i === index) ? { ...row, ...patch } : row)));

    const validate = () =>
    {
        const error = linkSendError(rows);

        if(!error) return true;

        const message = localizeWithFallback(error.key, error.key);

        simpleAlert?.(error.row ? localizeWithFallback('wiredfurni.params.link.row_error', `Row ${ error.row }: ${ message }`, [ 'number', 'error' ], [ String(error.row), message ]) : message);

        return false;
    };

    const save = () =>
    {
        const nextEndpointIds = (pickList === 'endpoints') ? [ ...furniIds ] : [ ...endpointIds ];
        const nextOtherIds = (pickList === 'other') ? [ ...furniIds ] : [ ...otherIds ];

        // The first list is what the box saves as its picks.
        if(pickList === 'other')
        {
            setPickList('endpoints');
            setFurniIds?.(nextEndpointIds);
        }

        const stored = writeLinkSend(rows);
        const furni = [ endpointSource ];
        const users: number[] = [];

        for(let i = 0; i < LINK_DATAFRAME_MAX_ROWS; i++)
        {
            furni.push(rows[i]?.furniSource ?? 0);
            users.push(rows[i]?.userSource ?? 0);
        }

        setIntParams(stored.intParams);
        setStringParam(stored.stringParam);
        setVariableIds(stored.variableIds);
        setSourceTypes({ furni, users });
        setFurniIds2?.(usesOtherPicks ? nextOtherIds.filter(id => (id > 0)) : []);
    };

    const selectionLimit = trigger?.maximumItemSelectionCount ?? 0;

    // The dropdown offers number, variable text and message; "number" is a typed value or a variable's value.
    const modeChoice = (row: IRowState) => ((row.mode === LINK_SEND_VARIABLE_VALUE) ? LINK_SEND_NUMBER : row.mode);

    return (
        <NativeTextTree>
            <WiredActionBaseView
                hasSpecialInput={ true }
                requiresFurni={ WiredFurniType.STUFF_SELECTION_OPTION_BY_ID }
                save={ save }
                validate={ validate }
                widthModifier={ 1.4 }
                allowScrolling
                selectionPreview={
                    <div className="flex flex-col gap-2">
                        <WiredFurniSelectionSourceRow
                            title={ localizeWithFallback('wiredfurni.params.link.endpoints', 'WIRED Links') }
                            titleIsLiteral={ true }
                            options={ [ { value: 100, label: 'wiredfurni.params.sources.furni.100' } ] }
                            value={ 100 }
                            selectionKind="primary"
                            selectionActive={ pickList === 'endpoints' }
                            selectionCount={ endpointIds.length }
                            selectionLimit={ selectionLimit }
                            selectionEnabledValues={ [ 100 ] }
                            showSelectionToggle={ usesOtherPicks }
                            onChange={ () =>
                            {} }
                            onSelectionActivate={ () => switchPickList('endpoints') } />
                        { usesOtherPicks && (
                            <WiredFurniSelectionSourceRow
                                title={ localizeWithFallback('wiredfurni.params.link.source_furni', 'Variable source furni') }
                                titleIsLiteral={ true }
                                options={ [ { value: SOURCE_OTHER_PICKS, label: 'wiredfurni.params.sources.furni.101' } ] }
                                value={ SOURCE_OTHER_PICKS }
                                selectionKind="secondary"
                                selectionActive={ pickList === 'other' }
                                selectionCount={ otherIds.length }
                                selectionLimit={ selectionLimit }
                                selectionEnabledValues={ [ SOURCE_OTHER_PICKS ] }
                                onChange={ () =>
                                {} }
                                onSelectionActivate={ () => switchPickList('other') } />
                        ) }
                    </div>
                }
                footer={ <WiredSourcesSelector showFurni={ true } furniSource={ endpointSource } furniSources={ LINK_SOURCES } furniTitle={ localizeWithFallback('wiredfurni.params.link.endpoints', 'WIRED Links') } onChangeFurni={ setEndpointSource } /> }
            >
                { invalid && <Text small className="text-[#ad3434]">{ localizeWithFallback('wiredfurni.error.link.invalid_dataframe', 'The dataframe configuration is invalid.') }</Text> }
                <div className="flex items-center justify-between gap-2">
                    <Text bold>{ localizeWithFallback('wiredfurni.params.link.row_count', 'Row count') }</Text>
                    <Select className="form-select form-select-sm w-[70px]" value={ rows.length } onChange={ event => setCount(parseInt(event.target.value, 10)) }>
                        { Array.from({ length: LINK_DATAFRAME_MAX_ROWS + 1 }, (_, count) => <option key={ count } value={ count }>{ count }</option>) }
                    </Select>
                </div>
                <div className="flex max-h-[55vh] flex-col gap-2 overflow-y-auto">
                    { rows.map((row, index) =>
                    {
                        const target = TARGETS.find(entry => (entry.value === row.target)) ?? TARGETS[0];
                        const usesVariable = (row.mode === LINK_SEND_VARIABLE_VALUE) || (row.mode === LINK_SEND_VARIABLE_TEXT);

                        return (
                            <div key={ index } className="flex flex-col gap-1 rounded border border-[#d6d1c4] p-1">
                                <Text bold small>{ localizeWithFallback('wiredfurni.params.link.row', `Row ${ index + 1 }`, [ 'number' ], [ String(index + 1) ]) }</Text>
                                <div className="flex items-center gap-1">
                                    <Text small>{ localizeWithFallback('wiredfurni.params.link.key', 'Key') }</Text>
                                    <input className="form-control form-control-sm grow" maxLength={ LINK_DATAFRAME_MAX_KEY_LENGTH } type="text" value={ row.key } onChange={ event => updateRow(index, { key: event.target.value }) } />
                                    <Select className="form-select form-select-sm w-[120px]" value={ modeChoice(row) } onChange={ event => updateRow(index, { mode: parseInt(event.target.value, 10) }) }>
                                        <option value={ LINK_SEND_NUMBER }>{ localizeWithFallback('wiredfurni.params.link.source.number', 'Number') }</option>
                                        <option value={ LINK_SEND_VARIABLE_TEXT }>{ localizeWithFallback('wiredfurni.params.link.source.variable_text', 'Variable text') }</option>
                                        <option value={ LINK_SEND_MESSAGE }>{ localizeWithFallback('wiredfurni.params.link.source.message', 'Custom message') }</option>
                                    </Select>
                                </div>
                                { (row.mode === LINK_SEND_MESSAGE) && (
                                    <div className="flex items-center gap-1">
                                        <Text small>{ localizeWithFallback('wiredfurni.params.link.message', 'Message:') }</Text>
                                        <input className="form-control form-control-sm grow" maxLength={ LINK_DATAFRAME_MAX_MESSAGE_BYTES } type="text" value={ row.text } onChange={ event => updateRow(index, { text: event.target.value }) } />
                                    </div>
                                ) }
                                { (row.mode !== LINK_SEND_MESSAGE) && (
                                    <div className="flex flex-col gap-1">
                                        { (row.mode !== LINK_SEND_VARIABLE_TEXT) && (
                                            <label className="flex cursor-pointer items-center gap-1">
                                                <Checkbox checked={ row.mode === LINK_SEND_VARIABLE_VALUE } onCheckedChange={ nextChecked => updateRow(index, { mode: nextChecked ? LINK_SEND_VARIABLE_VALUE : LINK_SEND_NUMBER }) } />
                                                <Text small>{ localizeWithFallback('wiredfurni.params.link.source.variable_value', 'Variable value') }</Text>
                                            </label>
                                        ) }
                                        { !usesVariable && (
                                            <div className="flex items-center gap-1">
                                                <Text small>{ localizeWithFallback('wiredfurni.params.link.value', 'Value') }</Text>
                                                <NumberInput className="form-control form-control-sm grow" value={ row.literal } onChange={ event => updateRow(index, { literal: Math.max(-2147483648, Math.min(2147483647, parseInt(event.target.value || '0', 10) || 0)) }) } />
                                            </div>
                                        ) }
                                        { usesVariable && (
                                            <>
                                                <Select className="form-select form-select-sm" value={ row.target } onChange={ event => updateRow(index, { target: parseInt(event.target.value, 10), variableId: '' }) }>
                                                    { TARGETS.map(entry => <option key={ entry.value } value={ entry.value }>{ localizeWithFallback(entry.key, entry.picker) }</option>) }
                                                </Select>
                                                <WiredVariablePicker entries={ entriesByTarget[target.picker] as IWiredVariablePickerEntry[] } recentScope="link-dataframe-send" selectedToken={ row.variableId } onSelect={ entry => updateRow(index, { variableId: entry.token }) } onClear={ () => updateRow(index, { variableId: '' }) } />
                                                { (row.target === LINK_TARGET_FURNI) && <WiredSourcesSelector showFurni={ true } furniSource={ row.furniSource } furniSources={ ROW_FURNI_SOURCES } furniTitle={ localizeWithFallback('wiredfurni.params.link.source_furni', 'Variable source furni') } onChangeFurni={ value => updateRow(index, { furniSource: value }) } /> }
                                                { (row.target === LINK_TARGET_USER) && <WiredSourcesSelector showUsers={ true } userSource={ row.userSource } userSources={ ROW_USER_SOURCES } usersTitle={ localizeWithFallback('wiredfurni.params.link.source_users', 'Variable source users') } onChangeUsers={ value => updateRow(index, { userSource: value }) } /> }
                                            </>
                                        ) }
                                    </div>
                                ) }
                            </div>
                        );
                    }) }
                </div>
            </WiredActionBaseView>
        </NativeTextTree>
    );
};
15Sujets
1Dépôts
20Membres
3En ligne
Profile
Inscrit
Dernière visite
Messages
0
Votes positifs
0
Votes négatifs
0

Nous utilisons des cookies pour assurer le bon fonctionnement du site et améliorer votre expérience. en savoir plus

Autoriser