feat: add advanced wired variable tools UI

This commit is contained in:
Lorenzune
2026-04-02 04:44:04 +02:00
parent 0a23bfaee4
commit 83540ff329
69 changed files with 10040 additions and 434 deletions
+1 -22
View File
@@ -1,9 +1,7 @@
{ {
"friendlist.search": "Zoek vrienden", "friendlist.search": "Zoek vrienden",
"widget.chooser.checkall": "Selecteer meubels", "widget.chooser.checkall": "Selecteer meubels",
"widget.chooser.btn.pickall": "pak de geselecteerde items op!", "widget.chooser.btn.pickall": "pak de geselecteerde items op!",
"wiredfurni.params.requireall.2": "Als een van de geselecteerde furni een avatar heeft",
"wiredfurni.params.requireall.3": "Als alle geselecteerde furni avatars op hen hebben",
"widget.settings.general": "Standaard", "widget.settings.general": "Standaard",
"widget.settings.general.title": "Pas de standaard nitro settings aan", "widget.settings.general.title": "Pas de standaard nitro settings aan",
"widget.settings.volume": "Volume", "widget.settings.volume": "Volume",
@@ -20,25 +18,6 @@
"widget.room.chat.clear_history": "leeg geschiedenis", "widget.room.chat.clear_history": "leeg geschiedenis",
"widget.room.youtube.shared": "YouTube word gedeeld", "widget.room.youtube.shared": "YouTube word gedeeld",
"widget.room.youtube.open_video": "Open de video", "widget.room.youtube.open_video": "Open de video",
"wiredfurni.params.area_selection.selected": "Geselecteerd gebied: Lengte=%x%, Breedte=%y%, breedte=%w%, hoogte=%h%",
"wiredfurni.params.sources.users.11": "L'utente cliccato",
"wiredfurni.params.setexecutions": "Quantità di esecuzioni: %amount%",
"wiredfurni.params.settimewindow": "Tempo massimo consentito: %timewindow% secondi",
"wiredfurni.params.eval_mode": "Condizioni che devono corrispondere:",
"wiredfurni.params.eval_mode.0": "Tutte",
"wiredfurni.params.eval_mode.1": "Almeno una",
"wiredfurni.params.eval_mode.2": "Non tutte",
"wiredfurni.params.eval_mode.3": "Nessuna",
"wiredfurni.params.eval_mode.cmp.0": "Meno di:",
"wiredfurni.params.eval_mode.cmp.1": "Esattamente:",
"wiredfurni.params.eval_mode.cmp.2": "Più di:",
"wiredfurni.error.condition_evaluation_furni": "Puoi selezionare solo la Condizione e i componenti aggiuntivi!",
"wiredfurni.params.texts.placeholder_name": "Nome del segnaposto:",
"wiredfurni.params.texts.placeholder_preview": "Usalo digitando <font color=\"#ffffaa\">%placeholder%</font> nei testi dei Wired.",
"wiredfurni.params.texts.placeholder_type": "Tipo di segnaposto:",
"wiredfurni.params.texts.placeholder_type.1": "Singolo",
"wiredfurni.params.texts.placeholder_type.2": "Multiplo",
"wiredfurni.params.texts.select_delimiter": "Seleziona il delimitatore:",
"widget.memenu.dance1": "Ballo 1", "widget.memenu.dance1": "Ballo 1",
"widget.memenu.dance2": "Ballo 2", "widget.memenu.dance2": "Ballo 2",
"widget.memenu.dance3": "Ballo 3", "widget.memenu.dance3": "Ballo 3",
+11 -11
View File
@@ -1,22 +1,22 @@
{ {
"socket.url": "ws://192.168.1.52:2096", "socket.url": "ws://192.168.1.52:2096",
"asset.url": "https://client.paxxo.online/nitro/bundled", "asset.url": "https://client.slogga.it/nitro/bundled",
"image.library.url": "https://client.paxxo.online/c_images/", "image.library.url": "https://client.slogga.it/c_images/",
"hof.furni.url": "https://client.paxxo.online/c_images/dcr/hof_furni", "hof.furni.url": "https://client.slogga.it/c_images/dcr/hof_furni",
"images.url": "https://client.paxxo.online/nitro/images", "images.url": "https://client.slogga.it/nitro/images",
"gamedata.url": "https://client.paxxo.online/nitro/gamedata", "gamedata.url": "https://client.slogga.it/nitro/gamedata",
"sounds.url": "${asset.url}/sounds/%sample%.mp3", "sounds.url": "${asset.url}/sounds/%sample%.mp3",
"external.texts.url": [ "external.texts.url": [
"${gamedata.url}/ExternalTexts.json", "${gamedata.url}/ExternalTexts.json",
"${gamedata.url}/UITexts.json" "${gamedata.url}/UITexts.json"
], ],
"external.samples.url": "${hof.furni.url}/mp3/sound_machine_sample_%sample%.mp3", "external.samples.url": "${hof.furni.url}/mp3/sound_machine_sample_%sample%.mp3",
"furnidata.url": "${gamedata.url}/FurnitureData.json?v=1", "furnidata.url": "${gamedata.url}/FurnitureData.json?v=2",
"productdata.url": "${gamedata.url}/ProductData.json?v=1", "productdata.url": "${gamedata.url}/ProductData.json?v=2",
"avatar.actions.url": "${gamedata.url}/HabboAvatarActions.json?v=1", "avatar.actions.url": "${gamedata.url}/HabboAvatarActions.json?v=2",
"avatar.figuredata.url": "${gamedata.url}/FigureData.json?v=1", "avatar.figuredata.url": "${gamedata.url}/FigureData.json?v=2",
"avatar.figuremap.url": "${gamedata.url}/FigureMap.json?v=1", "avatar.figuremap.url": "${gamedata.url}/FigureMap.json?v=2",
"avatar.effectmap.url": "${gamedata.url}/EffectMap.json?v=1", "avatar.effectmap.url": "${gamedata.url}/EffectMap.json?v=2",
"avatar.asset.url": "${asset.url}/figure/%libname%.nitro", "avatar.asset.url": "${asset.url}/figure/%libname%.nitro",
"avatar.asset.effect.url": "${asset.url}/effect/%libname%.nitro", "avatar.asset.effect.url": "${asset.url}/effect/%libname%.nitro",
"furni.asset.url": "${asset.url}/furniture/%libname%.nitro", "furni.asset.url": "${asset.url}/furniture/%libname%.nitro",
+17
View File
@@ -67,4 +67,21 @@ export class WiredActionLayoutCode
public static OR_EVAL_EXTRA: number = 66; public static OR_EVAL_EXTRA: number = 66;
public static TEXT_OUTPUT_USERNAME_EXTRA: number = 67; public static TEXT_OUTPUT_USERNAME_EXTRA: number = 67;
public static TEXT_OUTPUT_FURNI_NAME_EXTRA: number = 68; public static TEXT_OUTPUT_FURNI_NAME_EXTRA: number = 68;
public static GIVE_VARIABLE: number = 69;
public static USER_VARIABLE_EXTRA: number = 70;
public static FURNI_VARIABLE_EXTRA: number = 71;
public static ROOM_VARIABLE_EXTRA: number = 72;
public static REMOVE_VARIABLE: number = 73;
public static CHANGE_VARIABLE_VALUE: number = 74;
public static FURNI_WITH_VARIABLE_SELECTOR: number = 75;
public static USERS_WITH_VARIABLE_SELECTOR: number = 76;
public static FILTER_USERS_BY_VARIABLE_EXTRA: number = 77;
public static FILTER_FURNI_BY_VARIABLE_EXTRA: number = 78;
public static VARIABLE_TEXT_CONNECTOR_EXTRA: number = 79;
public static TEXT_OUTPUT_VARIABLE_EXTRA: number = 80;
public static VARIABLE_REFERENCE_EXTRA: number = 81;
public static VARIABLE_LEVELUP_SYSTEM_EXTRA: number = 82;
public static VARIABLE_ECHO_EXTRA: number = 83;
public static CONTEXT_VARIABLE_EXTRA: number = 84;
public static TEXT_INPUT_VARIABLE_EXTRA: number = 85;
} }
@@ -40,4 +40,8 @@ export class WiredConditionlayout
public static MATCH_DATE: number = 37; public static MATCH_DATE: number = 37;
public static ACTOR_DIR: number = 38; public static ACTOR_DIR: number = 38;
public static SLC_QUANTITY: number = 39; public static SLC_QUANTITY: number = 39;
public static HAS_VAR: number = 40;
public static NEG_HAS_VAR: number = 41;
public static VAR_VAL_MATCH: number = 42;
public static VAR_AGE_MATCH: number = 43;
} }
+1
View File
@@ -22,4 +22,5 @@ export class WiredTriggerLayout
public static CLICK_USER: number = 20; public static CLICK_USER: number = 20;
public static USER_PERFORMS_ACTION: number = 21; public static USER_PERFORMS_ACTION: number = 21;
public static CLOCK_COUNTER: number = 22; public static CLOCK_COUNTER: number = 22;
public static VARIABLE_CHANGED: number = 23;
} }
Binary file not shown.

After

Width:  |  Height:  |  Size: 177 B

@@ -0,0 +1,37 @@
<!DOCTYPE html>
<html lang="it">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Tailwind Bubble Component</title>
<script src="https://cdn.tailwindcss.com"></script>
</head>
<body class="bg-[#a5ac81] flex items-center justify-center min-h-screen">
<div class="relative group">
<div class="relative z-10 flex h-14 min-w-[65px] items-center justify-center
rounded-[14px] border-[3px] border-black bg-[#86aebc]
px-4 text-2xl font-sans font-medium text-white
shadow-[inset_0_0_0_2px_#b1d4e0] select-none">
0
</div>
<div class="absolute left-1/2 -bottom-[14px] z-0 -translate-x-1/2
w-0 h-0
border-l-[12px] border-l-transparent
border-r-[12px] border-r-transparent
border-t-[15px] border-t-black">
</div>
<div class="absolute left-1/2 -bottom-[8px] z-20 -translate-x-1/2
w-0 h-0
border-l-[9px] border-l-transparent
border-r-[9px] border-r-transparent
border-t-[12px] border-t-[#86aebc]">
</div>
</div>
</body>
</html>
Binary file not shown.

After

Width:  |  Height:  |  Size: 208 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 236 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 193 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 181 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 218 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 107 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 153 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 146 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 153 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 141 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 182 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 208 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 323 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

@@ -4,7 +4,7 @@ import { FaCrosshairs, FaRulerVertical, FaTimes } from 'react-icons/fa';
import { GrFormNextLink, GrRotateLeft, GrRotateRight } from 'react-icons/gr'; import { GrFormNextLink, GrRotateLeft, GrRotateRight } from 'react-icons/gr';
import { AvatarInfoFurni, GetGroupInformation, LocalizeText, SendMessageComposer } from '../../../../../api'; import { AvatarInfoFurni, GetGroupInformation, LocalizeText, SendMessageComposer } from '../../../../../api';
import { Button, Column, Flex, LayoutBadgeImageView, LayoutLimitedEditionCompactPlateView, LayoutRarityLevelView, LayoutRoomObjectImageView, Text, UserProfileIconView } from '../../../../../common'; import { Button, Column, Flex, LayoutBadgeImageView, LayoutLimitedEditionCompactPlateView, LayoutRarityLevelView, LayoutRoomObjectImageView, Text, UserProfileIconView } from '../../../../../common';
import { useMessageEvent, useNitroEvent, useRoom } from '../../../../../hooks'; import { useMessageEvent, useNitroEvent, useRoom, useWiredTools } from '../../../../../hooks';
import { NitroInput } from '../../../../../layout'; import { NitroInput } from '../../../../../layout';
interface InfoStandWidgetFurniViewProps interface InfoStandWidgetFurniViewProps
@@ -21,6 +21,7 @@ export const InfoStandWidgetFurniView: FC<InfoStandWidgetFurniViewProps> = props
{ {
const { avatarInfo = null, onClose = null } = props; const { avatarInfo = null, onClose = null } = props;
const { roomSession = null } = useRoom(); const { roomSession = null } = useRoom();
const { openInspectionForFurni, showInspectButton } = useWiredTools();
const [ pickupMode, setPickupMode ] = useState(0); const [ pickupMode, setPickupMode ] = useState(0);
const [ canMove, setCanMove ] = useState(false); const [ canMove, setCanMove ] = useState(false);
@@ -698,6 +699,10 @@ export const InfoStandWidgetFurniView: FC<InfoStandWidgetFurniViewProps> = props
</Column> </Column>
</Column> </Column>
<Flex gap={ 1 } justifyContent="end"> <Flex gap={ 1 } justifyContent="end">
{ showInspectButton &&
<Button variant="dark" onClick={ () => openInspectionForFurni(avatarInfo.id, avatarInfo.category) }>
Inspect
</Button> }
{ canMove && { canMove &&
<Button variant="dark" onClick={ event => processButtonAction('move') }> <Button variant="dark" onClick={ event => processButtonAction('move') }>
{ LocalizeText('infostand.button.move') } { LocalizeText('infostand.button.move') }
@@ -3,7 +3,7 @@ import { FC, useEffect, useMemo, useState } from 'react';
import { FaChevronLeft, FaChevronRight } from 'react-icons/fa'; import { FaChevronLeft, FaChevronRight } from 'react-icons/fa';
import { AvatarInfoUser, DispatchUiEvent, GetOwnRoomObject, GetUserProfile, LocalizeText, MessengerFriend, ReportType, RoomWidgetUpdateChatInputContentEvent, SendMessageComposer } from '../../../../../api'; import { AvatarInfoUser, DispatchUiEvent, GetOwnRoomObject, GetUserProfile, LocalizeText, MessengerFriend, ReportType, RoomWidgetUpdateChatInputContentEvent, SendMessageComposer } from '../../../../../api';
import { Flex } from '../../../../../common'; import { Flex } from '../../../../../common';
import { useFriends, useHelp, useRoom, useSessionInfo } from '../../../../../hooks'; import { useFriends, useHelp, useRoom, useSessionInfo, useWiredTools } from '../../../../../hooks';
import { ContextMenuHeaderView } from '../../context-menu/ContextMenuHeaderView'; import { ContextMenuHeaderView } from '../../context-menu/ContextMenuHeaderView';
import { ContextMenuListItemView } from '../../context-menu/ContextMenuListItemView'; import { ContextMenuListItemView } from '../../context-menu/ContextMenuListItemView';
import { ContextMenuView } from '../../context-menu/ContextMenuView'; import { ContextMenuView } from '../../context-menu/ContextMenuView';
@@ -30,6 +30,7 @@ export const AvatarInfoWidgetAvatarView: FC<AvatarInfoWidgetAvatarViewProps> = p
const { report = null } = useHelp(); const { report = null } = useHelp();
const { roomSession = null } = useRoom(); const { roomSession = null } = useRoom();
const { userRespectRemaining = 0, respectUser = null } = useSessionInfo(); const { userRespectRemaining = 0, respectUser = null } = useSessionInfo();
const { openInspectionForUser, showInspectButton } = useWiredTools();
const isShowGiveRights = useMemo(() => const isShowGiveRights = useMemo(() =>
{ {
@@ -157,6 +158,9 @@ export const AvatarInfoWidgetAvatarView: FC<AvatarInfoWidgetAvatarViewProps> = p
case 'report': case 'report':
report(ReportType.BULLY, { reportedUserId: avatarInfo.webID }); report(ReportType.BULLY, { reportedUserId: avatarInfo.webID });
break; break;
case 'inspect':
openInspectionForUser(avatarInfo.roomIndex);
break;
case 'pass_hand_item': case 'pass_hand_item':
SendMessageComposer(new RoomUnitGiveHandItemComposer(avatarInfo.webID)); SendMessageComposer(new RoomUnitGiveHandItemComposer(avatarInfo.webID));
break; break;
@@ -236,6 +240,10 @@ export const AvatarInfoWidgetAvatarView: FC<AvatarInfoWidgetAvatarViewProps> = p
<ContextMenuListItemView onClick={ event => processAction('report') }> <ContextMenuListItemView onClick={ event => processAction('report') }>
{ LocalizeText('infostand.button.report') } { LocalizeText('infostand.button.report') }
</ContextMenuListItemView> </ContextMenuListItemView>
{ showInspectButton &&
<ContextMenuListItemView onClick={ event => processAction('inspect') }>
Inspect
</ContextMenuListItemView> }
{ moderateMenuHasContent && { moderateMenuHasContent &&
<ContextMenuListItemView onClick={ event => processAction('moderate') }> <ContextMenuListItemView onClick={ event => processAction('moderate') }>
<FaChevronRight className="right fa-icon" /> <FaChevronRight className="right fa-icon" />
@@ -4,7 +4,7 @@ import { FaChevronLeft, FaChevronRight } from 'react-icons/fa';
import { AvatarInfoUser, DispatchUiEvent, GetCanStandUp, GetCanUseExpression, GetOwnPosture, GetUserProfile, HasHabboClub, HasHabboVip, IsRidingHorse, LocalizeText, PostureTypeEnum, SendMessageComposer } from '../../../../../api'; import { AvatarInfoUser, DispatchUiEvent, GetCanStandUp, GetCanUseExpression, GetOwnPosture, GetUserProfile, HasHabboClub, HasHabboVip, IsRidingHorse, LocalizeText, PostureTypeEnum, SendMessageComposer } from '../../../../../api';
import { LayoutCurrencyIcon } from '../../../../../common'; import { LayoutCurrencyIcon } from '../../../../../common';
import { HelpNameChangeEvent } from '../../../../../events'; import { HelpNameChangeEvent } from '../../../../../events';
import { useRoom } from '../../../../../hooks'; import { useRoom, useWiredTools } from '../../../../../hooks';
import { ContextMenuHeaderView } from '../../context-menu/ContextMenuHeaderView'; import { ContextMenuHeaderView } from '../../context-menu/ContextMenuHeaderView';
import { ContextMenuListItemView } from '../../context-menu/ContextMenuListItemView'; import { ContextMenuListItemView } from '../../context-menu/ContextMenuListItemView';
import { ContextMenuView } from '../../context-menu/ContextMenuView'; import { ContextMenuView } from '../../context-menu/ContextMenuView';
@@ -28,6 +28,7 @@ export const AvatarInfoWidgetOwnAvatarView: FC<AvatarInfoWidgetOwnAvatarViewProp
const { avatarInfo = null, isDancing = false, setIsDecorating = null, onClose = null } = props; const { avatarInfo = null, isDancing = false, setIsDecorating = null, onClose = null } = props;
const [ mode, setMode ] = useState((isDancing && HasHabboClub()) ? MODE_CLUB_DANCES : MODE_NORMAL); const [ mode, setMode ] = useState((isDancing && HasHabboClub()) ? MODE_CLUB_DANCES : MODE_NORMAL);
const { roomSession = null } = useRoom(); const { roomSession = null } = useRoom();
const { openInspectionForUser, showInspectButton } = useWiredTools();
const processAction = (name: string) => const processAction = (name: string) =>
{ {
@@ -103,6 +104,9 @@ export const AvatarInfoWidgetOwnAvatarView: FC<AvatarInfoWidgetOwnAvatarViewProp
case 'drop_carry_item': case 'drop_carry_item':
SendMessageComposer(new RoomUnitDropHandItemComposer()); SendMessageComposer(new RoomUnitDropHandItemComposer());
break; break;
case 'inspect':
openInspectionForUser(avatarInfo.roomIndex);
break;
} }
} }
} }
@@ -158,6 +162,10 @@ export const AvatarInfoWidgetOwnAvatarView: FC<AvatarInfoWidgetOwnAvatarViewProp
<ContextMenuListItemView onClick={ event => processAction('drop_carry_item') }> <ContextMenuListItemView onClick={ event => processAction('drop_carry_item') }>
{ LocalizeText('avatar.widget.drop_hand_item') } { LocalizeText('avatar.widget.drop_hand_item') }
</ContextMenuListItemView> } </ContextMenuListItemView> }
{ showInspectButton &&
<ContextMenuListItemView onClick={ event => processAction('inspect') }>
Inspect
</ContextMenuListItemView> }
</> } </> }
{ (mode === MODE_CLUB_DANCES) && { (mode === MODE_CLUB_DANCES) &&
<> <>
+4 -1
View File
@@ -3,7 +3,7 @@ import { AnimatePresence, motion } from 'framer-motion';
import { FC, useState } from 'react'; import { FC, useState } from 'react';
import { GetConfigurationValue, MessengerIconState, OpenMessengerChat, VisitDesktop } from '../../api'; import { GetConfigurationValue, MessengerIconState, OpenMessengerChat, VisitDesktop } from '../../api';
import { Flex, LayoutAvatarImageView, LayoutItemCountView } from '../../common'; import { Flex, LayoutAvatarImageView, LayoutItemCountView } from '../../common';
import { useAchievements, useFriends, useInventoryUnseenTracker, useMessageEvent, useMessenger, useNitroEvent, useSessionInfo } from '../../hooks'; import { useAchievements, useFriends, useInventoryUnseenTracker, useMessageEvent, useMessenger, useNitroEvent, useSessionInfo, useWiredTools } from '../../hooks';
import { ToolbarItemView } from './ToolbarItemView'; import { ToolbarItemView } from './ToolbarItemView';
import { ToolbarMeView } from './ToolbarMeView'; import { ToolbarMeView } from './ToolbarMeView';
@@ -17,6 +17,7 @@ export const ToolbarView: FC<{ isInRoom: boolean }> = props =>
const { getTotalUnseen = 0 } = useAchievements(); const { getTotalUnseen = 0 } = useAchievements();
const { requests = [] } = useFriends(); const { requests = [] } = useFriends();
const { iconState = MessengerIconState.HIDDEN } = useMessenger(); const { iconState = MessengerIconState.HIDDEN } = useMessenger();
const { openMonitor, showToolbarButton } = useWiredTools();
const isMod = GetSessionDataManager().isModerator; const isMod = GetSessionDataManager().isModerator;
useMessageEvent<PerkAllowancesMessageEvent>(PerkAllowancesMessageEvent, event => useMessageEvent<PerkAllowancesMessageEvent>(PerkAllowancesMessageEvent, event =>
@@ -92,6 +93,8 @@ export const ToolbarView: FC<{ isInRoom: boolean }> = props =>
{ (getFullCount > 0) && { (getFullCount > 0) &&
<LayoutItemCountView count={ getFullCount } /> } <LayoutItemCountView count={ getFullCount } /> }
</ToolbarItemView> </ToolbarItemView>
{ (isInRoom && showToolbarButton) &&
<ToolbarItemView icon="wired-tools" onClick={ openMonitor } /> }
{ isInRoom && { isInRoom &&
<ToolbarItemView icon="camera" onClick={ event => CreateLinkEvent('camera/toggle') } /> } <ToolbarItemView icon="camera" onClick={ event => CreateLinkEvent('camera/toggle') } /> }
{ isMod && { isMod &&
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,157 @@
import { FC } from 'react';
import { LocalizeText } from '../../api';
import { Button, Text } from '../../common';
import { useNotification, useRoom, useWiredTools } from '../../hooks';
const WIRED_ACCESS_EVERYONE = 1;
const WIRED_ACCESS_USERS_WITH_RIGHTS = 2;
const WIRED_ACCESS_GROUP_MEMBERS = 4;
const WIRED_ACCESS_GROUP_ADMINS = 8;
interface RoomAccessOption
{
bit: number;
label: string;
}
const toggleMaskBit = (mask: number, bit: number): number => ((mask & bit) ? (mask & ~bit) : (mask | bit));
const normalizeAccessMask = (mask: number): number => (((mask & WIRED_ACCESS_GROUP_MEMBERS) !== 0) ? (mask | WIRED_ACCESS_GROUP_ADMINS) : mask);
const buildInspectOptions = (): RoomAccessOption[] => [
{ bit: WIRED_ACCESS_EVERYONE, label: 'Everyone' },
{ bit: WIRED_ACCESS_USERS_WITH_RIGHTS, label: 'Users with rights' },
{ bit: WIRED_ACCESS_GROUP_MEMBERS, label: 'Group members' },
{ bit: WIRED_ACCESS_GROUP_ADMINS, label: 'Group admins' }
];
const buildModifyOptions = (): RoomAccessOption[] => [
{ bit: WIRED_ACCESS_USERS_WITH_RIGHTS, label: 'Users with rights' },
{ bit: WIRED_ACCESS_GROUP_MEMBERS, label: 'Group members' },
{ bit: WIRED_ACCESS_GROUP_ADMINS, label: 'Group admins' }
];
export const WiredToolsSettingsTabView: FC<{}> = () =>
{
const { roomSession = null } = useRoom();
const { showConfirm = null } = useNotification();
const { accountPreferences, roomSettings, saveRoomSettings, updateAccountPreferences } = useWiredTools();
const canManageSettings = roomSettings.canManageSettings;
const canReloadRoom = !!roomSession?.isRoomOwner;
const inspectOptions = buildInspectOptions();
const modifyOptions = buildModifyOptions();
const serverTimeZone = roomSession?.hotelTimeZone || 'UTC';
const updateInspectMask = (bit: number) =>
{
if(!canManageSettings) return;
saveRoomSettings(normalizeAccessMask(toggleMaskBit(roomSettings.inspectMask, bit)), roomSettings.modifyMask);
};
const updateModifyMask = (bit: number) =>
{
if(!canManageSettings) return;
const nextModifyMask = toggleMaskBit(roomSettings.modifyMask, bit);
const enabledModifyBit = ((nextModifyMask & bit) !== 0);
const normalizedModifyMask = normalizeAccessMask(nextModifyMask);
const nextInspectMask = normalizeAccessMask(enabledModifyBit ? (roomSettings.inspectMask | bit) : roomSettings.inspectMask);
saveRoomSettings(nextInspectMask, normalizedModifyMask);
};
const renderAccessOption = (option: RoomAccessOption, mask: number, onToggle: (bit: number) => void) =>
{
const checked = ((mask & option.bit) !== 0);
const disabled = !roomSettings.isLoaded || !canManageSettings;
return (
<label key={ option.label } className={ `flex items-center gap-2 text-[12px] ${ disabled ? 'text-[#8c877d]' : 'text-[#222]' }` }>
<input
checked={ checked }
className="form-check-input mt-0"
disabled={ disabled }
type="checkbox"
onChange={ () => onToggle(option.bit) } />
<span>{ option.label }</span>
</label>
);
};
return (
<div className="p-3 min-h-[360px] flex flex-col gap-3">
<Text bold>Room settings:</Text>
<div className="grid grid-cols-2 gap-3">
<div className="rounded bg-[#dfddd7] p-3 flex flex-col gap-2">
<Text bold small>Who can modify Wired:</Text>
{ modifyOptions.map(option => renderAccessOption(option, roomSettings.modifyMask, updateModifyMask)) }
</div>
<div className="rounded bg-[#dfddd7] p-3 flex flex-col gap-2">
<Text bold small>Who can inspect Wired:</Text>
{ inspectOptions.map(option => renderAccessOption(option, roomSettings.inspectMask, updateInspectMask)) }
</div>
<div className="rounded bg-[#dfddd7] p-3 flex flex-col gap-2">
<Text bold small>Timezone:</Text>
<select
className="w-full rounded border border-[#9d998e] bg-[#f4f0e8] px-2 py-[6px] text-[12px] text-[#555]"
disabled
value={ serverTimeZone }>
<option value={ serverTimeZone }>{ serverTimeZone }</option>
</select>
</div>
<div className="rounded bg-[#dfddd7] p-3 flex flex-col gap-2">
<Text bold small>Room state:</Text>
<div className="flex gap-2">
<Button
classNames={ [ 'flex-1' ] }
disabled={ !canReloadRoom }
variant="secondary"
onClick={ () => showConfirm(
LocalizeText('wiredmenu.settings.room_state.reload.warning'),
() => roomSession?.sendChatMessage(':reload', 0, ''),
null,
LocalizeText('generic.ok'),
LocalizeText('generic.cancel'),
LocalizeText('generic.alert.title')) }>
Reload
</Button>
<Button classNames={ [ 'flex-1' ] } disabled variant="danger">
Rollback
</Button>
</div>
</div>
</div>
<div className="flex flex-col gap-2">
<Text bold>Account preferences:</Text>
<div className="rounded bg-[#dfddd7] p-3 flex flex-col gap-2">
<Text bold small>General:</Text>
<label className="flex items-center gap-2 text-[12px] text-[#222]">
<input
checked={ accountPreferences.showToolbarButton }
className="form-check-input mt-0"
type="checkbox"
onChange={ event => updateAccountPreferences({ showToolbarButton: event.target.checked }) } />
<span>Show wired menu in toolbar</span>
</label>
<label className="flex items-center gap-2 text-[12px] text-[#222]">
<input
checked={ accountPreferences.showInspectButton }
className="form-check-input mt-0"
type="checkbox"
onChange={ event => updateAccountPreferences({ showInspectButton: event.target.checked }) } />
<span>Furni/user inspect button</span>
</label>
<label className="flex items-center gap-2 text-[12px] text-[#222]">
<input
checked={ accountPreferences.showSystemNotifications }
className="form-check-input mt-0"
type="checkbox"
onChange={ event => updateAccountPreferences({ showSystemNotifications: event.target.checked }) } />
<span>Show all system notifications</span>
</label>
</div>
</div>
</div>
);
};
+22 -15
View File
@@ -2,7 +2,7 @@ import { GetRoomEngine, GetSessionDataManager } from '@nitrots/nitro-renderer';
import { CSSProperties, FC, PropsWithChildren, ReactNode, useEffect, useState } from 'react'; import { CSSProperties, FC, PropsWithChildren, ReactNode, useEffect, useState } from 'react';
import { LocalizeText, WiredFurniType, WiredSelectionVisualizer } from '../../../api'; import { LocalizeText, WiredFurniType, WiredSelectionVisualizer } from '../../../api';
import { Button, NitroCardContentView, NitroCardHeaderView, NitroCardView, Text } from '../../../common'; import { Button, NitroCardContentView, NitroCardHeaderView, NitroCardView, Text } from '../../../common';
import { useWired } from '../../../hooks'; import { useWired, useWiredTools } from '../../../hooks';
import { WiredFurniSelectorView } from './WiredFurniSelectorView'; import { WiredFurniSelectorView } from './WiredFurniSelectorView';
export interface WiredBaseViewProps export interface WiredBaseViewProps
@@ -25,6 +25,7 @@ export const WiredBaseView: FC<PropsWithChildren<WiredBaseViewProps>> = props =>
const [ needsSave, setNeedsSave ] = useState<boolean>(false); const [ needsSave, setNeedsSave ] = useState<boolean>(false);
const [ showFooter, setShowFooter ] = useState(false); const [ showFooter, setShowFooter ] = useState(false);
const { trigger = null, setTrigger = null, setIntParams = null, setStringParam = null, setFurniIds = null, setAllowsFurni = null, saveWired = null } = useWired(); const { trigger = null, setTrigger = null, setIntParams = null, setStringParam = null, setFurniIds = null, setAllowsFurni = null, saveWired = null } = useWired();
const { roomSettings } = useWiredTools();
const clearRoomAreaSelection = () => const clearRoomAreaSelection = () =>
{ {
@@ -41,6 +42,8 @@ export const WiredBaseView: FC<PropsWithChildren<WiredBaseViewProps>> = props =>
const onSave = () => const onSave = () =>
{ {
if(!roomSettings.canModify) return;
if(validate && !validate()) return; if(validate && !validate()) return;
if(save) save(); if(save) save();
@@ -82,24 +85,28 @@ export const WiredBaseView: FC<PropsWithChildren<WiredBaseViewProps>> = props =>
setIntParams(trigger.intData); setIntParams(trigger.intData);
setStringParam(trigger.stringData); setStringParam(trigger.stringData);
} }
}, [ trigger, hasSpecialInput, setIntParams, setStringParam ]);
if(requiresFurni > WiredFurniType.STUFF_SELECTION_OPTION_NONE) useEffect(() =>
{
if(!trigger) return;
setFurniIds(prevValue =>
{ {
setFurniIds(prevValue => if(prevValue && prevValue.length) WiredSelectionVisualizer.clearSelectionShaderFromFurni(prevValue);
if(requiresFurni <= WiredFurniType.STUFF_SELECTION_OPTION_NONE) return [];
if(trigger.selectedItems && trigger.selectedItems.length)
{ {
if(prevValue && prevValue.length) WiredSelectionVisualizer.clearSelectionShaderFromFurni(prevValue); WiredSelectionVisualizer.applySelectionShaderToFurni(trigger.selectedItems);
if(trigger.selectedItems && trigger.selectedItems.length) return trigger.selectedItems;
{ }
WiredSelectionVisualizer.applySelectionShaderToFurni(trigger.selectedItems);
return trigger.selectedItems; return [];
} });
}, [ trigger, requiresFurni, setFurniIds ]);
return [];
});
}
}, [ trigger, hasSpecialInput, setIntParams, setStringParam, setFurniIds ]);
useEffect(() => useEffect(() =>
{ {
@@ -171,7 +178,7 @@ export const WiredBaseView: FC<PropsWithChildren<WiredBaseViewProps>> = props =>
</> } </> }
<div className="nitro-wired__divider" /> <div className="nitro-wired__divider" />
<div className="flex items-center gap-1 nitro-wired__actions"> <div className="flex items-center gap-1 nitro-wired__actions">
<Button fullWidth variant="success" classNames={ [ 'nitro-wired__button', 'nitro-wired__button--primary' ] } onClick={ onSave }>{ LocalizeText('wiredfurni.ready') }</Button> <Button disabled={ !roomSettings.canModify } fullWidth variant="success" classNames={ [ 'nitro-wired__button', 'nitro-wired__button--primary' ] } onClick={ onSave }>{ LocalizeText('wiredfurni.ready') }</Button>
<Button fullWidth variant="secondary" classNames={ [ 'nitro-wired__button', 'nitro-wired__button--secondary' ] } onClick={ onClose }>{ LocalizeText('cancel') }</Button> <Button fullWidth variant="secondary" classNames={ [ 'nitro-wired__button', 'nitro-wired__button--secondary' ] } onClick={ onClose }>{ LocalizeText('cancel') }</Button>
</div> </div>
</NitroCardContentView> </NitroCardContentView>
@@ -0,0 +1,413 @@
import { FC, useEffect, useLayoutEffect, useRef, useState } from 'react';
import { createPortal } from 'react-dom';
import { FaChevronRight } from 'react-icons/fa';
import allIcon from '../../../assets/images/wired/var/var_picker_all.png';
import internalIcon from '../../../assets/images/wired/var/var_picker_internal.png';
import recentIcon from '../../../assets/images/wired/var/var_picker_recent.png';
import searchClearIcon from '../../../assets/images/wired/var/ar_picker_cancel_search.png';
import searchIcon from '../../../assets/images/wired/var/var_picker_search.png';
import smartIcon from '../../../assets/images/wired/var/var_picker_smart.png';
import userMadeIcon from '../../../assets/images/wired/var/var_picker_usermade.png';
import { LocalizeText } from '../../../api';
import { Text } from '../../../common';
import { flattenWiredVariablePickerEntries, IWiredVariablePickerEntry } from './WiredVariablePickerData';
type WiredVariablePickerMode = 'all' | 'recent' | 'usermade' | 'smart' | 'internal' | 'search';
interface WiredVariablePickerProps
{
emptyText?: string;
entries: IWiredVariablePickerEntry[];
placeholder?: string;
recentScope: string;
selectedToken: string;
onSelect: (entry: IWiredVariablePickerEntry) => void;
}
const RECENT_PICKER_LIMIT = 12;
const RECENT_STORAGE_PREFIX = 'nitro.wired.variable-picker.recent';
const PICKER_MODES: Array<{ icon: string; key: WiredVariablePickerMode; }> = [
{ key: 'all', icon: allIcon },
{ key: 'recent', icon: recentIcon },
{ key: 'usermade', icon: userMadeIcon },
{ key: 'smart', icon: smartIcon },
{ key: 'internal', icon: internalIcon },
{ key: 'search', icon: searchIcon }
];
const normalizeSearch = (value: string) => value.trim().toLocaleLowerCase();
const applyQuery = (entries: IWiredVariablePickerEntry[], query: string): IWiredVariablePickerEntry[] =>
{
if(!query) return entries;
const nextEntries: IWiredVariablePickerEntry[] = [];
for(const entry of entries)
{
const ownMatch = entry.searchableText.toLocaleLowerCase().includes(query);
const matchingChildren = entry.children?.length ? applyQuery(entry.children, query) : [];
if(!ownMatch && !matchingChildren.length) continue;
nextEntries.push(matchingChildren.length ? { ...entry, children: matchingChildren } : entry);
}
return nextEntries;
};
const applyMode = (entries: IWiredVariablePickerEntry[], mode: WiredVariablePickerMode, recentTokens: string[]): IWiredVariablePickerEntry[] =>
{
const recentSet = new Set(recentTokens);
const filterEntries = (items: IWiredVariablePickerEntry[]): IWiredVariablePickerEntry[] =>
{
const filtered: IWiredVariablePickerEntry[] = [];
for(const entry of items)
{
if(mode === 'smart') continue;
const nextChildren = entry.children?.length ? filterEntries(entry.children) : [];
const childVisible = !!nextChildren.length;
const selfVisible = (() =>
{
switch(mode)
{
case 'recent': return recentSet.has(entry.token);
case 'usermade': return entry.kind === 'custom';
case 'internal': return entry.kind === 'internal';
case 'search':
case 'all':
default:
return true;
}
})();
if(!selfVisible && !childVisible) continue;
filtered.push(childVisible ? { ...entry, children: nextChildren } : entry);
}
return filtered;
};
if(mode === 'recent')
{
const flatEntries = flattenWiredVariablePickerEntries(entries)
.filter(entry => recentSet.has(entry.token))
.sort((left, right) => recentTokens.indexOf(left.token) - recentTokens.indexOf(right.token))
.map(entry => ({ ...entry, label: entry.displayLabel }));
return flatEntries.filter(entry => !entry.children?.length);
}
return filterEntries(entries);
};
export const WiredVariablePicker: FC<WiredVariablePickerProps> = props =>
{
const { entries = [], selectedToken = '', onSelect, recentScope, placeholder = LocalizeText('wiredfurni.variable_picker.search'), emptyText = 'Nothing to display' } = props;
const containerRef = useRef<HTMLDivElement>(null);
const panelRef = useRef<HTMLDivElement>(null);
const searchInputRef = useRef<HTMLInputElement>(null);
const submenuRef = useRef<HTMLDivElement>(null);
const storageKey = `${ RECENT_STORAGE_PREFIX }.${ recentScope }`;
const [ isOpen, setIsOpen ] = useState(false);
const [ mode, setMode ] = useState<WiredVariablePickerMode>('all');
const [ query, setQuery ] = useState('');
const [ recentTokens, setRecentTokens ] = useState<string[]>([]);
const [ activeParentToken, setActiveParentToken ] = useState('');
const [ panelPosition, setPanelPosition ] = useState<{ left: number; top: number; width: number; } | null>(null);
const [ submenuPosition, setSubmenuPosition ] = useState<{ left: number; top: number; } | null>(null);
const allEntries = flattenWiredVariablePickerEntries(entries);
const selectedEntry = allEntries.find(entry => (entry.token === selectedToken)) || null;
const modeEntries = applyMode(entries, mode, recentTokens);
const filteredEntries = applyQuery(modeEntries, normalizeSearch(query));
const activeParent = filteredEntries.find(entry => (entry.token === activeParentToken) && entry.children?.length) || null;
const portalTarget = (typeof document !== 'undefined') ? (document.getElementById('draggable-windows-container') ?? document.body) : null;
useEffect(() =>
{
try
{
const rawValue = window.localStorage.getItem(storageKey);
if(!rawValue)
{
setRecentTokens([]);
return;
}
const parsedValue = JSON.parse(rawValue) as string[];
setRecentTokens(Array.isArray(parsedValue) ? parsedValue.filter(token => typeof token === 'string') : []);
}
catch
{
setRecentTokens([]);
}
}, [ storageKey ]);
useEffect(() =>
{
if(!isOpen) return;
const handleClick = (event: MouseEvent) =>
{
if(containerRef.current?.contains(event.target as Node)) return;
if(panelRef.current?.contains(event.target as Node)) return;
if(submenuRef.current?.contains(event.target as Node)) return;
setIsOpen(false);
setActiveParentToken('');
setPanelPosition(null);
setSubmenuPosition(null);
};
const handleEscape = (event: KeyboardEvent) =>
{
if(event.key !== 'Escape') return;
setIsOpen(false);
setActiveParentToken('');
setPanelPosition(null);
setSubmenuPosition(null);
};
document.addEventListener('mousedown', handleClick);
document.addEventListener('keydown', handleEscape);
return () =>
{
document.removeEventListener('mousedown', handleClick);
document.removeEventListener('keydown', handleEscape);
};
}, [ isOpen ]);
useLayoutEffect(() =>
{
if(!isOpen)
{
setPanelPosition(null);
return;
}
const updatePanelPosition = () =>
{
const triggerRect = containerRef.current?.getBoundingClientRect();
if(!triggerRect)
{
setPanelPosition(null);
return;
}
const panelWidth = Math.max(202, Math.ceil(triggerRect.width));
const viewportPadding = 8;
const left = Math.min(Math.max(viewportPadding, triggerRect.left), Math.max(viewportPadding, window.innerWidth - panelWidth - viewportPadding));
setPanelPosition({
left,
top: triggerRect.bottom + 2,
width: panelWidth
});
setActiveParentToken('');
setSubmenuPosition(null);
};
updatePanelPosition();
window.addEventListener('resize', updatePanelPosition);
window.addEventListener('scroll', updatePanelPosition, true);
return () =>
{
window.removeEventListener('resize', updatePanelPosition);
window.removeEventListener('scroll', updatePanelPosition, true);
};
}, [ isOpen ]);
useEffect(() =>
{
if(!isOpen) return;
if(mode !== 'search') return;
searchInputRef.current?.focus();
}, [ isOpen, mode ]);
useEffect(() =>
{
if(!activeParentToken) return;
if(filteredEntries.some(entry => entry.token === activeParentToken && entry.children?.length)) return;
setActiveParentToken('');
setSubmenuPosition(null);
}, [ activeParentToken, filteredEntries ]);
const rememberSelection = (token: string) =>
{
if(!token) return;
const nextRecentTokens = [ token, ...recentTokens.filter(currentToken => (currentToken !== token)) ].slice(0, RECENT_PICKER_LIMIT);
setRecentTokens(nextRecentTokens);
try
{
window.localStorage.setItem(storageKey, JSON.stringify(nextRecentTokens));
}
catch
{
}
};
const handleSelect = (entry: IWiredVariablePickerEntry) =>
{
if(!entry.selectable) return;
rememberSelection(entry.token);
onSelect(entry);
setIsOpen(false);
setActiveParentToken('');
setSubmenuPosition(null);
};
const activateParent = (entry: IWiredVariablePickerEntry, element: HTMLButtonElement) =>
{
if(!entry.children?.length)
{
setActiveParentToken('');
setSubmenuPosition(null);
return;
}
const rowRect = element.getBoundingClientRect();
const panelRect = panelRef.current?.getBoundingClientRect();
const submenuWidth = 140;
const submenuHeight = Math.min((entry.children.length * 20) + 22, 168);
const viewportWidth = window.innerWidth;
const viewportHeight = window.innerHeight;
const leftAnchor = panelRect ? panelRect.right : rowRect.right;
const rightSpace = viewportWidth - leftAnchor;
const canOpenRight = (rightSpace >= (submenuWidth + 8));
const left = canOpenRight
? (leftAnchor + 6)
: Math.max(8, (panelRect ? panelRect.left : rowRect.left) - submenuWidth - 6);
const top = Math.min(Math.max(8, rowRect.top), Math.max(8, viewportHeight - submenuHeight - 8));
setSubmenuPosition({
left,
top
});
setActiveParentToken(entry.token);
};
const renderEntry = (entry: IWiredVariablePickerEntry) =>
{
const hasChildren = !!entry.children?.length;
return (
<button
key={ entry.id }
type="button"
className={ `nitro-wired__variable-picker-row ${ entry.selectable ? '' : 'is-disabled' } ${ selectedToken === entry.token ? 'is-selected' : '' }` }
onMouseEnter={ event => activateParent(entry, event.currentTarget) }
onClick={ event =>
{
if(hasChildren)
{
activateParent(entry, event.currentTarget);
return;
}
if(entry.selectable) handleSelect(entry);
} }>
<span className="nitro-wired__variable-picker-row-label">{ entry.label }</span>
{ hasChildren && <FaChevronRight className="nitro-wired__variable-picker-row-arrow" /> }
</button>
);
};
const renderPanel = () =>
{
if(!panelPosition) return null;
return (
<div
ref={ panelRef }
className="nitro-wired__variable-picker-panel is-portal"
style={ { left: panelPosition.left, top: panelPosition.top, width: panelPosition.width } }>
<div className="nitro-wired__variable-picker-toolbar">
{ PICKER_MODES.map(button => (
<button
key={ button.key }
type="button"
className={ `nitro-wired__variable-picker-mode ${ mode === button.key ? 'is-active' : '' }` }
onClick={ () =>
{
setMode(button.key);
if(button.key === 'search') setTimeout(() => searchInputRef.current?.focus(), 0);
} }>
<img src={ button.icon } alt={ button.key } />
</button>
)) }
</div>
<div className="nitro-wired__variable-picker-search">
<img className="nitro-wired__variable-picker-search-icon" src={ searchIcon } alt="search" />
<input
ref={ searchInputRef }
className="nitro-wired__variable-picker-search-input"
placeholder={ placeholder }
type="text"
value={ query }
onChange={ event => setQuery(event.target.value) } />
{ !!query.length &&
<button type="button" className="nitro-wired__variable-picker-clear" onClick={ () => setQuery('') }>
<img src={ searchClearIcon } alt="clear" />
</button> }
</div>
<div className="nitro-wired__variable-picker-list">
{ filteredEntries.length
? filteredEntries.map(renderEntry)
: <Text small className="nitro-wired__variable-picker-empty">{ emptyText }</Text> }
</div>
</div>
);
};
return (
<div className="nitro-wired__variable-picker" ref={ containerRef }>
<button type="button" className="form-select form-select-sm nitro-wired__variable-picker-trigger" onClick={ () => setIsOpen(value => !value) }>
<span className={ selectedEntry ? '' : 'nitro-wired__variable-picker-placeholder' }>{ selectedEntry?.displayLabel || placeholder }</span>
</button>
{ isOpen && panelPosition && portalTarget && createPortal(
<div className="nitro-wired nitro-wired__variable-picker-portal">
{ renderPanel() }
{ activeParent?.children?.length && submenuPosition &&
<div
ref={ submenuRef }
className="nitro-wired__variable-picker-submenu"
style={ { left: submenuPosition.left, top: submenuPosition.top } }>
{ activeParent.children.map(child => (
<button
key={ child.id }
type="button"
className={ `nitro-wired__variable-picker-row nitro-wired__variable-picker-subrow ${ child.selectable ? '' : 'is-disabled' } ${ selectedToken === child.token ? 'is-selected' : '' }` }
onClick={ () => handleSelect(child) }>
<span className="nitro-wired__variable-picker-row-label">{ child.label }</span>
</button>
)) }
</div> }
</div>,
portalTarget
) }
</div>
);
};
@@ -0,0 +1,423 @@
export type WiredVariablePickerTarget = 'user' | 'furni' | 'global' | 'context';
export type WiredVariablePickerUsage = 'give' | 'remove' | 'change-destination' | 'change-reference' | 'condition' | 'filter-main' | 'echo';
export interface IWiredVariableDefinitionLike
{
availability: number;
hasValue: boolean;
isReadOnly?: boolean;
itemId: number;
name: string;
}
export interface IWiredVariablePickerEntry
{
id: string;
token: string;
label: string;
displayLabel: string;
searchableText: string;
selectable: boolean;
hasValue: boolean;
kind: 'internal' | 'custom';
target: WiredVariablePickerTarget;
children?: IWiredVariablePickerEntry[];
}
interface IInternalVariableMeta
{
key: string;
canUseAsDestination: boolean;
canUseAsReference: boolean;
}
const INTERNAL_VARIABLE_ALIASES: Record<string, string> = {
'@position.x': '@position_x',
'@position.y': '@position_y',
'@effect': '@effect_id',
'@handitems': '@handitem_id',
'@is_mute': '@is_muted',
'@teams.red.score': '@team_red_score',
'@teams.green.score': '@team_green_score',
'@teams.blue.score': '@team_blue_score',
'@teams.yellow.score': '@team_yellow_score',
'@teams.red.size': '@team_red_size',
'@teams.green.size': '@team_green_size',
'@teams.blue.size': '@team_blue_size',
'@teams.yellow.size': '@team_yellow_size'
};
const CUSTOM_TOKEN_PREFIX = 'custom:';
const INTERNAL_TOKEN_PREFIX = 'internal:';
const GROUP_TOKEN_PREFIX = 'group:';
const createInternalMeta = (key: string, canUseAsDestination = false, canUseAsReference = false): IInternalVariableMeta =>
({
key,
canUseAsDestination,
canUseAsReference
});
export const normalizeInternalVariableKey = (key: string) =>
{
const normalizedKey = key?.trim();
if(!normalizedKey) return '';
return (INTERNAL_VARIABLE_ALIASES[normalizedKey] || normalizedKey);
};
const INTERNAL_VARIABLES: Record<'user' | 'furni' | 'global' | 'context', IInternalVariableMeta[]> = {
furni: [
createInternalMeta('~teleport.target_id', false, true),
createInternalMeta('@id', false, true),
createInternalMeta('@class_id', false, true),
createInternalMeta('@height', false, true),
createInternalMeta('@state', true, true),
createInternalMeta('@position_x', true, true),
createInternalMeta('@position_y', true, true),
createInternalMeta('@rotation', true, true),
createInternalMeta('@altitude', true, true),
createInternalMeta('@is_invisible', false, true),
createInternalMeta('@type', false, true),
createInternalMeta('@is_stackable', false, true),
createInternalMeta('@can_stand_on', false, true),
createInternalMeta('@can_sit_on', false, true),
createInternalMeta('@can_lay_on', false, true),
createInternalMeta('@wallitem_offset', false, true),
createInternalMeta('@dimensions.x', false, true),
createInternalMeta('@dimensions.y', false, true),
createInternalMeta('@owner_id', false, true)
],
user: [
createInternalMeta('@index', false, true),
createInternalMeta('@type', false, true),
createInternalMeta('@gender', false, true),
createInternalMeta('@level', false, true),
createInternalMeta('@achievement_score', false, true),
createInternalMeta('@is_hc', false, true),
createInternalMeta('@has_rights', false, true),
createInternalMeta('@is_group_admin', false, true),
createInternalMeta('@is_owner', false, true),
createInternalMeta('@is_muted', false, true),
createInternalMeta('@is_trading', false, true),
createInternalMeta('@is_frozen', false, true),
createInternalMeta('@effect_id', false, true),
createInternalMeta('@team_score', false, true),
createInternalMeta('@team_color', false, true),
createInternalMeta('@team_type', false, true),
createInternalMeta('@sign', false, true),
createInternalMeta('@dance', false, true),
createInternalMeta('@is_idle', false, true),
createInternalMeta('@handitem_id', false, true),
createInternalMeta('@position_x', true, true),
createInternalMeta('@position_y', true, true),
createInternalMeta('@direction', true, true),
createInternalMeta('@altitude', false, true),
createInternalMeta('@favourite_group_id', false, true),
createInternalMeta('@room_entry.method', false, true),
createInternalMeta('@room_entry.teleport_id', false, true),
createInternalMeta('@user_id', false, true),
createInternalMeta('@bot_id', false, true),
createInternalMeta('@pet_id', false, true),
createInternalMeta('@pet_owner_id', false, true)
],
global: [
createInternalMeta('@furni_count', false, true),
createInternalMeta('@user_count', false, true),
createInternalMeta('@wired_timer', false, true),
createInternalMeta('@team_red_score', false, true),
createInternalMeta('@team_green_score', false, true),
createInternalMeta('@team_blue_score', false, true),
createInternalMeta('@team_yellow_score', false, true),
createInternalMeta('@team_red_size', false, true),
createInternalMeta('@team_green_size', false, true),
createInternalMeta('@team_blue_size', false, true),
createInternalMeta('@team_yellow_size', false, true),
createInternalMeta('@room_id', false, true),
createInternalMeta('@group_id', false, true),
createInternalMeta('@timezone_server', false, true),
createInternalMeta('@timezone_client', false, true),
createInternalMeta('@current_time', false, true),
createInternalMeta('@current_time.millisecond_of_second', false, true),
createInternalMeta('@current_time.seconds_of_minute', false, true),
createInternalMeta('@current_time.minute_of_hour', false, true),
createInternalMeta('@current_time.hour_of_day', false, true),
createInternalMeta('@current_time.day_of_week', false, true),
createInternalMeta('@current_time.day_of_month', false, true),
createInternalMeta('@current_time.day_of_year', false, true),
createInternalMeta('@current_time.week_of_year', false, true),
createInternalMeta('@current_time.month_of_year', false, true),
createInternalMeta('@current_time.year', false, true)
],
context: [
createInternalMeta('@selector_furni_count', false, true),
createInternalMeta('@selector_user_count', false, true),
createInternalMeta('@signal_furni_count', false, true),
createInternalMeta('@signal_user_count', false, true),
createInternalMeta('@antenna_id', false, true),
createInternalMeta('@chat_type', false, true),
createInternalMeta('@chat_style', false, true)
]
};
const sortEntries = (left: IWiredVariablePickerEntry, right: IWiredVariablePickerEntry) =>
{
return left.displayLabel.localeCompare(right.displayLabel, undefined, { sensitivity: 'base' });
};
const getNormalizedInternalTarget = (target: WiredVariablePickerTarget): 'user' | 'furni' | 'global' | 'context' =>
{
if(target === 'furni') return 'furni';
if(target === 'user') return 'user';
if(target === 'context') return 'context';
return 'global';
};
const getInternalSelectable = (usage: WiredVariablePickerUsage, meta: IInternalVariableMeta) =>
{
switch(usage)
{
case 'condition': return true;
case 'filter-main': return meta.canUseAsReference;
case 'echo': return true;
case 'change-destination': return meta.canUseAsDestination;
case 'change-reference': return meta.canUseAsReference;
default: return false;
}
};
const getCustomSelectable = (usage: WiredVariablePickerUsage, definition: IWiredVariableDefinitionLike) =>
{
switch(usage)
{
case 'condition':
case 'filter-main':
return true;
case 'echo':
return definition.name.includes('.');
case 'change-reference':
return !!definition.hasValue;
case 'change-destination':
return (!!definition.hasValue && !definition.isReadOnly);
default:
return !definition.isReadOnly;
}
};
const getRootKey = (key: string) =>
{
const separatorIndex = key.indexOf('.');
if(separatorIndex < 0) return null;
return key.slice(0, separatorIndex);
};
const createInternalEntry = (target: WiredVariablePickerTarget, usage: WiredVariablePickerUsage, meta: IInternalVariableMeta): IWiredVariablePickerEntry =>
({
id: `${ INTERNAL_TOKEN_PREFIX }${ meta.key }`,
token: `${ INTERNAL_TOKEN_PREFIX }${ meta.key }`,
label: meta.key,
displayLabel: meta.key,
searchableText: meta.key,
selectable: getInternalSelectable(usage, meta),
hasValue: meta.canUseAsReference,
kind: 'internal',
target
});
const createCustomEntry = (target: WiredVariablePickerTarget, usage: WiredVariablePickerUsage, definition: IWiredVariableDefinitionLike): IWiredVariablePickerEntry =>
({
id: `${ CUSTOM_TOKEN_PREFIX }${ definition.itemId }`,
token: `${ CUSTOM_TOKEN_PREFIX }${ definition.itemId }`,
label: definition.name,
displayLabel: definition.name,
searchableText: definition.name,
selectable: getCustomSelectable(usage, definition),
hasValue: !!definition.hasValue,
kind: 'custom',
target
});
const groupEntries = (entries: IWiredVariablePickerEntry[]) =>
{
const groupedParents = new Map<string, { exact?: IWiredVariablePickerEntry; children: IWiredVariablePickerEntry[]; }>();
for(const entry of entries)
{
const displayLabel = entry.displayLabel?.trim();
if(!displayLabel?.length)
{
continue;
}
const rootKey = getRootKey(displayLabel) || displayLabel;
let group = groupedParents.get(rootKey);
if(!group)
{
group = { children: [] };
groupedParents.set(rootKey, group);
}
if(displayLabel === rootKey)
{
group.exact = {
...entry,
label: displayLabel,
displayLabel,
searchableText: displayLabel
};
continue;
}
const childLabel = displayLabel.slice(rootKey.length + 1).trim();
if(!childLabel.length) continue;
group.children.push({
...entry,
label: childLabel,
displayLabel,
searchableText: `${ displayLabel } ${ childLabel }`
});
}
const groupedEntries: IWiredVariablePickerEntry[] = [];
for(const [ rootKey, group ] of groupedParents)
{
const sortedChildren = [ ...group.children ]
.sort(sortEntries)
.filter((child, index, collection) => collection.findIndex(entry => (entry.token === child.token)) === index);
const shouldGroup = !!sortedChildren.length && (sortedChildren.length > 1 || !!group.exact);
if(!shouldGroup)
{
if(group.exact) groupedEntries.push(group.exact);
groupedEntries.push(...sortedChildren.map(child => ({ ...child, label: child.displayLabel })));
continue;
}
groupedEntries.push({
...(group.exact || {
id: `${ GROUP_TOKEN_PREFIX }${ rootKey }`,
token: `${ GROUP_TOKEN_PREFIX }${ rootKey }`,
label: rootKey,
displayLabel: rootKey,
searchableText: rootKey,
selectable: false,
hasValue: false,
kind: (sortedChildren[0]?.kind || 'custom'),
target: (sortedChildren[0]?.target || 'user')
}),
label: rootKey,
displayLabel: rootKey,
searchableText: `${ rootKey } ${ sortedChildren.map(child => child.displayLabel).join(' ') }`,
children: sortedChildren
});
}
return groupedEntries
.filter(entry => entry.displayLabel?.trim().length)
.sort(sortEntries)
.filter((entry, index, collection) => collection.findIndex(currentEntry => (currentEntry.token === entry.token)) === index);
};
export const createCustomVariableToken = (itemId: number) => (itemId > 0 ? `${ CUSTOM_TOKEN_PREFIX }${ itemId }` : '');
export const createInternalVariableToken = (key: string) =>
{
const normalizedKey = normalizeInternalVariableKey(key);
return normalizedKey ? `${ INTERNAL_TOKEN_PREFIX }${ normalizedKey }` : '';
};
export const isCustomVariableToken = (token: string) => !!token && token.startsWith(CUSTOM_TOKEN_PREFIX);
export const isInternalVariableToken = (token: string) => !!token && token.startsWith(INTERNAL_TOKEN_PREFIX);
export const getCustomVariableItemId = (token: string) => (isCustomVariableToken(token) ? parseInt(token.slice(CUSTOM_TOKEN_PREFIX.length), 10) || 0 : 0);
export const getInternalVariableKey = (token: string) => (isInternalVariableToken(token) ? normalizeInternalVariableKey(token.slice(INTERNAL_TOKEN_PREFIX.length)) : '');
export const normalizeVariableTokenFromWire = (value: string) =>
{
const normalizedValue = value?.trim();
if(!normalizedValue) return '';
if(isCustomVariableToken(normalizedValue)) return normalizedValue;
if(isInternalVariableToken(normalizedValue)) return createInternalVariableToken(normalizedValue.slice(INTERNAL_TOKEN_PREFIX.length));
const parsedValue = parseInt(normalizedValue, 10);
return (!Number.isNaN(parsedValue) && (parsedValue > 0)) ? createCustomVariableToken(parsedValue) : '';
};
export const createFallbackVariableEntry = (target: WiredVariablePickerTarget, token: string): IWiredVariablePickerEntry | null =>
{
if(!token) return null;
if(isCustomVariableToken(token))
{
const itemId = getCustomVariableItemId(token);
if(itemId <= 0) return null;
return {
id: token,
token,
label: `#${ itemId }`,
displayLabel: `#${ itemId }`,
searchableText: `#${ itemId }`,
selectable: true,
hasValue: false,
kind: 'custom',
target
};
}
if(isInternalVariableToken(token))
{
const key = getInternalVariableKey(token);
if(!key) return null;
return {
id: token,
token,
label: key,
displayLabel: key,
searchableText: key,
selectable: false,
hasValue: false,
kind: 'internal',
target
};
}
return null;
};
export const buildWiredVariablePickerEntries = (target: WiredVariablePickerTarget, usage: WiredVariablePickerUsage, customDefinitions: IWiredVariableDefinitionLike[]) =>
{
const internalTarget = getNormalizedInternalTarget(target);
const customEntries = groupEntries([ ...(customDefinitions || []) ]
.map(definition => createCustomEntry(target, usage, definition))
.sort(sortEntries));
const internalEntries = groupEntries(INTERNAL_VARIABLES[internalTarget].map(meta => createInternalEntry(target, usage, meta)));
return [ ...customEntries, ...internalEntries ];
};
export const flattenWiredVariablePickerEntries = (entries: IWiredVariablePickerEntry[]): IWiredVariablePickerEntry[] =>
{
const flattened: IWiredVariablePickerEntry[] = [];
for(const entry of entries)
{
flattened.push(entry);
if(entry.children?.length) flattened.push(...entry.children);
}
return flattened;
};
@@ -0,0 +1,483 @@
import { FC, useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { GetWiredTimeLocale, LocalizeText, WiredFurniType, WiredSelectionVisualizer } 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 { Slider, Text } from '../../../../common';
import { useWired, useWiredTools } from '../../../../hooks';
import { NitroInput } from '../../../../layout';
import { WiredFurniSelectionSourceRow } from '../WiredFurniSelectionSourceRow';
import { WiredVariablePicker } from '../WiredVariablePicker';
import { buildWiredVariablePickerEntries, createFallbackVariableEntry, flattenWiredVariablePickerEntries, normalizeVariableTokenFromWire } from '../WiredVariablePickerData';
import { CLICKED_USER_SOURCE, FURNI_SOURCES, sortWiredSourceOptions, USER_SOURCES, useAvailableUserSources, WiredSourceOption } from '../WiredSourcesSelector';
import { WiredActionBaseView } from './WiredActionBaseView';
type VariableTargetType = 'user' | 'furni' | 'global' | 'context';
type ReferenceMode = 'constant' | 'variable';
type SelectionMode = 'destination' | 'reference';
interface IVariableDefinition
{
availability: number;
hasValue: boolean;
isReadOnly?: boolean;
itemId: number;
name: string;
}
const TARGET_USER = 0;
const TARGET_FURNI = 1;
const TARGET_CONTEXT = 2;
const TARGET_GLOBAL = 3;
const REFERENCE_CONSTANT = 0;
const REFERENCE_VARIABLE = 1;
const SOURCE_TRIGGER = 0;
const SOURCE_SELECTED = 100;
const SOURCE_SECONDARY_SELECTED = 101;
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 OPERATION_OPTIONS = [ 0, 1, 2, 3, 4, 5, 6, 40, 41, 50, 60, 100, 101, 102, 103, 104, 105 ];
const SECONDARY_FURNI_SOURCES: WiredSourceOption[] = sortWiredSourceOptions([
{ value: SOURCE_TRIGGER, label: 'wiredfurni.params.sources.furni.0' },
{ value: SOURCE_SECONDARY_SELECTED, label: 'wiredfurni.params.sources.furni.101' },
{ value: 200, label: 'wiredfurni.params.sources.furni.200' },
{ value: 201, label: 'wiredfurni.params.sources.furni.201' }
], 'furni');
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 parseIds = (value: string): number[] =>
{
if(!value?.length) return [];
const ids = new Set<number>();
for(const part of value.split(/[;,\t]/))
{
const parsedValue = parseInt(part.trim(), 10);
if(!Number.isNaN(parsedValue) && (parsedValue > 0)) ids.add(parsedValue);
}
return [ ...ids ];
};
const serializeIds = (ids: number[]) => (ids?.length ? ids.filter(id => (id > 0)).join(';') : '');
const parseStringData = (value: string) => (value?.length ? value.split('\t', -1) : []);
const serializeStringData = (destinationVariableToken: string, referenceVariableToken: string, referenceFurniIds: number[]) => `${ destinationVariableToken || '' }\t${ referenceVariableToken || '' }\t${ serializeIds(referenceFurniIds) }`;
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 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 resolveSourceOptions = (baseOptions: WiredSourceOption[], selectedValue: number, fallbackOptions: WiredSourceOption[]) =>
{
if(!baseOptions.length) return baseOptions;
if(baseOptions.some(option => (option.value === selectedValue))) return baseOptions;
const fallbackOption = fallbackOptions.find(option => (option.value === selectedValue));
if(!fallbackOption) return baseOptions;
return [ ...baseOptions, fallbackOption ];
};
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 isGlobalTarget = (targetType: VariableTargetType) => (targetType === 'global');
const isFurniTarget = (targetType: VariableTargetType) => (targetType === 'furni');
const isContextTarget = (targetType: VariableTargetType) => (targetType === 'context');
export const WiredActionChangeVariableValueView: FC<{}> = () =>
{
const { trigger = null, furniIds = [], actionDelay = 0, setActionDelay = null, setAllowsFurni = null, setFurniIds = null, setIntParams = null, setStringParam = null } = useWired();
const { userVariableDefinitions = [], furniVariableDefinitions = [], roomVariableDefinitions = [], contextVariableDefinitions = [] } = useWiredTools();
const [ destinationTargetType, setDestinationTargetType ] = useState<VariableTargetType>('user');
const [ destinationVariableToken, setDestinationVariableToken ] = useState('');
const [ operation, setOperation ] = useState(0);
const [ referenceMode, setReferenceMode ] = useState<ReferenceMode>('constant');
const [ referenceConstantValueInput, setReferenceConstantValueInput ] = useState('0');
const [ referenceTargetType, setReferenceTargetType ] = useState<VariableTargetType>('user');
const [ referenceVariableToken, setReferenceVariableToken ] = useState('');
const [ destinationUserSource, setDestinationUserSource ] = useState(SOURCE_TRIGGER);
const [ destinationFurniSource, setDestinationFurniSource ] = useState(SOURCE_TRIGGER);
const [ referenceUserSource, setReferenceUserSource ] = useState(SOURCE_TRIGGER);
const [ referenceFurniSource, setReferenceFurniSource ] = useState(SOURCE_TRIGGER);
const [ destinationFurniIds, setDestinationFurniIds ] = useState<number[]>([]);
const [ referenceFurniIds, setReferenceFurniIds ] = useState<number[]>([]);
const [ selectionMode, setSelectionMode ] = useState<SelectionMode>('destination');
const highlightedIds = useRef<number[]>([]);
const availableUserSources = useAvailableUserSources(trigger, USER_SOURCES);
const orderedUserSources = useMemo(() => sortWiredSourceOptions(availableUserSources, 'users'), [ availableUserSources ]);
const orderedFurniSources = useMemo(() => sortWiredSourceOptions(FURNI_SOURCES, 'furni'), []);
const userSourceFallbackOptions = useMemo(() => sortWiredSourceOptions([ ...USER_SOURCES, CLICKED_USER_SOURCE ], 'users'), []);
const destinationDefinitions = useMemo(() => getTargetDefinitions(destinationTargetType, userVariableDefinitions, furniVariableDefinitions, roomVariableDefinitions, contextVariableDefinitions), [ contextVariableDefinitions, destinationTargetType, furniVariableDefinitions, roomVariableDefinitions, userVariableDefinitions ]);
const referenceDefinitions = useMemo(() => getTargetDefinitions(referenceTargetType, userVariableDefinitions, furniVariableDefinitions, roomVariableDefinitions, contextVariableDefinitions), [ contextVariableDefinitions, furniVariableDefinitions, referenceTargetType, roomVariableDefinitions, userVariableDefinitions ]);
const destinationVariableEntries = useMemo(() => buildWiredVariablePickerEntries(destinationTargetType, 'change-destination', destinationDefinitions), [ destinationDefinitions, destinationTargetType ]);
const resolvedDestinationVariableEntries = useMemo(() =>
{
if(!destinationVariableToken) return destinationVariableEntries;
if(flattenWiredVariablePickerEntries(destinationVariableEntries).some(entry => (entry.token === destinationVariableToken))) return destinationVariableEntries;
const fallbackEntry = createFallbackVariableEntry(destinationTargetType, destinationVariableToken);
return fallbackEntry ? [ fallbackEntry, ...destinationVariableEntries ] : destinationVariableEntries;
}, [ destinationTargetType, destinationVariableEntries, destinationVariableToken ]);
const referenceVariableEntries = useMemo(() => buildWiredVariablePickerEntries(referenceTargetType, 'change-reference', referenceDefinitions), [ referenceDefinitions, referenceTargetType ]);
const resolvedReferenceVariableEntries = useMemo(() =>
{
if(!referenceVariableToken) return referenceVariableEntries;
if(flattenWiredVariablePickerEntries(referenceVariableEntries).some(entry => (entry.token === referenceVariableToken))) return referenceVariableEntries;
const fallbackEntry = createFallbackVariableEntry(referenceTargetType, referenceVariableToken);
return fallbackEntry ? [ fallbackEntry, ...referenceVariableEntries ] : referenceVariableEntries;
}, [ referenceTargetType, referenceVariableEntries, referenceVariableToken ]);
const destinationSelectionEnabled = isFurniTarget(destinationTargetType) && (destinationFurniSource === SOURCE_SELECTED);
const referenceSelectionEnabled = (referenceMode === 'variable') && isFurniTarget(referenceTargetType) && (referenceFurniSource === SOURCE_SECONDARY_SELECTED);
const destinationSelectedSourceValue = isFurniTarget(destinationTargetType) ? destinationFurniSource : (isGlobalTarget(destinationTargetType) ? SOURCE_TRIGGER : destinationUserSource);
const referenceSelectedSourceValue = isFurniTarget(referenceTargetType) ? referenceFurniSource : (isGlobalTarget(referenceTargetType) ? SOURCE_TRIGGER : referenceUserSource);
const destinationSourceOptions = useMemo(() =>
{
if(isContextTarget(destinationTargetType)) return CONTEXT_SOURCE_OPTIONS;
if(isFurniTarget(destinationTargetType)) return resolveSourceOptions(orderedFurniSources, destinationSelectedSourceValue, orderedFurniSources);
if(isGlobalTarget(destinationTargetType)) return GLOBAL_SOURCE_OPTIONS;
return resolveSourceOptions(orderedUserSources, destinationSelectedSourceValue, userSourceFallbackOptions);
}, [ destinationSelectedSourceValue, destinationTargetType, orderedFurniSources, orderedUserSources, userSourceFallbackOptions ]);
const referenceSourceOptions = useMemo(() =>
{
if(isContextTarget(referenceTargetType)) return CONTEXT_SOURCE_OPTIONS;
if(isFurniTarget(referenceTargetType)) return resolveSourceOptions(SECONDARY_FURNI_SOURCES, referenceSelectedSourceValue, SECONDARY_FURNI_SOURCES);
if(isGlobalTarget(referenceTargetType)) return GLOBAL_SOURCE_OPTIONS;
return resolveSourceOptions(orderedUserSources, referenceSelectedSourceValue, userSourceFallbackOptions);
}, [ orderedUserSources, referenceSelectedSourceValue, referenceTargetType, userSourceFallbackOptions ]);
const syncHighlights = useCallback((nextDestinationIds: number[], nextReferenceIds: number[]) =>
{
if(highlightedIds.current.length)
{
WiredSelectionVisualizer.clearSelectionShaderFromFurni(highlightedIds.current);
WiredSelectionVisualizer.clearSecondarySelectionShaderFromFurni(highlightedIds.current);
}
const secondarySet = new Set(nextReferenceIds);
const primaryOnlyIds = nextDestinationIds.filter(id => !secondarySet.has(id));
if(primaryOnlyIds.length) WiredSelectionVisualizer.applySelectionShaderToFurni(primaryOnlyIds);
if(nextReferenceIds.length) WiredSelectionVisualizer.applySecondarySelectionShaderToFurni(nextReferenceIds);
highlightedIds.current = Array.from(new Set([ ...nextDestinationIds, ...nextReferenceIds ]));
}, []);
const switchSelection = useCallback((mode: SelectionMode) =>
{
if(mode === 'destination' && !destinationSelectionEnabled) return;
if(mode === 'reference' && !referenceSelectionEnabled) return;
const nextDestinationIds = (selectionMode === 'destination') ? [ ...furniIds ] : [ ...destinationFurniIds ];
const nextReferenceIds = (selectionMode === 'reference') ? [ ...furniIds ] : [ ...referenceFurniIds ];
setDestinationFurniIds(nextDestinationIds);
setReferenceFurniIds(nextReferenceIds);
setSelectionMode(mode);
setFurniIds([ ...(mode === 'destination' ? nextDestinationIds : nextReferenceIds) ]);
}, [ destinationFurniIds, destinationSelectionEnabled, furniIds, referenceFurniIds, referenceSelectionEnabled, selectionMode, setFurniIds ]);
useEffect(() =>
{
if(!trigger) return;
const stringParts = parseStringData(trigger.stringData);
const nextDestinationTargetType = normalizeTargetType((trigger.intData.length > 0) ? trigger.intData[0] : TARGET_USER);
const nextReferenceTargetType = normalizeTargetType((trigger.intData.length > 4) ? trigger.intData[4] : TARGET_USER);
const nextDestinationFurniIds = [ ...(trigger.selectedItems ?? []) ];
const nextReferenceFurniIds = parseIds((stringParts.length > 2) ? stringParts[2] : '');
setDestinationTargetType(nextDestinationTargetType);
setDestinationVariableToken(normalizeVariableTokenFromWire((stringParts.length > 0) ? stringParts[0] : ''));
setOperation((trigger.intData.length > 1) ? trigger.intData[1] : 0);
setReferenceMode(((trigger.intData.length > 2) ? trigger.intData[2] : REFERENCE_CONSTANT) === REFERENCE_VARIABLE ? 'variable' : 'constant');
setReferenceConstantValueInput(((trigger.intData.length > 3) ? trigger.intData[3] : 0).toString());
setReferenceTargetType(nextReferenceTargetType);
setReferenceVariableToken(normalizeVariableTokenFromWire((stringParts.length > 1) ? stringParts[1] : ''));
setDestinationUserSource((trigger.intData.length > 5) ? trigger.intData[5] : SOURCE_TRIGGER);
setDestinationFurniSource((trigger.intData.length > 6) ? trigger.intData[6] : (nextDestinationFurniIds.length ? SOURCE_SELECTED : SOURCE_TRIGGER));
setReferenceUserSource((trigger.intData.length > 7) ? trigger.intData[7] : SOURCE_TRIGGER);
setReferenceFurniSource((trigger.intData.length > 8) ? trigger.intData[8] : (nextReferenceFurniIds.length ? SOURCE_SECONDARY_SELECTED : SOURCE_TRIGGER));
setDestinationFurniIds(nextDestinationFurniIds);
setReferenceFurniIds(nextReferenceFurniIds);
setSelectionMode('destination');
setFurniIds([ ...nextDestinationFurniIds ]);
}, [ setFurniIds, trigger ]);
useEffect(() =>
{
if(selectionMode === 'destination') setDestinationFurniIds([ ...furniIds ]);
else setReferenceFurniIds([ ...furniIds ]);
}, [ furniIds, selectionMode ]);
useEffect(() => syncHighlights(destinationFurniIds, referenceFurniIds), [ destinationFurniIds, referenceFurniIds, syncHighlights ]);
useEffect(() =>
{
if(selectionMode === 'destination' && !destinationSelectionEnabled && referenceSelectionEnabled)
{
switchSelection('reference');
return;
}
if(selectionMode === 'reference' && !referenceSelectionEnabled && destinationSelectionEnabled)
{
switchSelection('destination');
return;
}
const canEditSelection = (selectionMode === 'destination') ? destinationSelectionEnabled : referenceSelectionEnabled;
setAllowsFurni(canEditSelection ? WiredFurniType.STUFF_SELECTION_OPTION_BY_ID : WiredFurniType.STUFF_SELECTION_OPTION_NONE);
}, [ destinationSelectionEnabled, referenceSelectionEnabled, selectionMode, setAllowsFurni, switchSelection ]);
useEffect(() =>
{
return () =>
{
if(!highlightedIds.current.length) return;
WiredSelectionVisualizer.clearSelectionShaderFromFurni(highlightedIds.current);
WiredSelectionVisualizer.clearSecondarySelectionShaderFromFurni(highlightedIds.current);
highlightedIds.current = [];
};
}, []);
const save = () =>
{
const nextDestinationFurniIds = (selectionMode === 'destination') ? [ ...furniIds ] : [ ...destinationFurniIds ];
const nextReferenceFurniIds = (selectionMode === 'reference') ? [ ...furniIds ] : [ ...referenceFurniIds ];
const parsedReferenceConstantValue = parseInt(referenceConstantValueInput.trim(), 10);
setDestinationFurniIds(nextDestinationFurniIds);
setReferenceFurniIds(nextReferenceFurniIds);
setStringParam(serializeStringData(destinationVariableToken, referenceMode === 'variable' ? referenceVariableToken : '', nextReferenceFurniIds));
setIntParams([
getTargetValue(destinationTargetType),
operation,
referenceMode === 'variable' ? REFERENCE_VARIABLE : REFERENCE_CONSTANT,
Number.isFinite(parsedReferenceConstantValue) ? parsedReferenceConstantValue : 0,
getTargetValue(referenceTargetType),
destinationUserSource,
destinationFurniSource,
referenceUserSource,
referenceFurniSource
]);
setFurniIds((isFurniTarget(destinationTargetType) && destinationFurniSource === SOURCE_SELECTED) ? [ ...nextDestinationFurniIds ] : []);
};
const validate = () =>
{
if(!destinationVariableToken) return false;
if(referenceMode === 'variable' && !referenceVariableToken) return false;
return true;
};
const selectionLimit = trigger?.maximumItemSelectionCount ?? 0;
const handleDestinationTargetChange = (targetType: VariableTargetType) =>
{
if(targetType === destinationTargetType) return;
setDestinationTargetType(targetType);
setDestinationVariableToken('');
};
const handleReferenceTargetChange = (targetType: VariableTargetType) =>
{
if(targetType === referenceTargetType) return;
setReferenceTargetType(targetType);
setReferenceVariableToken('');
};
return (
<WiredActionBaseView
hasSpecialInput={ true }
requiresFurni={ WiredFurniType.STUFF_SELECTION_OPTION_NONE }
save={ save }
validate={ validate }
cardStyle={ { width: 244 } }
hideDelay={ true }>
<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 } ${ destinationTargetType === button.key ? 'is-active' : '' }` }
onClick={ () => handleDestinationTargetChange(button.key) }>
<img src={ button.icon } alt={ button.key } />
</button>
)) }
</div>
</div>
<WiredVariablePicker
entries={ resolvedDestinationVariableEntries }
recentScope="variable-effects"
selectedToken={ destinationVariableToken }
onSelect={ entry => setDestinationVariableToken(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.operation') }</div>
<select className="form-select form-select-sm nitro-wired__give-var-select" value={ operation } onChange={ event => setOperation(parseInt(event.target.value, 10)) }>
{ OPERATION_OPTIONS.map(value => <option key={ value } value={ value }>{ LocalizeText(`wiredfurni.params.variables.operation.${ value }`) }</option>) }
</select>
</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.reference_value') }</div>
<label className="nitro-wired__change-var-radio">
<input checked={ referenceMode === 'constant' } type="radio" onChange={ () => setReferenceMode('constant') } />
<Text>{ LocalizeText('wiredfurni.params.operator.2') }</Text>
<NitroInput className="nitro-wired__give-var-number" type="number" value={ referenceConstantValueInput } onChange={ event => setReferenceConstantValueInput(event.target.value) } />
</label>
<div className="nitro-wired__change-var-reference-block">
<label className="nitro-wired__change-var-radio">
<input checked={ referenceMode === 'variable' } type="radio" onChange={ () => setReferenceMode('variable') } />
<Text>{ LocalizeText('wiredfurni.params.variables.reference_value.from_variable') }</Text>
<div className="nitro-wired__give-var-targets">
{ TARGET_BUTTONS.map(button => (
<button
key={ `reference-${ button.key }` }
type="button"
disabled={ button.disabled || (referenceMode !== 'variable') }
className={ `nitro-wired__give-var-target nitro-wired__give-var-target--${ button.key } ${ referenceTargetType === button.key ? 'is-active' : '' }` }
onClick={ () => handleReferenceTargetChange(button.key) }>
<img src={ button.icon } alt={ button.key } />
</button>
)) }
</div>
</label>
{ referenceMode === 'variable' &&
<WiredVariablePicker
entries={ resolvedReferenceVariableEntries }
recentScope="variable-effects"
selectedToken={ referenceVariableToken }
onSelect={ entry => setReferenceVariableToken(entry.token) } /> }
</div>
</div>
<div className="nitro-wired__divider" />
<div className="nitro-wired__give-var-section">
<div className="nitro-wired__give-var-section-title">{ LocalizeText('wiredfurni.params.delay', [ 'seconds' ], [ GetWiredTimeLocale(actionDelay) ]) }</div>
<Slider max={ 20 } min={ 0 } value={ actionDelay } onChange={ event => setActionDelay(event) } />
</div>
<div className="nitro-wired__divider" />
<div className="nitro-wired__give-var-section">
<WiredFurniSelectionSourceRow
title="wiredfurni.params.sources.merged.title.variables_destination"
options={ destinationSourceOptions }
value={ destinationSelectedSourceValue }
selectionKind="primary"
selectionActive={ selectionMode === 'destination' }
selectionCount={ destinationFurniIds.length }
selectionLimit={ selectionLimit }
selectionEnabledValues={ [ SOURCE_SELECTED ] }
showSelectionToggle={ isFurniTarget(destinationTargetType) }
onChange={ value =>
{
if(isFurniTarget(destinationTargetType))
{
setDestinationFurniSource(value);
return;
}
if(!isGlobalTarget(destinationTargetType) && !isContextTarget(destinationTargetType)) setDestinationUserSource(value);
} }
onSelectionActivate={ () => switchSelection('destination') } />
</div>
{ referenceMode === 'variable' &&
<>
<div className="nitro-wired__divider" />
<div className="nitro-wired__give-var-section">
<WiredFurniSelectionSourceRow
title="wiredfurni.params.sources.merged.title.variables_reference"
options={ referenceSourceOptions }
value={ referenceSelectedSourceValue }
selectionKind="secondary"
selectionActive={ selectionMode === 'reference' }
selectionCount={ referenceFurniIds.length }
selectionLimit={ selectionLimit }
selectionEnabledValues={ [ SOURCE_SECONDARY_SELECTED ] }
showSelectionToggle={ isFurniTarget(referenceTargetType) }
onChange={ value =>
{
if(isFurniTarget(referenceTargetType))
{
setReferenceFurniSource(value);
return;
}
if(!isGlobalTarget(referenceTargetType) && !isContextTarget(referenceTargetType)) setReferenceUserSource(value);
} }
onSelectionActivate={ () => switchSelection('reference') } />
</div>
</> }
</div>
</WiredActionBaseView>
);
};
@@ -0,0 +1,283 @@
import { FC, useEffect, useMemo, useState } from 'react';
import { FaChevronLeft, FaChevronRight } from 'react-icons/fa';
import { GetWiredTimeLocale, 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 userVariableIcon from '../../../../assets/images/wired/var/icon_source_user.png';
import { Button, Slider, Text } from '../../../../common';
import { useWired, useWiredTools } from '../../../../hooks';
import { NitroInput } from '../../../../layout';
import { CLICKED_USER_SOURCE, FURNI_SOURCES, sortWiredSourceOptions, USER_SOURCES, useAvailableUserSources } from '../WiredSourcesSelector';
import { WiredActionBaseView } from './WiredActionBaseView';
import { WiredVariablePicker } from '../WiredVariablePicker';
import { buildWiredVariablePickerEntries, createCustomVariableToken, createFallbackVariableEntry, flattenWiredVariablePickerEntries, getCustomVariableItemId, normalizeVariableTokenFromWire } from '../WiredVariablePickerData';
type VariableTargetType = 'user' | 'furni' | 'context';
const TARGET_USER = 0;
const TARGET_FURNI = 1;
const TARGET_CONTEXT = 2;
const SOURCE_SELECTED = 100;
const TARGET_BUTTONS: Array<{ key: VariableTargetType; icon: string; }> = [
{ key: 'furni', icon: furniVariableIcon },
{ key: 'user', icon: userVariableIcon },
{ key: 'context', icon: contextVariableIcon }
];
const normalizeTargetType = (value: number): VariableTargetType =>
{
switch(value)
{
case TARGET_FURNI: return 'furni';
case TARGET_CONTEXT: return 'context';
default: return 'user';
}
};
const getTargetValue = (value: VariableTargetType) =>
{
switch(value)
{
case 'furni': return TARGET_FURNI;
case 'context': return TARGET_CONTEXT;
default: return TARGET_USER;
}
};
export const WiredActionGiveVariableView: FC<{}> = () =>
{
const { trigger = null, furniIds = [], actionDelay = 0, setActionDelay = null, setIntParams = null, setFurniIds = null, setStringParam = null } = useWired();
const { userVariableDefinitions = [], furniVariableDefinitions = [], contextVariableDefinitions = [] } = useWiredTools();
const [ selectedTargetType, setSelectedTargetType ] = useState<VariableTargetType>('user');
const [ selectedVariableToken, setSelectedVariableToken ] = useState('');
const [ overrideExisting, setOverrideExisting ] = useState(false);
const [ initialValueInput, setInitialValueInput ] = useState('0');
const [ userSource, setUserSource ] = useState(0);
const [ furniSource, setFurniSource ] = useState(0);
const targetDefinitions = useMemo(() =>
{
if(selectedTargetType === 'furni') return furniVariableDefinitions;
if(selectedTargetType === 'context') return contextVariableDefinitions;
return userVariableDefinitions;
}, [ contextVariableDefinitions, furniVariableDefinitions, selectedTargetType, userVariableDefinitions ]);
const variableEntries = useMemo(() => buildWiredVariablePickerEntries(selectedTargetType, 'give', targetDefinitions), [ selectedTargetType, targetDefinitions ]);
const resolvedVariableEntries = useMemo(() =>
{
if(!selectedVariableToken) return variableEntries;
if(flattenWiredVariablePickerEntries(variableEntries).some(entry => (entry.token === selectedVariableToken))) return variableEntries;
const fallbackEntry = createFallbackVariableEntry(selectedTargetType, selectedVariableToken);
return fallbackEntry ? [ fallbackEntry, ...variableEntries ] : variableEntries;
}, [ selectedTargetType, selectedVariableToken, variableEntries ]);
const selectedVariableDefinition = useMemo(() => flattenWiredVariablePickerEntries(resolvedVariableEntries).find(entry => (entry.token === selectedVariableToken)) ?? null, [ resolvedVariableEntries, selectedVariableToken ]);
const availableUserSources = useAvailableUserSources(trigger, USER_SOURCES);
const orderedUserSources = useMemo(() => sortWiredSourceOptions(availableUserSources, 'users'), [ availableUserSources ]);
const orderedFurniSources = useMemo(() => sortWiredSourceOptions(FURNI_SOURCES, 'furni'), []);
const sourceOptions = ((selectedTargetType === 'user')
? orderedUserSources
: ((selectedTargetType === 'furni')
? orderedFurniSources
: []));
const selectedSourceValue = ((selectedTargetType === 'user') ? userSource : furniSource);
const resolvedSourceOptions = useMemo(() =>
{
if(selectedTargetType === 'context') return [];
if(sourceOptions.some(option => (option.value === selectedSourceValue))) return sourceOptions;
const fallbackOptions = ((selectedTargetType === 'user')
? sortWiredSourceOptions([ ...USER_SOURCES, CLICKED_USER_SOURCE ], 'users')
: orderedFurniSources);
const fallbackOption = fallbackOptions.find(option => (option.value === selectedSourceValue));
if(!fallbackOption) return sourceOptions;
return [ ...sourceOptions, fallbackOption ];
}, [ orderedFurniSources, selectedSourceValue, selectedTargetType, sourceOptions ]);
const selectedSourceIndex = resolvedSourceOptions.findIndex(option => (option.value === selectedSourceValue));
const selectedSourceOption = (selectedSourceIndex >= 0) ? resolvedSourceOptions[selectedSourceIndex] : null;
const handleTargetTypeChange = (value: VariableTargetType) =>
{
if(value === selectedTargetType) return;
setSelectedTargetType(value);
setSelectedVariableToken('');
};
useEffect(() =>
{
if(!trigger) return;
const parsedVariableItemId = parseInt((trigger.stringData || '').trim(), 10);
const nextTargetType = normalizeTargetType((trigger.intData.length > 0) ? trigger.intData[0] : TARGET_USER);
setSelectedTargetType(nextTargetType);
setSelectedVariableToken(normalizeVariableTokenFromWire((!Number.isNaN(parsedVariableItemId) && (parsedVariableItemId > 0))
? String(parsedVariableItemId)
: ((nextTargetType === 'user') && (trigger.selectedItems?.length ?? 0) > 0)
? String(trigger.selectedItems[0])
: ''));
setOverrideExisting((trigger.intData.length > 1) ? (trigger.intData[1] === 1) : false);
setInitialValueInput(((trigger.intData.length > 2) ? trigger.intData[2] : 0).toString());
setUserSource((trigger.intData.length > 3) ? trigger.intData[3] : 0);
setFurniSource((trigger.intData.length > 4) ? trigger.intData[4] : ((trigger.selectedItems?.length ?? 0) > 0 ? SOURCE_SELECTED : 0));
}, [ trigger ]);
useEffect(() =>
{
if(!selectedVariableDefinition) return;
if(selectedVariableDefinition.hasValue) return;
setInitialValueInput('0');
}, [ selectedVariableDefinition ]);
const save = () =>
{
const targetValue = getTargetValue(selectedTargetType);
const parsedInitialValue = parseInt(initialValueInput.trim(), 10);
const variableItemId = getCustomVariableItemId(selectedVariableToken);
setStringParam(variableItemId ? String(variableItemId) : '');
setIntParams([ targetValue, overrideExisting ? 1 : 0, Number.isFinite(parsedInitialValue) ? parsedInitialValue : 0, userSource, furniSource ]);
setFurniIds((selectedTargetType === 'furni' && furniSource === SOURCE_SELECTED) ? [ ...furniIds ] : []);
};
const validate = () => (getCustomVariableItemId(selectedVariableToken) > 0);
const requiresFurni = (selectedTargetType === 'furni')
? WiredFurniType.STUFF_SELECTION_OPTION_BY_ID_BY_TYPE_OR_FROM_CONTEXT
: WiredFurniType.STUFF_SELECTION_OPTION_NONE;
const missingVariablesText = (() =>
{
switch(selectedTargetType)
{
case 'furni': return 'No wf_var_furni variables found in this room.';
case 'context': return 'No wf_var_context variables found in this room.';
default: return 'No wf_var_user variables found in this room.';
}
})();
const cycleSource = (direction: number) =>
{
if(!resolvedSourceOptions.length) return;
const currentIndex = (selectedSourceIndex >= 0) ? selectedSourceIndex : 0;
const nextIndex = (currentIndex + direction + resolvedSourceOptions.length) % resolvedSourceOptions.length;
const nextSourceValue = resolvedSourceOptions[nextIndex].value;
if(selectedTargetType === 'user')
{
setUserSource(nextSourceValue);
return;
}
if(selectedTargetType === 'furni')
{
setFurniSource(nextSourceValue);
}
};
return (
<WiredActionBaseView
hasSpecialInput={ true }
requiresFurni={ requiresFurni }
save={ save }
validate={ validate }
cardStyle={ { width: 244 } }
hideDelay={ true }>
<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"
className={ `nitro-wired__give-var-target nitro-wired__give-var-target--${ button.key } ${ selectedTargetType === button.key ? 'is-active' : '' }` }
onClick={ () => handleTargetTypeChange(button.key) }>
<img src={ button.icon } alt={ button.key } />
</button>
)) }
</div>
</div>
<>
<WiredVariablePicker
entries={ resolvedVariableEntries }
recentScope="variable-effects"
selectedToken={ selectedVariableToken }
onSelect={ entry => setSelectedVariableToken(entry.token) } />
{ !targetDefinitions.length && <Text small>{ missingVariablesText }</Text> }
<label className="nitro-wired__give-var-checkbox">
<input checked={ overrideExisting } className="form-check-input" type="checkbox" onChange={ event => setOverrideExisting(event.target.checked) } />
<Text>{ LocalizeText('wiredfurni.params.variables.value_settings.override_existing') }</Text>
</label>
<div className="nitro-wired__divider" />
<div className="nitro-wired__give-var-section">
<div className="nitro-wired__give-var-section-title">{ LocalizeText('wiredfurni.params.variables.value_settings') }</div>
<div className="nitro-wired__give-var-input-row">
<Text>{ LocalizeText('wiredfurni.params.variables.value_settings.initial_value') }</Text>
<NitroInput
className={ `nitro-wired__give-var-number ${ !selectedVariableDefinition?.hasValue ? 'nitro-wired__give-var-number--blurred' : '' }` }
readOnly={ !selectedVariableDefinition?.hasValue }
type="number"
value={ initialValueInput }
onChange={ event => setInitialValueInput(event.target.value) } />
</div>
</div>
<div className="nitro-wired__divider" />
<div className="nitro-wired__give-var-section">
<div className="nitro-wired__give-var-section-title">{ LocalizeText('wiredfurni.params.delay', [ 'seconds' ], [ GetWiredTimeLocale(actionDelay) ]) }</div>
<Slider
max={ 20 }
min={ 0 }
value={ actionDelay }
onChange={ event => setActionDelay(event) } />
</div>
{ selectedTargetType !== 'context' &&
<>
<div className="nitro-wired__divider" />
<div className="nitro-wired__give-var-section">
<div className="nitro-wired__give-var-section-title">{ 'Destinazione variabile:' }</div>
<div className="flex items-center gap-1">
<Button
disabled={ resolvedSourceOptions.length <= 1 }
variant="primary"
classNames={ [ 'nitro-wired__picker-button' ] }
className="px-2 py-1"
onClick={ () => cycleSource(-1) }>
<FaChevronLeft />
</Button>
<div className="flex min-w-0 flex-1 items-center justify-center nitro-wired__picker-label">
<Text small className="text-center">{ selectedSourceOption ? LocalizeText(selectedSourceOption.label) : '-' }</Text>
</div>
<Button
disabled={ resolvedSourceOptions.length <= 1 }
variant="primary"
classNames={ [ 'nitro-wired__picker-button' ] }
className="px-2 py-1"
onClick={ () => cycleSource(1) }>
<FaChevronRight />
</Button>
</div>
</div>
</> }
</>
</div>
</WiredActionBaseView>
);
};
@@ -2,6 +2,9 @@ import { WiredActionLayoutCode } from '../../../../api';
import { WiredActionBotChangeFigureView } from './WiredActionBotChangeFigureView'; import { WiredActionBotChangeFigureView } from './WiredActionBotChangeFigureView';
import { WiredActionAdjustClockView } from './WiredActionAdjustClockView'; import { WiredActionAdjustClockView } from './WiredActionAdjustClockView';
import { WiredActionFreezeView } from './WiredActionFreezeView'; import { WiredActionFreezeView } from './WiredActionFreezeView';
import { WiredActionGiveVariableView } from './WiredActionGiveVariableView';
import { WiredActionChangeVariableValueView } from './WiredActionChangeVariableValueView';
import { WiredActionRemoveVariableView } from './WiredActionRemoveVariableView';
import { WiredActionControlClockView } from './WiredActionControlClockView'; import { WiredActionControlClockView } from './WiredActionControlClockView';
import { WiredActionFurniToFurniView } from './WiredActionFurniToFurniView'; import { WiredActionFurniToFurniView } from './WiredActionFurniToFurniView';
import { WiredActionSetAltitudeView } from './WiredActionSetAltitudeView'; import { WiredActionSetAltitudeView } from './WiredActionSetAltitudeView';
@@ -13,6 +16,7 @@ import { WiredSelectorFurniAltitudeView } from '../selectors/WiredSelectorFurniA
import { WiredSelectorFurniOnFurniView } from '../selectors/WiredSelectorFurniOnFurniView'; import { WiredSelectorFurniOnFurniView } from '../selectors/WiredSelectorFurniOnFurniView';
import { WiredSelectorFurniPicksView } from '../selectors/WiredSelectorFurniPicksView'; import { WiredSelectorFurniPicksView } from '../selectors/WiredSelectorFurniPicksView';
import { WiredSelectorFurniSignalView } from '../selectors/WiredSelectorFurniSignalView'; import { WiredSelectorFurniSignalView } from '../selectors/WiredSelectorFurniSignalView';
import { WiredSelectorFurniWithVariableView } from '../selectors/WiredSelectorFurniWithVariableView';
import { WiredSelectorUsersAreaView } from '../selectors/WiredSelectorUsersAreaView'; import { WiredSelectorUsersAreaView } from '../selectors/WiredSelectorUsersAreaView';
import { WiredSelectorUsersByTypeView } from '../selectors/WiredSelectorUsersByTypeView'; import { WiredSelectorUsersByTypeView } from '../selectors/WiredSelectorUsersByTypeView';
import { WiredSelectorUsersByActionView } from '../selectors/WiredSelectorUsersByActionView'; import { WiredSelectorUsersByActionView } from '../selectors/WiredSelectorUsersByActionView';
@@ -23,6 +27,7 @@ import { WiredSelectorUsersHandItemView } from '../selectors/WiredSelectorUsersH
import { WiredSelectorUsersNeighborhoodView } from '../selectors/WiredSelectorUsersNeighborhoodView'; import { WiredSelectorUsersNeighborhoodView } from '../selectors/WiredSelectorUsersNeighborhoodView';
import { WiredSelectorUsersSignalView } from '../selectors/WiredSelectorUsersSignalView'; import { WiredSelectorUsersSignalView } from '../selectors/WiredSelectorUsersSignalView';
import { WiredSelectorUsersTeamView } from '../selectors/WiredSelectorUsersTeamView'; import { WiredSelectorUsersTeamView } from '../selectors/WiredSelectorUsersTeamView';
import { WiredSelectorUsersWithVariableView } from '../selectors/WiredSelectorUsersWithVariableView';
import { WiredActionBotFollowAvatarView } from './WiredActionBotFollowAvatarView'; import { WiredActionBotFollowAvatarView } from './WiredActionBotFollowAvatarView';
import { WiredActionBotGiveHandItemView } from './WiredActionBotGiveHandItemView'; import { WiredActionBotGiveHandItemView } from './WiredActionBotGiveHandItemView';
import { WiredActionBotMoveView } from './WiredActionBotMoveView'; import { WiredActionBotMoveView } from './WiredActionBotMoveView';
@@ -51,7 +56,9 @@ import { WiredActionTeleportView } from './WiredActionTeleportView';
import { WiredActionToggleFurniStateView } from './WiredActionToggleFurniStateView'; import { WiredActionToggleFurniStateView } from './WiredActionToggleFurniStateView';
import { WiredActionUnfreezeView } from './WiredActionUnfreezeView'; import { WiredActionUnfreezeView } from './WiredActionUnfreezeView';
import { WiredExtraFilterFurniView } from '../extras/WiredExtraFilterFurniView'; import { WiredExtraFilterFurniView } from '../extras/WiredExtraFilterFurniView';
import { WiredExtraFilterFurniByVariableView } from '../extras/WiredExtraFilterFurniByVariableView';
import { WiredExtraFilterUserView } from '../extras/WiredExtraFilterUserView'; import { WiredExtraFilterUserView } from '../extras/WiredExtraFilterUserView';
import { WiredExtraFilterUsersByVariableView } from '../extras/WiredExtraFilterUsersByVariableView';
import { WiredExtraAnimationTimeView } from '../extras/WiredExtraAnimationTimeView'; import { WiredExtraAnimationTimeView } from '../extras/WiredExtraAnimationTimeView';
import { WiredExtraMoveCarryUsersView } from '../extras/WiredExtraMoveCarryUsersView'; import { WiredExtraMoveCarryUsersView } from '../extras/WiredExtraMoveCarryUsersView';
import { WiredExtraExecuteInOrderView } from '../extras/WiredExtraExecuteInOrderView'; import { WiredExtraExecuteInOrderView } from '../extras/WiredExtraExecuteInOrderView';
@@ -62,7 +69,17 @@ import { WiredExtraMovePhysicsView } from '../extras/WiredExtraMovePhysicsView';
import { WiredExtraRandomView } from '../extras/WiredExtraRandomView'; import { WiredExtraRandomView } from '../extras/WiredExtraRandomView';
import { WiredExtraTextOutputFurniNameView } from '../extras/WiredExtraTextOutputFurniNameView'; import { WiredExtraTextOutputFurniNameView } from '../extras/WiredExtraTextOutputFurniNameView';
import { WiredExtraTextOutputUsernameView } from '../extras/WiredExtraTextOutputUsernameView'; import { WiredExtraTextOutputUsernameView } from '../extras/WiredExtraTextOutputUsernameView';
import { WiredExtraTextOutputVariableView } from '../extras/WiredExtraTextOutputVariableView';
import { WiredExtraFurniVariableView } from '../extras/WiredExtraFurniVariableView';
import { WiredExtraRoomVariableView } from '../extras/WiredExtraRoomVariableView';
import { WiredExtraContextVariableView } from '../extras/WiredExtraContextVariableView';
import { WiredExtraUserVariableView } from '../extras/WiredExtraUserVariableView';
import { WiredExtraUnseenView } from '../extras/WiredExtraUnseenView'; import { WiredExtraUnseenView } from '../extras/WiredExtraUnseenView';
import { WiredExtraTextInputVariableView } from '../extras/WiredExtraTextInputVariableView';
import { WiredExtraVariableLevelUpSystemView } from '../extras/WiredExtraVariableLevelUpSystemView';
import { WiredExtraVariableEchoView } from '../extras/WiredExtraVariableEchoView';
import { WiredExtraVariableReferenceView } from '../extras/WiredExtraVariableReferenceView';
import { WiredExtraVariableTextConnectorView } from '../extras/WiredExtraVariableTextConnectorView';
export const WiredActionLayoutView = (code: number) => export const WiredActionLayoutView = (code: number) =>
{ {
@@ -106,6 +123,12 @@ export const WiredActionLayoutView = (code: number) =>
return <WiredActionGiveRewardView />; return <WiredActionGiveRewardView />;
case WiredActionLayoutCode.GIVE_SCORE: case WiredActionLayoutCode.GIVE_SCORE:
return <WiredActionGiveScoreView />; return <WiredActionGiveScoreView />;
case WiredActionLayoutCode.GIVE_VARIABLE:
return <WiredActionGiveVariableView />;
case WiredActionLayoutCode.CHANGE_VARIABLE_VALUE:
return <WiredActionChangeVariableValueView />;
case WiredActionLayoutCode.REMOVE_VARIABLE:
return <WiredActionRemoveVariableView />;
case WiredActionLayoutCode.GIVE_SCORE_TO_PREDEFINED_TEAM: case WiredActionLayoutCode.GIVE_SCORE_TO_PREDEFINED_TEAM:
return <WiredActionGiveScoreToPredefinedTeamView />; return <WiredActionGiveScoreToPredefinedTeamView />;
case WiredActionLayoutCode.JOIN_TEAM: case WiredActionLayoutCode.JOIN_TEAM:
@@ -152,6 +175,8 @@ export const WiredActionLayoutView = (code: number) =>
return <WiredSelectorFurniPicksView />; return <WiredSelectorFurniPicksView />;
case WiredActionLayoutCode.FURNI_SIGNAL_SELECTOR: case WiredActionLayoutCode.FURNI_SIGNAL_SELECTOR:
return <WiredSelectorFurniSignalView />; return <WiredSelectorFurniSignalView />;
case WiredActionLayoutCode.FURNI_WITH_VARIABLE_SELECTOR:
return <WiredSelectorFurniWithVariableView />;
case WiredActionLayoutCode.USERS_AREA_SELECTOR: case WiredActionLayoutCode.USERS_AREA_SELECTOR:
return <WiredSelectorUsersAreaView />; return <WiredSelectorUsersAreaView />;
case WiredActionLayoutCode.USERS_NEIGHBORHOOD_SELECTOR: case WiredActionLayoutCode.USERS_NEIGHBORHOOD_SELECTOR:
@@ -172,10 +197,16 @@ export const WiredActionLayoutView = (code: number) =>
return <WiredSelectorUsersHandItemView />; return <WiredSelectorUsersHandItemView />;
case WiredActionLayoutCode.USERS_TEAM_SELECTOR: case WiredActionLayoutCode.USERS_TEAM_SELECTOR:
return <WiredSelectorUsersTeamView />; return <WiredSelectorUsersTeamView />;
case WiredActionLayoutCode.USERS_WITH_VARIABLE_SELECTOR:
return <WiredSelectorUsersWithVariableView />;
case WiredActionLayoutCode.FILTER_FURNI_EXTRA: case WiredActionLayoutCode.FILTER_FURNI_EXTRA:
return <WiredExtraFilterFurniView />; return <WiredExtraFilterFurniView />;
case WiredActionLayoutCode.FILTER_USER_EXTRA: case WiredActionLayoutCode.FILTER_USER_EXTRA:
return <WiredExtraFilterUserView />; return <WiredExtraFilterUserView />;
case WiredActionLayoutCode.FILTER_USERS_BY_VARIABLE_EXTRA:
return <WiredExtraFilterUsersByVariableView />;
case WiredActionLayoutCode.FILTER_FURNI_BY_VARIABLE_EXTRA:
return <WiredExtraFilterFurniByVariableView />;
case WiredActionLayoutCode.MOVE_CARRY_USERS_EXTRA: case WiredActionLayoutCode.MOVE_CARRY_USERS_EXTRA:
return <WiredExtraMoveCarryUsersView />; return <WiredExtraMoveCarryUsersView />;
case WiredActionLayoutCode.MOVE_NO_ANIMATION_EXTRA: case WiredActionLayoutCode.MOVE_NO_ANIMATION_EXTRA:
@@ -198,6 +229,26 @@ export const WiredActionLayoutView = (code: number) =>
return <WiredExtraTextOutputUsernameView />; return <WiredExtraTextOutputUsernameView />;
case WiredActionLayoutCode.TEXT_OUTPUT_FURNI_NAME_EXTRA: case WiredActionLayoutCode.TEXT_OUTPUT_FURNI_NAME_EXTRA:
return <WiredExtraTextOutputFurniNameView />; return <WiredExtraTextOutputFurniNameView />;
case WiredActionLayoutCode.VARIABLE_TEXT_CONNECTOR_EXTRA:
return <WiredExtraVariableTextConnectorView />;
case WiredActionLayoutCode.TEXT_OUTPUT_VARIABLE_EXTRA:
return <WiredExtraTextOutputVariableView />;
case WiredActionLayoutCode.USER_VARIABLE_EXTRA:
return <WiredExtraUserVariableView />;
case WiredActionLayoutCode.FURNI_VARIABLE_EXTRA:
return <WiredExtraFurniVariableView />;
case WiredActionLayoutCode.ROOM_VARIABLE_EXTRA:
return <WiredExtraRoomVariableView />;
case WiredActionLayoutCode.CONTEXT_VARIABLE_EXTRA:
return <WiredExtraContextVariableView />;
case WiredActionLayoutCode.VARIABLE_REFERENCE_EXTRA:
return <WiredExtraVariableReferenceView />;
case WiredActionLayoutCode.VARIABLE_LEVELUP_SYSTEM_EXTRA:
return <WiredExtraVariableLevelUpSystemView />;
case WiredActionLayoutCode.VARIABLE_ECHO_EXTRA:
return <WiredExtraVariableEchoView />;
case WiredActionLayoutCode.TEXT_INPUT_VARIABLE_EXTRA:
return <WiredExtraTextInputVariableView />;
case WiredActionLayoutCode.SEND_SIGNAL: case WiredActionLayoutCode.SEND_SIGNAL:
return <WiredActionSendSignalView />; return <WiredActionSendSignalView />;
} }
@@ -0,0 +1,249 @@
import { FC, useEffect, useMemo, useState } from 'react';
import { FaChevronLeft, FaChevronRight } from 'react-icons/fa';
import { GetWiredTimeLocale, 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 userVariableIcon from '../../../../assets/images/wired/var/icon_source_user.png';
import { Button, Slider, Text } from '../../../../common';
import { useWired, useWiredTools } from '../../../../hooks';
import { CLICKED_USER_SOURCE, FURNI_SOURCES, sortWiredSourceOptions, USER_SOURCES, useAvailableUserSources } from '../WiredSourcesSelector';
import { WiredActionBaseView } from './WiredActionBaseView';
import { WiredVariablePicker } from '../WiredVariablePicker';
import { buildWiredVariablePickerEntries, createFallbackVariableEntry, flattenWiredVariablePickerEntries, getCustomVariableItemId, normalizeVariableTokenFromWire } from '../WiredVariablePickerData';
type VariableTargetType = 'user' | 'furni' | 'context';
const TARGET_USER = 0;
const TARGET_FURNI = 1;
const TARGET_CONTEXT = 2;
const SOURCE_SELECTED = 100;
const TARGET_BUTTONS: Array<{ key: VariableTargetType; icon: string; }> = [
{ key: 'furni', icon: furniVariableIcon },
{ key: 'user', icon: userVariableIcon },
{ key: 'context', icon: contextVariableIcon }
];
const normalizeTargetType = (value: number): VariableTargetType =>
{
switch(value)
{
case TARGET_FURNI: return 'furni';
case TARGET_CONTEXT: return 'context';
default: return 'user';
}
};
const getTargetValue = (value: VariableTargetType) =>
{
switch(value)
{
case 'furni': return TARGET_FURNI;
case 'context': return TARGET_CONTEXT;
default: return TARGET_USER;
}
};
export const WiredActionRemoveVariableView: FC<{}> = () =>
{
const { trigger = null, furniIds = [], actionDelay = 0, setActionDelay = null, setIntParams = null, setFurniIds = null, setStringParam = null } = useWired();
const { userVariableDefinitions = [], furniVariableDefinitions = [], contextVariableDefinitions = [] } = useWiredTools();
const [ selectedTargetType, setSelectedTargetType ] = useState<VariableTargetType>('user');
const [ selectedVariableToken, setSelectedVariableToken ] = useState('');
const [ userSource, setUserSource ] = useState(0);
const [ furniSource, setFurniSource ] = useState(0);
const targetDefinitions = useMemo(() =>
{
const definitions = (() =>
{
switch(selectedTargetType)
{
case 'furni': return furniVariableDefinitions;
case 'context': return contextVariableDefinitions;
default: return userVariableDefinitions;
}
})();
return definitions;
}, [ contextVariableDefinitions, furniVariableDefinitions, selectedTargetType, userVariableDefinitions ]);
const variableEntries = useMemo(() => buildWiredVariablePickerEntries(selectedTargetType, 'remove', targetDefinitions), [ selectedTargetType, targetDefinitions ]);
const resolvedVariableEntries = useMemo(() =>
{
if(!selectedVariableToken) return variableEntries;
if(flattenWiredVariablePickerEntries(variableEntries).some(entry => (entry.token === selectedVariableToken))) return variableEntries;
const fallbackEntry = createFallbackVariableEntry(selectedTargetType, selectedVariableToken);
return fallbackEntry ? [ fallbackEntry, ...variableEntries ] : variableEntries;
}, [ selectedTargetType, selectedVariableToken, variableEntries ]);
const availableUserSources = useAvailableUserSources(trigger, USER_SOURCES);
const orderedUserSources = useMemo(() => sortWiredSourceOptions(availableUserSources, 'users'), [ availableUserSources ]);
const orderedFurniSources = useMemo(() => sortWiredSourceOptions(FURNI_SOURCES, 'furni'), []);
const sourceOptions = ((selectedTargetType === 'user')
? orderedUserSources
: ((selectedTargetType === 'furni')
? orderedFurniSources
: []));
const selectedSourceValue = ((selectedTargetType === 'user') ? userSource : furniSource);
const resolvedSourceOptions = useMemo(() =>
{
if(selectedTargetType === 'context') return [];
if(sourceOptions.some(option => (option.value === selectedSourceValue))) return sourceOptions;
const fallbackOptions = ((selectedTargetType === 'user')
? sortWiredSourceOptions([ ...USER_SOURCES, CLICKED_USER_SOURCE ], 'users')
: orderedFurniSources);
const fallbackOption = fallbackOptions.find(option => (option.value === selectedSourceValue));
if(!fallbackOption) return sourceOptions;
return [ ...sourceOptions, fallbackOption ];
}, [ orderedFurniSources, selectedSourceValue, selectedTargetType, sourceOptions ]);
const selectedSourceIndex = resolvedSourceOptions.findIndex(option => (option.value === selectedSourceValue));
const selectedSourceOption = (selectedSourceIndex >= 0) ? resolvedSourceOptions[selectedSourceIndex] : null;
const handleTargetTypeChange = (value: VariableTargetType) =>
{
if(value === selectedTargetType) return;
setSelectedTargetType(value);
setSelectedVariableToken('');
};
useEffect(() =>
{
if(!trigger) return;
const parsedVariableItemId = parseInt((trigger.stringData || '').trim(), 10);
const nextTargetType = normalizeTargetType((trigger.intData.length > 0) ? trigger.intData[0] : TARGET_USER);
setSelectedTargetType(nextTargetType);
setSelectedVariableToken(normalizeVariableTokenFromWire((!Number.isNaN(parsedVariableItemId) && (parsedVariableItemId > 0)) ? String(parsedVariableItemId) : ''));
setUserSource((trigger.intData.length > 1) ? trigger.intData[1] : 0);
setFurniSource((trigger.intData.length > 2) ? trigger.intData[2] : ((trigger.selectedItems?.length ?? 0) > 0 ? SOURCE_SELECTED : 0));
}, [ trigger ]);
const save = () =>
{
const targetValue = getTargetValue(selectedTargetType);
const variableItemId = getCustomVariableItemId(selectedVariableToken);
setStringParam(variableItemId ? String(variableItemId) : '');
setIntParams([ targetValue, userSource, furniSource ]);
setFurniIds((selectedTargetType === 'furni' && furniSource === SOURCE_SELECTED) ? [ ...furniIds ] : []);
};
const validate = () => (getCustomVariableItemId(selectedVariableToken) > 0);
const requiresFurni = (selectedTargetType === 'furni')
? WiredFurniType.STUFF_SELECTION_OPTION_BY_ID_BY_TYPE_OR_FROM_CONTEXT
: WiredFurniType.STUFF_SELECTION_OPTION_NONE;
const missingVariablesText = (() =>
{
switch(selectedTargetType)
{
case 'furni': return 'No wf_var_furni variables found in this room.';
case 'context': return 'No wf_var_context variables found in this room.';
default: return 'No wf_var_user variables found in this room.';
}
})();
const cycleSource = (direction: number) =>
{
if(!resolvedSourceOptions.length) return;
const currentIndex = (selectedSourceIndex >= 0) ? selectedSourceIndex : 0;
const nextIndex = (currentIndex + direction + resolvedSourceOptions.length) % resolvedSourceOptions.length;
const nextSourceValue = resolvedSourceOptions[nextIndex].value;
if(selectedTargetType === 'user')
{
setUserSource(nextSourceValue);
return;
}
if(selectedTargetType === 'furni')
{
setFurniSource(nextSourceValue);
}
};
return (
<WiredActionBaseView
hasSpecialInput={ true }
requiresFurni={ requiresFurni }
save={ save }
validate={ validate }
cardStyle={ { width: 244 } }
hideDelay={ true }>
<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"
className={ `nitro-wired__give-var-target nitro-wired__give-var-target--${ button.key } ${ selectedTargetType === button.key ? 'is-active' : '' }` }
onClick={ () => handleTargetTypeChange(button.key) }>
<img src={ button.icon } alt={ button.key } />
</button>
)) }
</div>
</div>
<WiredVariablePicker
entries={ resolvedVariableEntries }
recentScope="variable-effects"
selectedToken={ selectedVariableToken }
onSelect={ entry => setSelectedVariableToken(entry.token) } />
{ !targetDefinitions.length && <Text small>{ missingVariablesText }</Text> }
<div className="nitro-wired__divider" />
<div className="nitro-wired__give-var-section">
<div className="nitro-wired__give-var-section-title">{ LocalizeText('wiredfurni.params.delay', [ 'seconds' ], [ GetWiredTimeLocale(actionDelay) ]) }</div>
<Slider
max={ 20 }
min={ 0 }
value={ actionDelay }
onChange={ event => setActionDelay(event) } />
</div>
{ selectedTargetType !== 'context' &&
<>
<div className="nitro-wired__divider" />
<div className="nitro-wired__give-var-section">
<div className="nitro-wired__give-var-section-title">{ 'Fonte variabile:' }</div>
<div className="flex items-center gap-1">
<Button
disabled={ resolvedSourceOptions.length <= 1 }
variant="primary"
classNames={ [ 'nitro-wired__picker-button' ] }
className="px-2 py-1"
onClick={ () => cycleSource(-1) }>
<FaChevronLeft />
</Button>
<div className="flex min-w-0 flex-1 items-center justify-center nitro-wired__picker-label">
<Text small className="text-center">{ selectedSourceOption ? LocalizeText(selectedSourceOption.label) : '-' }</Text>
</div>
<Button
disabled={ resolvedSourceOptions.length <= 1 }
variant="primary"
classNames={ [ 'nitro-wired__picker-button' ] }
className="px-2 py-1"
onClick={ () => cycleSource(1) }>
<FaChevronRight />
</Button>
</div>
</div>
</> }
</div>
</WiredActionBaseView>
);
};
@@ -6,8 +6,6 @@ import { WiredFurniSelectionSourceRow } from '../WiredFurniSelectionSourceRow';
import { FURNI_SOURCES, USER_SOURCES } from '../WiredSourcesSelector'; import { FURNI_SOURCES, USER_SOURCES } from '../WiredSourcesSelector';
import { WiredActionBaseView } from './WiredActionBaseView'; import { WiredActionBaseView } from './WiredActionBaseView';
const ANTENNA_INTERACTION_TYPES = [ 'antenna' ];
const SOURCE_TRIGGER = 0; const SOURCE_TRIGGER = 0;
const SOURCE_SELECTED = 100; const SOURCE_SELECTED = 100;
@@ -51,7 +49,7 @@ export const WiredActionSendSignalView: FC<{}> = () =>
const [ selectionMode, setSelectionMode ] = useState<SelectionMode>('antenna'); const [ selectionMode, setSelectionMode ] = useState<SelectionMode>('antenna');
const highlightedIds = useRef<number[]>([]); const highlightedIds = useRef<number[]>([]);
const { trigger = null, furniIds = [], setFurniIds = null, setIntParams = null, setStringParam = null, setAllowedInteractionTypes = null } = useWired(); const { trigger = null, furniIds = [], setFurniIds = null, setIntParams = null, setStringParam = null } = useWired();
useEffect(() => useEffect(() =>
{ {
@@ -72,14 +70,6 @@ export const WiredActionSendSignalView: FC<{}> = () =>
setSelectionMode('antenna'); setSelectionMode('antenna');
}, [ trigger ]); }, [ trigger ]);
useEffect(() =>
{
if(selectionMode === 'antenna') setAllowedInteractionTypes(ANTENNA_INTERACTION_TYPES);
else setAllowedInteractionTypes(null);
return () => setAllowedInteractionTypes(null);
}, [ selectionMode, setAllowedInteractionTypes ]);
useEffect(() => useEffect(() =>
{ {
if(selectionMode === 'antenna') setAntennaIds(furniIds); if(selectionMode === 'antenna') setAntennaIds(furniIds);
@@ -1,4 +1,4 @@
import { FC, PropsWithChildren, ReactNode } from 'react'; import { CSSProperties, FC, PropsWithChildren, ReactNode } from 'react';
import { WiredFurniType } from '../../../../api'; import { WiredFurniType } from '../../../../api';
import { WiredBaseView } from '../WiredBaseView'; import { WiredBaseView } from '../WiredBaseView';
@@ -7,6 +7,8 @@ export interface WiredConditionBaseViewProps
hasSpecialInput: boolean; hasSpecialInput: boolean;
requiresFurni: number; requiresFurni: number;
save: () => void; save: () => void;
validate?: () => boolean;
cardStyle?: CSSProperties;
footer?: ReactNode; footer?: ReactNode;
footerCollapsible?: boolean; footerCollapsible?: boolean;
selectionPreview?: ReactNode; selectionPreview?: ReactNode;
@@ -14,12 +16,12 @@ export interface WiredConditionBaseViewProps
export const WiredConditionBaseView: FC<PropsWithChildren<WiredConditionBaseViewProps>> = props => export const WiredConditionBaseView: FC<PropsWithChildren<WiredConditionBaseViewProps>> = props =>
{ {
const { requiresFurni = WiredFurniType.STUFF_SELECTION_OPTION_NONE, save = null, hasSpecialInput = false, children = null, footer = null, footerCollapsible = true, selectionPreview = null } = props; const { requiresFurni = WiredFurniType.STUFF_SELECTION_OPTION_NONE, save = null, validate = null, cardStyle = undefined, hasSpecialInput = false, children = null, footer = null, footerCollapsible = true, selectionPreview = null } = props;
const onSave = () => (save && save()); const onSave = () => (save && save());
return ( return (
<WiredBaseView hasSpecialInput={ hasSpecialInput } requiresFurni={ requiresFurni } save={ onSave } wiredType="condition" footer={ footer } footerCollapsible={ footerCollapsible } selectionPreview={ selectionPreview }> <WiredBaseView hasSpecialInput={ hasSpecialInput } requiresFurni={ requiresFurni } save={ onSave } validate={ validate } cardStyle={ cardStyle } wiredType="condition" footer={ footer } footerCollapsible={ footerCollapsible } selectionPreview={ selectionPreview }>
{ children } { children }
</WiredBaseView> </WiredBaseView>
); );
@@ -0,0 +1,241 @@
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 userVariableIcon from '../../../../assets/images/wired/var/icon_source_user.png';
import { Text } from '../../../../common';
import { useWired, useWiredTools } from '../../../../hooks';
import { WiredFurniSelectionSourceRow } from '../WiredFurniSelectionSourceRow';
import { WiredVariablePicker } from '../WiredVariablePicker';
import { buildWiredVariablePickerEntries, createFallbackVariableEntry, flattenWiredVariablePickerEntries, normalizeVariableTokenFromWire } from '../WiredVariablePickerData';
import { FURNI_SOURCES, sortWiredSourceOptions, USER_SOURCES, useAvailableUserSources } from '../WiredSourcesSelector';
import { WiredConditionBaseView } from './WiredConditionBaseView';
interface WiredConditionHasVariableViewProps
{
negative?: boolean;
}
type ConditionVariableTargetType = 'user' | 'furni' | 'context';
interface IVariableDefinition
{
availability: number;
hasValue: boolean;
itemId: number;
name: string;
}
const TARGET_USER = 0;
const TARGET_FURNI = 1;
const TARGET_CONTEXT = 2;
const SOURCE_TRIGGER = 0;
const SOURCE_SELECTED = 100;
const QUANTIFIER_ALL = 0;
const QUANTIFIER_ANY = 1;
const TARGET_BUTTONS: Array<{ key: ConditionVariableTargetType; icon: string; disabled?: boolean; }> = [
{ key: 'furni', icon: furniVariableIcon },
{ key: 'user', icon: userVariableIcon },
{ key: 'context', icon: contextVariableIcon }
];
const CONTEXT_SOURCE_OPTIONS = [ { value: SOURCE_TRIGGER, label: 'Current execution' } ];
const getTargetValue = (value: ConditionVariableTargetType) =>
{
switch(value)
{
case 'furni': return TARGET_FURNI;
case 'context': return TARGET_CONTEXT;
default: return TARGET_USER;
}
};
const normalizeTargetType = (value: number): ConditionVariableTargetType =>
{
switch(value)
{
case TARGET_FURNI: return 'furni';
case TARGET_CONTEXT: return 'context';
default: return 'user';
}
};
const getTargetDefinitions = (targetType: ConditionVariableTargetType, userDefinitions: IVariableDefinition[], furniDefinitions: IVariableDefinition[], contextDefinitions: IVariableDefinition[]) =>
{
switch(targetType)
{
case 'furni': return furniDefinitions;
case 'context': return contextDefinitions;
default: return userDefinitions;
}
};
const getSourceTitle = (targetType: ConditionVariableTargetType) =>
{
switch(targetType)
{
case 'furni': return LocalizeText('wiredfurni.params.sources.furni.title');
case 'context': return LocalizeText('wiredfurni.params.sources.merged.title.variables');
default: return LocalizeText('wiredfurni.params.sources.users.title');
}
};
export const WiredConditionHasVariableView: FC<WiredConditionHasVariableViewProps> = ({ negative = false }) =>
{
const { trigger = null, furniIds = [], setFurniIds = null, setIntParams = null, setStringParam = null } = useWired();
const { userVariableDefinitions = [], furniVariableDefinitions = [], contextVariableDefinitions = [] } = useWiredTools();
const [ targetType, setTargetType ] = useState<ConditionVariableTargetType>('user');
const [ variableToken, setVariableToken ] = useState('');
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, contextVariableDefinitions), [ contextVariableDefinitions, furniVariableDefinitions, targetType, userVariableDefinitions ]);
const variableEntries = useMemo(() => buildWiredVariablePickerEntries(targetType, 'condition', 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 '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);
const nextUserSource = (intData.length > 1) ? intData[1] : SOURCE_TRIGGER;
const nextFurniSource = (intData.length > 2) ? intData[2] : (((trigger.selectedItems?.length ?? 0) > 0) ? SOURCE_SELECTED : SOURCE_TRIGGER);
const nextQuantifier = ((intData.length > 3) && (intData[3] === QUANTIFIER_ANY)) ? QUANTIFIER_ANY : QUANTIFIER_ALL;
setTargetType(nextTargetType);
setVariableToken(normalizeVariableTokenFromWire(trigger.stringData || ''));
setUserSource(nextUserSource);
setFurniSource(nextFurniSource);
setQuantifier(nextQuantifier);
}, [ trigger ]);
useEffect(() =>
{
if(targetType !== 'user') return;
if(orderedUserSources.some(option => (option.value === userSource))) return;
setUserSource(SOURCE_TRIGGER);
}, [ orderedUserSources, targetType, userSource ]);
const save = () =>
{
setStringParam(variableToken);
setIntParams([
getTargetValue(targetType),
userSource,
furniSource,
quantifier
]);
if(requiresFurni <= WiredFurniType.STUFF_SELECTION_OPTION_NONE) setFurniIds([]);
};
const validate = () => !!variableToken.length;
const handleTargetChange = (nextTargetType: ConditionVariableTargetType) =>
{
if(nextTargetType === targetType) return;
setTargetType(nextTargetType);
setVariableToken('');
};
return (
<WiredConditionBaseView
hasSpecialInput={ true }
requiresFurni={ requiresFurni }
save={ save }
validate={ validate }
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={ `wiredConditionHasVariableQuantifier-${ negative ? 'neg' : 'pos' }` } type="radio" onChange={ () => setQuantifier(value) } />
<Text>{ LocalizeText(`wiredfurni.params.quantifier.variables.${ value }`) }</Text>
</label>
)) }
</div>
<WiredFurniSelectionSourceRow
title={ getSourceTitle(targetType) }
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);
return;
}
} } />
</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>
</WiredConditionBaseView>
);
};
@@ -17,6 +17,9 @@ import { WiredConditionFurniHasFurniOnView } from './WiredConditionFurniHasFurni
import { WiredConditionFurniHasNotFurniOnView } from './WiredConditionFurniHasNotFurniOnView'; import { WiredConditionFurniHasNotFurniOnView } from './WiredConditionFurniHasNotFurniOnView';
import { WiredConditionFurniIsOfTypeView } from './WiredConditionFurniIsOfTypeView'; import { WiredConditionFurniIsOfTypeView } from './WiredConditionFurniIsOfTypeView';
import { WiredConditionFurniMatchesSnapshotView } from './WiredConditionFurniMatchesSnapshotView'; import { WiredConditionFurniMatchesSnapshotView } from './WiredConditionFurniMatchesSnapshotView';
import { WiredConditionHasVariableView } from './WiredConditionHasVariableView';
import { WiredConditionVariableAgeMatchView } from './WiredConditionVariableAgeMatchView';
import { WiredConditionVariableValueMatchView } from './WiredConditionVariableValueMatchView';
import { WiredConditionTimeElapsedLessView } from './WiredConditionTimeElapsedLessView'; import { WiredConditionTimeElapsedLessView } from './WiredConditionTimeElapsedLessView';
import { WiredConditionTimeElapsedMoreView } from './WiredConditionTimeElapsedMoreView'; import { WiredConditionTimeElapsedMoreView } from './WiredConditionTimeElapsedMoreView';
import { WiredConditionTeamHasRankView } from './WiredConditionTeamHasRankView'; import { WiredConditionTeamHasRankView } from './WiredConditionTeamHasRankView';
@@ -38,6 +41,14 @@ export const WiredConditionLayoutView = (code: number) =>
return <WiredConditionActorDirView />; return <WiredConditionActorDirView />;
case WiredConditionlayout.SLC_QUANTITY: case WiredConditionlayout.SLC_QUANTITY:
return <WiredConditionSelectionQuantityView />; return <WiredConditionSelectionQuantityView />;
case WiredConditionlayout.HAS_VAR:
return <WiredConditionHasVariableView />;
case WiredConditionlayout.NEG_HAS_VAR:
return <WiredConditionHasVariableView negative={ true } />;
case WiredConditionlayout.VAR_VAL_MATCH:
return <WiredConditionVariableValueMatchView />;
case WiredConditionlayout.VAR_AGE_MATCH:
return <WiredConditionVariableAgeMatchView />;
case WiredConditionlayout.TRIGGERER_MATCH: case WiredConditionlayout.TRIGGERER_MATCH:
return <WiredConditionTriggererMatchView />; return <WiredConditionTriggererMatchView />;
case WiredConditionlayout.NOT_TRIGGERER_MATCH: case WiredConditionlayout.NOT_TRIGGERER_MATCH:
@@ -0,0 +1,335 @@
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>
);
};
@@ -0,0 +1,500 @@
import { FC, useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { LocalizeText, WiredFurniType, WiredSelectionVisualizer } 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 { buildWiredVariablePickerEntries, createFallbackVariableEntry, flattenWiredVariablePickerEntries, normalizeVariableTokenFromWire } from '../WiredVariablePickerData';
import { CLICKED_USER_SOURCE, FURNI_SOURCES, sortWiredSourceOptions, USER_SOURCES, useAvailableUserSources, WiredSourceOption } from '../WiredSourcesSelector';
import { WiredConditionBaseView } from './WiredConditionBaseView';
type VariableTargetType = 'user' | 'furni' | 'global' | 'context';
type ReferenceMode = 'constant' | 'variable';
type SelectionMode = 'destination' | 'reference';
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 REFERENCE_CONSTANT = 0;
const REFERENCE_VARIABLE = 1;
const SOURCE_TRIGGER = 0;
const SOURCE_SELECTED = 100;
const SOURCE_SECONDARY_SELECTED = 101;
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 COMPARISON_OPTIONS = [
{ value: 0, label: '>' },
{ value: 1, label: '≥' },
{ value: 2, label: '=' },
{ value: 3, label: '≤' },
{ value: 4, label: '<' },
{ value: 5, label: '≠' }
];
const SECONDARY_FURNI_SOURCES: WiredSourceOption[] = sortWiredSourceOptions([
{ value: SOURCE_TRIGGER, label: 'wiredfurni.params.sources.furni.0' },
{ value: SOURCE_SECONDARY_SELECTED, label: 'wiredfurni.params.sources.furni.101' },
{ value: 200, label: 'wiredfurni.params.sources.furni.200' },
{ value: 201, label: 'wiredfurni.params.sources.furni.201' }
], 'furni');
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 parseIds = (value: string): number[] =>
{
if(!value?.length) return [];
const ids = new Set<number>();
for(const part of value.split(/[;,\t]/))
{
const parsedValue = parseInt(part.trim(), 10);
if(!Number.isNaN(parsedValue) && (parsedValue > 0)) ids.add(parsedValue);
}
return [ ...ids ];
};
const serializeIds = (ids: number[]) => (ids?.length ? ids.filter(id => (id > 0)).join(';') : '');
const parseStringData = (value: string) => (value?.length ? value.split('\t', -1) : []);
const serializeStringData = (destinationVariableToken: string, referenceVariableToken: string, referenceFurniIds: number[]) => `${ destinationVariableToken || '' }\t${ referenceVariableToken || '' }\t${ serializeIds(referenceFurniIds) }`;
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 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 resolveSourceOptions = (baseOptions: WiredSourceOption[], selectedValue: number, fallbackOptions: WiredSourceOption[]) =>
{
if(!baseOptions.length) return baseOptions;
if(baseOptions.some(option => (option.value === selectedValue))) return baseOptions;
const fallbackOption = fallbackOptions.find(option => (option.value === selectedValue));
if(!fallbackOption) return baseOptions;
return [ ...baseOptions, fallbackOption ];
};
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 isGlobalTarget = (targetType: VariableTargetType) => (targetType === 'global');
const isFurniTarget = (targetType: VariableTargetType) => (targetType === 'furni');
const isContextTarget = (targetType: VariableTargetType) => (targetType === 'context');
export const WiredConditionVariableValueMatchView: FC<{}> = () =>
{
const { trigger = null, furniIds = [], setAllowsFurni = null, setFurniIds = null, setIntParams = null, setStringParam = null } = useWired();
const { userVariableDefinitions = [], furniVariableDefinitions = [], roomVariableDefinitions = [], contextVariableDefinitions = [] } = useWiredTools();
const [ targetType, setTargetType ] = useState<VariableTargetType>('user');
const [ variableToken, setVariableToken ] = useState('');
const [ comparison, setComparison ] = useState(2);
const [ referenceMode, setReferenceMode ] = useState<ReferenceMode>('constant');
const [ referenceConstantValueInput, setReferenceConstantValueInput ] = useState('0');
const [ referenceTargetType, setReferenceTargetType ] = useState<VariableTargetType>('user');
const [ referenceVariableToken, setReferenceVariableToken ] = useState('');
const [ userSource, setUserSource ] = useState(SOURCE_TRIGGER);
const [ furniSource, setFurniSource ] = useState(SOURCE_TRIGGER);
const [ referenceUserSource, setReferenceUserSource ] = useState(SOURCE_TRIGGER);
const [ referenceFurniSource, setReferenceFurniSource ] = useState(SOURCE_TRIGGER);
const [ quantifier, setQuantifier ] = useState(QUANTIFIER_ALL);
const [ destinationFurniIds, setDestinationFurniIds ] = useState<number[]>([]);
const [ referenceFurniIds, setReferenceFurniIds ] = useState<number[]>([]);
const [ selectionMode, setSelectionMode ] = useState<SelectionMode>('destination');
const highlightedIds = useRef<number[]>([]);
const availableUserSources = useAvailableUserSources(trigger, USER_SOURCES);
const orderedUserSources = useMemo(() => sortWiredSourceOptions(availableUserSources, 'users'), [ availableUserSources ]);
const orderedFurniSources = useMemo(() => sortWiredSourceOptions(FURNI_SOURCES, 'furni'), []);
const userSourceFallbackOptions = useMemo(() => sortWiredSourceOptions([ ...USER_SOURCES, CLICKED_USER_SOURCE ], 'users'), []);
const targetDefinitions = useMemo(() => getTargetDefinitions(targetType, userVariableDefinitions, furniVariableDefinitions, roomVariableDefinitions, contextVariableDefinitions), [ contextVariableDefinitions, furniVariableDefinitions, roomVariableDefinitions, targetType, userVariableDefinitions ]);
const referenceDefinitions = useMemo(() => getTargetDefinitions(referenceTargetType, userVariableDefinitions, furniVariableDefinitions, roomVariableDefinitions, contextVariableDefinitions), [ contextVariableDefinitions, furniVariableDefinitions, referenceTargetType, roomVariableDefinitions, userVariableDefinitions ]);
const variableEntries = useMemo(() => buildWiredVariablePickerEntries(targetType, 'change-reference', targetDefinitions), [ targetDefinitions, targetType ]);
const referenceVariableEntries = useMemo(() => buildWiredVariablePickerEntries(referenceTargetType, 'change-reference', referenceDefinitions), [ referenceDefinitions, referenceTargetType ]);
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 resolvedReferenceVariableEntries = useMemo(() =>
{
if(!referenceVariableToken) return referenceVariableEntries;
if(flattenWiredVariablePickerEntries(referenceVariableEntries).some(entry => (entry.token === referenceVariableToken))) return referenceVariableEntries;
const fallbackEntry = createFallbackVariableEntry(referenceTargetType, referenceVariableToken);
return fallbackEntry ? [ fallbackEntry, ...referenceVariableEntries ] : referenceVariableEntries;
}, [ referenceTargetType, referenceVariableEntries, referenceVariableToken ]);
const destinationSelectionEnabled = isFurniTarget(targetType) && (furniSource === SOURCE_SELECTED);
const referenceSelectionEnabled = (referenceMode === 'variable') && isFurniTarget(referenceTargetType) && (referenceFurniSource === SOURCE_SECONDARY_SELECTED);
const destinationSelectedSourceValue = isFurniTarget(targetType) ? furniSource : ((isGlobalTarget(targetType) || isContextTarget(targetType)) ? SOURCE_TRIGGER : userSource);
const referenceSelectedSourceValue = isFurniTarget(referenceTargetType) ? referenceFurniSource : ((isGlobalTarget(referenceTargetType) || isContextTarget(referenceTargetType)) ? SOURCE_TRIGGER : referenceUserSource);
const destinationSourceOptions = useMemo(() =>
{
if(isFurniTarget(targetType)) return resolveSourceOptions(orderedFurniSources, destinationSelectedSourceValue, orderedFurniSources);
if(isGlobalTarget(targetType)) return GLOBAL_SOURCE_OPTIONS;
if(isContextTarget(targetType)) return CONTEXT_SOURCE_OPTIONS;
return resolveSourceOptions(orderedUserSources, destinationSelectedSourceValue, userSourceFallbackOptions);
}, [ destinationSelectedSourceValue, orderedFurniSources, orderedUserSources, targetType, userSourceFallbackOptions ]);
const referenceSourceOptions = useMemo(() =>
{
if(isFurniTarget(referenceTargetType)) return resolveSourceOptions(SECONDARY_FURNI_SOURCES, referenceSelectedSourceValue, SECONDARY_FURNI_SOURCES);
if(isGlobalTarget(referenceTargetType)) return GLOBAL_SOURCE_OPTIONS;
if(isContextTarget(referenceTargetType)) return CONTEXT_SOURCE_OPTIONS;
return resolveSourceOptions(orderedUserSources, referenceSelectedSourceValue, userSourceFallbackOptions);
}, [ orderedUserSources, referenceSelectedSourceValue, referenceTargetType, userSourceFallbackOptions ]);
const syncHighlights = useCallback((nextDestinationIds: number[], nextReferenceIds: number[]) =>
{
if(highlightedIds.current.length)
{
WiredSelectionVisualizer.clearSelectionShaderFromFurni(highlightedIds.current);
WiredSelectionVisualizer.clearSecondarySelectionShaderFromFurni(highlightedIds.current);
}
const secondarySet = new Set(nextReferenceIds);
const primaryOnlyIds = nextDestinationIds.filter(id => !secondarySet.has(id));
if(primaryOnlyIds.length) WiredSelectionVisualizer.applySelectionShaderToFurni(primaryOnlyIds);
if(nextReferenceIds.length) WiredSelectionVisualizer.applySecondarySelectionShaderToFurni(nextReferenceIds);
highlightedIds.current = Array.from(new Set([ ...nextDestinationIds, ...nextReferenceIds ]));
}, []);
const switchSelection = useCallback((mode: SelectionMode) =>
{
if(mode === 'destination' && !destinationSelectionEnabled) return;
if(mode === 'reference' && !referenceSelectionEnabled) return;
const nextDestinationIds = (selectionMode === 'destination') ? [ ...furniIds ] : [ ...destinationFurniIds ];
const nextReferenceIds = (selectionMode === 'reference') ? [ ...furniIds ] : [ ...referenceFurniIds ];
setDestinationFurniIds(nextDestinationIds);
setReferenceFurniIds(nextReferenceIds);
setSelectionMode(mode);
setFurniIds([ ...(mode === 'destination' ? nextDestinationIds : nextReferenceIds) ]);
}, [ destinationFurniIds, destinationSelectionEnabled, furniIds, referenceFurniIds, referenceSelectionEnabled, selectionMode, setFurniIds ]);
useEffect(() =>
{
if(!trigger) return;
const stringParts = parseStringData(trigger.stringData);
const nextTargetType = normalizeTargetType((trigger.intData.length > 0) ? trigger.intData[0] : TARGET_USER);
const nextReferenceTargetType = normalizeTargetType((trigger.intData.length > 4) ? trigger.intData[4] : TARGET_USER);
const nextDestinationFurniIds = [ ...(trigger.selectedItems ?? []) ];
const nextReferenceFurniIds = parseIds((stringParts.length > 2) ? stringParts[2] : '');
setTargetType(nextTargetType);
setVariableToken(normalizeVariableTokenFromWire((stringParts.length > 0) ? stringParts[0] : ''));
setComparison((trigger.intData.length > 1) ? trigger.intData[1] : 2);
setReferenceMode(((trigger.intData.length > 2) ? trigger.intData[2] : REFERENCE_CONSTANT) === REFERENCE_VARIABLE ? 'variable' : 'constant');
setReferenceConstantValueInput(((trigger.intData.length > 3) ? trigger.intData[3] : 0).toString());
setReferenceTargetType(nextReferenceTargetType);
setReferenceVariableToken(normalizeVariableTokenFromWire((stringParts.length > 1) ? stringParts[1] : ''));
setUserSource((trigger.intData.length > 5) ? trigger.intData[5] : SOURCE_TRIGGER);
setFurniSource((trigger.intData.length > 6) ? trigger.intData[6] : (nextDestinationFurniIds.length ? SOURCE_SELECTED : SOURCE_TRIGGER));
setReferenceUserSource((trigger.intData.length > 7) ? trigger.intData[7] : SOURCE_TRIGGER);
setReferenceFurniSource((trigger.intData.length > 8) ? trigger.intData[8] : (nextReferenceFurniIds.length ? SOURCE_SECONDARY_SELECTED : SOURCE_TRIGGER));
setQuantifier((trigger.intData.length > 9) ? ((trigger.intData[9] === QUANTIFIER_ANY) ? QUANTIFIER_ANY : QUANTIFIER_ALL) : QUANTIFIER_ALL);
setDestinationFurniIds(nextDestinationFurniIds);
setReferenceFurniIds(nextReferenceFurniIds);
setSelectionMode('destination');
setFurniIds([ ...nextDestinationFurniIds ]);
}, [ setFurniIds, trigger ]);
useEffect(() =>
{
if(selectionMode === 'destination') setDestinationFurniIds([ ...furniIds ]);
else setReferenceFurniIds([ ...furniIds ]);
}, [ furniIds, selectionMode ]);
useEffect(() => syncHighlights(destinationFurniIds, referenceFurniIds), [ destinationFurniIds, referenceFurniIds, syncHighlights ]);
useEffect(() =>
{
if(selectionMode === 'destination' && !destinationSelectionEnabled && referenceSelectionEnabled)
{
switchSelection('reference');
return;
}
if(selectionMode === 'reference' && !referenceSelectionEnabled && destinationSelectionEnabled)
{
switchSelection('destination');
return;
}
const canEditSelection = (selectionMode === 'destination') ? destinationSelectionEnabled : referenceSelectionEnabled;
setAllowsFurni(canEditSelection ? WiredFurniType.STUFF_SELECTION_OPTION_BY_ID : WiredFurniType.STUFF_SELECTION_OPTION_NONE);
}, [ destinationSelectionEnabled, referenceSelectionEnabled, selectionMode, setAllowsFurni, switchSelection ]);
useEffect(() =>
{
return () =>
{
if(!highlightedIds.current.length) return;
WiredSelectionVisualizer.clearSelectionShaderFromFurni(highlightedIds.current);
WiredSelectionVisualizer.clearSecondarySelectionShaderFromFurni(highlightedIds.current);
highlightedIds.current = [];
};
}, []);
const save = () =>
{
const nextDestinationFurniIds = (selectionMode === 'destination') ? [ ...furniIds ] : [ ...destinationFurniIds ];
const nextReferenceFurniIds = (selectionMode === 'reference') ? [ ...furniIds ] : [ ...referenceFurniIds ];
const parsedReferenceConstantValue = parseInt(referenceConstantValueInput.trim(), 10);
setDestinationFurniIds(nextDestinationFurniIds);
setReferenceFurniIds(nextReferenceFurniIds);
setStringParam(serializeStringData(variableToken, referenceMode === 'variable' ? referenceVariableToken : '', nextReferenceFurniIds));
setIntParams([
getTargetValue(targetType),
comparison,
referenceMode === 'variable' ? REFERENCE_VARIABLE : REFERENCE_CONSTANT,
Number.isFinite(parsedReferenceConstantValue) ? parsedReferenceConstantValue : 0,
getTargetValue(referenceTargetType),
userSource,
furniSource,
referenceUserSource,
referenceFurniSource,
quantifier
]);
setFurniIds((isFurniTarget(targetType) && furniSource === SOURCE_SELECTED) ? [ ...nextDestinationFurniIds ] : []);
};
const validate = () =>
{
if(!variableToken) return false;
if(referenceMode === 'variable' && !referenceVariableToken) return false;
return true;
};
const selectionLimit = trigger?.maximumItemSelectionCount ?? 0;
const handleTargetChange = (nextTargetType: VariableTargetType) =>
{
if(nextTargetType === targetType) return;
setTargetType(nextTargetType);
setVariableToken('');
};
const handleReferenceTargetChange = (nextTargetType: VariableTargetType) =>
{
if(nextTargetType === referenceTargetType) return;
setReferenceTargetType(nextTargetType);
setReferenceVariableToken('');
};
return (
<WiredConditionBaseView
hasSpecialInput={ true }
requiresFurni={ WiredFurniType.STUFF_SELECTION_OPTION_NONE }
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="wiredConditionVariableValueQuantifier" type="radio" onChange={ () => setQuantifier(value) } />
<Text>{ LocalizeText(`wiredfurni.params.quantifier.variables.${ value }`) }</Text>
</label>
)) }
</div>
<WiredFurniSelectionSourceRow
title="wiredfurni.params.sources.merged.title.variables_destination"
options={ destinationSourceOptions }
value={ destinationSelectedSourceValue }
selectionKind="primary"
selectionActive={ destinationSelectionEnabled }
selectionCount={ destinationFurniIds.length }
selectionLimit={ selectionLimit }
selectionEnabledValues={ [ SOURCE_SELECTED ] }
showSelectionToggle={ false }
onChange={ value =>
{
if(isFurniTarget(targetType))
{
setFurniSource(value);
return;
}
if(targetType === 'user')
{
setUserSource(value);
}
} } />
{ referenceMode === 'variable' &&
<WiredFurniSelectionSourceRow
title="wiredfurni.params.sources.merged.title.variables_reference"
options={ referenceSourceOptions }
value={ referenceSelectedSourceValue }
selectionKind="secondary"
selectionActive={ referenceSelectionEnabled }
selectionCount={ referenceFurniIds.length }
selectionLimit={ selectionLimit }
selectionEnabledValues={ [ SOURCE_SECONDARY_SELECTED ] }
showSelectionToggle={ false }
onChange={ value =>
{
if(isFurniTarget(referenceTargetType))
{
setReferenceFurniSource(value);
return;
}
if(referenceTargetType === 'user')
{
setReferenceUserSource(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.choose_type') }</div>
<div className="flex flex-wrap gap-2">
{ COMPARISON_OPTIONS.map(option => (
<label key={ option.value } className="flex items-center gap-1">
<input checked={ comparison === option.value } className="form-check-input" name="wiredConditionVariableComparison" type="radio" onChange={ () => setComparison(option.value) } />
<Text>{ option.label }</Text>
</label>
)) }
</div>
</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.reference_value') }</div>
<label className="nitro-wired__change-var-radio">
<input checked={ referenceMode === 'constant' } type="radio" onChange={ () => setReferenceMode('constant') } />
<Text>{ LocalizeText('wiredfurni.params.operator.2') }</Text>
<NitroInput className="nitro-wired__give-var-number" type="number" value={ referenceConstantValueInput } onChange={ event => setReferenceConstantValueInput(event.target.value) } />
</label>
<div className="nitro-wired__change-var-reference-block">
<label className="nitro-wired__change-var-radio">
<input checked={ referenceMode === 'variable' } type="radio" onChange={ () => setReferenceMode('variable') } />
<Text>{ LocalizeText('wiredfurni.params.variables.reference_value.from_variable') }</Text>
<div className="nitro-wired__give-var-targets">
{ TARGET_BUTTONS.map(button => (
<button
key={ `reference-${ button.key }` }
type="button"
disabled={ button.disabled || (referenceMode !== 'variable') }
className={ `nitro-wired__give-var-target nitro-wired__give-var-target--${ button.key } ${ referenceTargetType === button.key ? 'is-active' : '' }` }
onClick={ () => handleReferenceTargetChange(button.key) }>
<img src={ button.icon } alt={ button.key } />
</button>
)) }
</div>
</label>
{ referenceMode === 'variable' &&
<WiredVariablePicker
entries={ resolvedReferenceVariableEntries }
recentScope="variable-conditions"
selectedToken={ referenceVariableToken }
onSelect={ entry => setReferenceVariableToken(entry.token) } /> }
</div>
</div>
</div>
</WiredConditionBaseView>
);
};
@@ -0,0 +1,64 @@
import { FC, useEffect, 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 MAX_NAME_LENGTH = 40;
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);
};
export const WiredExtraContextVariableView: FC<{}> = () =>
{
const { trigger = null, setIntParams = null, setStringParam = null } = useWired();
const [ variableName, setVariableName ] = useState('');
const [ hasValue, setHasValue ] = useState(false);
useEffect(() =>
{
if(!trigger) return;
setVariableName(normalizeVariableName(trigger.stringData));
setHasValue((trigger.intData.length > 0) ? (trigger.intData[0] === 1) : false);
}, [ trigger ]);
const save = () =>
{
setStringParam(normalizeVariableName(variableName));
setIntParams([ hasValue ? 1 : 0 ]);
};
return (
<WiredExtraBaseView hasSpecialInput={ true } requiresFurni={ WiredFurniType.STUFF_SELECTION_OPTION_NONE } save={ save } 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.settings') }</Text>
<label className="flex items-center gap-1 cursor-pointer">
<input checked={ hasValue } className="form-check-input" type="checkbox" onChange={ event => setHasValue(event.target.checked) } />
<Text>{ LocalizeText('wiredfurni.params.variables.settings.has_value') }</Text>
</label>
</div>
</div>
</WiredExtraBaseView>
);
};
@@ -0,0 +1,315 @@
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 { buildWiredVariablePickerEntries, createFallbackVariableEntry, flattenWiredVariablePickerEntries, normalizeVariableTokenFromWire, WiredVariablePickerTarget } from '../WiredVariablePickerData';
import { CLICKED_USER_SOURCE, sortWiredSourceOptions, USER_SOURCES, useAvailableUserSources, WiredSourceOption } from '../WiredSourcesSelector';
import { WiredExtraBaseView } from './WiredExtraBaseView';
type VariableTargetType = 'user' | 'furni' | 'global' | 'context';
type AmountMode = 'constant' | 'variable';
interface IVariableDefinition
{
availability: number;
hasValue: boolean;
itemId: number;
name: string;
}
interface WiredExtraFilterByVariableViewProps
{
target: 'user' | 'furni';
}
const TARGET_USER = 0;
const TARGET_FURNI = 1;
const TARGET_CONTEXT = 2;
const TARGET_GLOBAL = 3;
const AMOUNT_CONSTANT = 0;
const AMOUNT_VARIABLE = 1;
const SOURCE_TRIGGER = 0;
const SOURCE_SECONDARY_SELECTED = 101;
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 SORT_OPTIONS = [ 0, 1, 2, 3, 4, 5 ];
const SECONDARY_FURNI_SOURCES: WiredSourceOption[] = sortWiredSourceOptions([
{ value: SOURCE_TRIGGER, label: 'wiredfurni.params.sources.furni.0' },
{ value: SOURCE_SECONDARY_SELECTED, label: 'wiredfurni.params.sources.furni.101' },
{ value: 200, label: 'wiredfurni.params.sources.furni.200' },
{ value: 201, label: 'wiredfurni.params.sources.furni.201' }
], 'furni');
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 parseStringData = (value: string) => (value?.length ? value.split('\t', -1) : []);
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 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 getReferenceDefinitions = (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 resolveSourceOptions = (baseOptions: WiredSourceOption[], selectedValue: number, fallbackOptions: WiredSourceOption[]) =>
{
if(!baseOptions.length) return baseOptions;
if(baseOptions.some(option => (option.value === selectedValue))) return baseOptions;
const fallbackOption = fallbackOptions.find(option => (option.value === selectedValue));
return fallbackOption ? [ ...baseOptions, fallbackOption ] : baseOptions;
};
export const WiredExtraFilterByVariableView: FC<WiredExtraFilterByVariableViewProps> = ({ target }) =>
{
const { trigger = null, furniIds = [], setFurniIds = null, setIntParams = null, setStringParam = null } = useWired();
const { userVariableDefinitions = [], furniVariableDefinitions = [], roomVariableDefinitions = [], contextVariableDefinitions = [] } = useWiredTools();
const [ variableToken, setVariableToken ] = useState('');
const [ sortBy, setSortBy ] = useState(0);
const [ amountMode, setAmountMode ] = useState<AmountMode>('constant');
const [ amountInput, setAmountInput ] = useState('1');
const [ referenceTargetType, setReferenceTargetType ] = useState<VariableTargetType>('user');
const [ referenceVariableToken, setReferenceVariableToken ] = useState('');
const [ referenceUserSource, setReferenceUserSource ] = useState(SOURCE_TRIGGER);
const [ referenceFurniSource, setReferenceFurniSource ] = useState(SOURCE_TRIGGER);
const [ referenceFurniIds, setReferenceFurniIds ] = useState<number[]>([]);
const availableUserSources = useAvailableUserSources(trigger, USER_SOURCES);
const orderedUserSources = useMemo(() => sortWiredSourceOptions(availableUserSources, 'users'), [ availableUserSources ]);
const userSourceFallbackOptions = useMemo(() => sortWiredSourceOptions([ ...USER_SOURCES, CLICKED_USER_SOURCE ], 'users'), []);
const mainDefinitions = target === 'user' ? userVariableDefinitions : furniVariableDefinitions;
const mainEntries = useMemo(() => buildWiredVariablePickerEntries(target as WiredVariablePickerTarget, 'filter-main', mainDefinitions), [ mainDefinitions, target ]);
const resolvedMainEntries = useMemo(() =>
{
if(!variableToken) return mainEntries;
if(flattenWiredVariablePickerEntries(mainEntries).some(entry => (entry.token === variableToken))) return mainEntries;
const fallbackEntry = createFallbackVariableEntry(target as WiredVariablePickerTarget, variableToken);
return fallbackEntry ? [ fallbackEntry, ...mainEntries ] : mainEntries;
}, [ mainEntries, target, variableToken ]);
const referenceDefinitions = useMemo(() => getReferenceDefinitions(referenceTargetType, userVariableDefinitions, furniVariableDefinitions, roomVariableDefinitions, contextVariableDefinitions), [ contextVariableDefinitions, furniVariableDefinitions, referenceTargetType, roomVariableDefinitions, userVariableDefinitions ]);
const referenceEntries = useMemo(() => buildWiredVariablePickerEntries(referenceTargetType, 'change-reference', referenceDefinitions), [ referenceDefinitions, referenceTargetType ]);
const resolvedReferenceEntries = useMemo(() =>
{
if(!referenceVariableToken) return referenceEntries;
if(flattenWiredVariablePickerEntries(referenceEntries).some(entry => (entry.token === referenceVariableToken))) return referenceEntries;
const fallbackEntry = createFallbackVariableEntry(referenceTargetType, referenceVariableToken);
return fallbackEntry ? [ fallbackEntry, ...referenceEntries ] : referenceEntries;
}, [ referenceEntries, referenceTargetType, referenceVariableToken ]);
const referenceSelectionEnabled = amountMode === 'variable' && referenceTargetType === 'furni' && referenceFurniSource === SOURCE_SECONDARY_SELECTED;
const selectionLimit = trigger?.maximumItemSelectionCount ?? 0;
const requiresFurni = referenceSelectionEnabled ? WiredFurniType.STUFF_SELECTION_OPTION_BY_ID : WiredFurniType.STUFF_SELECTION_OPTION_NONE;
const selectedReferenceSourceValue = referenceTargetType === 'furni' ? referenceFurniSource : ((referenceTargetType === 'global' || referenceTargetType === 'context') ? SOURCE_TRIGGER : referenceUserSource);
const referenceSourceOptions = useMemo(() =>
{
if(referenceTargetType === 'furni') return resolveSourceOptions(SECONDARY_FURNI_SOURCES, selectedReferenceSourceValue, SECONDARY_FURNI_SOURCES);
if(referenceTargetType === 'global') return GLOBAL_SOURCE_OPTIONS;
if(referenceTargetType === 'context') return CONTEXT_SOURCE_OPTIONS;
return resolveSourceOptions(orderedUserSources, selectedReferenceSourceValue, userSourceFallbackOptions);
}, [ orderedUserSources, referenceTargetType, selectedReferenceSourceValue, userSourceFallbackOptions ]);
useEffect(() =>
{
if(!trigger) return;
const stringParts = parseStringData(trigger.stringData);
const nextReferenceFurniIds = [ ...(trigger.selectedItems ?? []) ];
setVariableToken(normalizeVariableTokenFromWire((stringParts.length > 0) ? stringParts[0] : ''));
setReferenceVariableToken(normalizeVariableTokenFromWire((stringParts.length > 1) ? stringParts[1] : ''));
setSortBy((trigger.intData.length > 0) ? trigger.intData[0] : 0);
setAmountMode(((trigger.intData.length > 1) ? trigger.intData[1] : AMOUNT_CONSTANT) === AMOUNT_VARIABLE ? 'variable' : 'constant');
setAmountInput(((trigger.intData.length > 2) ? trigger.intData[2] : 1).toString());
setReferenceTargetType(normalizeTargetType((trigger.intData.length > 3) ? trigger.intData[3] : TARGET_USER));
setReferenceUserSource((trigger.intData.length > 4) ? trigger.intData[4] : SOURCE_TRIGGER);
setReferenceFurniSource((trigger.intData.length > 5) ? trigger.intData[5] : (nextReferenceFurniIds.length ? SOURCE_SECONDARY_SELECTED : SOURCE_TRIGGER));
setReferenceFurniIds(nextReferenceFurniIds);
setFurniIds(nextReferenceFurniIds);
}, [ setFurniIds, trigger ]);
useEffect(() =>
{
if(referenceSelectionEnabled) setReferenceFurniIds([ ...furniIds ]);
}, [ furniIds, referenceSelectionEnabled ]);
useEffect(() =>
{
if(referenceTargetType !== 'user') return;
if(orderedUserSources.some(option => (option.value === referenceUserSource))) return;
setReferenceUserSource(SOURCE_TRIGGER);
}, [ orderedUserSources, referenceTargetType, referenceUserSource ]);
const save = () =>
{
const parsedAmount = parseInt(amountInput.trim(), 10);
const nextReferenceFurniIds = referenceSelectionEnabled ? [ ...furniIds ] : [ ...referenceFurniIds ];
setReferenceFurniIds(nextReferenceFurniIds);
setStringParam(`${ variableToken || '' }\t${ amountMode === 'variable' ? referenceVariableToken : '' }`);
setIntParams([
sortBy,
amountMode === 'variable' ? AMOUNT_VARIABLE : AMOUNT_CONSTANT,
Number.isFinite(parsedAmount) ? parsedAmount : 0,
getTargetValue(referenceTargetType),
referenceUserSource,
referenceFurniSource
]);
setFurniIds(referenceSelectionEnabled ? nextReferenceFurniIds : []);
};
const validate = () =>
{
if(!variableToken) return false;
if(amountMode === 'variable' && !referenceVariableToken) return false;
return true;
};
const handleReferenceTargetChange = (targetType: VariableTargetType) =>
{
if(targetType === referenceTargetType) return;
if(referenceTargetType === 'furni') setFurniIds([]);
setReferenceTargetType(targetType);
setReferenceVariableToken('');
};
return (
<WiredExtraBaseView hasSpecialInput={ true } requiresFurni={ requiresFurni } save={ save } validate={ validate } cardStyle={ { width: 260 } }>
<div className="nitro-wired__give-var">
<div className="flex flex-col gap-1">
<Text>{ LocalizeText('wiredfurni.params.variables.variable_selection') }</Text>
<WiredVariablePicker
entries={ resolvedMainEntries }
recentScope={ `variable-extra-${ target }` }
selectedToken={ variableToken }
onSelect={ entry => setVariableToken(entry.token) } />
</div>
<div className="nitro-wired__divider" />
<div className="flex flex-col gap-1">
<Text>{ LocalizeText('wiredfurni.params.variables.sort_by') }</Text>
<select className="form-select form-select-sm" value={ sortBy } onChange={ event => setSortBy(parseInt(event.target.value, 10) || 0) }>
{ SORT_OPTIONS.map(option => <option key={ option } value={ option }>{ LocalizeText(`wiredfurni.params.variables.sort_by.${ option }`) }</option>) }
</select>
</div>
<div className="nitro-wired__divider" />
<div className="nitro-wired__give-var-section">
<div className="nitro-wired__give-var-section-title">{ LocalizeText('wiredfurni.params.setfilter') }</div>
<label className="nitro-wired__change-var-radio">
<input checked={ amountMode === 'constant' } type="radio" onChange={ () => setAmountMode('constant') } />
<Text>{ LocalizeText('wiredfurni.params.variables.reference_value.set_value') }</Text>
<NitroInput className="nitro-wired__give-var-number" type="number" value={ amountInput } onChange={ event => setAmountInput(event.target.value) } />
</label>
<div className="nitro-wired__change-var-reference-block">
<label className="nitro-wired__change-var-radio">
<input checked={ amountMode === 'variable' } type="radio" onChange={ () => setAmountMode('variable') } />
<Text>{ LocalizeText('wiredfurni.params.variables.reference_value.from_variable') }</Text>
<div className="nitro-wired__give-var-targets">
{ TARGET_BUTTONS.map(button => (
<button
key={ `reference-${ button.key }` }
type="button"
disabled={ button.disabled || (amountMode !== 'variable') }
className={ `nitro-wired__give-var-target nitro-wired__give-var-target--${ button.key } ${ referenceTargetType === button.key ? 'is-active' : '' }` }
onClick={ () => handleReferenceTargetChange(button.key) }>
<img src={ button.icon } alt={ button.key } />
</button>
)) }
</div>
</label>
{ amountMode === 'variable' &&
<WiredVariablePicker
entries={ resolvedReferenceEntries }
recentScope={ `variable-extra-reference-${ target }` }
selectedToken={ referenceVariableToken }
onSelect={ entry => setReferenceVariableToken(entry.token) } /> }
</div>
</div>
{ amountMode === 'variable' &&
<>
<div className="nitro-wired__divider" />
<WiredFurniSelectionSourceRow
title="wiredfurni.params.sources.merged.title.variables_reference"
options={ referenceSourceOptions }
value={ selectedReferenceSourceValue }
selectionKind="primary"
selectionActive={ true }
selectionCount={ referenceSelectionEnabled ? furniIds.length : referenceFurniIds.length }
selectionLimit={ selectionLimit }
selectionEnabledValues={ [ SOURCE_SECONDARY_SELECTED ] }
showSelectionToggle={ false }
onChange={ value =>
{
if(referenceTargetType === 'furni')
{
if(referenceFurniSource === SOURCE_SECONDARY_SELECTED) setReferenceFurniIds([ ...furniIds ]);
setReferenceFurniSource(value);
setFurniIds(value === SOURCE_SECONDARY_SELECTED ? [ ...referenceFurniIds ] : []);
return;
}
if(referenceTargetType === 'user') setReferenceUserSource(value);
} } />
</> }
</div>
</WiredExtraBaseView>
);
};
@@ -0,0 +1,7 @@
import { FC } from 'react';
import { WiredExtraFilterByVariableView } from './WiredExtraFilterByVariableView';
export const WiredExtraFilterFurniByVariableView: FC<{}> = () =>
{
return <WiredExtraFilterByVariableView target="furni" />;
};
@@ -0,0 +1,7 @@
import { FC } from 'react';
import { WiredExtraFilterByVariableView } from './WiredExtraFilterByVariableView';
export const WiredExtraFilterUsersByVariableView: FC<{}> = () =>
{
return <WiredExtraFilterByVariableView target="user" />;
};
@@ -0,0 +1,8 @@
import { FC } from 'react';
import { LocalizeText } from '../../../../api';
import { WiredExtraVariableView } from './WiredExtraUserVariableView';
export const WiredExtraFurniVariableView: FC<{}> = () =>
{
return <WiredExtraVariableView availabilityRadioName="wiredFurniVariableAvailability" availabilityRoomText={ LocalizeText('wiredfurni.params.variables.availability.1') } availabilityRoomValue={ 1 } />;
};
@@ -0,0 +1,86 @@
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 AVAILABILITY_ROOM_ACTIVE = 1;
const AVAILABILITY_PERMANENT = 10;
const AVAILABILITY_SHARED = 11;
const MAX_NAME_LENGTH = 40;
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);
};
export const WiredExtraRoomVariableView: FC<{}> = () =>
{
const { trigger = null, setIntParams = null, setStringParam = null } = useWired();
const [ variableName, setVariableName ] = useState('');
const [ availability, setAvailability ] = useState(AVAILABILITY_ROOM_ACTIVE);
const [ currentValue, setCurrentValue ] = useState(0);
const normalizedCurrentValue = useMemo(() => (Number.isFinite(currentValue) ? currentValue : 0), [ currentValue ]);
useEffect(() =>
{
if(!trigger) return;
setVariableName(normalizeVariableName(trigger.stringData));
const nextAvailability = (trigger.intData.length > 0) ? trigger.intData[0] : AVAILABILITY_ROOM_ACTIVE;
setAvailability((nextAvailability === AVAILABILITY_PERMANENT || nextAvailability === AVAILABILITY_SHARED) ? nextAvailability : AVAILABILITY_ROOM_ACTIVE);
setCurrentValue((trigger.intData.length > 1) ? trigger.intData[1] : 0);
}, [ trigger ]);
const save = () =>
{
setStringParam(normalizeVariableName(variableName));
setIntParams([ availability, normalizedCurrentValue ]);
};
return (
<WiredExtraBaseView hasSpecialInput={ true } requiresFurni={ WiredFurniType.STUFF_SELECTION_OPTION_NONE } save={ save } 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.availability') }</Text>
<label className="flex items-center gap-1 cursor-pointer">
<input checked={ (availability === AVAILABILITY_ROOM_ACTIVE) } className="form-check-input" name="wiredRoomVariableAvailability" type="radio" onChange={ () => setAvailability(AVAILABILITY_ROOM_ACTIVE) } />
<Text>{ LocalizeText('wiredfurni.params.variables.availability.1') }</Text>
</label>
<label className="flex items-center gap-1 cursor-pointer">
<input checked={ (availability === AVAILABILITY_PERMANENT) } className="form-check-input" name="wiredRoomVariableAvailability" type="radio" onChange={ () => setAvailability(AVAILABILITY_PERMANENT) } />
<Text>{ LocalizeText('wiredfurni.params.variables.availability.10') }</Text>
</label>
<label className="flex items-center gap-1 cursor-pointer">
<input checked={ (availability === AVAILABILITY_SHARED) } className="form-check-input" name="wiredRoomVariableAvailability" type="radio" onChange={ () => setAvailability(AVAILABILITY_SHARED) } />
<Text>{ LocalizeText('wiredfurni.params.variables.availability.11') }</Text>
</label>
</div>
<div className="flex flex-col gap-1">
<Text>{ LocalizeText('wiredfurni.params.variables.inspection') }</Text>
<Text>{ LocalizeText('wiredfurni.params.variables.inspection.current_value', [ 'value' ], [ normalizedCurrentValue.toString() ]) }</Text>
</div>
</div>
</WiredExtraBaseView>
);
};
@@ -0,0 +1,155 @@
import { FC, useEffect, useMemo, useState } from 'react';
import { LocalizeText, WiredFurniType } from '../../../../api';
import { Text } from '../../../../common';
import { useWired, useWiredTools } from '../../../../hooks';
import { NitroInput } from '../../../../layout';
import { WiredVariablePicker } from '../WiredVariablePicker';
import { buildWiredVariablePickerEntries, createFallbackVariableEntry, flattenWiredVariablePickerEntries, getCustomVariableItemId, isCustomVariableToken, normalizeVariableTokenFromWire } from '../WiredVariablePickerData';
import { WiredExtraBaseView } from './WiredExtraBaseView';
import { WiredPlaceholderPreview } from './WiredPlaceholderPreview';
interface IVariableDefinition
{
hasValue: boolean;
isTextConnected: boolean;
itemId: number;
name: string;
}
const DISPLAY_NUMERIC = 1;
const DISPLAY_TEXTUAL = 2;
const DEFAULT_CAPTURER_NAME = '';
const MAX_CAPTURER_NAME_LENGTH = 32;
const PLACEHOLDER_WRAPPER_PATTERN = /^#(.*)#$/;
const splitStringData = (value: string) =>
{
if(!value?.length) return [ '', DEFAULT_CAPTURER_NAME ];
const parts = value.split('\t');
if(parts.length === 1) return [ parts[0], DEFAULT_CAPTURER_NAME ];
return [ parts[0], parts[1] ];
};
const normalizeDisplayType = (value: number) => ((value === DISPLAY_TEXTUAL) ? DISPLAY_TEXTUAL : DISPLAY_NUMERIC);
const normalizeCapturerName = (value: string) =>
{
let normalizedValue = (value ?? '').trim().replace(/[\t\r\n]/g, '');
if(PLACEHOLDER_WRAPPER_PATTERN.test(normalizedValue))
{
normalizedValue = normalizedValue.substring(1, normalizedValue.length - 1).trim();
}
return normalizedValue.slice(0, MAX_CAPTURER_NAME_LENGTH);
};
const escapeHtml = (value: string) => value
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#39;');
export const WiredExtraTextInputVariableView: FC<{}> = () =>
{
const { trigger = null, setIntParams = null, setStringParam = null } = useWired();
const { contextVariableDefinitions = [] } = useWiredTools();
const [ variableToken, setVariableToken ] = useState('');
const [ capturerName, setCapturerName ] = useState(DEFAULT_CAPTURER_NAME);
const [ displayType, setDisplayType ] = useState(DISPLAY_NUMERIC);
const targetDefinitions = useMemo(() => contextVariableDefinitions.filter(definition => !!definition.hasValue), [ contextVariableDefinitions ]);
const variableEntries = useMemo(() => buildWiredVariablePickerEntries('context', 'change-reference', targetDefinitions).filter(entry => (entry.kind === 'custom')), [ targetDefinitions ]);
const resolvedVariableEntries = useMemo(() =>
{
if(!variableToken || !isCustomVariableToken(variableToken)) return variableEntries;
if(flattenWiredVariablePickerEntries(variableEntries).some(entry => (entry.token === variableToken))) return variableEntries;
const fallbackEntry = createFallbackVariableEntry('context', variableToken);
return (fallbackEntry && (fallbackEntry.kind === 'custom')) ? [ fallbackEntry, ...variableEntries ] : variableEntries;
}, [ variableEntries, variableToken ]);
const selectedVariableDefinition = useMemo(() =>
{
if(!isCustomVariableToken(variableToken)) return null;
const itemId = getCustomVariableItemId(variableToken);
return (targetDefinitions.find(definition => (definition.itemId === itemId)) ?? null) as IVariableDefinition | null;
}, [ targetDefinitions, variableToken ]);
const canUseTextDisplay = !!selectedVariableDefinition?.isTextConnected;
useEffect(() =>
{
if(!trigger) return;
const [ nextVariableToken, nextCapturerName ] = splitStringData(trigger.stringData);
setVariableToken(normalizeVariableTokenFromWire(nextVariableToken));
setCapturerName(normalizeCapturerName(nextCapturerName));
setDisplayType(normalizeDisplayType((trigger.intData.length > 0) ? trigger.intData[0] : DISPLAY_NUMERIC));
}, [ trigger ]);
useEffect(() =>
{
if(canUseTextDisplay || (displayType !== DISPLAY_TEXTUAL)) return;
setDisplayType(DISPLAY_NUMERIC);
}, [ canUseTextDisplay, displayType ]);
const previewToken = useMemo(() =>
{
const effectiveName = normalizeCapturerName(capturerName) || 'capturer';
return `#${ effectiveName }#`;
}, [ capturerName ]);
const previewHtml = useMemo(() => LocalizeText('wiredfurni.params.texts.placeholder_preview', [ 'placeholder' ], [ escapeHtml(previewToken) ]), [ previewToken ]);
const save = () =>
{
const variableItemId = getCustomVariableItemId(variableToken);
setIntParams([ canUseTextDisplay ? normalizeDisplayType(displayType) : DISPLAY_NUMERIC ]);
setStringParam(`${ variableItemId ? String(variableItemId) : '' }\t${ normalizeCapturerName(capturerName) }`);
};
const validate = () => !!normalizeCapturerName(capturerName).length && (getCustomVariableItemId(variableToken) > 0);
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.texts.capturer_name') }</Text>
<NitroInput maxLength={ MAX_CAPTURER_NAME_LENGTH } type="text" value={ capturerName } onChange={ event => setCapturerName(normalizeCapturerName(event.target.value)) } />
</div>
<WiredPlaceholderPreview previewHtml={ previewHtml } previewToken={ previewToken } />
<div className="flex flex-col gap-1">
<Text>{ LocalizeText('wiredfurni.params.variables.variable_selection') }</Text>
<WiredVariablePicker entries={ resolvedVariableEntries } recentScope="variable-text-input" selectedToken={ variableToken } onSelect={ entry => setVariableToken(entry.token) } />
{ !targetDefinitions.length && <Text small>{ 'No wf_var_context variables with value found in this room.' }</Text> }
</div>
<div className="flex flex-col gap-1">
<Text>{ LocalizeText('wiredfurni.params.texts.variable_input_type') }</Text>
<label className="flex items-center gap-1 cursor-pointer">
<input checked={ (displayType === DISPLAY_NUMERIC) } className="form-check-input" name="wiredTextInputVariableType" type="radio" onChange={ () => setDisplayType(DISPLAY_NUMERIC) } />
<Text>{ LocalizeText('wiredfurni.params.texts.variable_display_type.1') }</Text>
</label>
<label className="flex items-center gap-1 cursor-pointer">
<input checked={ (displayType === DISPLAY_TEXTUAL) } className="form-check-input" disabled={ !canUseTextDisplay } name="wiredTextInputVariableType" type="radio" onChange={ () => setDisplayType(DISPLAY_TEXTUAL) } />
<Text>{ LocalizeText('wiredfurni.params.texts.variable_display_type.2') }</Text>
</label>
<Text small>{ LocalizeText('wiredfurni.params.texts.variable_display_type.2.info') }</Text>
</div>
</div>
</WiredExtraBaseView>
);
};
@@ -5,6 +5,7 @@ import { useWired } from '../../../../hooks';
import { NitroInput } from '../../../../layout'; import { NitroInput } from '../../../../layout';
import { WiredSourcesSelector } from '../WiredSourcesSelector'; import { WiredSourcesSelector } from '../WiredSourcesSelector';
import { WiredExtraBaseView } from './WiredExtraBaseView'; import { WiredExtraBaseView } from './WiredExtraBaseView';
import { WiredPlaceholderPreview } from './WiredPlaceholderPreview';
const TYPE_SINGLE = 1; const TYPE_SINGLE = 1;
const TYPE_MULTIPLE = 2; const TYPE_MULTIPLE = 2;
@@ -100,7 +101,7 @@ export const WiredExtraTextOutputFurniNameView: FC<{}> = () =>
<Text>{ LocalizeText('wiredfurni.params.texts.placeholder_name') }</Text> <Text>{ LocalizeText('wiredfurni.params.texts.placeholder_name') }</Text>
<NitroInput maxLength={ MAX_PLACEHOLDER_NAME_LENGTH } type="text" value={ placeholderName } onChange={ event => setPlaceholderName(normalizePlaceholderName(event.target.value)) } /> <NitroInput maxLength={ MAX_PLACEHOLDER_NAME_LENGTH } type="text" value={ placeholderName } onChange={ event => setPlaceholderName(normalizePlaceholderName(event.target.value)) } />
</div> </div>
<Text dangerouslySetInnerHTML={ { __html: previewHtml } } /> <WiredPlaceholderPreview previewHtml={ previewHtml } previewToken={ previewToken } />
<div className="flex flex-col gap-1"> <div className="flex flex-col gap-1">
<Text>{ LocalizeText('wiredfurni.params.texts.placeholder_type') }</Text> <Text>{ LocalizeText('wiredfurni.params.texts.placeholder_type') }</Text>
<label className="flex items-center gap-1 cursor-pointer"> <label className="flex items-center gap-1 cursor-pointer">
@@ -5,6 +5,7 @@ import { useWired } from '../../../../hooks';
import { NitroInput } from '../../../../layout'; import { NitroInput } from '../../../../layout';
import { WiredSourcesSelector, CLICKED_USER_SOURCE_VALUE } from '../WiredSourcesSelector'; import { WiredSourcesSelector, CLICKED_USER_SOURCE_VALUE } from '../WiredSourcesSelector';
import { WiredExtraBaseView } from './WiredExtraBaseView'; import { WiredExtraBaseView } from './WiredExtraBaseView';
import { WiredPlaceholderPreview } from './WiredPlaceholderPreview';
const TYPE_SINGLE = 1; const TYPE_SINGLE = 1;
const TYPE_MULTIPLE = 2; const TYPE_MULTIPLE = 2;
@@ -100,7 +101,7 @@ export const WiredExtraTextOutputUsernameView: FC<{}> = () =>
<Text>{ LocalizeText('wiredfurni.params.texts.placeholder_name') }</Text> <Text>{ LocalizeText('wiredfurni.params.texts.placeholder_name') }</Text>
<NitroInput maxLength={ MAX_PLACEHOLDER_NAME_LENGTH } type="text" value={ placeholderName } onChange={ event => setPlaceholderName(normalizePlaceholderName(event.target.value)) } /> <NitroInput maxLength={ MAX_PLACEHOLDER_NAME_LENGTH } type="text" value={ placeholderName } onChange={ event => setPlaceholderName(normalizePlaceholderName(event.target.value)) } />
</div> </div>
<Text dangerouslySetInnerHTML={ { __html: previewHtml } } /> <WiredPlaceholderPreview previewHtml={ previewHtml } previewToken={ previewToken } />
<div className="flex flex-col gap-1"> <div className="flex flex-col gap-1">
<Text>{ LocalizeText('wiredfurni.params.texts.placeholder_type') }</Text> <Text>{ LocalizeText('wiredfurni.params.texts.placeholder_type') }</Text>
<label className="flex items-center gap-1 cursor-pointer"> <label className="flex items-center gap-1 cursor-pointer">
@@ -0,0 +1,318 @@
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 { WiredVariablePicker } from '../WiredVariablePicker';
import { buildWiredVariablePickerEntries, createFallbackVariableEntry, flattenWiredVariablePickerEntries, getCustomVariableItemId, isCustomVariableToken, normalizeVariableTokenFromWire } from '../WiredVariablePickerData';
import { WiredFurniSelectionSourceRow } from '../WiredFurniSelectionSourceRow';
import { CLICKED_USER_SOURCE_VALUE, WiredSourcesSelector } from '../WiredSourcesSelector';
import { WiredExtraBaseView } from './WiredExtraBaseView';
import { WiredPlaceholderPreview } from './WiredPlaceholderPreview';
type VariableTargetType = 'user' | 'furni' | 'global' | 'context';
interface IVariableDefinition
{
availability: number;
hasValue: boolean;
isTextConnected: boolean;
itemId: number;
isReadOnly?: boolean;
name: string;
}
const TARGET_USER = 0;
const TARGET_FURNI = 1;
const TARGET_CONTEXT = 2;
const TARGET_GLOBAL = 3;
const DISPLAY_NUMERIC = 1;
const DISPLAY_TEXTUAL = 2;
const TYPE_SINGLE = 1;
const TYPE_MULTIPLE = 2;
const DEFAULT_PLACEHOLDER_NAME = '';
const DEFAULT_DELIMITER = ', ';
const MAX_PLACEHOLDER_NAME_LENGTH = 32;
const MAX_DELIMITER_LENGTH = 16;
const PLACEHOLDER_WRAPPER_PATTERN = /^\$\((.*)\)$/;
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 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 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 normalizeDisplayType = (value: number) => ((value === DISPLAY_TEXTUAL) ? DISPLAY_TEXTUAL : DISPLAY_NUMERIC);
const normalizePlaceholderType = (value: number) => ((value === TYPE_MULTIPLE) ? TYPE_MULTIPLE : TYPE_SINGLE);
const normalizeUserSource = (value: number) => ((value === 0) || (value === 200) || (value === 201) || (value === CLICKED_USER_SOURCE_VALUE) ? value : 0);
const normalizeFurniSource = (value: number) => ((value === 0) || (value === 100) || (value === 200) || (value === 201) ? value : 0);
const normalizePlaceholderName = (value: string) =>
{
let normalizedValue = (value ?? '').trim().replace(/[\t\r\n]/g, '');
if(PLACEHOLDER_WRAPPER_PATTERN.test(normalizedValue))
{
normalizedValue = normalizedValue.substring(2, normalizedValue.length - 1).trim();
}
return normalizedValue.slice(0, MAX_PLACEHOLDER_NAME_LENGTH);
};
const normalizeDelimiter = (value: string) =>
{
if(value === undefined || value === null) return DEFAULT_DELIMITER;
return value.replace(/[\t\r\n]/g, '').slice(0, MAX_DELIMITER_LENGTH);
};
const splitStringData = (value: string) =>
{
if(!value?.length) return [ '', DEFAULT_PLACEHOLDER_NAME, DEFAULT_DELIMITER ];
const parts = value.split('\t');
if(parts.length === 1) return [ parts[0], DEFAULT_PLACEHOLDER_NAME, DEFAULT_DELIMITER ];
if(parts.length === 2) return [ parts[0], parts[1], DEFAULT_DELIMITER ];
return [ parts[0], parts[1], parts[2] ];
};
const escapeHtml = (value: string) => value
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#39;');
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 serializeStringData = (variableToken: string, placeholderName: string, delimiter: string) => `${ variableToken || '' }\t${ normalizePlaceholderName(placeholderName) }\t${ normalizeDelimiter(delimiter) }`;
export const WiredExtraTextOutputVariableView: 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 [ displayType, setDisplayType ] = useState(DISPLAY_NUMERIC);
const [ placeholderType, setPlaceholderType ] = useState(TYPE_SINGLE);
const [ placeholderName, setPlaceholderName ] = useState(DEFAULT_PLACEHOLDER_NAME);
const [ delimiter, setDelimiter ] = useState(DEFAULT_DELIMITER);
const [ userSource, setUserSource ] = useState(0);
const [ furniSource, setFurniSource ] = useState(0);
const targetDefinitions = useMemo(() => getTargetDefinitions(targetType, userVariableDefinitions, furniVariableDefinitions, roomVariableDefinitions, contextVariableDefinitions), [ contextVariableDefinitions, furniVariableDefinitions, roomVariableDefinitions, targetType, userVariableDefinitions ]);
const variableEntries = useMemo(() => buildWiredVariablePickerEntries(targetType, 'change-reference', targetDefinitions), [ targetDefinitions, targetType ]);
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 selectedCustomDefinition = useMemo(() =>
{
if(!isCustomVariableToken(variableToken)) return null;
const itemId = getCustomVariableItemId(variableToken);
return (targetDefinitions.find(definition => (definition.itemId === itemId)) ?? null);
}, [ targetDefinitions, variableToken ]);
const canUseTextDisplay = !!selectedCustomDefinition?.isTextConnected;
useEffect(() =>
{
if(!trigger) return;
const [ nextVariableToken, nextPlaceholderName, nextDelimiter ] = splitStringData(trigger.stringData);
setTargetType(normalizeTargetType((trigger.intData.length > 0) ? trigger.intData[0] : TARGET_USER));
setVariableToken(normalizeVariableTokenFromWire(nextVariableToken));
setDisplayType(normalizeDisplayType((trigger.intData.length > 1) ? trigger.intData[1] : DISPLAY_NUMERIC));
setPlaceholderType(normalizePlaceholderType((trigger.intData.length > 2) ? trigger.intData[2] : TYPE_SINGLE));
setUserSource(normalizeUserSource((trigger.intData.length > 3) ? trigger.intData[3] : 0));
setFurniSource(normalizeFurniSource((trigger.intData.length > 4) ? trigger.intData[4] : 0));
setPlaceholderName(normalizePlaceholderName(nextPlaceholderName));
setDelimiter(normalizeDelimiter(nextDelimiter));
setFurniIds([ ...(trigger.selectedItems ?? []) ]);
}, [ setFurniIds, trigger ]);
useEffect(() =>
{
if(canUseTextDisplay || (displayType !== DISPLAY_TEXTUAL)) return;
setDisplayType(DISPLAY_NUMERIC);
}, [ canUseTextDisplay, displayType ]);
const previewToken = useMemo(() =>
{
const effectiveName = normalizePlaceholderName(placeholderName) || 'placeholder';
return `$(${ effectiveName })`;
}, [ placeholderName ]);
const previewHtml = useMemo(() => LocalizeText('wiredfurni.params.texts.placeholder_preview', [ 'placeholder' ], [ escapeHtml(previewToken) ]), [ previewToken ]);
const save = () =>
{
setIntParams([
getTargetValue(targetType),
(canUseTextDisplay ? normalizeDisplayType(displayType) : DISPLAY_NUMERIC),
normalizePlaceholderType(placeholderType),
normalizeUserSource(userSource),
normalizeFurniSource(furniSource)
]);
setStringParam(serializeStringData(variableToken, placeholderName, delimiter));
setFurniIds(((targetType === 'furni') && (furniSource === 100)) ? [ ...furniIds ] : []);
};
const validate = () =>
{
return !!variableToken;
};
const footer = useMemo(() =>
{
if(targetType === 'global')
{
return <WiredFurniSelectionSourceRow title="wiredfurni.params.sources.merged.title.variables" options={ [ { value: 0, label: 'wiredfurni.params.sources.global' } ] } value={ 0 } selectionKind="primary" selectionActive={ false } selectionCount={ 0 } selectionLimit={ 0 } selectionEnabledValues={ [] } showSelectionToggle={ false } onChange={ () => null } />;
}
if(targetType === 'context')
{
return <WiredFurniSelectionSourceRow title="wiredfurni.params.sources.merged.title.variables" options={ [ { value: 0, label: 'Current execution' } ] } value={ 0 } selectionKind="primary" selectionActive={ false } selectionCount={ 0 } selectionLimit={ 0 } selectionEnabledValues={ [] } showSelectionToggle={ false } onChange={ () => null } />;
}
return (
<WiredSourcesSelector
showFurni={ targetType === 'furni' }
showUsers={ targetType === 'user' }
furniSource={ furniSource }
userSource={ userSource }
furniTitle="wiredfurni.params.sources.merged.title.variables"
usersTitle="wiredfurni.params.sources.merged.title.variables"
onChangeFurni={ value => setFurniSource(normalizeFurniSource(value)) }
onChangeUsers={ value => setUserSource(normalizeUserSource(value)) } />
);
}, [ furniSource, targetType, userSource ]);
const handleTargetChange = (nextTargetType: VariableTargetType) =>
{
if(nextTargetType === targetType) return;
setTargetType(nextTargetType);
setVariableToken('');
};
return (
<WiredExtraBaseView
hasSpecialInput={ true }
requiresFurni={ (targetType === 'furni') ? WiredFurniType.STUFF_SELECTION_OPTION_BY_ID_BY_TYPE_OR_FROM_CONTEXT : WiredFurniType.STUFF_SELECTION_OPTION_NONE }
save={ save }
validate={ validate }
cardStyle={ { width: 400 } }
footer={ footer }>
<div className="flex flex-col gap-2">
<div className="flex flex-col gap-1">
<Text>{ LocalizeText('wiredfurni.params.texts.placeholder_name') }</Text>
<NitroInput maxLength={ MAX_PLACEHOLDER_NAME_LENGTH } type="text" value={ placeholderName } onChange={ event => setPlaceholderName(normalizePlaceholderName(event.target.value)) } />
</div>
<WiredPlaceholderPreview previewHtml={ previewHtml } previewToken={ previewToken } />
<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-text-output"
selectedToken={ variableToken }
onSelect={ entry => setVariableToken(entry.token) } />
<div className="nitro-wired__give-var-section">
<Text>{ LocalizeText('wiredfurni.params.texts.variable_display_type') }</Text>
<label className="flex items-center gap-1 cursor-pointer">
<input checked={ (displayType === DISPLAY_NUMERIC) } className="form-check-input" name="wiredTextOutputVariableDisplayType" type="radio" onChange={ () => setDisplayType(DISPLAY_NUMERIC) } />
<Text>{ LocalizeText('wiredfurni.params.texts.variable_display_type.1') }</Text>
</label>
<label className="flex items-center gap-1 cursor-pointer">
<input checked={ (displayType === DISPLAY_TEXTUAL) } className="form-check-input" disabled={ !canUseTextDisplay } name="wiredTextOutputVariableDisplayType" type="radio" onChange={ () => setDisplayType(DISPLAY_TEXTUAL) } />
<Text>{ LocalizeText('wiredfurni.params.texts.variable_display_type.2') }</Text>
</label>
<Text small>{ LocalizeText('wiredfurni.params.texts.variable_display_type.2.info') }</Text>
</div>
<div className="flex flex-col gap-1">
<Text>{ LocalizeText('wiredfurni.params.texts.placeholder_type') }</Text>
<label className="flex items-center gap-1 cursor-pointer">
<input checked={ (placeholderType === TYPE_SINGLE) } className="form-check-input" name="wiredTextOutputVariablePlaceholderType" type="radio" onChange={ () => setPlaceholderType(TYPE_SINGLE) } />
<Text>{ LocalizeText('wiredfurni.params.texts.placeholder_type.1') }</Text>
</label>
<label className="flex items-center gap-1 cursor-pointer">
<input checked={ (placeholderType === TYPE_MULTIPLE) } className="form-check-input" name="wiredTextOutputVariablePlaceholderType" type="radio" onChange={ () => setPlaceholderType(TYPE_MULTIPLE) } />
<Text>{ LocalizeText('wiredfurni.params.texts.placeholder_type.2') }</Text>
</label>
</div>
{ placeholderType === TYPE_MULTIPLE &&
<div className="flex flex-col gap-1">
<Text>{ LocalizeText('wiredfurni.params.texts.select_delimiter') }</Text>
<NitroInput maxLength={ MAX_DELIMITER_LENGTH } type="text" value={ delimiter } onChange={ event => setDelimiter(normalizeDelimiter(event.target.value)) } />
</div> }
</div>
</WiredExtraBaseView>
);
};
@@ -0,0 +1,116 @@
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 AVAILABILITY_ROOM = 0;
const AVAILABILITY_PERMANENT = 10;
const AVAILABILITY_SHARED = 11;
const MAX_NAME_LENGTH = 40;
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);
};
interface WiredExtraVariableViewProps
{
availabilityRoomValue: number;
availabilityRoomText: string;
availabilityRadioName: string;
showSharedAvailability?: boolean;
}
export const WiredExtraVariableView: FC<WiredExtraVariableViewProps> = props =>
{
const { trigger = null, setIntParams = null, setStringParam = null } = useWired();
const [ variableName, setVariableName ] = useState('');
const [ hasValue, setHasValue ] = useState(false);
const [ availability, setAvailability ] = useState(props.availabilityRoomValue);
const roomAvailabilityText = useMemo(() =>
{
const localizedText = props.availabilityRoomText;
if(localizedText && (localizedText !== 'wiredfurni.params.variables.availability.1')) return localizedText;
return 'Mentre la stanza è attiva';
}, [ props.availabilityRoomText ]);
const normalizeAvailability = useMemo(() => (value: number) =>
{
if(props.showSharedAvailability && (value === AVAILABILITY_SHARED)) return AVAILABILITY_SHARED;
if(value === AVAILABILITY_PERMANENT) return AVAILABILITY_PERMANENT;
return props.availabilityRoomValue;
}, [ props.availabilityRoomValue, props.showSharedAvailability ]);
useEffect(() =>
{
if(!trigger) return;
setVariableName(normalizeVariableName(trigger.stringData));
setHasValue((trigger.intData.length > 0) ? (trigger.intData[0] === 1) : false);
setAvailability(normalizeAvailability((trigger.intData.length > 1) ? trigger.intData[1] : props.availabilityRoomValue));
}, [ normalizeAvailability, props.availabilityRoomValue, trigger ]);
const save = () =>
{
setStringParam(normalizeVariableName(variableName));
setIntParams([ hasValue ? 1 : 0, normalizeAvailability(availability) ]);
};
return (
<WiredExtraBaseView hasSpecialInput={ true } requiresFurni={ WiredFurniType.STUFF_SELECTION_OPTION_NONE } save={ save } 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.settings') }</Text>
<label className="flex items-center gap-1 cursor-pointer">
<input checked={ hasValue } className="form-check-input" type="checkbox" onChange={ event => setHasValue(event.target.checked) } />
<Text>{ LocalizeText('wiredfurni.params.variables.settings.has_value') }</Text>
</label>
</div>
<div className="flex flex-col gap-1">
<Text>{ LocalizeText('wiredfurni.params.variables.availability') }</Text>
<label className="flex items-center gap-1 cursor-pointer">
<input checked={ (availability === props.availabilityRoomValue) } className="form-check-input" name={ props.availabilityRadioName } type="radio" onChange={ () => setAvailability(props.availabilityRoomValue) } />
<Text>{ roomAvailabilityText }</Text>
</label>
<label className="flex items-center gap-1 cursor-pointer">
<input checked={ (availability === AVAILABILITY_PERMANENT) } className="form-check-input" name={ props.availabilityRadioName } type="radio" onChange={ () => setAvailability(AVAILABILITY_PERMANENT) } />
<Text>{ LocalizeText('wiredfurni.params.variables.availability.10') }</Text>
</label>
{ !!props.showSharedAvailability &&
<label className="flex items-center gap-1 cursor-pointer">
<input checked={ (availability === AVAILABILITY_SHARED) } className="form-check-input" name={ props.availabilityRadioName } type="radio" onChange={ () => setAvailability(AVAILABILITY_SHARED) } />
<Text>{ LocalizeText('wiredfurni.params.variables.availability.11') }</Text>
</label> }
</div>
</div>
</WiredExtraBaseView>
);
};
export const WiredExtraUserVariableView: FC<{}> = () =>
{
return <WiredExtraVariableView availabilityRadioName="wiredUserVariableAvailability" availabilityRoomText={ LocalizeText('wiredfurni.params.variables.availability.0') } availabilityRoomValue={ AVAILABILITY_ROOM } showSharedAvailability={ true } />;
};
@@ -0,0 +1,202 @@
import { FC, useEffect, useMemo, useState } from 'react';
import { LocalizeText, WiredFurniType } from '../../../../api';
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 { WiredVariablePicker } from '../WiredVariablePicker';
import { buildWiredVariablePickerEntries, createFallbackVariableEntry, flattenWiredVariablePickerEntries, getCustomVariableItemId, IWiredVariablePickerEntry } from '../WiredVariablePickerData';
import { WiredExtraBaseView } from './WiredExtraBaseView';
const TARGET_USER = 0;
const TARGET_FURNI = 1;
const TARGET_ROOM = 3;
const MAX_NAME_LENGTH = 40;
type EchoSourceTarget = 'user' | 'furni' | 'global';
interface IEchoEditorData
{
sourceTargetType?: number;
sourceVariableItemId?: number;
sourceVariableName?: string;
sourceVariableToken?: string;
variableName?: string;
}
const TARGET_BUTTONS: Array<{ key: EchoSourceTarget; icon: string; }> = [
{ key: 'furni', icon: furniVariableIcon },
{ key: 'user', icon: userVariableIcon },
{ key: 'global', icon: globalVariableIcon }
];
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): IEchoEditorData =>
{
if(!value?.trim().startsWith('{')) return {};
try
{
return (JSON.parse(value) as IEchoEditorData) || {};
}
catch
{
return {};
}
};
const normalizeTargetType = (value: number): EchoSourceTarget =>
{
switch(value)
{
case TARGET_FURNI: return 'furni';
case TARGET_ROOM: return 'global';
default: return 'user';
}
};
const getTargetValue = (targetType: EchoSourceTarget) =>
{
switch(targetType)
{
case 'furni': return TARGET_FURNI;
case 'global': return TARGET_ROOM;
default: return TARGET_USER;
}
};
export const WiredExtraVariableEchoView: FC<{}> = () =>
{
const { trigger = null, setIntParams = null, setStringParam = null } = useWired();
const { userVariableDefinitions = [], furniVariableDefinitions = [], roomVariableDefinitions = [] } = useWiredTools();
const [ variableName, setVariableName ] = useState('');
const [ sourceTargetType, setSourceTargetType ] = useState<EchoSourceTarget>('user');
const [ sourceVariableToken, setSourceVariableToken ] = useState('');
const [ fallbackSourceName, setFallbackSourceName ] = useState('');
const targetDefinitions = useMemo(() =>
{
switch(sourceTargetType)
{
case 'furni': return furniVariableDefinitions;
case 'global': return roomVariableDefinitions;
default: return userVariableDefinitions;
}
}, [ furniVariableDefinitions, roomVariableDefinitions, sourceTargetType, userVariableDefinitions ]);
const variableEntries = useMemo(() => buildWiredVariablePickerEntries(sourceTargetType, 'echo', targetDefinitions), [ sourceTargetType, targetDefinitions ]);
const resolvedVariableEntries = useMemo(() =>
{
if(!sourceVariableToken) return variableEntries;
if(flattenWiredVariablePickerEntries(variableEntries).some(entry => (entry.token === sourceVariableToken))) return variableEntries;
const fallbackEntry = createFallbackVariableEntry(sourceTargetType, sourceVariableToken);
if(fallbackEntry) return [ fallbackEntry, ...variableEntries ];
if(!fallbackSourceName) return variableEntries;
return [ {
id: sourceVariableToken,
token: sourceVariableToken,
label: fallbackSourceName,
displayLabel: fallbackSourceName,
searchableText: fallbackSourceName,
selectable: true,
hasValue: true,
kind: 'custom',
target: sourceTargetType
}, ...variableEntries ];
}, [ fallbackSourceName, sourceTargetType, sourceVariableToken, variableEntries ]);
const selectedEntry = useMemo(() => flattenWiredVariablePickerEntries(resolvedVariableEntries).find(entry => (entry.token === sourceVariableToken)) ?? null, [ resolvedVariableEntries, sourceVariableToken ]);
useEffect(() =>
{
if(!trigger)
{
setVariableName('');
setSourceTargetType('user');
setSourceVariableToken('');
setFallbackSourceName('');
return;
}
const editorData = parseEditorData(trigger.stringData);
setVariableName(normalizeVariableName(editorData.variableName || ''));
setSourceTargetType(normalizeTargetType(editorData.sourceTargetType ?? TARGET_USER));
setSourceVariableToken((editorData.sourceVariableToken || '').trim());
setFallbackSourceName((editorData.sourceVariableName || '').trim());
}, [ trigger ]);
const save = () =>
{
setIntParams([]);
setStringParam(JSON.stringify({
variableName: normalizeVariableName(variableName),
sourceTargetType: getTargetValue(sourceTargetType),
sourceVariableToken,
sourceVariableItemId: getCustomVariableItemId(sourceVariableToken),
sourceVariableName: selectedEntry?.displayLabel || fallbackSourceName || ''
}));
};
const validate = () => !!sourceVariableToken;
const handleTargetTypeChange = (nextValue: EchoSourceTarget) =>
{
if(nextValue === sourceTargetType) return;
setSourceTargetType(nextValue);
setSourceVariableToken('');
};
return (
<WiredExtraBaseView hasSpecialInput={ true } requiresFurni={ WiredFurniType.STUFF_SELECTION_OPTION_NONE } save={ save } validate={ validate } cardStyle={ { width: 244 } }>
<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="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"
className={ `nitro-wired__give-var-target nitro-wired__give-var-target--${ button.key } ${ sourceTargetType === button.key ? 'is-active' : '' }` }
onClick={ () => handleTargetTypeChange(button.key) }>
<img src={ button.icon } alt={ button.key } />
</button>
)) }
</div>
</div>
<WiredVariablePicker
entries={ resolvedVariableEntries as IWiredVariablePickerEntry[] }
recentScope="variable-echo"
selectedToken={ sourceVariableToken }
onSelect={ entry => setSourceVariableToken(entry.token) } />
</div>
</WiredExtraBaseView>
);
};
@@ -0,0 +1,437 @@
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 MODE_LINEAR = 1;
const MODE_EXPONENTIAL = 2;
const MODE_MANUAL = 3;
const SUB_CURRENT_LEVEL = 0;
const SUB_CURRENT_XP = 1;
const SUB_LEVEL_PROGRESS = 2;
const SUB_LEVEL_PROGRESS_PERCENT = 3;
const SUB_TOTAL_XP_REQUIRED = 4;
const SUB_XP_REMAINING = 5;
const SUB_IS_AT_MAX = 6;
const SUB_MAX_LEVEL = 7;
const DEFAULT_STEP_SIZE = 100;
const DEFAULT_MAX_LEVEL = 10;
const DEFAULT_FIRST_LEVEL_XP = 100;
const DEFAULT_INCREASE_FACTOR = 100;
const DEFAULT_INTERPOLATION_TEXT = '';
const DEFAULT_SUBVARIABLES = [ SUB_CURRENT_LEVEL, SUB_CURRENT_XP ];
const DEFAULT_PLACEHOLDER = '5=100 (Level 5 = 100 XP)\n10=500\n20=4000\n...';
interface IVariableLevelUpEditorData
{
mode?: number;
stepSize?: number;
maxLevel?: number;
firstLevelXp?: number;
increaseFactor?: number;
interpolationText?: string;
subvariables?: number[] | null;
}
interface ILevelEntry
{
level: number;
requiredXp: number;
}
const localizeOrFallback = (key: string, fallback: string, params?: string[], values?: string[]) =>
{
const localized = params?.length ? LocalizeText(key, params, values ?? []) : LocalizeText(key);
return (!localized || (localized === key)) ? fallback : localized;
};
const normalizeMode = (value: number) =>
{
switch(value)
{
case MODE_EXPONENTIAL:
case MODE_MANUAL:
return value;
default:
return MODE_LINEAR;
}
};
const normalizeNonNegativeInt = (value: number, fallback: number) =>
{
if(!Number.isFinite(value)) return fallback;
return Math.max(0, Math.trunc(value));
};
const normalizePositiveInt = (value: number, fallback: number) =>
{
if(!Number.isFinite(value)) return fallback;
return Math.max(1, Math.trunc(value));
};
const normalizeInterpolationText = (value: string) => (value ?? '').replace(/\r/g, '');
const normalizeSubvariables = (value?: number[] | null) =>
{
if(value === null) return [ ...DEFAULT_SUBVARIABLES ];
if(!Array.isArray(value)) return [ ...DEFAULT_SUBVARIABLES ];
return [ ...new Set(value.filter(subvariable => Number.isInteger(subvariable) && (subvariable >= SUB_CURRENT_LEVEL) && (subvariable <= SUB_MAX_LEVEL))) ];
};
const parseEditorData = (value: string): IVariableLevelUpEditorData =>
{
if(!value?.trim()) return {};
if(!value.trim().startsWith('{'))
{
return {
mode: MODE_MANUAL,
interpolationText: normalizeInterpolationText(value)
};
}
try
{
return (JSON.parse(value) as IVariableLevelUpEditorData) || {};
}
catch
{
return {};
}
};
const parseIntInput = (value: string, fallback: number) =>
{
const parsedValue = parseInt((value ?? '').trim(), 10);
return Number.isFinite(parsedValue) ? parsedValue : fallback;
};
const parseManualAnchors = (value: string) =>
{
const anchors = new Map<number, number>();
const lines = normalizeInterpolationText(value).split('\n');
for(const rawLine of lines)
{
const trimmedLine = rawLine.trim();
if(!trimmedLine.length) continue;
const separator = trimmedLine.includes('=') ? '=' : (trimmedLine.includes(',') ? ',' : '');
if(!separator.length) continue;
const [ rawLevel, rawXp ] = trimmedLine.split(separator, 2).map(part => part.trim());
const level = parseInt(rawLevel, 10);
const xp = parseInt(rawXp, 10);
if(!Number.isFinite(level) || !Number.isFinite(xp) || (level <= 0)) continue;
anchors.set(level, Math.max(0, xp));
}
if(!anchors.has(1)) anchors.set(1, 0);
return [ ...anchors.entries() ].sort((left, right) => left[0] - right[0]);
};
const buildLinearEntries = (stepSize: number, maxLevel: number) =>
{
const entries: ILevelEntry[] = [];
for(let level = 1; level <= maxLevel; level++)
{
entries.push({
level,
requiredXp: Math.max(0, (level - 1) * stepSize)
});
}
return entries;
};
const buildExponentialEntries = (firstLevelXp: number, increaseFactor: number, maxLevel: number) =>
{
const entries: ILevelEntry[] = [ { level: 1, requiredXp: 0 } ];
let nextIncrement = Math.max(0, firstLevelXp);
let threshold = 0;
for(let level = 2; level <= maxLevel; level++)
{
threshold += nextIncrement;
entries.push({
level,
requiredXp: Math.max(0, Math.round(threshold))
});
nextIncrement = Math.max(0, Math.round(nextIncrement * ((100 + Math.max(0, increaseFactor)) / 100)));
}
return entries;
};
const buildManualEntries = (value: string) =>
{
const anchors = parseManualAnchors(value);
if(!anchors.length) return [ { level: 1, requiredXp: 0 } ];
const entries = new Map<number, number>();
for(let index = 0; index < anchors.length; index++)
{
const [ currentLevel, currentXp ] = anchors[index];
entries.set(currentLevel, currentXp);
if(index >= (anchors.length - 1)) continue;
const [ nextLevel, nextXp ] = anchors[index + 1];
if(nextLevel <= currentLevel) continue;
const deltaLevel = nextLevel - currentLevel;
const deltaXp = nextXp - currentXp;
for(let level = currentLevel + 1; level < nextLevel; level++)
{
const progress = (level - currentLevel) / deltaLevel;
entries.set(level, Math.max(0, Math.round(currentXp + (deltaXp * progress))));
}
}
return [ ...entries.entries() ]
.sort((left, right) => left[0] - right[0])
.map(([ level, requiredXp ]) => ({ level, requiredXp }));
};
const buildPreviewEntries = (mode: number, stepSize: number, maxLevel: number, firstLevelXp: number, increaseFactor: number, interpolationText: string) =>
{
switch(mode)
{
case MODE_EXPONENTIAL:
return buildExponentialEntries(firstLevelXp, increaseFactor, maxLevel);
case MODE_MANUAL:
return buildManualEntries(interpolationText);
default:
return buildLinearEntries(stepSize, maxLevel);
}
};
export const WiredExtraVariableLevelUpSystemView: FC<{}> = () =>
{
const { trigger = null, setIntParams = null, setStringParam = null } = useWired();
const [ mode, setMode ] = useState(MODE_LINEAR);
const [ stepSizeInput, setStepSizeInput ] = useState(DEFAULT_STEP_SIZE.toString());
const [ maxLevelInput, setMaxLevelInput ] = useState(DEFAULT_MAX_LEVEL.toString());
const [ firstLevelXpInput, setFirstLevelXpInput ] = useState(DEFAULT_FIRST_LEVEL_XP.toString());
const [ increaseFactorInput, setIncreaseFactorInput ] = useState(DEFAULT_INCREASE_FACTOR.toString());
const [ interpolationText, setInterpolationText ] = useState(DEFAULT_INTERPOLATION_TEXT);
const [ selectedSubvariables, setSelectedSubvariables ] = useState<number[]>(DEFAULT_SUBVARIABLES);
const [ isModeSectionOpen, setIsModeSectionOpen ] = useState(true);
const [ isPreviewSectionOpen, setIsPreviewSectionOpen ] = useState(true);
const [ isSubvariablesSectionOpen, setIsSubvariablesSectionOpen ] = useState(true);
useEffect(() =>
{
if(!trigger)
{
setMode(MODE_LINEAR);
setStepSizeInput(DEFAULT_STEP_SIZE.toString());
setMaxLevelInput(DEFAULT_MAX_LEVEL.toString());
setFirstLevelXpInput(DEFAULT_FIRST_LEVEL_XP.toString());
setIncreaseFactorInput(DEFAULT_INCREASE_FACTOR.toString());
setInterpolationText(DEFAULT_INTERPOLATION_TEXT);
setSelectedSubvariables([ ...DEFAULT_SUBVARIABLES ]);
return;
}
const editorData = parseEditorData(trigger.stringData);
setMode(normalizeMode(editorData.mode ?? MODE_LINEAR));
setStepSizeInput(normalizeNonNegativeInt(editorData.stepSize ?? DEFAULT_STEP_SIZE, DEFAULT_STEP_SIZE).toString());
setMaxLevelInput(normalizePositiveInt(editorData.maxLevel ?? DEFAULT_MAX_LEVEL, DEFAULT_MAX_LEVEL).toString());
setFirstLevelXpInput(normalizeNonNegativeInt(editorData.firstLevelXp ?? DEFAULT_FIRST_LEVEL_XP, DEFAULT_FIRST_LEVEL_XP).toString());
setIncreaseFactorInput(normalizeNonNegativeInt(editorData.increaseFactor ?? DEFAULT_INCREASE_FACTOR, DEFAULT_INCREASE_FACTOR).toString());
setInterpolationText(normalizeInterpolationText(editorData.interpolationText ?? DEFAULT_INTERPOLATION_TEXT));
setSelectedSubvariables(normalizeSubvariables(editorData.subvariables));
}, [ trigger ]);
const normalizedStepSize = useMemo(() => normalizeNonNegativeInt(parseIntInput(stepSizeInput, DEFAULT_STEP_SIZE), DEFAULT_STEP_SIZE), [ stepSizeInput ]);
const normalizedMaxLevel = useMemo(() => normalizePositiveInt(parseIntInput(maxLevelInput, DEFAULT_MAX_LEVEL), DEFAULT_MAX_LEVEL), [ maxLevelInput ]);
const normalizedFirstLevelXp = useMemo(() => normalizeNonNegativeInt(parseIntInput(firstLevelXpInput, DEFAULT_FIRST_LEVEL_XP), DEFAULT_FIRST_LEVEL_XP), [ firstLevelXpInput ]);
const normalizedIncreaseFactor = useMemo(() => normalizeNonNegativeInt(parseIntInput(increaseFactorInput, DEFAULT_INCREASE_FACTOR), DEFAULT_INCREASE_FACTOR), [ increaseFactorInput ]);
const normalizedInterpolation = useMemo(() => normalizeInterpolationText(interpolationText), [ interpolationText ]);
const previewEntries = useMemo(() => buildPreviewEntries(mode, normalizedStepSize, normalizedMaxLevel, normalizedFirstLevelXp, normalizedIncreaseFactor, normalizedInterpolation), [ mode, normalizedFirstLevelXp, normalizedIncreaseFactor, normalizedInterpolation, normalizedMaxLevel, normalizedStepSize ]);
const interpolationPlaceholder = useMemo(() =>
{
const localizedText = LocalizeText('wiredfurni.params.levelup.interpolation_placeholder');
if(!localizedText || (localizedText === 'wiredfurni.params.levelup.interpolation_placeholder')) return DEFAULT_PLACEHOLDER;
return localizedText.includes('5,100') ? localizedText.replace(/,/g, '=') : localizedText;
}, []);
const save = () =>
{
setIntParams([]);
setStringParam(JSON.stringify({
mode,
stepSize: normalizedStepSize,
maxLevel: normalizedMaxLevel,
firstLevelXp: normalizedFirstLevelXp,
increaseFactor: normalizedIncreaseFactor,
interpolationText: normalizedInterpolation,
subvariables: [ ...selectedSubvariables ].sort((left, right) => left - right)
}));
};
const toggleSubvariable = (subvariable: number) =>
{
setSelectedSubvariables(previousValue =>
{
if(previousValue.includes(subvariable))
{
return previousValue.filter(value => value !== subvariable);
}
return [ ...previousValue, subvariable ].sort((left, right) => left - right);
});
};
const modeOptions = [
{ value: MODE_LINEAR, label: localizeOrFallback('wiredfurni.params.levelup.mode.1', 'Lineare') },
{ value: MODE_EXPONENTIAL, label: localizeOrFallback('wiredfurni.params.levelup.mode.2', 'Esponenziale') },
{ value: MODE_MANUAL, label: localizeOrFallback('wiredfurni.params.levelup.mode.3', 'Manuale') }
];
const subvariableOptions = [
{ key: SUB_CURRENT_LEVEL, suffix: 'current_level' },
{ key: SUB_CURRENT_XP, suffix: 'current_xp' },
{ key: SUB_LEVEL_PROGRESS, suffix: 'level_progress' },
{ key: SUB_LEVEL_PROGRESS_PERCENT, suffix: 'level_progress_percent' },
{ key: SUB_TOTAL_XP_REQUIRED, suffix: 'total_xp_required' },
{ key: SUB_XP_REMAINING, suffix: 'xp_remaining' },
{ key: SUB_IS_AT_MAX, suffix: 'is_at_max' },
{ key: SUB_MAX_LEVEL, suffix: 'max_level' }
];
return (
<WiredExtraBaseView hasSpecialInput={ true } requiresFurni={ WiredFurniType.STUFF_SELECTION_OPTION_NONE } save={ save } cardStyle={ { width: 260 } }>
<div className="nitro-wired__levelup">
<div className="nitro-wired__levelup-section">
<button type="button" className="nitro-wired__levelup-section-header" onClick={ () => setIsModeSectionOpen(value => !value) }>
<Text bold>{ LocalizeText('wiredfurni.params.levelup.mode') }</Text>
<span className={ `nitro-wired__levelup-chevron ${ isModeSectionOpen ? 'is-open' : '' }` }>^</span>
</button>
{ isModeSectionOpen &&
<div className="nitro-wired__levelup-section-body">
<div className={ `nitro-wired__levelup-mode-block ${ mode === MODE_LINEAR ? 'is-active' : 'is-inactive' }` }>
<label className="nitro-wired__levelup-mode-label">
<input checked={ mode === MODE_LINEAR } className="form-check-input" name="wiredVariableLevelUpMode" type="radio" onChange={ () => setMode(MODE_LINEAR) } />
<Text>{ localizeOrFallback('wiredfurni.params.levelup.mode.1', 'Lineare') }</Text>
</label>
<div className="nitro-wired__levelup-fields">
<div className="nitro-wired__levelup-field-row">
<Text>{ LocalizeText('wiredfurni.params.levelup.step_size') }</Text>
<NitroInput className="nitro-wired__levelup-number" disabled={ mode !== MODE_LINEAR } type="number" value={ stepSizeInput } onChange={ event => setStepSizeInput(event.target.value) } />
</div>
<div className="nitro-wired__levelup-field-row">
<Text>{ LocalizeText('wiredfurni.params.levelup.max_level') }</Text>
<NitroInput className="nitro-wired__levelup-number" disabled={ mode !== MODE_LINEAR } type="number" value={ maxLevelInput } onChange={ event => setMaxLevelInput(event.target.value) } />
</div>
</div>
</div>
<div className={ `nitro-wired__levelup-mode-block ${ mode === MODE_EXPONENTIAL ? 'is-active' : 'is-inactive' }` }>
<label className="nitro-wired__levelup-mode-label">
<input checked={ mode === MODE_EXPONENTIAL } className="form-check-input" name="wiredVariableLevelUpMode" type="radio" onChange={ () => setMode(MODE_EXPONENTIAL) } />
<Text>{ localizeOrFallback('wiredfurni.params.levelup.mode.2', 'Esponenziale') }</Text>
</label>
<div className="nitro-wired__levelup-fields">
<div className="nitro-wired__levelup-field-row">
<Text>{ LocalizeText('wiredfurni.params.levelup.first_level_xp') }</Text>
<NitroInput className="nitro-wired__levelup-number" disabled={ mode !== MODE_EXPONENTIAL } type="number" value={ firstLevelXpInput } onChange={ event => setFirstLevelXpInput(event.target.value) } />
</div>
<div className="nitro-wired__levelup-field-row">
<Text>{ LocalizeText('wiredfurni.params.levelup.increase_factor') }</Text>
<NitroInput className="nitro-wired__levelup-number" disabled={ mode !== MODE_EXPONENTIAL } type="number" value={ increaseFactorInput } onChange={ event => setIncreaseFactorInput(event.target.value) } />
</div>
<div className="nitro-wired__levelup-field-row">
<Text>{ LocalizeText('wiredfurni.params.levelup.max_level') }</Text>
<NitroInput className="nitro-wired__levelup-number" disabled={ mode !== MODE_EXPONENTIAL } type="number" value={ maxLevelInput } onChange={ event => setMaxLevelInput(event.target.value) } />
</div>
</div>
</div>
<div className={ `nitro-wired__levelup-mode-block ${ mode === MODE_MANUAL ? 'is-active' : 'is-inactive' }` }>
<label className="nitro-wired__levelup-mode-label">
<input checked={ mode === MODE_MANUAL } className="form-check-input" name="wiredVariableLevelUpMode" type="radio" onChange={ () => setMode(MODE_MANUAL) } />
<Text>{ localizeOrFallback('wiredfurni.params.levelup.mode.3', 'Inserimento manuale') }</Text>
</label>
<textarea
className="form-control form-control-sm nitro-wired__levelup-textarea"
disabled={ mode !== MODE_MANUAL }
placeholder={ interpolationPlaceholder }
value={ interpolationText }
onChange={ event => setInterpolationText(event.target.value) } />
</div>
</div> }
</div>
<div className="nitro-wired__divider" />
<div className="nitro-wired__levelup-section">
<button type="button" className="nitro-wired__levelup-section-header" onClick={ () => setIsPreviewSectionOpen(value => !value) }>
<Text bold>{ LocalizeText('wiredfurni.params.levelup.preview') }</Text>
<span className={ `nitro-wired__levelup-chevron ${ isPreviewSectionOpen ? 'is-open' : '' }` }>^</span>
</button>
{ isPreviewSectionOpen &&
<div className="nitro-wired__levelup-preview">
{ previewEntries.map(entry => (
<div key={ entry.level } className="nitro-wired__levelup-preview-entry">
{ localizeOrFallback('wiredfurni.params.levelup.preview.entry', `Livello: ${ entry.level } - XP: ${ entry.requiredXp }`, [ 'lvl', 'xp' ], [ entry.level.toString(), entry.requiredXp.toString() ]) }
</div>
)) }
</div> }
</div>
<div className="nitro-wired__divider" />
<div className="nitro-wired__levelup-section">
<button type="button" className="nitro-wired__levelup-section-header" onClick={ () => setIsSubvariablesSectionOpen(value => !value) }>
<Text bold>{ LocalizeText('wiredfurni.params.create_subvariables') }</Text>
<span className={ `nitro-wired__levelup-chevron ${ isSubvariablesSectionOpen ? 'is-open' : '' }` }>^</span>
</button>
{ isSubvariablesSectionOpen &&
<div className="nitro-wired__levelup-subvariables">
{ subvariableOptions.map(subvariable => (
<div key={ subvariable.key } className="nitro-wired__levelup-subvariable-row">
<label className="nitro-wired__levelup-subvariable-label">
<input checked={ selectedSubvariables.includes(subvariable.key) } className="form-check-input" type="checkbox" onChange={ () => toggleSubvariable(subvariable.key) } />
<Text>{ LocalizeText(`wiredfurni.params.levelup.subvariable.${ subvariable.key }`) }</Text>
</label>
<input className="nitro-wired__levelup-subvariable-token" readOnly tabIndex={ -1 } type="text" value={ subvariable.suffix } />
</div>
)) }
</div> }
</div>
</div>
</WiredExtraBaseView>
);
};
@@ -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>
);
};
@@ -0,0 +1,49 @@
import { FC, useEffect, useState } from 'react';
import { LocalizeText, WiredFurniType } from '../../../../api';
import { Text } from '../../../../common';
import { useWired } from '../../../../hooks';
import { WiredExtraBaseView } from './WiredExtraBaseView';
const DEFAULT_CONNECTOR_PLACEHOLDER = '0=text 1\n1=text 2\n2 = text 3';
export const WiredExtraVariableTextConnectorView: FC<{}> = () =>
{
const { trigger = null, setIntParams = null, setStringParam = null } = useWired();
const [ mappingsText, setMappingsText ] = useState('');
useEffect(() =>
{
if(!trigger) return;
setMappingsText(trigger.stringData || '');
}, [ trigger ]);
const save = () =>
{
setIntParams([]);
setStringParam(mappingsText ?? '');
};
const placeholderText = (() =>
{
const localizedText = LocalizeText('wiredfurni.params.variables.connect_text.caption');
if(!localizedText || (localizedText === 'wiredfurni.params.variables.connect_text.caption')) return DEFAULT_CONNECTOR_PLACEHOLDER;
if(localizedText.includes('0,text0') || localizedText.includes('1,text1')) return DEFAULT_CONNECTOR_PLACEHOLDER;
return localizedText;
})();
return (
<WiredExtraBaseView hasSpecialInput={ true } requiresFurni={ WiredFurniType.STUFF_SELECTION_OPTION_NONE } save={ save } cardStyle={ { width: 400 } }>
<div className="flex flex-col gap-2">
<Text bold>{ LocalizeText('wiredfurni.params.variables.connect_text.title') }</Text>
<textarea
className="form-control form-control-sm nitro-wired__resizable-textarea"
placeholder={ placeholderText }
value={ mappingsText }
onChange={ event => setMappingsText(event.target.value) } />
</div>
</WiredExtraBaseView>
);
};
@@ -0,0 +1,77 @@
import { FC, useEffect, useState } from 'react';
import { Text } from '../../../../common';
interface WiredPlaceholderPreviewProps
{
previewHtml: string;
previewToken: string;
}
const copyToClipboard = async (value: string) =>
{
if(!value) return false;
try
{
if(navigator?.clipboard?.writeText)
{
await navigator.clipboard.writeText(value);
return true;
}
}
catch
{
}
try
{
const textArea = document.createElement('textarea');
textArea.value = value;
textArea.setAttribute('readonly', '');
textArea.style.position = 'fixed';
textArea.style.opacity = '0';
document.body.appendChild(textArea);
textArea.select();
const copied = document.execCommand('copy');
document.body.removeChild(textArea);
return copied;
}
catch
{
return false;
}
};
export const WiredPlaceholderPreview: FC<WiredPlaceholderPreviewProps> = props =>
{
const { previewHtml, previewToken } = props;
const [ copied, setCopied ] = useState(false);
useEffect(() =>
{
if(!copied) return;
const timeout = window.setTimeout(() => setCopied(false), 1200);
return () => window.clearTimeout(timeout);
}, [ copied ]);
const handleCopy = async () =>
{
const didCopy = await copyToClipboard(previewToken);
setCopied(didCopy);
};
return (
<button type="button" className={ `nitro-wired__placeholder-preview ${ copied ? 'is-copied' : '' }` } onClick={ handleCopy }>
<Text dangerouslySetInnerHTML={ { __html: previewHtml } } />
</button>
);
};
@@ -0,0 +1,7 @@
import { FC } from 'react';
import { WiredSelectorWithVariableView } from './WiredSelectorWithVariableView';
export const WiredSelectorFurniWithVariableView: FC<{}> = () =>
{
return <WiredSelectorWithVariableView selectorTarget="furni" />;
};
@@ -37,7 +37,7 @@ export const WiredSelectorUsersByNameView: FC<{}> = () =>
<div className="flex flex-col gap-1"> <div className="flex flex-col gap-1">
<Text bold>{ LocalizeText('wiredfurni.params.enter_names') }</Text> <Text bold>{ LocalizeText('wiredfurni.params.enter_names') }</Text>
<textarea <textarea
className="form-control form-control-sm min-h-[140px] resize-none" className="form-control form-control-sm nitro-wired__resizable-textarea"
value={ namesText } value={ namesText }
onChange={ event => setNamesText(event.target.value) } /> onChange={ event => setNamesText(event.target.value) } />
</div> </div>
@@ -0,0 +1,7 @@
import { FC } from 'react';
import { WiredSelectorWithVariableView } from './WiredSelectorWithVariableView';
export const WiredSelectorUsersWithVariableView: FC<{}> = () =>
{
return <WiredSelectorWithVariableView selectorTarget="user" />;
};
@@ -0,0 +1,366 @@
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 { buildWiredVariablePickerEntries, createFallbackVariableEntry, flattenWiredVariablePickerEntries, normalizeVariableTokenFromWire, WiredVariablePickerTarget } from '../WiredVariablePickerData';
import { CLICKED_USER_SOURCE, FURNI_SOURCES, sortWiredSourceOptions, USER_SOURCES, useAvailableUserSources, WiredSourceOption } from '../WiredSourcesSelector';
import { WiredSelectorBaseView } from './WiredSelectorBaseView';
type VariableTargetType = 'user' | 'furni' | 'global' | 'context';
type ReferenceMode = 'constant' | 'variable';
interface IVariableDefinition
{
availability: number;
hasValue: boolean;
itemId: number;
name: string;
}
interface WiredSelectorWithVariableViewProps
{
selectorTarget: 'user' | 'furni';
}
const TARGET_USER = 0;
const TARGET_FURNI = 1;
const TARGET_CONTEXT = 2;
const TARGET_GLOBAL = 3;
const REFERENCE_CONSTANT = 0;
const REFERENCE_VARIABLE = 1;
const SOURCE_TRIGGER = 0;
const SOURCE_SECONDARY_SELECTED = 101;
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 COMPARISON_OPTIONS = [
{ value: 0, label: '>' },
{ value: 1, label: '≥' },
{ value: 2, label: '=' },
{ value: 3, label: '≤' },
{ value: 4, label: '<' },
{ value: 5, label: '≠' }
];
const SECONDARY_FURNI_SOURCES: WiredSourceOption[] = sortWiredSourceOptions([
{ value: SOURCE_TRIGGER, label: 'wiredfurni.params.sources.furni.0' },
{ value: SOURCE_SECONDARY_SELECTED, label: 'wiredfurni.params.sources.furni.101' },
{ value: 200, label: 'wiredfurni.params.sources.furni.200' },
{ value: 201, label: 'wiredfurni.params.sources.furni.201' }
], 'furni');
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 parseStringData = (value: string) => (value?.length ? value.split('\t', -1) : []);
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 getReferenceDefinitions = (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 resolveSourceOptions = (baseOptions: WiredSourceOption[], selectedValue: number, fallbackOptions: WiredSourceOption[]) =>
{
if(!baseOptions.length) return baseOptions;
if(baseOptions.some(option => (option.value === selectedValue))) return baseOptions;
const fallbackOption = fallbackOptions.find(option => (option.value === selectedValue));
return fallbackOption ? [ ...baseOptions, fallbackOption ] : baseOptions;
};
export const WiredSelectorWithVariableView: FC<WiredSelectorWithVariableViewProps> = ({ selectorTarget }) =>
{
const { trigger = null, furniIds = [], setFurniIds = null, setIntParams = null, setStringParam = null } = useWired();
const { userVariableDefinitions = [], furniVariableDefinitions = [], roomVariableDefinitions = [], contextVariableDefinitions = [] } = useWiredTools();
const [ variableToken, setVariableToken ] = useState('');
const [ selectByValue, setSelectByValue ] = useState(false);
const [ comparison, setComparison ] = useState(2);
const [ referenceMode, setReferenceMode ] = useState<ReferenceMode>('constant');
const [ referenceConstantValueInput, setReferenceConstantValueInput ] = useState('0');
const [ referenceTargetType, setReferenceTargetType ] = useState<VariableTargetType>('user');
const [ referenceVariableToken, setReferenceVariableToken ] = useState('');
const [ referenceUserSource, setReferenceUserSource ] = useState(SOURCE_TRIGGER);
const [ referenceFurniSource, setReferenceFurniSource ] = useState(SOURCE_TRIGGER);
const [ referenceFurniIds, setReferenceFurniIds ] = useState<number[]>([]);
const [ filterExisting, setFilterExisting ] = useState(false);
const [ invert, setInvert ] = useState(false);
const availableUserSources = useAvailableUserSources(trigger, USER_SOURCES);
const orderedUserSources = useMemo(() => sortWiredSourceOptions(availableUserSources, 'users'), [ availableUserSources ]);
const orderedFurniSources = useMemo(() => sortWiredSourceOptions(SECONDARY_FURNI_SOURCES, 'furni'), []);
const userSourceFallbackOptions = useMemo(() => sortWiredSourceOptions([ ...USER_SOURCES, CLICKED_USER_SOURCE ], 'users'), []);
const mainDefinitions = selectorTarget === 'user' ? userVariableDefinitions : furniVariableDefinitions;
const mainEntries = useMemo(() => buildWiredVariablePickerEntries(selectorTarget as WiredVariablePickerTarget, 'condition', mainDefinitions), [ mainDefinitions, selectorTarget ]);
const resolvedMainEntries = useMemo(() =>
{
if(!variableToken) return mainEntries;
if(flattenWiredVariablePickerEntries(mainEntries).some(entry => (entry.token === variableToken))) return mainEntries;
const fallbackEntry = createFallbackVariableEntry(selectorTarget as WiredVariablePickerTarget, variableToken);
return fallbackEntry ? [ fallbackEntry, ...mainEntries ] : mainEntries;
}, [ mainEntries, selectorTarget, variableToken ]);
const selectedMainEntry = useMemo(() => flattenWiredVariablePickerEntries(resolvedMainEntries).find(entry => (entry.token === variableToken)) || null, [ resolvedMainEntries, variableToken ]);
const canSelectByValue = !!selectedMainEntry?.hasValue;
const referenceDefinitions = useMemo(() => getReferenceDefinitions(referenceTargetType, userVariableDefinitions, furniVariableDefinitions, roomVariableDefinitions, contextVariableDefinitions), [ contextVariableDefinitions, furniVariableDefinitions, referenceTargetType, roomVariableDefinitions, userVariableDefinitions ]);
const referenceEntries = useMemo(() => buildWiredVariablePickerEntries(referenceTargetType, 'change-reference', referenceDefinitions), [ referenceDefinitions, referenceTargetType ]);
const resolvedReferenceEntries = useMemo(() =>
{
if(!referenceVariableToken) return referenceEntries;
if(flattenWiredVariablePickerEntries(referenceEntries).some(entry => (entry.token === referenceVariableToken))) return referenceEntries;
const fallbackEntry = createFallbackVariableEntry(referenceTargetType, referenceVariableToken);
return fallbackEntry ? [ fallbackEntry, ...referenceEntries ] : referenceEntries;
}, [ referenceEntries, referenceTargetType, referenceVariableToken ]);
const referenceSelectionEnabled = selectByValue && referenceMode === 'variable' && referenceTargetType === 'furni' && referenceFurniSource === SOURCE_SECONDARY_SELECTED;
const selectionLimit = trigger?.maximumItemSelectionCount ?? 0;
const requiresFurni = referenceSelectionEnabled ? WiredFurniType.STUFF_SELECTION_OPTION_BY_ID : WiredFurniType.STUFF_SELECTION_OPTION_NONE;
const selectedReferenceSourceValue = referenceTargetType === 'furni' ? referenceFurniSource : ((referenceTargetType === 'global' || referenceTargetType === 'context') ? SOURCE_TRIGGER : referenceUserSource);
const referenceSourceOptions = useMemo(() =>
{
if(referenceTargetType === 'furni') return resolveSourceOptions(orderedFurniSources, selectedReferenceSourceValue, orderedFurniSources);
if(referenceTargetType === 'global') return GLOBAL_SOURCE_OPTIONS;
if(referenceTargetType === 'context') return CONTEXT_SOURCE_OPTIONS;
return resolveSourceOptions(orderedUserSources, selectedReferenceSourceValue, userSourceFallbackOptions);
}, [ orderedFurniSources, orderedUserSources, referenceTargetType, selectedReferenceSourceValue, userSourceFallbackOptions ]);
useEffect(() =>
{
if(!trigger) return;
const stringParts = parseStringData(trigger.stringData);
const nextReferenceFurniIds = [ ...(trigger.selectedItems ?? []) ];
setVariableToken(normalizeVariableTokenFromWire((stringParts.length > 0) ? stringParts[0] : ''));
setReferenceVariableToken(normalizeVariableTokenFromWire((stringParts.length > 1) ? stringParts[1] : ''));
setSelectByValue((trigger.intData.length > 0) ? (trigger.intData[0] === 1) : false);
setComparison((trigger.intData.length > 1) ? trigger.intData[1] : 2);
setReferenceMode(((trigger.intData.length > 2) ? trigger.intData[2] : REFERENCE_CONSTANT) === REFERENCE_VARIABLE ? 'variable' : 'constant');
setReferenceConstantValueInput(((trigger.intData.length > 3) ? trigger.intData[3] : 0).toString());
setReferenceTargetType(normalizeTargetType((trigger.intData.length > 4) ? trigger.intData[4] : TARGET_USER));
setReferenceUserSource((trigger.intData.length > 5) ? trigger.intData[5] : SOURCE_TRIGGER);
setReferenceFurniSource((trigger.intData.length > 6) ? trigger.intData[6] : (nextReferenceFurniIds.length ? SOURCE_SECONDARY_SELECTED : SOURCE_TRIGGER));
setFilterExisting((trigger.intData.length > 7) ? (trigger.intData[7] === 1) : false);
setInvert((trigger.intData.length > 8) ? (trigger.intData[8] === 1) : false);
setReferenceFurniIds(nextReferenceFurniIds);
setFurniIds(nextReferenceFurniIds);
}, [ setFurniIds, trigger ]);
useEffect(() =>
{
if(!canSelectByValue && selectByValue) setSelectByValue(false);
}, [ canSelectByValue, selectByValue ]);
useEffect(() =>
{
if(referenceSelectionEnabled) setReferenceFurniIds([ ...furniIds ]);
}, [ furniIds, referenceSelectionEnabled ]);
useEffect(() =>
{
if(referenceTargetType !== 'user') return;
if(orderedUserSources.some(option => (option.value === referenceUserSource))) return;
setReferenceUserSource(SOURCE_TRIGGER);
}, [ orderedUserSources, referenceTargetType, referenceUserSource ]);
const save = () =>
{
const nextReferenceFurniIds = referenceSelectionEnabled ? [ ...furniIds ] : [ ...referenceFurniIds ];
const parsedReferenceConstantValue = parseInt(referenceConstantValueInput.trim(), 10);
setReferenceFurniIds(nextReferenceFurniIds);
setStringParam(`${ variableToken || '' }\t${ (selectByValue && referenceMode === 'variable') ? referenceVariableToken : '' }`);
setIntParams([
selectByValue ? 1 : 0,
comparison,
referenceMode === 'variable' ? REFERENCE_VARIABLE : REFERENCE_CONSTANT,
Number.isFinite(parsedReferenceConstantValue) ? parsedReferenceConstantValue : 0,
getTargetValue(referenceTargetType),
referenceUserSource,
referenceFurniSource,
filterExisting ? 1 : 0,
invert ? 1 : 0
]);
setFurniIds(referenceSelectionEnabled ? nextReferenceFurniIds : []);
};
const validate = () =>
{
if(!variableToken) return false;
if(selectByValue && !canSelectByValue) return false;
if(selectByValue && referenceMode === 'variable' && !referenceVariableToken) return false;
return true;
};
const handleReferenceTargetChange = (targetType: VariableTargetType) =>
{
if(targetType === referenceTargetType) return;
if(referenceSelectionEnabled) setFurniIds([ ...furniIds ]);
if(referenceTargetType === 'furni') setFurniIds([]);
setReferenceTargetType(targetType);
setReferenceVariableToken('');
};
return (
<WiredSelectorBaseView hasSpecialInput={ true } requiresFurni={ requiresFurni } save={ save } validate={ validate } hideDelay={ true } cardStyle={ { width: 260 } }>
<div className="nitro-wired__give-var">
<div className="flex flex-col gap-1">
<Text>{ LocalizeText('wiredfurni.params.variables.variable_selection') }</Text>
<WiredVariablePicker
entries={ resolvedMainEntries }
recentScope={ `variable-selectors-${ selectorTarget }` }
selectedToken={ variableToken }
onSelect={ entry => setVariableToken(entry.token) } />
</div>
<div className="nitro-wired__divider" />
<label className="flex items-center gap-2">
<input className="form-check-input" type="checkbox" checked={ selectByValue } disabled={ !canSelectByValue } onChange={ event => setSelectByValue(event.target.checked) } />
<Text small>{ LocalizeText('wiredfurni.params.variables.value_settings.select_by_value') }</Text>
</label>
<div className={ `flex flex-col gap-2 ${ !selectByValue ? 'opacity-60' : '' }` }>
<div className="nitro-wired__divider" />
<div className="flex flex-col gap-1">
<Text bold>{ LocalizeText('wiredfurni.params.comparison_selection') }</Text>
<div className="flex flex-wrap items-center gap-2">
{ COMPARISON_OPTIONS.map(option => (
<label key={ option.value } className="flex items-center gap-1">
<input checked={ comparison === option.value } className="form-check-input" type="radio" disabled={ !selectByValue } onChange={ () => setComparison(option.value) } />
<Text>{ option.label }</Text>
</label>
)) }
</div>
</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.reference_value') }</div>
<label className="nitro-wired__change-var-radio">
<input checked={ referenceMode === 'constant' } type="radio" disabled={ !selectByValue } onChange={ () => setReferenceMode('constant') } />
<Text>{ LocalizeText('wiredfurni.params.operator.2') }</Text>
<NitroInput className="nitro-wired__give-var-number" type="number" value={ referenceConstantValueInput } disabled={ !selectByValue } onChange={ event => setReferenceConstantValueInput(event.target.value) } />
</label>
<div className="nitro-wired__change-var-reference-block">
<label className="nitro-wired__change-var-radio">
<input checked={ referenceMode === 'variable' } type="radio" disabled={ !selectByValue } onChange={ () => setReferenceMode('variable') } />
<Text>{ LocalizeText('wiredfurni.params.variables.reference_value.from_variable') }</Text>
<div className="nitro-wired__give-var-targets">
{ TARGET_BUTTONS.map(button => (
<button
key={ `reference-${ button.key }` }
type="button"
disabled={ button.disabled || !selectByValue || (referenceMode !== 'variable') }
className={ `nitro-wired__give-var-target nitro-wired__give-var-target--${ button.key } ${ referenceTargetType === button.key ? 'is-active' : '' }` }
onClick={ () => handleReferenceTargetChange(button.key) }>
<img src={ button.icon } alt={ button.key } />
</button>
)) }
</div>
</label>
{ (selectByValue && referenceMode === 'variable') &&
<WiredVariablePicker
entries={ resolvedReferenceEntries }
recentScope="variable-selectors-reference"
selectedToken={ referenceVariableToken }
onSelect={ entry => setReferenceVariableToken(entry.token) } /> }
</div>
</div>
{ (selectByValue && referenceMode === 'variable') &&
<>
<div className="nitro-wired__divider" />
<WiredFurniSelectionSourceRow
title="wiredfurni.params.sources.merged.title.variables_reference"
options={ referenceSourceOptions }
value={ selectedReferenceSourceValue }
selectionKind="primary"
selectionActive={ true }
selectionCount={ referenceSelectionEnabled ? furniIds.length : referenceFurniIds.length }
selectionLimit={ selectionLimit }
selectionEnabledValues={ [ SOURCE_SECONDARY_SELECTED ] }
showSelectionToggle={ false }
onChange={ value =>
{
if(referenceTargetType === 'furni')
{
if(referenceFurniSource === SOURCE_SECONDARY_SELECTED) setReferenceFurniIds([ ...furniIds ]);
setReferenceFurniSource(value);
setFurniIds(value === SOURCE_SECONDARY_SELECTED ? [ ...referenceFurniIds ] : []);
return;
}
if(referenceTargetType === 'user') setReferenceUserSource(value);
} } />
</> }
</div>
<div className="nitro-wired__divider" />
<Text bold>{ LocalizeText('wiredfurni.params.selector_options_selector') }</Text>
<label className="flex items-center gap-2">
<input className="form-check-input" type="checkbox" checked={ filterExisting } onChange={ event => setFilterExisting(event.target.checked) } />
<Text small>{ LocalizeText('wiredfurni.params.selector_option.0') }</Text>
</label>
<label className="flex items-center gap-2">
<input className="form-check-input" type="checkbox" checked={ invert } onChange={ event => setInvert(event.target.checked) } />
<Text small>{ LocalizeText('wiredfurni.params.selector_option.1') }</Text>
</label>
</div>
</WiredSelectorBaseView>
);
};
@@ -12,6 +12,7 @@ import { WiredTriggerClickUserView } from './WiredTriggerClickUserView';
import { WiredTriggerClockCounterView } from './WiredTriggerClockCounterView'; import { WiredTriggerClockCounterView } from './WiredTriggerClockCounterView';
import { WiredTriggerCollisionView } from './WiredTriggerCollisionView'; import { WiredTriggerCollisionView } from './WiredTriggerCollisionView';
import { WiredTriggerUserPerformsActionView } from './WiredTriggerUserPerformsActionView'; import { WiredTriggerUserPerformsActionView } from './WiredTriggerUserPerformsActionView';
import { WiredTriggerVariableChangedView } from './WiredTriggerVariableChangedView';
import { WiredTriggeExecuteOnceView } from './WiredTriggerExecuteOnceView'; import { WiredTriggeExecuteOnceView } from './WiredTriggerExecuteOnceView';
import { WiredTriggeExecutePeriodicallyLongView } from './WiredTriggerExecutePeriodicallyLongView'; import { WiredTriggeExecutePeriodicallyLongView } from './WiredTriggerExecutePeriodicallyLongView';
import { WiredTriggeExecutePeriodicallyView } from './WiredTriggerExecutePeriodicallyView'; import { WiredTriggeExecutePeriodicallyView } from './WiredTriggerExecutePeriodicallyView';
@@ -48,6 +49,8 @@ export const WiredTriggerLayoutView = (code: number) =>
return <WiredTriggerClickUserView />; return <WiredTriggerClickUserView />;
case WiredTriggerLayout.CLOCK_COUNTER: case WiredTriggerLayout.CLOCK_COUNTER:
return <WiredTriggerClockCounterView />; return <WiredTriggerClockCounterView />;
case WiredTriggerLayout.VARIABLE_CHANGED:
return <WiredTriggerVariableChangedView />;
case WiredTriggerLayout.USER_PERFORMS_ACTION: case WiredTriggerLayout.USER_PERFORMS_ACTION:
return <WiredTriggerUserPerformsActionView />; return <WiredTriggerUserPerformsActionView />;
case WiredTriggerLayout.COLLISION: case WiredTriggerLayout.COLLISION:
@@ -10,8 +10,6 @@ const FURNI_SOURCE_OPTIONS: WiredSourceOption[] = [
{ value: 200, label: 'wiredfurni.params.sources.furni.200' } { value: 200, label: 'wiredfurni.params.sources.furni.200' }
]; ];
const ANTENNA_INTERACTION_TYPES = [ 'antenna' ];
const ANTENNA_ERROR_MESSAGE = 'Puoi selezionare solo furni antenna.';
const normalizeFurniSource = (value: number) => (FURNI_SOURCE_OPTIONS.some(option => (option.value === value)) ? value : 100); const normalizeFurniSource = (value: number) => (FURNI_SOURCE_OPTIONS.some(option => (option.value === value)) ? value : 100);
export const WiredTriggerReceiveSignalView: FC<{}> = () => export const WiredTriggerReceiveSignalView: FC<{}> = () =>
@@ -21,7 +19,7 @@ export const WiredTriggerReceiveSignalView: FC<{}> = () =>
const [ channel, setChannel ] = useState(0); const [ channel, setChannel ] = useState(0);
const [ furniSource, setFurniSource ] = useState(100); const [ furniSource, setFurniSource ] = useState(100);
const { trigger = null, setIntParams = null, setAllowedInteractionTypes = null, setAllowedInteractionErrorKey = null } = useWired(); const { trigger = null, setIntParams = null } = useWired();
const save = () => setIntParams([ channel, furniSource ]); const save = () => setIntParams([ channel, furniSource ]);
@@ -37,18 +35,6 @@ export const WiredTriggerReceiveSignalView: FC<{}> = () =>
else setFurniSource(100); else setFurniSource(100);
}, [ trigger ]); }, [ trigger ]);
useEffect(() =>
{
setAllowedInteractionTypes(ANTENNA_INTERACTION_TYPES);
setAllowedInteractionErrorKey(ANTENNA_ERROR_MESSAGE);
return () =>
{
setAllowedInteractionTypes(null);
setAllowedInteractionErrorKey(null);
};
}, [ setAllowedInteractionErrorKey, setAllowedInteractionTypes ]);
return ( return (
<WiredTriggerBaseView <WiredTriggerBaseView
hasSpecialInput={ true } hasSpecialInput={ true }
@@ -0,0 +1,198 @@
import { FC, useEffect, useMemo, useState } from 'react';
import { LocalizeText, WiredFurniType } from '../../../../api';
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 { WiredVariablePicker } from '../WiredVariablePicker';
import { IWiredVariablePickerEntry, buildWiredVariablePickerEntries, createFallbackVariableEntry, flattenWiredVariablePickerEntries, normalizeVariableTokenFromWire } from '../WiredVariablePickerData';
import { WiredTriggerBaseView } from './WiredTriggerBaseView';
type VariableTargetType = 'user' | 'furni' | 'global';
const TARGET_USER = 0;
const TARGET_FURNI = 1;
const TARGET_GLOBAL = 3;
const TARGET_BUTTONS: Array<{ key: VariableTargetType; icon: string; }> = [
{ key: 'furni', icon: furniVariableIcon },
{ key: 'user', icon: userVariableIcon },
{ key: 'global', icon: globalVariableIcon }
];
const filterCustomEntries = (entries: IWiredVariablePickerEntry[]): IWiredVariablePickerEntry[] =>
{
return entries
.filter(entry => (entry.kind === 'custom'))
.map(entry => ({
...entry,
children: entry.children?.filter(child => (child.kind === 'custom'))
}));
};
const normalizeTargetType = (value: number): VariableTargetType =>
{
switch(value)
{
case TARGET_FURNI: return 'furni';
case TARGET_GLOBAL: return 'global';
default: return 'user';
}
};
const getTargetValue = (value: VariableTargetType) =>
{
switch(value)
{
case 'furni': return TARGET_FURNI;
case 'global': return TARGET_GLOBAL;
default: return TARGET_USER;
}
};
export const WiredTriggerVariableChangedView: FC<{}> = () =>
{
const { trigger = null, setIntParams = null, setStringParam = null } = useWired();
const { userVariableDefinitions = [], furniVariableDefinitions = [], roomVariableDefinitions = [] } = useWiredTools();
const [ targetType, setTargetType ] = useState<VariableTargetType>('user');
const [ variableToken, setVariableToken ] = useState('');
const [ createdEnabled, setCreatedEnabled ] = useState(true);
const [ valueChangedEnabled, setValueChangedEnabled ] = useState(true);
const [ increasedEnabled, setIncreasedEnabled ] = useState(true);
const [ decreasedEnabled, setDecreasedEnabled ] = useState(true);
const [ unchangedEnabled, setUnchangedEnabled ] = useState(true);
const [ deletedEnabled, setDeletedEnabled ] = useState(true);
const variableDefinitions = useMemo(() =>
{
switch(targetType)
{
case 'furni': return furniVariableDefinitions;
case 'global': return roomVariableDefinitions;
default: return userVariableDefinitions;
}
}, [ furniVariableDefinitions, roomVariableDefinitions, targetType, userVariableDefinitions ]);
const variableEntries = useMemo(() => filterCustomEntries(buildWiredVariablePickerEntries(targetType, 'condition', 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.kind === 'custom') ? [ fallbackEntry, ...variableEntries ] : variableEntries;
}, [ targetType, variableEntries, variableToken ]);
const effectiveCreatedEnabled = (targetType === 'global') ? false : createdEnabled;
const effectiveDeletedEnabled = (targetType === 'global') ? false : deletedEnabled;
const effectiveIncreasedEnabled = valueChangedEnabled && increasedEnabled;
const effectiveDecreasedEnabled = valueChangedEnabled && decreasedEnabled;
const effectiveUnchangedEnabled = valueChangedEnabled && unchangedEnabled;
useEffect(() =>
{
if(!trigger) return;
const intData = trigger.intData || [];
setTargetType(normalizeTargetType((intData.length > 0) ? intData[0] : TARGET_USER));
setVariableToken(normalizeVariableTokenFromWire(trigger.stringData || ''));
setCreatedEnabled((intData.length <= 1) || (intData[1] === 1));
setValueChangedEnabled((intData.length <= 2) || (intData[2] === 1));
setIncreasedEnabled((intData.length <= 3) || (intData[3] === 1));
setDecreasedEnabled((intData.length <= 4) || (intData[4] === 1));
setUnchangedEnabled((intData.length <= 5) || (intData[5] === 1));
setDeletedEnabled((intData.length <= 6) || (intData[6] === 1));
}, [ trigger ]);
useEffect(() =>
{
if(targetType !== 'global') return;
setCreatedEnabled(false);
setDeletedEnabled(false);
}, [ targetType ]);
const save = () =>
{
setStringParam(variableToken);
setIntParams([
getTargetValue(targetType),
effectiveCreatedEnabled ? 1 : 0,
valueChangedEnabled ? 1 : 0,
effectiveIncreasedEnabled ? 1 : 0,
effectiveDecreasedEnabled ? 1 : 0,
effectiveUnchangedEnabled ? 1 : 0,
effectiveDeletedEnabled ? 1 : 0
]);
};
return (
<WiredTriggerBaseView hasSpecialInput={ true } requiresFurni={ WiredFurniType.STUFF_SELECTION_OPTION_NONE } save={ save }>
<div className="nitro-wired__give-var" style={ { width: 244 } }>
<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"
className={ `nitro-wired__give-var-target nitro-wired__give-var-target--${ button.key } ${ targetType === button.key ? 'is-active' : '' }` }
onClick={ () =>
{
if(targetType === button.key) return;
setTargetType(button.key);
setVariableToken('');
} }>
<img src={ button.icon } alt={ button.key } />
</button>
)) }
</div>
</div>
<WiredVariablePicker
entries={ resolvedVariableEntries }
recentScope="variable-triggers"
selectedToken={ variableToken }
onSelect={ entry => setVariableToken(entry.token) } />
<div className="nitro-wired__divider" />
<div className="flex flex-col gap-1">
<Text bold>{ LocalizeText('wiredfurni.params.variables.trigger_options') }</Text>
<label className="flex items-center gap-1">
<input checked={ effectiveCreatedEnabled } className="form-check-input" disabled={ targetType === 'global' } type="checkbox" onChange={ event => setCreatedEnabled(event.target.checked) } />
<Text>{ LocalizeText('wiredfurni.params.variables.trigger_options.0') }</Text>
</label>
<label className="flex items-center gap-1">
<input checked={ valueChangedEnabled } className="form-check-input" type="checkbox" onChange={ event => setValueChangedEnabled(event.target.checked) } />
<Text>{ LocalizeText('wiredfurni.params.variables.trigger_options.1') }</Text>
</label>
<div className="ml-3 flex flex-col gap-1">
<label className="flex items-center gap-1">
<input checked={ effectiveIncreasedEnabled } className="form-check-input" disabled={ !valueChangedEnabled } type="checkbox" onChange={ event => setIncreasedEnabled(event.target.checked) } />
<Text>{ LocalizeText('wiredfurni.params.variables.trigger_options.1.0') }</Text>
</label>
<label className="flex items-center gap-1">
<input checked={ effectiveDecreasedEnabled } className="form-check-input" disabled={ !valueChangedEnabled } type="checkbox" onChange={ event => setDecreasedEnabled(event.target.checked) } />
<Text>{ LocalizeText('wiredfurni.params.variables.trigger_options.1.1') }</Text>
</label>
<label className="flex items-center gap-1">
<input checked={ effectiveUnchangedEnabled } className="form-check-input" disabled={ !valueChangedEnabled } type="checkbox" onChange={ event => setUnchangedEnabled(event.target.checked) } />
<Text>{ LocalizeText('wiredfurni.params.variables.trigger_options.1.2') }</Text>
</label>
</div>
<label className="flex items-center gap-1">
<input checked={ effectiveDeletedEnabled } className="form-check-input" disabled={ targetType === 'global' } type="checkbox" onChange={ event => setDeletedEnabled(event.target.checked) } />
<Text>{ LocalizeText('wiredfurni.params.variables.trigger_options.2') }</Text>
</label>
</div>
</div>
</WiredTriggerBaseView>
);
};
+8 -1
View File
@@ -178,6 +178,13 @@
height: 32px; height: 32px;
} }
.nitro-icon.icon-wired-tools {
background-image: url("@/assets/images/wiredtools/wired_menu.png");
background-size: contain;
width: 40px;
height: 40px;
}
.nitro-icon.icon-message.is-unseen { .nitro-icon.icon-message.is-unseen {
background-image: url("@/assets/images/toolbar/icons/message_unsee.gif"); background-image: url("@/assets/images/toolbar/icons/message_unsee.gif");
} }
@@ -697,4 +704,4 @@
.nitro-icon.icon-navigator-search-delete:active { .nitro-icon.icon-navigator-search-delete:active {
background-image: url("@/assets/images/navigator/saves-search/delete_search_click.png"); background-image: url("@/assets/images/navigator/saves-search/delete_search_click.png");
} }
+524
View File
@@ -615,6 +615,7 @@ body {
background: #d9d9d9 !important; background: #d9d9d9 !important;
padding: 4px !important; padding: 4px !important;
gap: 4px !important; gap: 4px !important;
overflow: visible !important;
} }
.nitro-wired__section { .nitro-wired__section {
@@ -623,6 +624,10 @@ body {
gap: 3px; gap: 3px;
} }
.nitro-wired__section--body {
overflow: visible !important;
}
.nitro-wired__section .font-bold { .nitro-wired__section .font-bold {
width: 100%; width: 100%;
font-weight: 400 !important; font-weight: 400 !important;
@@ -817,6 +822,513 @@ body {
word-break: break-word; word-break: break-word;
} }
.nitro-wired__give-var {
display: flex;
flex-direction: column;
gap: 4px;
}
.nitro-wired__give-var-heading {
display: flex;
align-items: center;
justify-content: space-between;
gap: 4px;
}
.nitro-wired__give-var-targets {
display: inline-flex;
align-items: center;
gap: 2px;
}
.nitro-wired__give-var-target {
display: inline-flex;
align-items: center;
justify-content: center;
min-width: 18px;
min-height: 18px;
padding: 0;
border: 1px solid #8e8e8e;
border-radius: 4px;
background: transparent;
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.55);
transition: filter 0.12s ease-in-out;
}
.nitro-wired__give-var-target:hover {
filter: brightness(0.98);
}
.nitro-wired__give-var-target img {
width: auto;
height: auto;
max-width: 12px;
max-height: 12px;
image-rendering: pixelated;
}
.nitro-wired__give-var-target--furni.is-active {
background: linear-gradient(180deg, #f7db6d 0%, #ddb948 100%);
}
.nitro-wired__give-var-target--user.is-active {
background: linear-gradient(180deg, #74be68 0%, #4f9644 100%);
}
.nitro-wired__give-var-target--global.is-active {
background: linear-gradient(180deg, #7bc4f0 0%, #5497d2 100%);
}
.nitro-wired__give-var-target--context.is-active {
background: linear-gradient(180deg, #f0ad65 0%, #d68739 100%);
}
.nitro-wired__give-var-target.is-active {
border-color: #4d4d4d;
box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.72), inset 0 1px 0 rgba(255, 255, 255, 0.92);
}
.nitro-wired__give-var-target:disabled {
opacity: 0.72;
cursor: default;
}
.nitro-wired__give-var-select {
min-height: 23px;
}
.nitro-wired__variable-picker {
position: relative;
width: 100%;
}
.nitro-wired__variable-picker-trigger {
display: flex;
align-items: center;
width: 100%;
min-height: 23px;
padding: 0 22px 0 8px;
border: 1px solid #8e8e8e;
border-radius: 4px;
background: linear-gradient(180deg, #ffffff 0%, #ededed 100%);
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.85);
font-size: 11px;
text-align: left;
position: relative;
}
.nitro-wired__variable-picker-trigger::after {
content: '';
position: absolute;
top: 50%;
right: 8px;
width: 0;
height: 0;
margin-top: -2px;
border-left: 4px solid transparent;
border-right: 4px solid transparent;
border-top: 5px solid #555;
}
.nitro-wired__variable-picker-placeholder {
color: #7a7a7a;
}
.nitro-wired__variable-picker-panel {
position: absolute;
top: calc(100% + 2px);
left: 0;
z-index: 30;
width: 202px;
padding: 4px;
border: 1px solid #8e8e8e;
border-radius: 4px;
background: #f5f5f5;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
overflow: visible;
}
.nitro-wired__variable-picker-panel.is-portal {
position: fixed;
top: 0;
left: 0;
z-index: 5001;
margin: 0;
pointer-events: auto;
}
.nitro-wired__variable-picker-toolbar {
display: flex;
align-items: center;
gap: 4px;
padding-bottom: 4px;
border-bottom: 1px solid #d0d0d0;
}
.nitro-wired__variable-picker-mode {
display: inline-flex;
align-items: center;
justify-content: center;
width: 20px;
height: 20px;
padding: 0;
border: 1px solid transparent;
border-radius: 3px;
background: transparent;
}
.nitro-wired__variable-picker-mode.is-active {
border-color: #8e8e8e;
background: linear-gradient(180deg, #f3cc58 0%, #d2a93a 100%);
}
.nitro-wired__variable-picker-mode img {
width: auto;
height: auto;
max-width: 13px;
max-height: 13px;
image-rendering: pixelated;
}
.nitro-wired__variable-picker-search {
display: flex;
align-items: center;
gap: 4px;
min-height: 24px;
padding: 0 4px;
margin-top: 4px;
border: 1px solid #c4c4c4;
border-radius: 3px;
background: #fff;
}
.nitro-wired__variable-picker-search-icon {
width: auto;
height: auto;
max-width: 10px;
max-height: 10px;
opacity: 0.75;
image-rendering: pixelated;
}
.nitro-wired__variable-picker-search-input {
flex: 1 1 auto;
min-width: 0;
border: 0;
background: transparent;
font-size: 11px;
line-height: 1;
outline: none;
}
.nitro-wired__variable-picker-clear {
display: inline-flex;
align-items: center;
justify-content: center;
width: 14px;
height: 14px;
padding: 0;
border: 0;
background: transparent;
}
.nitro-wired__variable-picker-clear img {
width: auto;
height: auto;
max-width: 10px;
max-height: 10px;
image-rendering: pixelated;
}
.nitro-wired__variable-picker-list,
.nitro-wired__variable-picker-submenu {
display: flex;
flex-direction: column;
max-height: 140px;
margin-top: 4px;
overflow-y: auto;
border: 1px solid #c8c8c8;
border-radius: 3px;
background: #fff;
}
.nitro-wired__variable-picker-row {
display: flex;
align-items: center;
justify-content: space-between;
gap: 6px;
min-height: 20px;
padding: 0 6px;
border: 0;
background: transparent;
font-size: 11px;
text-align: left;
}
.nitro-wired__variable-picker-row:nth-child(even) {
background: #f3f3f3;
}
.nitro-wired__variable-picker-row:hover {
background: #dfe8f6;
}
.nitro-wired__variable-picker-row.is-selected {
background: #d7dfea;
}
.nitro-wired__variable-picker-row.is-disabled {
color: #7b7b7b;
}
.nitro-wired__variable-picker-row-label {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.nitro-wired__variable-picker-row-arrow {
flex: 0 0 auto;
font-size: 9px;
}
.nitro-wired__variable-picker-empty {
display: flex;
align-items: center;
justify-content: center;
min-height: 86px;
color: #8a8a8a;
}
.nitro-wired__variable-picker-submenu {
position: fixed;
width: 120px;
margin-top: 0;
z-index: 5000;
pointer-events: auto;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.18);
}
.nitro-wired__give-var-checkbox {
display: flex;
align-items: center;
gap: 4px;
cursor: pointer;
white-space: nowrap;
font-size: 13px;
line-height: 1;
}
.nitro-wired__give-var-section {
display: flex;
flex-direction: column;
gap: 4px;
}
.nitro-wired__give-var-section-title {
font-size: 11px;
font-weight: 700;
line-height: 1.1;
}
.nitro-wired__give-var-input-row {
display: flex;
align-items: center;
gap: 6px;
}
.nitro-wired__give-var-number {
width: 48px;
min-width: 48px;
min-height: 18px !important;
height: 18px;
padding: 0 4px !important;
line-height: 18px;
text-align: center;
}
.nitro-wired__give-var-number--blurred {
filter: blur(0.8px) !important;
opacity: 0.78;
pointer-events: none;
}
.nitro-wired__resizable-textarea {
min-height: 140px !important;
resize: vertical !important;
overflow: auto;
}
.nitro-wired__levelup {
display: flex;
flex-direction: column;
gap: 6px;
}
.nitro-wired__levelup-section {
display: flex;
flex-direction: column;
gap: 4px;
}
.nitro-wired__levelup-section-header {
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
padding: 0;
border: 0;
background: transparent;
text-align: left;
}
.nitro-wired__levelup-chevron {
font-size: 12px;
line-height: 1;
transform: rotate(180deg);
transition: transform 0.12s ease;
}
.nitro-wired__levelup-chevron.is-open {
transform: rotate(0deg);
}
.nitro-wired__levelup-section-body {
display: flex;
flex-direction: column;
gap: 8px;
}
.nitro-wired__levelup-mode-block {
display: flex;
flex-direction: column;
gap: 4px;
}
.nitro-wired__levelup-mode-block.is-inactive {
opacity: 0.72;
}
.nitro-wired__levelup-mode-label {
display: flex;
align-items: center;
gap: 4px;
cursor: pointer;
}
.nitro-wired__levelup-fields {
display: flex;
flex-direction: column;
gap: 4px;
padding-left: 22px;
}
.nitro-wired__levelup-field-row {
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
min-height: 20px;
}
.nitro-wired__levelup-number {
width: 74px;
min-width: 74px;
min-height: 20px !important;
height: 20px;
padding: 0 6px !important;
line-height: 20px;
text-align: center;
}
.nitro-wired__levelup-textarea {
min-height: 88px !important;
resize: vertical !important;
overflow: auto;
}
.nitro-wired__levelup-preview {
max-height: 120px;
overflow-y: auto;
padding-right: 2px;
}
.nitro-wired__levelup-preview-entry {
padding: 1px 0;
font-size: 11px;
line-height: 1.25;
}
.nitro-wired__levelup-subvariables {
display: flex;
flex-direction: column;
gap: 4px;
}
.nitro-wired__levelup-subvariable-row {
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
}
.nitro-wired__levelup-subvariable-label {
display: flex;
align-items: center;
gap: 4px;
min-width: 0;
flex: 1 1 auto;
cursor: pointer;
}
.nitro-wired__levelup-subvariable-token {
width: 96px;
min-width: 96px;
height: 20px;
padding: 0 6px;
border: 1px solid #cfcfcf;
border-radius: 6px;
background: #f3f3f3;
color: #9a9a9a;
font-size: 11px;
line-height: 20px;
}
.nitro-wired__placeholder-preview {
display: flex;
width: 100%;
align-items: flex-start;
padding: 6px 8px;
border: 1px solid #b7b7b7;
border-radius: 5px;
background: linear-gradient(180deg, #fbfbfb 0%, #efefef 100%);
text-align: left;
cursor: pointer;
transition: background 0.12s ease, border-color 0.12s ease;
}
.nitro-wired__placeholder-preview:hover {
background: linear-gradient(180deg, #ffffff 0%, #f4f4f4 100%);
border-color: #909090;
}
.nitro-wired__placeholder-preview.is-copied {
border-color: #73a753;
background: linear-gradient(180deg, #f8fff2 0%, #eef9e2 100%);
}
.nitro-wired__change-var-radio {
display: flex;
align-items: center;
gap: 4px;
}
.nitro-wired__change-var-reference-block {
display: flex;
flex-direction: column;
gap: 4px;
}
.nitro-slider { .nitro-slider {
height: 16px; height: 16px;
} }
@@ -973,6 +1485,18 @@ body {
} }
} }
.nitro-wired__variable-picker-portal {
position: fixed;
inset: 0;
z-index: 5000;
pointer-events: none;
overflow: visible !important;
background: transparent !important;
border: 0 !important;
border-radius: 0 !important;
box-shadow: none !important;
}
/* ── Avatar Editor misc ─────────────────────────────────────────────────── */ /* ── Avatar Editor misc ─────────────────────────────────────────────────── */
+1
View File
@@ -22,5 +22,6 @@ export * from './session';
export * from './useLocalStorage'; export * from './useLocalStorage';
export * from './useSharedVisibility'; export * from './useSharedVisibility';
export * from './wired'; export * from './wired';
export * from './wired-tools';
export * from './useChatWindow'; export * from './useChatWindow';
export * from './useOnClickChat'; export * from './useOnClickChat';
+1
View File
@@ -0,0 +1 @@
export * from './useWiredTools';
+618
View File
@@ -0,0 +1,618 @@
import { CreateLinkEvent, GetSessionDataManager, WiredRoomSettingsDataEvent, WiredRoomSettingsRequestComposer, WiredRoomSettingsSaveComposer, WiredUserVariableManageComposer, WiredUserVariableUpdateComposer, WiredUserVariablesDataEvent, WiredUserVariablesRequestComposer } from '@nitrots/nitro-renderer';
import { useCallback, useEffect, useMemo, useState } from 'react';
import { useBetween } from 'use-between';
import { LocalizeText, NotificationAlertType, SendMessageComposer } from '../../api';
import { useMessageEvent } from '../events';
import { useNotification } from '../notification';
import { useRoom } from '../rooms';
export interface IWiredAccountPreferences
{
showInspectButton: boolean;
showSystemNotifications: boolean;
showToolbarButton: boolean;
}
export interface IWiredRoomSettings
{
canInspect: boolean;
canManageSettings: boolean;
canModify: boolean;
inspectMask: number;
isLoaded: boolean;
modifyMask: number;
roomId: number;
}
export interface IWiredUserVariableDefinition
{
availability: number;
hasValue: boolean;
isReadOnly?: boolean;
isTextConnected: boolean;
itemId: number;
name: string;
}
export interface IWiredUserVariableAssignment
{
createdAt: number;
hasValue: boolean;
updatedAt: number;
value: number | null;
variableItemId: number;
}
export interface IWiredFurniVariableDefinition
{
availability: number;
hasValue: boolean;
isReadOnly?: boolean;
isTextConnected: boolean;
itemId: number;
name: string;
}
export interface IWiredFurniVariableAssignment
{
createdAt: number;
hasValue: boolean;
updatedAt: number;
value: number | null;
variableItemId: number;
}
export interface IWiredRoomVariableDefinition
{
availability: number;
hasValue: boolean;
isReadOnly?: boolean;
isTextConnected: boolean;
itemId: number;
name: string;
}
export interface IWiredRoomVariableAssignment
{
createdAt: number;
hasValue: boolean;
updatedAt: number;
value: number | null;
variableItemId: number;
}
export interface IWiredContextVariableDefinition
{
availability: number;
hasValue: boolean;
isReadOnly?: boolean;
isTextConnected: boolean;
itemId: number;
name: string;
}
const WIRED_VARIABLE_TARGET_USER = 0;
const WIRED_VARIABLE_TARGET_FURNI = 1;
const WIRED_VARIABLE_TARGET_ROOM = 3;
const WIRED_VARIABLE_MANAGE_ACTION_ASSIGN = 0;
const WIRED_VARIABLE_MANAGE_ACTION_REMOVE = 1;
const WIRED_TOOLS_STORAGE_PREFIX = 'nitro.wired.tools.preferences';
const getCurrentUnixTime = () => Math.floor(Date.now() / 1000);
const DEFAULT_ACCOUNT_PREFERENCES: IWiredAccountPreferences = {
showToolbarButton: false,
showInspectButton: false,
showSystemNotifications: false
};
const DEFAULT_ROOM_SETTINGS: IWiredRoomSettings = {
roomId: 0,
inspectMask: 0,
modifyMask: 0,
canInspect: false,
canModify: false,
canManageSettings: false,
isLoaded: false
};
const useWiredToolsState = () =>
{
const { roomSession = null } = useRoom();
const { simpleAlert = null } = useNotification();
const [ accountPreferences, setAccountPreferences ] = useState<IWiredAccountPreferences>(DEFAULT_ACCOUNT_PREFERENCES);
const [ roomSettings, setRoomSettings ] = useState<IWiredRoomSettings>(DEFAULT_ROOM_SETTINGS);
const [ userVariableDefinitions, setUserVariableDefinitions ] = useState<IWiredUserVariableDefinition[]>([]);
const [ userVariableAssignments, setUserVariableAssignments ] = useState<Record<number, IWiredUserVariableAssignment[]>>({});
const [ furniVariableDefinitions, setFurniVariableDefinitions ] = useState<IWiredFurniVariableDefinition[]>([]);
const [ furniVariableAssignments, setFurniVariableAssignments ] = useState<Record<number, IWiredFurniVariableAssignment[]>>({});
const [ roomVariableDefinitions, setRoomVariableDefinitions ] = useState<IWiredRoomVariableDefinition[]>([]);
const [ roomVariableAssignments, setRoomVariableAssignments ] = useState<IWiredRoomVariableAssignment[]>([]);
const [ contextVariableDefinitions, setContextVariableDefinitions ] = useState<IWiredContextVariableDefinition[]>([]);
const [ areUserVariablesLoaded, setAreUserVariablesLoaded ] = useState(false);
const storageKey = useMemo(() =>
{
const userId = GetSessionDataManager().userId;
return `${ WIRED_TOOLS_STORAGE_PREFIX }.${ userId || 'guest' }`;
}, []);
useEffect(() =>
{
try
{
const rawValue = window.localStorage.getItem(storageKey);
if(!rawValue)
{
setAccountPreferences(DEFAULT_ACCOUNT_PREFERENCES);
return;
}
const parsedValue = JSON.parse(rawValue) as Partial<IWiredAccountPreferences>;
setAccountPreferences({
...DEFAULT_ACCOUNT_PREFERENCES,
...(parsedValue || {})
});
}
catch
{
setAccountPreferences(DEFAULT_ACCOUNT_PREFERENCES);
}
}, [ storageKey ]);
useEffect(() =>
{
try
{
window.localStorage.setItem(storageKey, JSON.stringify(accountPreferences));
}
catch
{
}
}, [ accountPreferences, storageKey ]);
useEffect(() =>
{
if(!roomSession?.roomId)
{
setRoomSettings(DEFAULT_ROOM_SETTINGS);
setUserVariableDefinitions([]);
setUserVariableAssignments({});
setFurniVariableDefinitions([]);
setFurniVariableAssignments({});
setRoomVariableDefinitions([]);
setRoomVariableAssignments([]);
setContextVariableDefinitions([]);
setAreUserVariablesLoaded(false);
return;
}
setRoomSettings(prevValue => ({
...DEFAULT_ROOM_SETTINGS,
roomId: roomSession.roomId,
canInspect: prevValue.roomId === roomSession.roomId ? prevValue.canInspect : false,
canModify: prevValue.roomId === roomSession.roomId ? prevValue.canModify : false,
canManageSettings: prevValue.roomId === roomSession.roomId ? prevValue.canManageSettings : false
}));
SendMessageComposer(new WiredRoomSettingsRequestComposer());
}, [ roomSession?.roomId ]);
useEffect(() =>
{
if(!roomSession?.roomId || !roomSettings.canInspect)
{
setUserVariableDefinitions([]);
setUserVariableAssignments({});
setFurniVariableDefinitions([]);
setFurniVariableAssignments({});
setRoomVariableDefinitions([]);
setRoomVariableAssignments([]);
setContextVariableDefinitions([]);
setAreUserVariablesLoaded(false);
return;
}
SendMessageComposer(new WiredUserVariablesRequestComposer());
}, [ roomSession?.roomId, roomSettings.canInspect ]);
useMessageEvent<WiredRoomSettingsDataEvent>(WiredRoomSettingsDataEvent, event =>
{
const parser = event.getParser();
if(roomSession?.roomId && parser.roomId && (parser.roomId !== roomSession.roomId)) return;
setRoomSettings({
roomId: parser.roomId,
inspectMask: parser.inspectMask,
modifyMask: parser.modifyMask,
canInspect: parser.canInspect,
canModify: parser.canModify,
canManageSettings: parser.canManageSettings,
isLoaded: true
});
});
useMessageEvent<WiredUserVariablesDataEvent>(WiredUserVariablesDataEvent, event =>
{
const parser = event.getParser();
if(roomSession?.roomId && parser.roomId && (parser.roomId !== roomSession.roomId)) return;
const nextAssignments: Record<number, IWiredUserVariableAssignment[]> = {};
const nextFurniAssignments: Record<number, IWiredFurniVariableAssignment[]> = {};
for(const userEntry of (parser.users || []))
{
nextAssignments[userEntry.userId] = [ ...(userEntry.assignments || []) ];
}
for(const furniEntry of (parser.furnis || []))
{
nextFurniAssignments[furniEntry.furniId] = [ ...(furniEntry.assignments || []) ];
}
setUserVariableDefinitions([ ...(parser.definitions || []) ]);
setUserVariableAssignments(nextAssignments);
setFurniVariableDefinitions([ ...(parser.furniDefinitions || []) ]);
setFurniVariableAssignments(nextFurniAssignments);
setRoomVariableDefinitions([ ...(parser.roomDefinitions || []) ]);
setRoomVariableAssignments([ ...(parser.roomAssignments || []) ]);
setContextVariableDefinitions([ ...(parser.contextDefinitions || []) ]);
setAreUserVariablesLoaded(true);
});
const updateAccountPreferences = useCallback((partialPreferences: Partial<IWiredAccountPreferences>) =>
{
setAccountPreferences(prevValue => ({
...prevValue,
...partialPreferences
}));
}, []);
const saveRoomSettings = useCallback((inspectMask: number, modifyMask: number) =>
{
if(!roomSettings.canManageSettings) return;
setRoomSettings(prevValue => ({
...prevValue,
inspectMask,
modifyMask
}));
SendMessageComposer(new WiredRoomSettingsSaveComposer(inspectMask, modifyMask));
}, [ roomSettings.canManageSettings ]);
const requestUserVariables = useCallback(() =>
{
if(!roomSettings.canInspect) return;
SendMessageComposer(new WiredUserVariablesRequestComposer());
}, [ roomSettings.canInspect ]);
const updateUserVariableValue = useCallback((userId: number, variableItemId: number, value: number) =>
{
if(!roomSettings.canModify) return;
setUserVariableAssignments(prevValue =>
{
const existingAssignments = prevValue[userId];
if(!existingAssignments?.length) return prevValue;
let didChange = false;
const nextAssignments = existingAssignments.map(assignment =>
{
if(assignment.variableItemId !== variableItemId) return assignment;
didChange = true;
return {
...assignment,
hasValue: true,
value,
updatedAt: getCurrentUnixTime()
};
});
if(!didChange) return prevValue;
return {
...prevValue,
[userId]: nextAssignments
};
});
SendMessageComposer(new WiredUserVariableUpdateComposer(WIRED_VARIABLE_TARGET_USER, userId, variableItemId, value));
}, [ roomSettings.canModify ]);
const updateFurniVariableValue = useCallback((furniId: number, variableItemId: number, value: number) =>
{
if(!roomSettings.canModify) return;
setFurniVariableAssignments(prevValue =>
{
const existingAssignments = prevValue[furniId];
if(!existingAssignments?.length) return prevValue;
let didChange = false;
const nextAssignments = existingAssignments.map(assignment =>
{
if(assignment.variableItemId !== variableItemId) return assignment;
didChange = true;
return {
...assignment,
hasValue: true,
value,
updatedAt: getCurrentUnixTime()
};
});
if(!didChange) return prevValue;
return {
...prevValue,
[furniId]: nextAssignments
};
});
SendMessageComposer(new WiredUserVariableUpdateComposer(WIRED_VARIABLE_TARGET_FURNI, furniId, variableItemId, value));
}, [ roomSettings.canModify ]);
const updateRoomVariableValue = useCallback((variableItemId: number, value: number) =>
{
if(!roomSettings.canModify) return;
setRoomVariableAssignments(prevValue =>
{
const now = getCurrentUnixTime();
let didChange = false;
const nextAssignments = prevValue.map(assignment =>
{
if(assignment.variableItemId !== variableItemId) return assignment;
didChange = true;
return {
...assignment,
hasValue: true,
value,
updatedAt: now
};
});
if(didChange) return nextAssignments;
return [
...prevValue,
{
variableItemId,
hasValue: true,
value,
createdAt: 0,
updatedAt: now
}
];
});
SendMessageComposer(new WiredUserVariableUpdateComposer(WIRED_VARIABLE_TARGET_ROOM, roomSettings.roomId, variableItemId, value));
}, [ roomSettings.canModify, roomSettings.roomId ]);
const assignUserVariable = useCallback((userId: number, variableItemId: number, value: number) =>
{
if(!roomSettings.canModify) return;
const definition = userVariableDefinitions.find(entry => (entry.itemId === variableItemId));
if(!definition) return;
const now = getCurrentUnixTime();
const normalizedValue = (definition.hasValue ? value : null);
setUserVariableAssignments(prevValue =>
{
const existingAssignments = [ ...(prevValue[userId] || []) ];
const existingIndex = existingAssignments.findIndex(assignment => (assignment.variableItemId === variableItemId));
if(existingIndex >= 0)
{
const existingAssignment = existingAssignments[existingIndex];
existingAssignments[existingIndex] = {
...existingAssignment,
hasValue: definition.hasValue,
value: normalizedValue,
updatedAt: now
};
}
else
{
existingAssignments.push({
variableItemId,
hasValue: definition.hasValue,
value: normalizedValue,
createdAt: now,
updatedAt: now
});
}
return {
...prevValue,
[userId]: existingAssignments
};
});
SendMessageComposer(new WiredUserVariableManageComposer(WIRED_VARIABLE_MANAGE_ACTION_ASSIGN, WIRED_VARIABLE_TARGET_USER, userId, variableItemId, Number(normalizedValue ?? 0)));
}, [ roomSettings.canModify, userVariableDefinitions ]);
const removeUserVariable = useCallback((userId: number, variableItemId: number) =>
{
if(!roomSettings.canModify) return;
setUserVariableAssignments(prevValue =>
{
const existingAssignments = prevValue[userId];
if(!existingAssignments?.length) return prevValue;
const nextAssignments = existingAssignments.filter(assignment => (assignment.variableItemId !== variableItemId));
if(nextAssignments.length === existingAssignments.length) return prevValue;
const nextValue = { ...prevValue };
if(nextAssignments.length) nextValue[userId] = nextAssignments;
else delete nextValue[userId];
return nextValue;
});
SendMessageComposer(new WiredUserVariableManageComposer(WIRED_VARIABLE_MANAGE_ACTION_REMOVE, WIRED_VARIABLE_TARGET_USER, userId, variableItemId, 0));
}, [ roomSettings.canModify ]);
const assignFurniVariable = useCallback((furniId: number, variableItemId: number, value: number) =>
{
if(!roomSettings.canModify) return;
const definition = furniVariableDefinitions.find(entry => (entry.itemId === variableItemId));
if(!definition) return;
const now = getCurrentUnixTime();
const normalizedValue = (definition.hasValue ? value : null);
setFurniVariableAssignments(prevValue =>
{
const existingAssignments = [ ...(prevValue[furniId] || []) ];
const existingIndex = existingAssignments.findIndex(assignment => (assignment.variableItemId === variableItemId));
if(existingIndex >= 0)
{
const existingAssignment = existingAssignments[existingIndex];
existingAssignments[existingIndex] = {
...existingAssignment,
hasValue: definition.hasValue,
value: normalizedValue,
updatedAt: now
};
}
else
{
existingAssignments.push({
variableItemId,
hasValue: definition.hasValue,
value: normalizedValue,
createdAt: now,
updatedAt: now
});
}
return {
...prevValue,
[furniId]: existingAssignments
};
});
SendMessageComposer(new WiredUserVariableManageComposer(WIRED_VARIABLE_MANAGE_ACTION_ASSIGN, WIRED_VARIABLE_TARGET_FURNI, furniId, variableItemId, Number(normalizedValue ?? 0)));
}, [ furniVariableDefinitions, roomSettings.canModify ]);
const removeFurniVariable = useCallback((furniId: number, variableItemId: number) =>
{
if(!roomSettings.canModify) return;
setFurniVariableAssignments(prevValue =>
{
const existingAssignments = prevValue[furniId];
if(!existingAssignments?.length) return prevValue;
const nextAssignments = existingAssignments.filter(assignment => (assignment.variableItemId !== variableItemId));
if(nextAssignments.length === existingAssignments.length) return prevValue;
const nextValue = { ...prevValue };
if(nextAssignments.length) nextValue[furniId] = nextAssignments;
else delete nextValue[furniId];
return nextValue;
});
SendMessageComposer(new WiredUserVariableManageComposer(WIRED_VARIABLE_MANAGE_ACTION_REMOVE, WIRED_VARIABLE_TARGET_FURNI, furniId, variableItemId, 0));
}, [ roomSettings.canModify ]);
const showInvalidRoomAlert = useCallback(() =>
{
if(!simpleAlert) return;
simpleAlert(LocalizeText('wiredmenu.invalid_room.desc'), NotificationAlertType.ALERT, null, null, LocalizeText('generic.alert.title'));
}, [ simpleAlert ]);
const openMonitor = useCallback(() =>
{
if(!roomSettings.canInspect)
{
showInvalidRoomAlert();
return;
}
CreateLinkEvent('wired-tools/show');
}, [ roomSettings.canInspect, showInvalidRoomAlert ]);
const openInspectionForFurni = useCallback((objectId: number, category: number) =>
{
if(!roomSettings.canInspect)
{
showInvalidRoomAlert();
return;
}
CreateLinkEvent(`wired-tools/inspection/furni/${ objectId }/${ category }`);
}, [ roomSettings.canInspect, showInvalidRoomAlert ]);
const openInspectionForUser = useCallback((roomIndex: number) =>
{
if(!roomSettings.canInspect)
{
showInvalidRoomAlert();
return;
}
CreateLinkEvent(`wired-tools/inspection/user/${ roomIndex }`);
}, [ roomSettings.canInspect, showInvalidRoomAlert ]);
const showToolbarButton = !!roomSession?.roomId && roomSettings.canInspect && accountPreferences.showToolbarButton;
const showInspectButton = !!roomSession?.roomId && roomSettings.canInspect && accountPreferences.showInspectButton;
return {
accountPreferences,
roomSettings,
showInspectButton,
showToolbarButton,
userVariableDefinitions,
userVariableAssignments,
furniVariableDefinitions,
furniVariableAssignments,
roomVariableDefinitions,
roomVariableAssignments,
contextVariableDefinitions,
areUserVariablesLoaded,
updateAccountPreferences,
saveRoomSettings,
requestUserVariables,
assignUserVariable,
removeUserVariable,
updateUserVariableValue,
assignFurniVariable,
removeFurniVariable,
updateFurniVariableValue,
updateRoomVariableValue,
openMonitor,
openInspectionForFurni,
openInspectionForUser
};
};
export const useWiredTools = () => useBetween(useWiredToolsState);
+243 -245
View File
@@ -170,7 +170,7 @@
"@babel/helper-string-parser" "^7.27.1" "@babel/helper-string-parser" "^7.27.1"
"@babel/helper-validator-identifier" "^7.28.5" "@babel/helper-validator-identifier" "^7.28.5"
"@emnapi/core@^1.7.1", "@emnapi/core@^1.8.1": "@emnapi/core@^1.8.1":
version "1.9.1" version "1.9.1"
resolved "https://registry.yarnpkg.com/@emnapi/core/-/core-1.9.1.tgz#2143069c744ca2442074f8078462e51edd63c7bd" resolved "https://registry.yarnpkg.com/@emnapi/core/-/core-1.9.1.tgz#2143069c744ca2442074f8078462e51edd63c7bd"
integrity sha512-mukuNALVsoix/w1BJwFzwXBN/dHeejQtuVzcDsfOEsdpCumXb/E9j8w11h5S54tT1xhifGfbbSm/ICrObRb3KA== integrity sha512-mukuNALVsoix/w1BJwFzwXBN/dHeejQtuVzcDsfOEsdpCumXb/E9j8w11h5S54tT1xhifGfbbSm/ICrObRb3KA==
@@ -178,7 +178,7 @@
"@emnapi/wasi-threads" "1.2.0" "@emnapi/wasi-threads" "1.2.0"
tslib "^2.4.0" tslib "^2.4.0"
"@emnapi/runtime@^1.7.1", "@emnapi/runtime@^1.8.1": "@emnapi/runtime@^1.8.1":
version "1.9.1" version "1.9.1"
resolved "https://registry.yarnpkg.com/@emnapi/runtime/-/runtime-1.9.1.tgz#115ff2a0d589865be6bd8e9d701e499c473f2a8d" resolved "https://registry.yarnpkg.com/@emnapi/runtime/-/runtime-1.9.1.tgz#115ff2a0d589865be6bd8e9d701e499c473f2a8d"
integrity sha512-VYi5+ZVLhpgK4hQ0TAjiQiZ6ol0oe4mBx7mVv7IflsiEp0OWoVsp/+f9Vc1hOhE0TtkORVrI1GvzyreqpgWtkA== integrity sha512-VYi5+ZVLhpgK4hQ0TAjiQiZ6ol0oe4mBx7mVv7IflsiEp0OWoVsp/+f9Vc1hOhE0TtkORVrI1GvzyreqpgWtkA==
@@ -438,12 +438,10 @@
"@jridgewell/sourcemap-codec" "^1.4.14" "@jridgewell/sourcemap-codec" "^1.4.14"
"@napi-rs/wasm-runtime@^1.1.1": "@napi-rs/wasm-runtime@^1.1.1":
version "1.1.1" version "1.1.2"
resolved "https://registry.yarnpkg.com/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.1.tgz#c3705ab549d176b8dc5172723d6156c3dc426af2" resolved "https://registry.yarnpkg.com/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.2.tgz#e25454b4d44cfabd21d1bc801705359870e33ecc"
integrity sha512-p64ah1M1ld8xjWv3qbvFwHiFVWrq1yFvV4f7w+mzaqiR4IlSgkqhcRdHwsGgomwzBH51sRY4NEowLxnaBjcW/A== integrity sha512-sNXv5oLJ7ob93xkZ1XnxisYhGYXfaG9f65/ZgYuAu3qt7b3NadcOEhLvx28hv31PgX8SZJRYrAIPQilQmFpLVw==
dependencies: dependencies:
"@emnapi/core" "^1.7.1"
"@emnapi/runtime" "^1.7.1"
"@tybys/wasm-util" "^0.10.1" "@tybys/wasm-util" "^0.10.1"
"@parcel/watcher-android-arm64@2.5.6": "@parcel/watcher-android-arm64@2.5.6":
@@ -581,135 +579,135 @@
resolved "https://registry.yarnpkg.com/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.3.tgz#8a88cc92a0f741befc7bc109cb1a4c6b9408e1c5" resolved "https://registry.yarnpkg.com/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.3.tgz#8a88cc92a0f741befc7bc109cb1a4c6b9408e1c5"
integrity sha512-eybk3TjzzzV97Dlj5c+XrBFW57eTNhzod66y9HrBlzJ6NsCrWCp/2kaPS3K9wJmurBC0Tdw4yPjXKZqlznim3Q== integrity sha512-eybk3TjzzzV97Dlj5c+XrBFW57eTNhzod66y9HrBlzJ6NsCrWCp/2kaPS3K9wJmurBC0Tdw4yPjXKZqlznim3Q==
"@rollup/rollup-android-arm-eabi@4.60.0": "@rollup/rollup-android-arm-eabi@4.60.1":
version "4.60.0" version "4.60.1"
resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.0.tgz#7e158ddfc16f78da99c0d5ccbae6cae403ef3284" resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.1.tgz#043f145716234529052ef9e1ce1d847ffbe9e674"
integrity sha512-WOhNW9K8bR3kf4zLxbfg6Pxu2ybOUbB2AjMDHSQx86LIF4rH4Ft7vmMwNt0loO0eonglSNy4cpD3MKXXKQu0/A== integrity sha512-d6FinEBLdIiK+1uACUttJKfgZREXrF0Qc2SmLII7W2AD8FfiZ9Wjd+rD/iRuf5s5dWrr1GgwXCvPqOuDquOowA==
"@rollup/rollup-android-arm64@4.60.0": "@rollup/rollup-android-arm64@4.60.1":
version "4.60.0" version "4.60.1"
resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.60.0.tgz#49f4ae0e22b6f9ffbcd3818b9a0758fa2d10b1cd" resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.60.1.tgz#023e1bd146e7519087dfd9e8b29e4cf9f8ecd35c"
integrity sha512-u6JHLll5QKRvjciE78bQXDmqRqNs5M/3GVqZeMwvmjaNODJih/WIrJlFVEihvV0MiYFmd+ZyPr9wxOVbPAG2Iw== integrity sha512-YjG/EwIDvvYI1YvYbHvDz/BYHtkY4ygUIXHnTdLhG+hKIQFBiosfWiACWortsKPKU/+dUwQQCKQM3qrDe8c9BA==
"@rollup/rollup-darwin-arm64@4.60.0": "@rollup/rollup-darwin-arm64@4.60.1":
version "4.60.0" version "4.60.1"
resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.60.0.tgz#bb200269069acf5c1c4d79ad142524f77e8b8236" resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.60.1.tgz#55ccb5487c02419954c57a7a80602885d616e1ee"
integrity sha512-qEF7CsKKzSRc20Ciu2Zw1wRrBz4g56F7r/vRwY430UPp/nt1x21Q/fpJ9N5l47WWvJlkNCPJz3QRVw008fi7yA== integrity sha512-mjCpF7GmkRtSJwon+Rq1N8+pI+8l7w5g9Z3vWj4T7abguC4Czwi3Yu/pFaLvA3TTeMVjnu3ctigusqWUfjZzvw==
"@rollup/rollup-darwin-x64@4.60.0": "@rollup/rollup-darwin-x64@4.60.1":
version "4.60.0" version "4.60.1"
resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.60.0.tgz#1bf7a92b27ebdd5e0d1d48503c7811160773be1a" resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.60.1.tgz#254b65404b14488c83225e88b8819376ad71a784"
integrity sha512-WADYozJ4QCnXCH4wPB+3FuGmDPoFseVCUrANmA5LWwGmC6FL14BWC7pcq+FstOZv3baGX65tZ378uT6WG8ynTw== integrity sha512-haZ7hJ1JT4e9hqkoT9R/19XW2QKqjfJVv+i5AGg57S+nLk9lQnJ1F/eZloRO3o9Scy9CM3wQ9l+dkXtcBgN5Ew==
"@rollup/rollup-freebsd-arm64@4.60.0": "@rollup/rollup-freebsd-arm64@4.60.1":
version "4.60.0" version "4.60.1"
resolved "https://registry.yarnpkg.com/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.60.0.tgz#5ccf537b99c5175008444702193ad0b1c36f7f16" resolved "https://registry.yarnpkg.com/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.60.1.tgz#6377ff38c052c76fcaffb7b2728d3172fe676fe6"
integrity sha512-6b8wGHJlDrGeSE3aH5mGNHBjA0TTkxdoNHik5EkvPHCt351XnigA4pS7Wsj/Eo9Y8RBU6f35cjN9SYmCFBtzxw== integrity sha512-czw90wpQq3ZsAVBlinZjAYTKduOjTywlG7fEeWKUA7oCmpA8xdTkxZZlwNJKWqILlq0wehoZcJYfBvOyhPTQ6w==
"@rollup/rollup-freebsd-x64@4.60.0": "@rollup/rollup-freebsd-x64@4.60.1":
version "4.60.0" version "4.60.1"
resolved "https://registry.yarnpkg.com/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.60.0.tgz#1196ecd7bf4e128624ef83cd1f9d785114474a77" resolved "https://registry.yarnpkg.com/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.60.1.tgz#ba3902309d088eaf7139b916f09b7140b28b406d"
integrity sha512-h25Ga0t4jaylMB8M/JKAyrvvfxGRjnPQIR8lnCayyzEjEOx2EJIlIiMbhpWxDRKGKF8jbNH01NnN663dH638mA== integrity sha512-KVB2rqsxTHuBtfOeySEyzEOB7ltlB/ux38iu2rBQzkjbwRVlkhAGIEDiiYnO2kFOkJp+Z7pUXKyrRRFuFUKt+g==
"@rollup/rollup-linux-arm-gnueabihf@4.60.0": "@rollup/rollup-linux-arm-gnueabihf@4.60.1":
version "4.60.0" version "4.60.1"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.60.0.tgz#cc147633a4af229fee83a737bf2334fbac3dc28e" resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.60.1.tgz#e011b9a14638267e53b446286e838dbdaf53f167"
integrity sha512-RzeBwv0B3qtVBWtcuABtSuCzToo2IEAIQrcyB/b2zMvBWVbjo8bZDjACUpnaafaxhTw2W+imQbP2BD1usasK4g== integrity sha512-L+34Qqil+v5uC0zEubW7uByo78WOCIrBvci69E7sFASRl0X7b/MB6Cqd1lky/CtcSVTydWa2WZwFuWexjS5o6g==
"@rollup/rollup-linux-arm-musleabihf@4.60.0": "@rollup/rollup-linux-arm-musleabihf@4.60.1":
version "4.60.0" version "4.60.1"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.60.0.tgz#3559f9f060153ea54594a42c3b87a297bedcc26e" resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.60.1.tgz#0bce9ce9a009490abd28fd922dd97ed521311afe"
integrity sha512-Sf7zusNI2CIU1HLzuu9Tc5YGAHEZs5Lu7N1ssJG4Tkw6e0MEsN7NdjUDDfGNHy2IU+ENyWT+L2obgWiguWibWQ== integrity sha512-n83O8rt4v34hgFzlkb1ycniJh7IR5RCIqt6mz1VRJD6pmhRi0CXdmfnLu9dIUS6buzh60IvACM842Ffb3xd6Gg==
"@rollup/rollup-linux-arm64-gnu@4.60.0": "@rollup/rollup-linux-arm64-gnu@4.60.1":
version "4.60.0" version "4.60.1"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.60.0.tgz#e91f887b154123485cfc4b59befe2080fcd8f2df" resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.60.1.tgz#6f6cfbbf324fbb4ceff213abdf7f322fd45d25ff"
integrity sha512-DX2x7CMcrJzsE91q7/O02IJQ5/aLkVtYFryqCjduJhUfGKG6yJV8hxaw8pZa93lLEpPTP/ohdN4wFz7yp/ry9A== integrity sha512-Nql7sTeAzhTAja3QXeAI48+/+GjBJ+QmAH13snn0AJSNL50JsDqotyudHyMbO2RbJkskbMbFJfIJKWA6R1LCJQ==
"@rollup/rollup-linux-arm64-musl@4.60.0": "@rollup/rollup-linux-arm64-musl@4.60.1":
version "4.60.0" version "4.60.1"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.60.0.tgz#660752f040df9ba44a24765df698928917c0bf21" resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.60.1.tgz#f7cb3eecaea9c151ef77342af05f38ae924bf795"
integrity sha512-09EL+yFVbJZlhcQfShpswwRZ0Rg+z/CsSELFCnPt3iK+iqwGsI4zht3secj5vLEs957QvFFXnzAT0FFPIxSrkQ== integrity sha512-+pUymDhd0ys9GcKZPPWlFiZ67sTWV5UU6zOJat02M1+PiuSGDziyRuI/pPue3hoUwm2uGfxdL+trT6Z9rxnlMA==
"@rollup/rollup-linux-loong64-gnu@4.60.0": "@rollup/rollup-linux-loong64-gnu@4.60.1":
version "4.60.0" version "4.60.1"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.60.0.tgz#cb0e939a5fa479ccef264f3f45b31971695f869c" resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.60.1.tgz#499bfac6bb669fd88bb664357bf6be996a28b92f"
integrity sha512-i9IcCMPr3EXm8EQg5jnja0Zyc1iFxJjZWlb4wr7U2Wx/GrddOuEafxRdMPRYVaXjgbhvqalp6np07hN1w9kAKw== integrity sha512-VSvgvQeIcsEvY4bKDHEDWcpW4Yw7BtlKG1GUT4FzBUlEKQK0rWHYBqQt6Fm2taXS+1bXvJT6kICu5ZwqKCnvlQ==
"@rollup/rollup-linux-loong64-musl@4.60.0": "@rollup/rollup-linux-loong64-musl@4.60.1":
version "4.60.0" version "4.60.1"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.60.0.tgz#42f86fbc82cd1a81be2d346476dd3231cf5ee442" resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.60.1.tgz#127dfac08764764396bbe04453c545d38a3ab518"
integrity sha512-DGzdJK9kyJ+B78MCkWeGnpXJ91tK/iKA6HwHxF4TAlPIY7GXEvMe8hBFRgdrR9Ly4qebR/7gfUs9y2IoaVEyog== integrity sha512-4LqhUomJqwe641gsPp6xLfhqWMbQV04KtPp7/dIp0nzPxAkNY1AbwL5W0MQpcalLYk07vaW9Kp1PBhdpZYYcEw==
"@rollup/rollup-linux-ppc64-gnu@4.60.0": "@rollup/rollup-linux-ppc64-gnu@4.60.1":
version "4.60.0" version "4.60.1"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.60.0.tgz#39776a647a789dc95ea049277c5ef8f098df77f9" resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.60.1.tgz#6a72f4d95852aac18326c5bf708393e8f3a41b70"
integrity sha512-RwpnLsqC8qbS8z1H1AxBA1H6qknR4YpPR9w2XX0vo2Sz10miu57PkNcnHVaZkbqyw/kUWfKMI73jhmfi9BRMUQ== integrity sha512-tLQQ9aPvkBxOc/EUT6j3pyeMD6Hb8QF2BTBnCQWP/uu1lhc9AIrIjKnLYMEroIz/JvtGYgI9dF3AxHZNaEH0rw==
"@rollup/rollup-linux-ppc64-musl@4.60.0": "@rollup/rollup-linux-ppc64-musl@4.60.1":
version "4.60.0" version "4.60.1"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.60.0.tgz#466f20029a8e8b3bb2954c7ddebc9586420cac2c" resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.60.1.tgz#ba8674666b00d6f9066cb9a5771a8430c34d2de6"
integrity sha512-Z8pPf54Ly3aqtdWC3G4rFigZgNvd+qJlOE52fmko3KST9SoGfAdSRCwyoyG05q1HrrAblLbk1/PSIV+80/pxLg== integrity sha512-RMxFhJwc9fSXP6PqmAz4cbv3kAyvD1etJFjTx4ONqFP9DkTkXsAMU4v3Vyc5BgzC+anz7nS/9tp4obsKfqkDHg==
"@rollup/rollup-linux-riscv64-gnu@4.60.0": "@rollup/rollup-linux-riscv64-gnu@4.60.1":
version "4.60.0" version "4.60.1"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.60.0.tgz#cff9877c78f12e7aa6246f6902ad913e99edb2b7" resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.60.1.tgz#17cc38b2a71e302547cad29bcf78d0db2618c922"
integrity sha512-3a3qQustp3COCGvnP4SvrMHnPQ9d1vzCakQVRTliaz8cIp/wULGjiGpbcqrkv0WrHTEp8bQD/B3HBjzujVWLOA== integrity sha512-QKgFl+Yc1eEk6MmOBfRHYF6lTxiiiV3/z/BRrbSiW2I7AFTXoBFvdMEyglohPj//2mZS4hDOqeB0H1ACh3sBbg==
"@rollup/rollup-linux-riscv64-musl@4.60.0": "@rollup/rollup-linux-riscv64-musl@4.60.1":
version "4.60.0" version "4.60.1"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.60.0.tgz#9a762fb99b5a82a921017f56491b7e892b9fb17d" resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.60.1.tgz#e36a41e2d8bd247331bd5cfc13b8c951d33454a2"
integrity sha512-pjZDsVH/1VsghMJ2/kAaxt6dL0psT6ZexQVrijczOf+PeP2BUqTHYejk3l6TlPRydggINOeNRhvpLa0AYpCWSQ== integrity sha512-RAjXjP/8c6ZtzatZcA1RaQr6O1TRhzC+adn8YZDnChliZHviqIjmvFwHcxi4JKPSDAt6Uhf/7vqcBzQJy0PDJg==
"@rollup/rollup-linux-s390x-gnu@4.60.0": "@rollup/rollup-linux-s390x-gnu@4.60.1":
version "4.60.0" version "4.60.1"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.60.0.tgz#9d25ad8ac7dab681935baf78ac5ea92d14629cdf" resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.60.1.tgz#1687265f1f4bdea0726c761a58c2db9933609d68"
integrity sha512-3ObQs0BhvPgiUVZrN7gqCSvmFuMWvWvsjG5ayJ3Lraqv+2KhOsp+pUbigqbeWqueGIsnn+09HBw27rJ+gYK4VQ== integrity sha512-wcuocpaOlaL1COBYiA89O6yfjlp3RwKDeTIA0hM7OpmhR1Bjo9j31G1uQVpDlTvwxGn2nQs65fBFL5UFd76FcQ==
"@rollup/rollup-linux-x64-gnu@4.60.0": "@rollup/rollup-linux-x64-gnu@4.60.1":
version "4.60.0" version "4.60.1"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.60.0.tgz#5e5139e11819fa38a052368da79422cb4afcf466" resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.60.1.tgz#56a6a0d9076f2a05a976031493b24a20ddcc0e77"
integrity sha512-EtylprDtQPdS5rXvAayrNDYoJhIz1/vzN2fEubo3yLE7tfAw+948dO0g4M0vkTVFhKojnF+n6C8bDNe+gDRdTg== integrity sha512-77PpsFQUCOiZR9+LQEFg9GClyfkNXj1MP6wRnzYs0EeWbPcHs02AXu4xuUbM1zhwn3wqaizle3AEYg5aeoohhg==
"@rollup/rollup-linux-x64-musl@4.60.0": "@rollup/rollup-linux-x64-musl@4.60.1":
version "4.60.0" version "4.60.1"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.60.0.tgz#b6211d46e11b1f945f5504cc794fce839331ed08" resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.60.1.tgz#bc240ebb5b9fd8d41ca8a80cb458452e8c187e0f"
integrity sha512-k09oiRCi/bHU9UVFqD17r3eJR9bn03TyKraCrlz5ULFJGdJGi7VOmm9jl44vOJvRJ6P7WuBi/s2A97LxxHGIdw== integrity sha512-5cIATbk5vynAjqqmyBjlciMJl1+R/CwX9oLk/EyiFXDWd95KpHdrOJT//rnUl4cUcskrd0jCCw3wpZnhIHdD9w==
"@rollup/rollup-openbsd-x64@4.60.0": "@rollup/rollup-openbsd-x64@4.60.1":
version "4.60.0" version "4.60.1"
resolved "https://registry.yarnpkg.com/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.60.0.tgz#e6e09eebaa7012bb9c7331b437a9e992bd94ca35" resolved "https://registry.yarnpkg.com/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.60.1.tgz#6f80d48a006c4b2ffa7724e95a3e33f6975872af"
integrity sha512-1o/0/pIhozoSaDJoDcec+IVLbnRtQmHwPV730+AOD29lHEEo4F5BEUB24H0OBdhbBBDwIOSuf7vgg0Ywxdfiiw== integrity sha512-cl0w09WsCi17mcmWqqglez9Gk8isgeWvoUZ3WiJFYSR3zjBQc2J5/ihSjpl+VLjPqjQ/1hJRcqBfLjssREQILw==
"@rollup/rollup-openharmony-arm64@4.60.0": "@rollup/rollup-openharmony-arm64@4.60.1":
version "4.60.0" version "4.60.1"
resolved "https://registry.yarnpkg.com/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.60.0.tgz#f7d99ae857032498e57a5e7259fb7100fd24a87e" resolved "https://registry.yarnpkg.com/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.60.1.tgz#8f6db6f70d0a48abd833b263cd6dd3e7199c4c0e"
integrity sha512-pESDkos/PDzYwtyzB5p/UoNU/8fJo68vcXM9ZW2V0kjYayj1KaaUfi1NmTUTUpMn4UhU4gTuK8gIaFO4UGuMbA== integrity sha512-4Cv23ZrONRbNtbZa37mLSueXUCtN7MXccChtKpUnQNgF010rjrjfHx3QxkS2PI7LqGT5xXyYs1a7LbzAwT0iCA==
"@rollup/rollup-win32-arm64-msvc@4.60.0": "@rollup/rollup-win32-arm64-msvc@4.60.1":
version "4.60.0" version "4.60.1"
resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.60.0.tgz#41e392f5d9f3bf1253fdaf2f6d6f6b1bfc452856" resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.60.1.tgz#b68989bfa815d0b3d4e302ecd90bda744438b177"
integrity sha512-hj1wFStD7B1YBeYmvY+lWXZ7ey73YGPcViMShYikqKT1GtstIKQAtfUI6yrzPjAy/O7pO0VLXGmUVWXQMaYgTQ== integrity sha512-i1okWYkA4FJICtr7KpYzFpRTHgy5jdDbZiWfvny21iIKky5YExiDXP+zbXzm3dUcFpkEeYNHgQ5fuG236JPq0g==
"@rollup/rollup-win32-ia32-msvc@4.60.0": "@rollup/rollup-win32-ia32-msvc@4.60.1":
version "4.60.0" version "4.60.1"
resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.60.0.tgz#f41b0490be0e5d3cf459b4dc076a192b532adea9" resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.60.1.tgz#c098e45338c50f22f1b288476354f025b746285b"
integrity sha512-SyaIPFoxmUPlNDq5EHkTbiKzmSEmq/gOYFI/3HHJ8iS/v1mbugVa7dXUzcJGQfoytp9DJFLhHH4U3/eTy2Bq4w== integrity sha512-u09m3CuwLzShA0EYKMNiFgcjjzwqtUMLmuCJLeZWjjOYA3IT2Di09KaxGBTP9xVztWyIWjVdsB2E9goMjZvTQg==
"@rollup/rollup-win32-x64-gnu@4.60.0": "@rollup/rollup-win32-x64-gnu@4.60.1":
version "4.60.0" version "4.60.1"
resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.60.0.tgz#0fcf9f1fcb750f0317b13aac3b3231687e6397a5" resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.60.1.tgz#2c9e15be155b79d05999953b1737b2903842e903"
integrity sha512-RdcryEfzZr+lAr5kRm2ucN9aVlCCa2QNq4hXelZxb8GG0NJSazq44Z3PCCc8wISRuCVnGs0lQJVX5Vp6fKA+IA== integrity sha512-k+600V9Zl1CM7eZxJgMyTUzmrmhB/0XZnF4pRypKAlAgxmedUA+1v9R+XOFv56W4SlHEzfeMtzujLJD22Uz5zg==
"@rollup/rollup-win32-x64-msvc@4.60.0": "@rollup/rollup-win32-x64-msvc@4.60.1":
version "4.60.0" version "4.60.1"
resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.60.0.tgz#3afdb30405f6d4248df5e72e1ca86c5eab55fab8" resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.60.1.tgz#23b860113e9f87eea015d1fa3a4240a52b42fcd4"
integrity sha512-PrsWNQ8BuE00O3Xsx3ALh2Df8fAj9+cvvX9AIA6o4KpATR98c9mud4XtDWVvsEuyia5U4tVSTKygawyJkjm60w== integrity sha512-lWMnixq/QzxyhTV6NjQJ4SFo1J6PvOX8vUx5Wb4bBPsEb+8xZ89Bz6kOXpfXj9ak9AHTQVQzlgzBEc1SyM27xQ==
"@swc/helpers@^0.5.0": "@swc/helpers@^0.5.0":
version "0.5.19" version "0.5.20"
resolved "https://registry.yarnpkg.com/@swc/helpers/-/helpers-0.5.19.tgz#9a8c8a0bdaecfdfb9b8ae5421c0c8e09246dfee9" resolved "https://registry.yarnpkg.com/@swc/helpers/-/helpers-0.5.20.tgz#d1d0f1e18ff6592c96a4931b4031298619129585"
integrity sha512-QamiFeIK3txNjgUTNppE6MiG3p7TdninpZu0E0PbqVh1a9FNLT2FRhisaa4NcaX52XVhA5l7Pk58Ft7Sqi/2sA== integrity sha512-2egEBHUMasdypIzrprsu8g+OEVd7Vp2MM3a2eVlM/cyFYto0nGz5BX5BTgh/ShZZI9ed+ozEq+Ngt+rgmUs8tw==
dependencies: dependencies:
tslib "^2.8.0" tslib "^2.8.0"
@@ -942,100 +940,100 @@
resolved "https://registry.yarnpkg.com/@types/warning/-/warning-3.0.4.tgz#ebc0c83180dc83994d902bbd51ab0af8a445b1f9" resolved "https://registry.yarnpkg.com/@types/warning/-/warning-3.0.4.tgz#ebc0c83180dc83994d902bbd51ab0af8a445b1f9"
integrity sha512-CqN8MnISMwQbLJXO3doBAV4Yw9hx9/Pyr2rZ78+NfaCnhyRA/nKrpyk6E7mKw17ZOaQdLpK9GiUjrqLzBlN3sg== integrity sha512-CqN8MnISMwQbLJXO3doBAV4Yw9hx9/Pyr2rZ78+NfaCnhyRA/nKrpyk6E7mKw17ZOaQdLpK9GiUjrqLzBlN3sg==
"@typescript-eslint/eslint-plugin@8.57.2", "@typescript-eslint/eslint-plugin@^8.56.0": "@typescript-eslint/eslint-plugin@8.58.0", "@typescript-eslint/eslint-plugin@^8.56.0":
version "8.57.2" version "8.58.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.57.2.tgz#ad0dcefeca9c2ecbe09f730d478063666aee010b" resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.58.0.tgz#ad40e492f1931f46da1bd888e52b9e56df9063aa"
integrity sha512-NZZgp0Fm2IkD+La5PR81sd+g+8oS6JwJje+aRWsDocxHkjyRw0J5L5ZTlN3LI1LlOcGL7ph3eaIUmTXMIjLk0w== integrity sha512-RLkVSiNuUP1C2ROIWfqX+YcUfLaSnxGE/8M+Y57lopVwg9VTYYfhuz15Yf1IzCKgZj6/rIbYTmJCUSqr76r0Wg==
dependencies: dependencies:
"@eslint-community/regexpp" "^4.12.2" "@eslint-community/regexpp" "^4.12.2"
"@typescript-eslint/scope-manager" "8.57.2" "@typescript-eslint/scope-manager" "8.58.0"
"@typescript-eslint/type-utils" "8.57.2" "@typescript-eslint/type-utils" "8.58.0"
"@typescript-eslint/utils" "8.57.2" "@typescript-eslint/utils" "8.58.0"
"@typescript-eslint/visitor-keys" "8.57.2" "@typescript-eslint/visitor-keys" "8.58.0"
ignore "^7.0.5" ignore "^7.0.5"
natural-compare "^1.4.0" natural-compare "^1.4.0"
ts-api-utils "^2.4.0" ts-api-utils "^2.5.0"
"@typescript-eslint/parser@8.57.2", "@typescript-eslint/parser@^8.56.0": "@typescript-eslint/parser@8.58.0", "@typescript-eslint/parser@^8.56.0":
version "8.57.2" version "8.58.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-8.57.2.tgz#b819955e39f976c0d4f95b5ed67fe22f85cd6898" resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-8.58.0.tgz#da04ece1967b6c2fe8f10c3473dabf3825795ef7"
integrity sha512-30ScMRHIAD33JJQkgfGW1t8CURZtjc2JpTrq5n2HFhOefbAhb7ucc7xJwdWcrEtqUIYJ73Nybpsggii6GtAHjA== integrity sha512-rLoGZIf9afaRBYsPUMtvkDWykwXwUPL60HebR4JgTI8mxfFe2cQTu3AGitANp4b9B2QlVru6WzjgB2IzJKiCSA==
dependencies: dependencies:
"@typescript-eslint/scope-manager" "8.57.2" "@typescript-eslint/scope-manager" "8.58.0"
"@typescript-eslint/types" "8.57.2" "@typescript-eslint/types" "8.58.0"
"@typescript-eslint/typescript-estree" "8.57.2" "@typescript-eslint/typescript-estree" "8.58.0"
"@typescript-eslint/visitor-keys" "8.57.2" "@typescript-eslint/visitor-keys" "8.58.0"
debug "^4.4.3" debug "^4.4.3"
"@typescript-eslint/project-service@8.57.2": "@typescript-eslint/project-service@8.58.0":
version "8.57.2" version "8.58.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/project-service/-/project-service-8.57.2.tgz#dfbc7777f9f633f2b06b558cda3836e76f856e3c" resolved "https://registry.yarnpkg.com/@typescript-eslint/project-service/-/project-service-8.58.0.tgz#66ceda0aabf7427aec3e2713fa43eb278dead2aa"
integrity sha512-FuH0wipFywXRTHf+bTTjNyuNQQsQC3qh/dYzaM4I4W0jrCqjCVuUh99+xd9KamUfmCGPvbO8NDngo/vsnNVqgw== integrity sha512-8Q/wBPWLQP1j16NxoPNIKpDZFMaxl7yWIoqXWYeWO+Bbd2mjgvoF0dxP2jKZg5+x49rgKdf7Ck473M8PC3V9lg==
dependencies: dependencies:
"@typescript-eslint/tsconfig-utils" "^8.57.2" "@typescript-eslint/tsconfig-utils" "^8.58.0"
"@typescript-eslint/types" "^8.57.2" "@typescript-eslint/types" "^8.58.0"
debug "^4.4.3" debug "^4.4.3"
"@typescript-eslint/scope-manager@8.57.2": "@typescript-eslint/scope-manager@8.58.0":
version "8.57.2" version "8.58.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-8.57.2.tgz#734dcde40677f430b5d963108337295bdbc09dae" resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-8.58.0.tgz#e304142775e49a1b7ac3c8bf2536714447c72cab"
integrity sha512-snZKH+W4WbWkrBqj4gUNRIGb/jipDW3qMqVJ4C9rzdFc+wLwruxk+2a5D+uoFcKPAqyqEnSb4l2ULuZf95eSkw== integrity sha512-W1Lur1oF50FxSnNdGp3Vs6P+yBRSmZiw4IIjEeYxd8UQJwhUF0gDgDD/W/Tgmh73mxgEU3qX0Bzdl/NGuSPEpQ==
dependencies: dependencies:
"@typescript-eslint/types" "8.57.2" "@typescript-eslint/types" "8.58.0"
"@typescript-eslint/visitor-keys" "8.57.2" "@typescript-eslint/visitor-keys" "8.58.0"
"@typescript-eslint/tsconfig-utils@8.57.2", "@typescript-eslint/tsconfig-utils@^8.57.2": "@typescript-eslint/tsconfig-utils@8.58.0", "@typescript-eslint/tsconfig-utils@^8.58.0":
version "8.57.2" version "8.58.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.57.2.tgz#cf82dc11e884103ec13188a7352591efaa1a887e" resolved "https://registry.yarnpkg.com/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.58.0.tgz#c5a8edb21f31e0fdee565724e1b984171c559482"
integrity sha512-3Lm5DSM+DCowsUOJC+YqHHnKEfFh5CoGkj5Z31NQSNF4l5wdOwqGn99wmwN/LImhfY3KJnmordBq/4+VDe2eKw== integrity sha512-doNSZEVJsWEu4htiVC+PR6NpM+pa+a4ClH9INRWOWCUzMst/VA9c4gXq92F8GUD1rwhNvRLkgjfYtFXegXQF7A==
"@typescript-eslint/type-utils@8.57.2": "@typescript-eslint/type-utils@8.58.0":
version "8.57.2" version "8.58.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-8.57.2.tgz#3ec65a94e73776252991a3cf0a15d220734c28f5" resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-8.58.0.tgz#ce0e72cd967ffbbe8de322db6089bd4374be352f"
integrity sha512-Co6ZCShm6kIbAM/s+oYVpKFfW7LBc6FXoPXjTRQ449PPNBY8U0KZXuevz5IFuuUj2H9ss40atTaf9dlGLzbWZg== integrity sha512-aGsCQImkDIqMyx1u4PrVlbi/krmDsQUs4zAcCV6M7yPcPev+RqVlndsJy9kJ8TLihW9TZ0kbDAzctpLn5o+lOg==
dependencies: dependencies:
"@typescript-eslint/types" "8.57.2" "@typescript-eslint/types" "8.58.0"
"@typescript-eslint/typescript-estree" "8.57.2" "@typescript-eslint/typescript-estree" "8.58.0"
"@typescript-eslint/utils" "8.57.2" "@typescript-eslint/utils" "8.58.0"
debug "^4.4.3" debug "^4.4.3"
ts-api-utils "^2.4.0" ts-api-utils "^2.5.0"
"@typescript-eslint/types@8.57.2", "@typescript-eslint/types@^8.57.2": "@typescript-eslint/types@8.58.0", "@typescript-eslint/types@^8.58.0":
version "8.57.2" version "8.58.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-8.57.2.tgz#efe0da4c28b505ed458f113aa960dce2c5c671f4" resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-8.58.0.tgz#e94ae7abdc1c6530e71183c1007b61fa93112a5a"
integrity sha512-/iZM6FnM4tnx9csuTxspMW4BOSegshwX5oBDznJ7S4WggL7Vczz5d2W11ecc4vRrQMQHXRSxzrCsyG5EsPPTbA== integrity sha512-O9CjxypDT89fbHxRfETNoAnHj/i6IpRK0CvbVN3qibxlLdo5p5hcLmUuCCrHMpxiWSwKyI8mCP7qRNYuOJ0Uww==
"@typescript-eslint/typescript-estree@8.57.2": "@typescript-eslint/typescript-estree@8.58.0":
version "8.57.2" version "8.58.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.57.2.tgz#432e61a6cf2ab565837da387e5262c159672abea" resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.58.0.tgz#ed233faa8e2f2a2e1357c3e7d553d6465a0ee59a"
integrity sha512-2MKM+I6g8tJxfSmFKOnHv2t8Sk3T6rF20A1Puk0svLK+uVapDZB/4pfAeB7nE83uAZrU6OxW+HmOd5wHVdXwXA== integrity sha512-7vv5UWbHqew/dvs+D3e1RvLv1v2eeZ9txRHPnEEBUgSNLx5ghdzjHa0sgLWYVKssH+lYmV0JaWdoubo0ncGYLA==
dependencies: dependencies:
"@typescript-eslint/project-service" "8.57.2" "@typescript-eslint/project-service" "8.58.0"
"@typescript-eslint/tsconfig-utils" "8.57.2" "@typescript-eslint/tsconfig-utils" "8.58.0"
"@typescript-eslint/types" "8.57.2" "@typescript-eslint/types" "8.58.0"
"@typescript-eslint/visitor-keys" "8.57.2" "@typescript-eslint/visitor-keys" "8.58.0"
debug "^4.4.3" debug "^4.4.3"
minimatch "^10.2.2" minimatch "^10.2.2"
semver "^7.7.3" semver "^7.7.3"
tinyglobby "^0.2.15" tinyglobby "^0.2.15"
ts-api-utils "^2.4.0" ts-api-utils "^2.5.0"
"@typescript-eslint/utils@8.57.2": "@typescript-eslint/utils@8.58.0":
version "8.57.2" version "8.58.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-8.57.2.tgz#46a8974c24326fb8899486728428a0f1a3115014" resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-8.58.0.tgz#21a74a7963b0d288b719a4121c7dd555adaab3c3"
integrity sha512-krRIbvPK1ju1WBKIefiX+bngPs+odIQUtR7kymzPfo1POVw3jlF+nLkmexdSSd4UCbDcQn+wMBATOOmpBbqgKg== integrity sha512-RfeSqcFeHMHlAWzt4TBjWOAtoW9lnsAGiP3GbaX9uVgTYYrMbVnGONEfUCiSss+xMHFl+eHZiipmA8WkQ7FuNA==
dependencies: dependencies:
"@eslint-community/eslint-utils" "^4.9.1" "@eslint-community/eslint-utils" "^4.9.1"
"@typescript-eslint/scope-manager" "8.57.2" "@typescript-eslint/scope-manager" "8.58.0"
"@typescript-eslint/types" "8.57.2" "@typescript-eslint/types" "8.58.0"
"@typescript-eslint/typescript-estree" "8.57.2" "@typescript-eslint/typescript-estree" "8.58.0"
"@typescript-eslint/visitor-keys@8.57.2": "@typescript-eslint/visitor-keys@8.58.0":
version "8.57.2" version "8.58.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.57.2.tgz#a5c9605774247336c0412beb7dc288ab2a07c11e" resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.58.0.tgz#2abd55a4be70fd55967aceaba4330b9ba9f45189"
integrity sha512-zhahknjobV2FiD6Ee9iLbS7OV9zi10rG26odsQdfBO/hjSzUQbkIYgda+iNKK1zNiW2ey+Lf8MU5btN17V3dUw== integrity sha512-XJ9UD9+bbDo4a4epraTwG3TsNPeiB9aShrUneAVXy8q4LuwowN+qu89/6ByLMINqvIMeI9H9hOHQtg/ijrYXzQ==
dependencies: dependencies:
"@typescript-eslint/types" "8.57.2" "@typescript-eslint/types" "8.58.0"
eslint-visitor-keys "^5.0.0" eslint-visitor-keys "^5.0.0"
"@vitejs/plugin-react@^5.1.4": "@vitejs/plugin-react@^5.1.4":
@@ -1181,36 +1179,36 @@ balanced-match@^4.0.2:
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-4.0.4.tgz#bfb10662feed8196a2c62e7c68e17720c274179a" resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-4.0.4.tgz#bfb10662feed8196a2c62e7c68e17720c274179a"
integrity sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA== integrity sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==
baseline-browser-mapping@^2.9.0: baseline-browser-mapping@^2.10.12:
version "2.10.10" version "2.10.13"
resolved "https://registry.yarnpkg.com/baseline-browser-mapping/-/baseline-browser-mapping-2.10.10.tgz#e74bd066724c1d8d7d8ea75fc3be25389a7a5c56" resolved "https://registry.yarnpkg.com/baseline-browser-mapping/-/baseline-browser-mapping-2.10.13.tgz#5a154cc4589193015a274e3d18319b0d76b9224e"
integrity sha512-sUoJ3IMxx4AyRqO4MLeHlnGDkyXRoUG0/AI9fjK+vS72ekpV0yWVY7O0BVjmBcRtkNcsAO2QDZ4tdKKGoI6YaQ== integrity sha512-BL2sTuHOdy0YT1lYieUxTw/QMtPBC3pmlJC6xk8BBYVv6vcw3SGdKemQ+Xsx9ik2F/lYDO9tqsFQH1r9PFuHKw==
brace-expansion@^1.1.7: brace-expansion@^1.1.7:
version "1.1.12" version "1.1.13"
resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.12.tgz#ab9b454466e5a8cc3a187beaad580412a9c5b843" resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.13.tgz#d37875c01dc9eff988dd49d112a57cb67b54efe6"
integrity sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg== integrity sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w==
dependencies: dependencies:
balanced-match "^1.0.0" balanced-match "^1.0.0"
concat-map "0.0.1" concat-map "0.0.1"
brace-expansion@^5.0.2: brace-expansion@^5.0.5:
version "5.0.4" version "5.0.5"
resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-5.0.4.tgz#614daaecd0a688f660bbbc909a8748c3d80d4336" resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-5.0.5.tgz#dcc3a37116b79f3e1b46db994ced5d570e930fdb"
integrity sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg== integrity sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==
dependencies: dependencies:
balanced-match "^4.0.2" balanced-match "^4.0.2"
browserslist@^4.24.0, browserslist@^4.28.1: browserslist@^4.24.0, browserslist@^4.28.1:
version "4.28.1" version "4.28.2"
resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.28.1.tgz#7f534594628c53c63101079e27e40de490456a95" resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.28.2.tgz#f50b65362ef48974ca9f50b3680566d786b811d2"
integrity sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA== integrity sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==
dependencies: dependencies:
baseline-browser-mapping "^2.9.0" baseline-browser-mapping "^2.10.12"
caniuse-lite "^1.0.30001759" caniuse-lite "^1.0.30001782"
electron-to-chromium "^1.5.263" electron-to-chromium "^1.5.328"
node-releases "^2.0.27" node-releases "^2.0.36"
update-browserslist-db "^1.2.0" update-browserslist-db "^1.2.3"
call-bind-apply-helpers@^1.0.0, call-bind-apply-helpers@^1.0.1, call-bind-apply-helpers@^1.0.2: call-bind-apply-helpers@^1.0.0, call-bind-apply-helpers@^1.0.1, call-bind-apply-helpers@^1.0.2:
version "1.0.2" version "1.0.2"
@@ -1238,10 +1236,10 @@ call-bound@^1.0.2, call-bound@^1.0.3, call-bound@^1.0.4:
call-bind-apply-helpers "^1.0.2" call-bind-apply-helpers "^1.0.2"
get-intrinsic "^1.3.0" get-intrinsic "^1.3.0"
caniuse-lite@^1.0.30001759, caniuse-lite@^1.0.30001774: caniuse-lite@^1.0.30001774, caniuse-lite@^1.0.30001782:
version "1.0.30001781" version "1.0.30001784"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001781.tgz#344b47c03eb8168b79c3c158b872bcfbdd02a400" resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001784.tgz#bdf9733a0813ccfb5ab4d02f2127e62ee4c6b718"
integrity sha512-RdwNCyMsNBftLjW6w01z8bKEvT6e/5tpPVEgtn22TiLGlstHOVecsX2KHFkD5e/vRnIE4EGzpuIODb3mtswtkw== integrity sha512-WU346nBTklUV9YfUl60fqRbU5ZqyXlqvo1SgigE1OAXK5bFL8LL9q1K7aap3N739l4BvNqnkm3YrGHiY9sfUQw==
chokidar@^4.0.0: chokidar@^4.0.0:
version "4.0.3" version "4.0.3"
@@ -1389,10 +1387,10 @@ dunder-proto@^1.0.0, dunder-proto@^1.0.1:
es-errors "^1.3.0" es-errors "^1.3.0"
gopd "^1.2.0" gopd "^1.2.0"
electron-to-chromium@^1.5.263: electron-to-chromium@^1.5.328:
version "1.5.322" version "1.5.330"
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.322.tgz#9c24e49f7098ca19bc87c0e9c7e0ad6ffe4fddca" resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.330.tgz#0efe031938fc8fc82126162a7bd466ba7e24cd38"
integrity sha512-vFU34OcrvMcH66T+dYC3G4nURmgfDVewMIu6Q2urXpumAPSMmzvcn04KVVV8Opikq8Vs5nUbO/8laNhNRqSzYw== integrity sha512-jFNydB5kFtYUobh4IkWUnXeyDbjf/r9gcUEXe1xcrcUxIGfTdzPXA+ld6zBRbwvgIGVzDll/LTIiDztEtckSnA==
emoji-mart@^5.6.0: emoji-mart@^5.6.0:
version "5.6.0" version "5.6.0"
@@ -2337,11 +2335,11 @@ mini-svg-data-uri@^1.2.3:
integrity sha512-r9deDe9p5FJUPZAk3A59wGH7Ii9YrjjWw0jmw/liSbHl2CHiyXj6FcDXDu2K3TjVAXqiJdaw3xxwlZZr9E6nHg== integrity sha512-r9deDe9p5FJUPZAk3A59wGH7Ii9YrjjWw0jmw/liSbHl2CHiyXj6FcDXDu2K3TjVAXqiJdaw3xxwlZZr9E6nHg==
minimatch@^10.2.2, minimatch@^10.2.4: minimatch@^10.2.2, minimatch@^10.2.4:
version "10.2.4" version "10.2.5"
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-10.2.4.tgz#465b3accbd0218b8281f5301e27cedc697f96fde" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-10.2.5.tgz#bd48687a0be38ed2961399105600f832095861d1"
integrity sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg== integrity sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==
dependencies: dependencies:
brace-expansion "^5.0.2" brace-expansion "^5.0.5"
minimatch@^3.1.2: minimatch@^3.1.2:
version "3.1.5" version "3.1.5"
@@ -2397,7 +2395,7 @@ node-exports-info@^1.6.0:
object.entries "^1.1.9" object.entries "^1.1.9"
semver "^6.3.1" semver "^6.3.1"
node-releases@^2.0.27: node-releases@^2.0.36:
version "2.0.36" version "2.0.36"
resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.36.tgz#99fd6552aaeda9e17c4713b57a63964a2e325e9d" resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.36.tgz#99fd6552aaeda9e17c4713b57a63964a2e325e9d"
integrity sha512-TdC8FSgHz8Mwtw9g5L4gR/Sh9XhSP/0DEkQxfEFXOpiul5IiHgHan2VhYYb6agDSfp4KuvltmGApc8HMgUrIkA== integrity sha512-TdC8FSgHz8Mwtw9g5L4gR/Sh9XhSP/0DEkQxfEFXOpiul5IiHgHan2VhYYb6agDSfp4KuvltmGApc8HMgUrIkA==
@@ -2706,37 +2704,37 @@ resolve@^2.0.0-next.5:
supports-preserve-symlinks-flag "^1.0.0" supports-preserve-symlinks-flag "^1.0.0"
rollup@^4.43.0: rollup@^4.43.0:
version "4.60.0" version "4.60.1"
resolved "https://registry.yarnpkg.com/rollup/-/rollup-4.60.0.tgz#d7d68c8cda873e96e08b2443505609b7e7be9eb8" resolved "https://registry.yarnpkg.com/rollup/-/rollup-4.60.1.tgz#b4aa2bcb3a5e1437b5fad40d43fe42d4bde7a42d"
integrity sha512-yqjxruMGBQJ2gG4HtjZtAfXArHomazDHoFwFFmZZl0r7Pdo7qCIXKqKHZc8yeoMgzJJ+pO6pEEHa+V7uzWlrAQ== integrity sha512-VmtB2rFU/GroZ4oL8+ZqXgSA38O6GR8KSIvWmEFv63pQ0G6KaBH9s07PO8XTXP4vI+3UJUEypOfjkGfmSBBR0w==
dependencies: dependencies:
"@types/estree" "1.0.8" "@types/estree" "1.0.8"
optionalDependencies: optionalDependencies:
"@rollup/rollup-android-arm-eabi" "4.60.0" "@rollup/rollup-android-arm-eabi" "4.60.1"
"@rollup/rollup-android-arm64" "4.60.0" "@rollup/rollup-android-arm64" "4.60.1"
"@rollup/rollup-darwin-arm64" "4.60.0" "@rollup/rollup-darwin-arm64" "4.60.1"
"@rollup/rollup-darwin-x64" "4.60.0" "@rollup/rollup-darwin-x64" "4.60.1"
"@rollup/rollup-freebsd-arm64" "4.60.0" "@rollup/rollup-freebsd-arm64" "4.60.1"
"@rollup/rollup-freebsd-x64" "4.60.0" "@rollup/rollup-freebsd-x64" "4.60.1"
"@rollup/rollup-linux-arm-gnueabihf" "4.60.0" "@rollup/rollup-linux-arm-gnueabihf" "4.60.1"
"@rollup/rollup-linux-arm-musleabihf" "4.60.0" "@rollup/rollup-linux-arm-musleabihf" "4.60.1"
"@rollup/rollup-linux-arm64-gnu" "4.60.0" "@rollup/rollup-linux-arm64-gnu" "4.60.1"
"@rollup/rollup-linux-arm64-musl" "4.60.0" "@rollup/rollup-linux-arm64-musl" "4.60.1"
"@rollup/rollup-linux-loong64-gnu" "4.60.0" "@rollup/rollup-linux-loong64-gnu" "4.60.1"
"@rollup/rollup-linux-loong64-musl" "4.60.0" "@rollup/rollup-linux-loong64-musl" "4.60.1"
"@rollup/rollup-linux-ppc64-gnu" "4.60.0" "@rollup/rollup-linux-ppc64-gnu" "4.60.1"
"@rollup/rollup-linux-ppc64-musl" "4.60.0" "@rollup/rollup-linux-ppc64-musl" "4.60.1"
"@rollup/rollup-linux-riscv64-gnu" "4.60.0" "@rollup/rollup-linux-riscv64-gnu" "4.60.1"
"@rollup/rollup-linux-riscv64-musl" "4.60.0" "@rollup/rollup-linux-riscv64-musl" "4.60.1"
"@rollup/rollup-linux-s390x-gnu" "4.60.0" "@rollup/rollup-linux-s390x-gnu" "4.60.1"
"@rollup/rollup-linux-x64-gnu" "4.60.0" "@rollup/rollup-linux-x64-gnu" "4.60.1"
"@rollup/rollup-linux-x64-musl" "4.60.0" "@rollup/rollup-linux-x64-musl" "4.60.1"
"@rollup/rollup-openbsd-x64" "4.60.0" "@rollup/rollup-openbsd-x64" "4.60.1"
"@rollup/rollup-openharmony-arm64" "4.60.0" "@rollup/rollup-openharmony-arm64" "4.60.1"
"@rollup/rollup-win32-arm64-msvc" "4.60.0" "@rollup/rollup-win32-arm64-msvc" "4.60.1"
"@rollup/rollup-win32-ia32-msvc" "4.60.0" "@rollup/rollup-win32-ia32-msvc" "4.60.1"
"@rollup/rollup-win32-x64-gnu" "4.60.0" "@rollup/rollup-win32-x64-gnu" "4.60.1"
"@rollup/rollup-win32-x64-msvc" "4.60.0" "@rollup/rollup-win32-x64-msvc" "4.60.1"
fsevents "~2.3.2" fsevents "~2.3.2"
safe-array-concat@^1.1.3: safe-array-concat@^1.1.3:
@@ -2976,7 +2974,7 @@ tinyglobby@^0.2.15:
fdir "^6.5.0" fdir "^6.5.0"
picomatch "^4.0.3" picomatch "^4.0.3"
ts-api-utils@^2.4.0: ts-api-utils@^2.5.0:
version "2.5.0" version "2.5.0"
resolved "https://registry.yarnpkg.com/ts-api-utils/-/ts-api-utils-2.5.0.tgz#4acd4a155e22734990a5ed1fe9e97f113bcb37c1" resolved "https://registry.yarnpkg.com/ts-api-utils/-/ts-api-utils-2.5.0.tgz#4acd4a155e22734990a5ed1fe9e97f113bcb37c1"
integrity sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA== integrity sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==
@@ -3044,14 +3042,14 @@ typed-array-length@^1.0.7:
reflect.getprototypeof "^1.0.6" reflect.getprototypeof "^1.0.6"
typescript-eslint@^8.56.0: typescript-eslint@^8.56.0:
version "8.57.2" version "8.58.0"
resolved "https://registry.yarnpkg.com/typescript-eslint/-/typescript-eslint-8.57.2.tgz#d64c6648dda5b15176708701537ab0b55ba3c83d" resolved "https://registry.yarnpkg.com/typescript-eslint/-/typescript-eslint-8.58.0.tgz#5758b1b68ae7ec05d756b98c63a1f6953a01172b"
integrity sha512-VEPQ0iPgWO/sBaZOU1xo4nuNdODVOajPnTIbog2GKYr31nIlZ0fWPoCQgGfF3ETyBl1vn63F/p50Um9Z4J8O8A== integrity sha512-e2TQzKfaI85fO+F3QywtX+tCTsu/D3WW5LVU6nz8hTFKFZ8yBJ6mSYRpXqdR3mFjPWmO0eWsTa5f+UpAOe/FMA==
dependencies: dependencies:
"@typescript-eslint/eslint-plugin" "8.57.2" "@typescript-eslint/eslint-plugin" "8.58.0"
"@typescript-eslint/parser" "8.57.2" "@typescript-eslint/parser" "8.58.0"
"@typescript-eslint/typescript-estree" "8.57.2" "@typescript-eslint/typescript-estree" "8.58.0"
"@typescript-eslint/utils" "8.57.2" "@typescript-eslint/utils" "8.58.0"
typescript@^5.9.3: typescript@^5.9.3:
version "5.9.3" version "5.9.3"
@@ -3088,7 +3086,7 @@ undici-types@~7.18.0:
resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-7.18.2.tgz#29357a89e7b7ca4aef3bf0fd3fd0cd73884229e9" resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-7.18.2.tgz#29357a89e7b7ca4aef3bf0fd3fd0cd73884229e9"
integrity sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w== integrity sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==
update-browserslist-db@^1.2.0: update-browserslist-db@^1.2.3:
version "1.2.3" version "1.2.3"
resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz#64d76db58713136acbeb4c49114366cc6cc2e80d" resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz#64d76db58713136acbeb4c49114366cc6cc2e80d"
integrity sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w== integrity sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==