feat: add advanced wired variable tools UI
@@ -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",
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
|
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>
|
||||||
|
After Width: | Height: | Size: 208 B |
|
After Width: | Height: | Size: 236 B |
|
After Width: | Height: | Size: 193 B |
|
After Width: | Height: | Size: 181 B |
|
After Width: | Height: | Size: 218 B |
|
After Width: | Height: | Size: 107 B |
|
After Width: | Height: | Size: 153 B |
|
After Width: | Height: | Size: 146 B |
|
After Width: | Height: | Size: 153 B |
|
After Width: | Height: | Size: 141 B |
|
After Width: | Height: | Size: 182 B |
|
After Width: | Height: | Size: 208 B |
|
After Width: | Height: | Size: 323 B |
|
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) &&
|
||||||
<>
|
<>
|
||||||
|
|||||||
@@ -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 &&
|
||||||
|
|||||||
@@ -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>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -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, '&')
|
||||||
|
.replace(/</g, '<')
|
||||||
|
.replace(/>/g, '>')
|
||||||
|
.replace(/"/g, '"')
|
||||||
|
.replace(/'/g, ''');
|
||||||
|
|
||||||
|
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, '&')
|
||||||
|
.replace(/</g, '<')
|
||||||
|
.replace(/>/g, '>')
|
||||||
|
.replace(/"/g, '"')
|
||||||
|
.replace(/'/g, ''');
|
||||||
|
|
||||||
|
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>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -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");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 ─────────────────────────────────────────────────── */
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -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';
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
export * from './useWiredTools';
|
||||||
@@ -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);
|
||||||
@@ -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==
|
||||||
|
|||||||