mirror of
https://github.com/duckietm/Nitro-V3.git
synced 2026-06-19 15:06:20 +00:00
Split WiredCreatorToolsView: extract types/constants/helpers into 3 sibling files
The single-file WiredCreatorToolsView.tsx was 4493 lines, which is one of the main reasons the React Compiler reports "Compilation Skipped: Existing memoization could not be preserved" on this module. Split is conservative — only the pure leading sections move out, the component itself is untouched (state, effects, JSX all stay in place). New files (sibling to the view): - WiredCreatorTools.types.ts (~233 lines): every interface and type alias declared at the top of the original file. - WiredCreatorTools.constants.ts (~225 lines): TABS, MONITOR_LOG_ORDER, poll constants, MONITOR_ERROR_INFO, INSPECTION_ELEMENTS, VARIABLES_ELEMENTS, EDITABLE_*, VARIABLE_DEFINITIONS, WIRED_FREEZE_EFFECT_IDS, TEAM_COLOR_NAMES, WEEKDAY/MONTH/DIRECTION names. The createVariableDefinition factory is kept as a local helper in this file (only used to build VARIABLE_DEFINITIONS). - WiredCreatorTools.helpers.ts (~147 lines): createEmptyMonitorSnapshot, getHotelTimeFormatter (with its module-private cache map), getHotelDateTimeParts, formatMonitorLatestOccurrence, formatMonitorHistoryOccurrence, formatVariableTimestamp, formatMonitorSource, normalizeMonitorReason. All pure (or cache-stable), no closure on component state. WiredCreatorToolsView.tsx changes: - 4493 -> 3901 lines (-592, ~13% reduction). - The four inspection-icon asset imports (furni/global/user/context) move to the constants file alongside the only consumers (INSPECTION_ELEMENTS / VARIABLES_ELEMENTS). - AvatarInfoFurni was only referenced by the extracted InspectionFurniSelection interface and is removed from the main file's api import. - New import block at the top pulls back the symbols actually used by the component body. Verification: - yarn eslint on the three new files: 0 errors / 0 warnings. - yarn eslint on WiredCreatorToolsView.tsx: 26 errors before split, 26 errors after split (identical pre-existing set; nothing new introduced). - yarn tsc --noEmit on the four files: clean (only the project-wide pre-existing TS2307 about @nitrots/nitro-renderer not being installed locally remains, same as before). This unblocks future per-tab splits (Monitor / Inspection / Variables JSX panels are still inline in the view and represent the next ~1600 lines that could move out, but require introducing a shared state context first since the current setState chain is intertwined). https://claude.ai/code/session_01GrR87LAqnAEyKG2ZbmQt5Q
This commit is contained in:
@@ -0,0 +1,225 @@
|
||||
import contextInspectionIcon from '../../assets/images/wiredtools/context.png';
|
||||
import furniInspectionIcon from '../../assets/images/wiredtools/furni.png';
|
||||
import globalInspectionIcon from '../../assets/images/wiredtools/global.png';
|
||||
import userInspectionIcon from '../../assets/images/wiredtools/user.png';
|
||||
import { InspectionElementButton, VariableDefinition, VariablesElementButton, VariablesElementType, WiredToolsTab } from './WiredCreatorTools.types';
|
||||
|
||||
export const TABS: Array<{ key: WiredToolsTab; label: string; }> = [
|
||||
{ key: 'monitor', label: 'Monitor' },
|
||||
{ key: 'variables', label: 'Variables' },
|
||||
{ key: 'inspection', label: 'Inspection' },
|
||||
{ key: 'chests', label: 'Chests' },
|
||||
{ key: 'settings', label: 'Settings' }
|
||||
];
|
||||
|
||||
export const MONITOR_LOG_ORDER: string[] = [
|
||||
'EXECUTION_CAP',
|
||||
'DELAYED_EVENTS_CAP',
|
||||
'EXECUTOR_OVERLOAD',
|
||||
'MARKED_AS_HEAVY',
|
||||
'KILLED',
|
||||
'RECURSION_TIMEOUT'
|
||||
];
|
||||
|
||||
export const WIRED_MONITOR_ACTION_FETCH = 0;
|
||||
export const WIRED_MONITOR_ACTION_CLEAR_LOGS = 1;
|
||||
export const WIRED_MONITOR_POLL_MS = 50;
|
||||
export const WIRED_VARIABLES_POLL_MS = 50;
|
||||
export const WIRED_INSPECTION_REFRESH_MS = 50;
|
||||
export const WIRED_CLOCK_REFRESH_MS = 50;
|
||||
|
||||
export const MONITOR_ERROR_INFO: Record<string, { description: string[]; severity: string; title: string; }> = {
|
||||
EXECUTION_CAP: {
|
||||
title: 'EXECUTION_CAP',
|
||||
severity: 'ERROR',
|
||||
description: [
|
||||
'This error occurs when the maximum Wired usage limit is about to be exceeded by a Wired execution.',
|
||||
'When this happens, the current execution is cancelled so the room never goes over the configured usage budget.',
|
||||
'If this happens too often, it usually means the setup is too complex for the amount of triggers firing in a short time.'
|
||||
]
|
||||
},
|
||||
DELAYED_EVENTS_CAP: {
|
||||
title: 'DELAYED_EVENTS_CAP',
|
||||
severity: 'ERROR',
|
||||
description: [
|
||||
'Delayed Wired events happen when effects are scheduled to run later.',
|
||||
'There is a limit to how many delayed events can be pending at the same time. Once the limit is reached, new delayed executions are refused.',
|
||||
'If this appears often, the setup is likely relying too heavily on delayed effects and should be simplified.'
|
||||
]
|
||||
},
|
||||
EXECUTOR_OVERLOAD: {
|
||||
title: 'EXECUTOR_OVERLOAD',
|
||||
severity: 'ERROR',
|
||||
description: [
|
||||
'This error occurs when the Wired engine is receiving a lot of instructions and the room cannot keep up with the execution time.',
|
||||
'This can be a sign of server pressure or of a setup that is too expensive to evaluate repeatedly.',
|
||||
'If the room is also marked as heavy, it is a good sign that the setup should be reduced or optimized.'
|
||||
]
|
||||
},
|
||||
MARKED_AS_HEAVY: {
|
||||
title: 'MARKED_AS_HEAVY',
|
||||
severity: 'WARNING',
|
||||
description: [
|
||||
'The room is being considered heavy because its Wired usage stays high across multiple monitor windows.',
|
||||
'This is not a fatal error by itself, but it means the room is consuming a significant portion of the execution budget.',
|
||||
'If the room is not intentionally complex, it is worth reviewing the setup before it starts triggering harder limits.'
|
||||
]
|
||||
},
|
||||
KILLED: {
|
||||
title: 'KILLED',
|
||||
severity: 'ERROR',
|
||||
description: [
|
||||
'This happens when the room is temporarily halted by the protection layer because the Wired flow looks abusive or unstable.',
|
||||
'While the room is killed, Wired execution is paused for a cooldown period.',
|
||||
'This is usually caused by loops, event spam, or repeated limit violations.'
|
||||
]
|
||||
},
|
||||
RECURSION_TIMEOUT: {
|
||||
title: 'RECURSION_TIMEOUT',
|
||||
severity: 'ERROR',
|
||||
description: [
|
||||
'Recursive Wired events happen when signals keep re-triggering other stacks in the same room.',
|
||||
'When the recursion depth limit is reached, execution is stopped to prevent runaway loops.',
|
||||
'In most cases this means two or more stacks are indirectly calling each other too many times.'
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
export const INSPECTION_ELEMENTS: InspectionElementButton[] = [
|
||||
{ key: 'furni', label: 'Furni', icon: furniInspectionIcon },
|
||||
{ key: 'user', label: 'User', icon: userInspectionIcon },
|
||||
{ key: 'global', label: 'Global', icon: globalInspectionIcon }
|
||||
];
|
||||
|
||||
export const VARIABLES_ELEMENTS: VariablesElementButton[] = [
|
||||
{ key: 'furni', label: 'Furni', icon: furniInspectionIcon },
|
||||
{ key: 'user', label: 'User', icon: userInspectionIcon },
|
||||
{ key: 'global', label: 'Global', icon: globalInspectionIcon },
|
||||
{ key: 'context', label: 'Context', icon: contextInspectionIcon }
|
||||
];
|
||||
|
||||
export const EDITABLE_FURNI_VARIABLES: string[] = [ '@position_x', '@position_y', '@rotation', '@altitude', '@state', '@wallitem_offset' ];
|
||||
export const EDITABLE_USER_VARIABLES: string[] = [ '@position_x', '@position_y', '@direction' ];
|
||||
|
||||
const createVariableDefinition = (key: string, target: 'Furni' | 'User' | 'Global' | 'Context', availability: string = 'Always', canWriteTo = false): VariableDefinition =>
|
||||
({
|
||||
key,
|
||||
target,
|
||||
type: 'Internal',
|
||||
hasValue: true,
|
||||
availability,
|
||||
canWriteTo,
|
||||
canCreateDelete: false,
|
||||
canIntercept: false,
|
||||
hasCreationTime: false,
|
||||
hasUpdateTime: false,
|
||||
isTextConnected: false,
|
||||
isAlwaysAvailable: (availability === 'Always')
|
||||
});
|
||||
|
||||
export const VARIABLE_DEFINITIONS: Record<VariablesElementType, VariableDefinition[]> = {
|
||||
furni: [
|
||||
createVariableDefinition('~teleport.target_id', 'Furni', 'Conditional'),
|
||||
createVariableDefinition('@id', 'Furni'),
|
||||
createVariableDefinition('@class_id', 'Furni'),
|
||||
createVariableDefinition('@height', 'Furni'),
|
||||
createVariableDefinition('@state', 'Furni', 'Always', true),
|
||||
createVariableDefinition('@position_x', 'Furni', 'Always', true),
|
||||
createVariableDefinition('@position_y', 'Furni', 'Always', true),
|
||||
createVariableDefinition('@rotation', 'Furni', 'Always', true),
|
||||
createVariableDefinition('@altitude', 'Furni', 'Always', true),
|
||||
createVariableDefinition('@is_invisible', 'Furni', 'Conditional'),
|
||||
createVariableDefinition('@wallitem_offset', 'Furni', 'Conditional', true),
|
||||
createVariableDefinition('@type', 'Furni'),
|
||||
createVariableDefinition('@can_sit_on', 'Furni', 'Conditional'),
|
||||
createVariableDefinition('@can_lay_on', 'Furni', 'Conditional'),
|
||||
createVariableDefinition('@can_stand_on', 'Furni', 'Conditional'),
|
||||
createVariableDefinition('@is_stackable', 'Furni', 'Conditional'),
|
||||
createVariableDefinition('@dimensions.x', 'Furni'),
|
||||
createVariableDefinition('@dimensions.y', 'Furni'),
|
||||
createVariableDefinition('@owner_id', 'Furni')
|
||||
],
|
||||
user: [
|
||||
createVariableDefinition('@index', 'User'),
|
||||
createVariableDefinition('@type', 'User'),
|
||||
createVariableDefinition('@gender', 'User'),
|
||||
createVariableDefinition('@level', 'User'),
|
||||
createVariableDefinition('@achievement_score', 'User'),
|
||||
createVariableDefinition('@is_hc', 'User', 'Conditional'),
|
||||
createVariableDefinition('@has_rights', 'User', 'Conditional'),
|
||||
createVariableDefinition('@is_owner', 'User', 'Conditional'),
|
||||
createVariableDefinition('@is_group_admin', 'User', 'Conditional'),
|
||||
createVariableDefinition('@is_muted', 'User', 'Conditional'),
|
||||
createVariableDefinition('@is_trading', 'User', 'Conditional'),
|
||||
createVariableDefinition('@is_frozen', 'User', 'Conditional'),
|
||||
createVariableDefinition('@effect_id', 'User', 'Conditional'),
|
||||
createVariableDefinition('@team_score', 'User', 'Conditional'),
|
||||
createVariableDefinition('@team_color', 'User', 'Conditional'),
|
||||
createVariableDefinition('@team_type', 'User', 'Conditional'),
|
||||
createVariableDefinition('@sign', 'User', 'Conditional'),
|
||||
createVariableDefinition('@dance', 'User', 'Conditional'),
|
||||
createVariableDefinition('@is_idle', 'User', 'Conditional'),
|
||||
createVariableDefinition('@handitem_id', 'User', 'Conditional'),
|
||||
createVariableDefinition('@position_x', 'User', 'Always', true),
|
||||
createVariableDefinition('@position_y', 'User', 'Always', true),
|
||||
createVariableDefinition('@direction', 'User', 'Always', true),
|
||||
createVariableDefinition('@altitude', 'User'),
|
||||
createVariableDefinition('@favourite_group_id', 'User', 'Conditional'),
|
||||
createVariableDefinition('@room_entry.method', 'User', 'Conditional'),
|
||||
createVariableDefinition('@room_entry.teleport_id', 'User', 'Conditional'),
|
||||
createVariableDefinition('@user_id', 'User', 'Conditional'),
|
||||
createVariableDefinition('@bot_id', 'User', 'Conditional'),
|
||||
createVariableDefinition('@pet_id', 'User', 'Conditional'),
|
||||
createVariableDefinition('@pet_owner_id', 'User', 'Conditional')
|
||||
],
|
||||
global: [
|
||||
createVariableDefinition('@furni_count', 'Global'),
|
||||
createVariableDefinition('@user_count', 'Global'),
|
||||
createVariableDefinition('@wired_timer', 'Global'),
|
||||
createVariableDefinition('@team_red_score', 'Global'),
|
||||
createVariableDefinition('@team_green_score', 'Global'),
|
||||
createVariableDefinition('@team_blue_score', 'Global'),
|
||||
createVariableDefinition('@team_yellow_score', 'Global'),
|
||||
createVariableDefinition('@team_red_size', 'Global'),
|
||||
createVariableDefinition('@team_green_size', 'Global'),
|
||||
createVariableDefinition('@team_blue_size', 'Global'),
|
||||
createVariableDefinition('@team_yellow_size', 'Global'),
|
||||
createVariableDefinition('@room_id', 'Global'),
|
||||
createVariableDefinition('@group_id', 'Global'),
|
||||
createVariableDefinition('@timezone_server', 'Global'),
|
||||
createVariableDefinition('@timezone_client', 'Global'),
|
||||
createVariableDefinition('@current_time', 'Global'),
|
||||
createVariableDefinition('@current_time.millisecond_of_second', 'Global'),
|
||||
createVariableDefinition('@current_time.seconds_of_minute', 'Global'),
|
||||
createVariableDefinition('@current_time.minute_of_hour', 'Global'),
|
||||
createVariableDefinition('@current_time.hour_of_day', 'Global'),
|
||||
createVariableDefinition('@current_time.day_of_week', 'Global'),
|
||||
createVariableDefinition('@current_time.day_of_month', 'Global'),
|
||||
createVariableDefinition('@current_time.day_of_year', 'Global'),
|
||||
createVariableDefinition('@current_time.week_of_year', 'Global'),
|
||||
createVariableDefinition('@current_time.month_of_year', 'Global'),
|
||||
createVariableDefinition('@current_time.year', 'Global')
|
||||
],
|
||||
context: [
|
||||
createVariableDefinition('@selector_furni_count', 'Context', 'Conditional'),
|
||||
createVariableDefinition('@selector_user_count', 'Context', 'Conditional'),
|
||||
createVariableDefinition('@signal_furni_count', 'Context', 'Conditional'),
|
||||
createVariableDefinition('@signal_user_count', 'Context', 'Conditional'),
|
||||
createVariableDefinition('@antenna_id', 'Context', 'Conditional'),
|
||||
createVariableDefinition('@chat_type', 'Context', 'Conditional'),
|
||||
createVariableDefinition('@chat_style', 'Context', 'Conditional')
|
||||
]
|
||||
};
|
||||
|
||||
export const WIRED_FREEZE_EFFECT_IDS: Set<number> = new Set([ 218, 12, 11, 53, 163 ]);
|
||||
|
||||
export const TEAM_COLOR_NAMES: Record<number, string> = {
|
||||
1: 'red',
|
||||
2: 'green',
|
||||
3: 'blue',
|
||||
4: 'yellow'
|
||||
};
|
||||
|
||||
export const WEEKDAY_NAMES: string[] = [ 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday' ];
|
||||
export const MONTH_NAMES: string[] = [ 'January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December' ];
|
||||
export const DIRECTION_NAMES: string[] = [ 'North', 'North-East', 'East', 'South-East', 'South', 'South-West', 'West', 'North-West' ];
|
||||
@@ -0,0 +1,147 @@
|
||||
import { HotelDateTimeParts, MonitorSnapshot } from './WiredCreatorTools.types';
|
||||
|
||||
const HOTEL_TIME_FORMATTERS: Map<string, Intl.DateTimeFormat> = new Map();
|
||||
|
||||
export const createEmptyMonitorSnapshot = (): MonitorSnapshot =>
|
||||
({
|
||||
usageCurrentWindow: 0,
|
||||
usageLimitPerWindow: 0,
|
||||
isHeavy: false,
|
||||
delayedEventsPending: 0,
|
||||
delayedEventsLimit: 0,
|
||||
averageExecutionMs: 0,
|
||||
peakExecutionMs: 0,
|
||||
recursionDepthCurrent: 0,
|
||||
recursionDepthLimit: 0,
|
||||
killedRemainingSeconds: 0,
|
||||
usageWindowMs: 0,
|
||||
overloadAverageThresholdMs: 0,
|
||||
overloadPeakThresholdMs: 0,
|
||||
heavyUsageThresholdPercent: 0,
|
||||
heavyConsecutiveWindowsThreshold: 0,
|
||||
overloadConsecutiveWindowsThreshold: 0,
|
||||
heavyDelayedThresholdPercent: 0,
|
||||
logs: [],
|
||||
history: []
|
||||
});
|
||||
|
||||
export const getHotelTimeFormatter = (timeZone: string): Intl.DateTimeFormat =>
|
||||
{
|
||||
const formatterTimeZone = (timeZone || 'UTC');
|
||||
const existingFormatter = HOTEL_TIME_FORMATTERS.get(formatterTimeZone);
|
||||
|
||||
if(existingFormatter) return existingFormatter;
|
||||
|
||||
let formatter: Intl.DateTimeFormat = null;
|
||||
|
||||
try
|
||||
{
|
||||
formatter = new Intl.DateTimeFormat('en-GB', {
|
||||
timeZone: formatterTimeZone,
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
second: '2-digit',
|
||||
hourCycle: 'h23'
|
||||
});
|
||||
}
|
||||
catch
|
||||
{
|
||||
formatter = new Intl.DateTimeFormat('en-GB', {
|
||||
timeZone: 'UTC',
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
second: '2-digit',
|
||||
hourCycle: 'h23'
|
||||
});
|
||||
}
|
||||
|
||||
HOTEL_TIME_FORMATTERS.set(formatterTimeZone, formatter);
|
||||
|
||||
return formatter;
|
||||
};
|
||||
|
||||
export const getHotelDateTimeParts = (epochMs: number, timeZone: string): HotelDateTimeParts =>
|
||||
{
|
||||
const normalizedEpochMs = Number.isFinite(epochMs) ? epochMs : Date.now();
|
||||
const date = new Date(normalizedEpochMs);
|
||||
const formatter = getHotelTimeFormatter(timeZone);
|
||||
const formattedParts = formatter.formatToParts(date);
|
||||
const partsMap = new Map<string, string>();
|
||||
|
||||
for(const part of formattedParts)
|
||||
{
|
||||
if(part.type === 'literal') continue;
|
||||
|
||||
partsMap.set(part.type, part.value);
|
||||
}
|
||||
|
||||
return {
|
||||
year: Number(partsMap.get('year') ?? date.getUTCFullYear()),
|
||||
month: Number(partsMap.get('month') ?? (date.getUTCMonth() + 1)),
|
||||
day: Number(partsMap.get('day') ?? date.getUTCDate()),
|
||||
hour: Number(partsMap.get('hour') ?? date.getUTCHours()),
|
||||
minute: Number(partsMap.get('minute') ?? date.getUTCMinutes()),
|
||||
second: Number(partsMap.get('second') ?? date.getUTCSeconds()),
|
||||
millisecond: (((normalizedEpochMs % 1000) + 1000) % 1000)
|
||||
};
|
||||
};
|
||||
|
||||
export const formatMonitorLatestOccurrence = (latestOccurrenceSeconds: number, nowMs: number): string =>
|
||||
{
|
||||
if(latestOccurrenceSeconds <= 0) return '/';
|
||||
|
||||
const diffMs = Math.max(0, (nowMs - (latestOccurrenceSeconds * 1000)));
|
||||
const diffSeconds = Math.floor(diffMs / 1000);
|
||||
|
||||
if(diffSeconds < 5) return 'Just now';
|
||||
if(diffSeconds < 60) return `${ diffSeconds }s ago`;
|
||||
|
||||
const diffMinutes = Math.floor(diffSeconds / 60);
|
||||
|
||||
if(diffMinutes < 60) return `${ diffMinutes }m ago`;
|
||||
|
||||
const diffHours = Math.floor(diffMinutes / 60);
|
||||
|
||||
if(diffHours < 24) return `${ diffHours }h ago`;
|
||||
|
||||
const diffDays = Math.floor(diffHours / 24);
|
||||
|
||||
return `${ diffDays }d ago`;
|
||||
};
|
||||
|
||||
export const formatMonitorHistoryOccurrence = (occurredAtSeconds: number): string =>
|
||||
{
|
||||
if(occurredAtSeconds <= 0) return '/';
|
||||
|
||||
return new Date(occurredAtSeconds * 1000).toLocaleString('en-GB');
|
||||
};
|
||||
|
||||
export const formatVariableTimestamp = (timestamp: number): string =>
|
||||
{
|
||||
if(!timestamp || (timestamp <= 0)) return '/';
|
||||
|
||||
return new Date(timestamp * 1000).toLocaleString('en-GB');
|
||||
};
|
||||
|
||||
export const formatMonitorSource = (sourceLabel: string, sourceId: number): string =>
|
||||
{
|
||||
const normalizedLabel = (sourceLabel || '').trim();
|
||||
|
||||
if(!normalizedLabel && !(sourceId > 0)) return 'Room monitor';
|
||||
if(sourceId > 0) return `${ normalizedLabel || 'wired' } (#${ sourceId })`;
|
||||
|
||||
return normalizedLabel;
|
||||
};
|
||||
|
||||
export const normalizeMonitorReason = (reason: string): string =>
|
||||
{
|
||||
const normalizedReason = (reason || '').trim();
|
||||
|
||||
return normalizedReason || 'No detailed reason was recorded for this entry.';
|
||||
};
|
||||
@@ -0,0 +1,233 @@
|
||||
import { AvatarInfoFurni } from '../../api';
|
||||
|
||||
export type WiredToolsTab = 'monitor' | 'variables' | 'inspection' | 'chests' | 'settings';
|
||||
export type InspectionElementType = 'furni' | 'user' | 'global';
|
||||
export type VariablesElementType = InspectionElementType | 'context';
|
||||
|
||||
export interface InspectionElementButton
|
||||
{
|
||||
key: InspectionElementType;
|
||||
label: string;
|
||||
icon: string;
|
||||
}
|
||||
|
||||
export interface VariablesElementButton
|
||||
{
|
||||
key: VariablesElementType;
|
||||
label: string;
|
||||
icon: string;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
export interface InspectionFurniSelection
|
||||
{
|
||||
objectId: number;
|
||||
category: number;
|
||||
info: AvatarInfoFurni;
|
||||
}
|
||||
|
||||
export interface InspectionFurniLiveState
|
||||
{
|
||||
positionX: number;
|
||||
positionY: number;
|
||||
altitude: number;
|
||||
rotation: number;
|
||||
state: number;
|
||||
}
|
||||
|
||||
export interface InspectionUserSelection
|
||||
{
|
||||
kind: 'user' | 'bot' | 'rentable_bot' | 'pet';
|
||||
roomIndex: number;
|
||||
name: string;
|
||||
figure: string;
|
||||
gender: string;
|
||||
userId: number;
|
||||
level: number;
|
||||
achievementScore: number;
|
||||
isHC: boolean;
|
||||
hasRights: boolean;
|
||||
isOwner: boolean;
|
||||
favouriteGroupId: number;
|
||||
roomEntryMethod: string;
|
||||
roomEntryTeleportId: number;
|
||||
posture?: string;
|
||||
}
|
||||
|
||||
export interface InspectionUserLiveState
|
||||
{
|
||||
positionX: number;
|
||||
positionY: number;
|
||||
altitude: number;
|
||||
direction: number;
|
||||
}
|
||||
|
||||
export interface MonitorStat
|
||||
{
|
||||
label: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
export interface MonitorLog
|
||||
{
|
||||
type: string;
|
||||
category: string;
|
||||
amount: string;
|
||||
latest: string;
|
||||
latestReason: string;
|
||||
latestSourceId: number;
|
||||
latestSourceLabel: string;
|
||||
}
|
||||
|
||||
export interface MonitorSnapshot
|
||||
{
|
||||
usageCurrentWindow: number;
|
||||
usageLimitPerWindow: number;
|
||||
isHeavy: boolean;
|
||||
delayedEventsPending: number;
|
||||
delayedEventsLimit: number;
|
||||
averageExecutionMs: number;
|
||||
peakExecutionMs: number;
|
||||
recursionDepthCurrent: number;
|
||||
recursionDepthLimit: number;
|
||||
killedRemainingSeconds: number;
|
||||
usageWindowMs: number;
|
||||
overloadAverageThresholdMs: number;
|
||||
overloadPeakThresholdMs: number;
|
||||
heavyUsageThresholdPercent: number;
|
||||
heavyConsecutiveWindowsThreshold: number;
|
||||
overloadConsecutiveWindowsThreshold: number;
|
||||
heavyDelayedThresholdPercent: number;
|
||||
logs: Array<{
|
||||
amount: number;
|
||||
latestOccurrenceSeconds: number;
|
||||
latestReason: string;
|
||||
latestSourceId: number;
|
||||
latestSourceLabel: string;
|
||||
severity: string;
|
||||
type: string;
|
||||
}>;
|
||||
history: Array<{
|
||||
occurredAtSeconds: number;
|
||||
reason: string;
|
||||
sourceId: number;
|
||||
sourceLabel: string;
|
||||
severity: string;
|
||||
type: string;
|
||||
}>;
|
||||
}
|
||||
|
||||
export interface MonitorLogDetails
|
||||
{
|
||||
amount?: string;
|
||||
latest?: string;
|
||||
occurredAt?: string;
|
||||
reason: string;
|
||||
severity: string;
|
||||
sourceId: number;
|
||||
sourceLabel: string;
|
||||
type: string;
|
||||
}
|
||||
|
||||
export interface InspectionVariable
|
||||
{
|
||||
key: string;
|
||||
value: string;
|
||||
editable?: boolean;
|
||||
valueClassName?: string;
|
||||
}
|
||||
|
||||
export interface VariableDefinition
|
||||
{
|
||||
key: string;
|
||||
itemId?: number;
|
||||
target: 'Furni' | 'User' | 'Global' | 'Context';
|
||||
type: string;
|
||||
hasValue: boolean;
|
||||
isReadOnly?: boolean;
|
||||
availability: string;
|
||||
canWriteTo: boolean;
|
||||
canCreateDelete: boolean;
|
||||
canIntercept: boolean;
|
||||
hasCreationTime: boolean;
|
||||
hasUpdateTime: boolean;
|
||||
isTextConnected: boolean;
|
||||
isAlwaysAvailable?: boolean;
|
||||
}
|
||||
|
||||
export interface VariableTextValue
|
||||
{
|
||||
value: string;
|
||||
text: string;
|
||||
}
|
||||
|
||||
export interface VariableManageEntry
|
||||
{
|
||||
categoryLabel: string;
|
||||
createdAt: number;
|
||||
entityId: number;
|
||||
entityName: string;
|
||||
manageLabel: string;
|
||||
updatedAt: number;
|
||||
value: number | null;
|
||||
}
|
||||
|
||||
export interface VariableHighlightTarget
|
||||
{
|
||||
category: number;
|
||||
hasValue: boolean;
|
||||
objectId: number;
|
||||
value: number | null;
|
||||
}
|
||||
|
||||
export interface VariableHighlightOverlay extends VariableHighlightTarget
|
||||
{
|
||||
key: string;
|
||||
x: number;
|
||||
y: number;
|
||||
}
|
||||
|
||||
export interface ManagedHolderVariableEntry
|
||||
{
|
||||
availability: string;
|
||||
createdAt: number;
|
||||
hasValue: boolean;
|
||||
name: string;
|
||||
isReadOnly?: boolean;
|
||||
updatedAt: number;
|
||||
value: number | null;
|
||||
variableItemId: number;
|
||||
}
|
||||
|
||||
export interface InspectionUserTeamData
|
||||
{
|
||||
colorId: number;
|
||||
typeId: number;
|
||||
score: number;
|
||||
}
|
||||
|
||||
export interface TeamEffectData
|
||||
{
|
||||
colorId: number;
|
||||
typeId: number;
|
||||
}
|
||||
|
||||
export interface ParsedWallLocation
|
||||
{
|
||||
width: number;
|
||||
height: number;
|
||||
localX: number;
|
||||
localY: number;
|
||||
direction: string;
|
||||
}
|
||||
|
||||
export interface HotelDateTimeParts
|
||||
{
|
||||
year: number;
|
||||
month: number;
|
||||
day: number;
|
||||
hour: number;
|
||||
minute: number;
|
||||
second: number;
|
||||
millisecond: number;
|
||||
}
|
||||
@@ -1,609 +1,16 @@
|
||||
import { AddLinkEventTracker, AvatarExpressionEnum, FigureUpdateEvent, FurnitureFloorUpdateEvent, FurnitureMultiStateComposer, FurnitureWallMultiStateComposer, FurnitureWallUpdateComposer, FurnitureWallUpdateEvent, GetLocalizationManager, GetRoomEngine, GetSessionDataManager, GetStage, GetTicker, ILinkEventTracker, RemoveLinkEventTracker, RoomControllerLevel, RoomObjectCategory, RoomObjectType, RoomObjectVariable, RoomUnitDanceEvent, RoomUnitEffectEvent, RoomUnitExpressionEvent, RoomUnitHandItemEvent, RoomUnitInfoEvent, RoomUnitStatusEvent, UpdateFurniturePositionComposer, Vector3d, WiredUserInspectMoveComposer } from '@nitrots/nitro-renderer';
|
||||
import { WiredMonitorDataEvent, WiredMonitorRequestComposer } from '@nitrots/nitro-renderer';
|
||||
import { FC, KeyboardEvent, useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import furniInspectionIcon from '../../assets/images/wiredtools/furni.png';
|
||||
import globalInspectionIcon from '../../assets/images/wiredtools/global.png';
|
||||
import userInspectionIcon from '../../assets/images/wiredtools/user.png';
|
||||
import contextInspectionIcon from '../../assets/images/wiredtools/context.png';
|
||||
import wiredGlobalPlaceholderImage from '../../assets/images/wiredtools/wired_global_placeholder.png';
|
||||
import wiredMonitorImage from '../../assets/images/wiredtools/wired_monitor.png';
|
||||
import { AvatarInfoFurni, AvatarInfoUtilities, GetRoomObjectBounds, GetRoomObjectScreenLocation, LocalizeText, NotificationAlertType, SendMessageComposer, WiredSelectionVisualizer } from '../../api';
|
||||
import { AvatarInfoUtilities, GetRoomObjectBounds, GetRoomObjectScreenLocation, LocalizeText, NotificationAlertType, SendMessageComposer, WiredSelectionVisualizer } from '../../api';
|
||||
import { Button, DraggableWindowPosition, LayoutAvatarImageView, LayoutPetImageView, LayoutRoomObjectImageView, NitroCardContentView, NitroCardHeaderView, NitroCardTabsItemView, NitroCardTabsView, NitroCardView, Text } from '../../common';
|
||||
import { useInventoryTrade, useMessageEvent, useNotification, useObjectSelectedEvent, useRoom, useWiredTools } from '../../hooks';
|
||||
import { DIRECTION_NAMES, EDITABLE_FURNI_VARIABLES, EDITABLE_USER_VARIABLES, INSPECTION_ELEMENTS, MONITOR_ERROR_INFO, MONITOR_LOG_ORDER, MONTH_NAMES, TABS, TEAM_COLOR_NAMES, VARIABLES_ELEMENTS, VARIABLE_DEFINITIONS, WEEKDAY_NAMES, WIRED_CLOCK_REFRESH_MS, WIRED_FREEZE_EFFECT_IDS, WIRED_INSPECTION_REFRESH_MS, WIRED_MONITOR_ACTION_CLEAR_LOGS, WIRED_MONITOR_ACTION_FETCH, WIRED_MONITOR_POLL_MS, WIRED_VARIABLES_POLL_MS } from './WiredCreatorTools.constants';
|
||||
import { createEmptyMonitorSnapshot, formatMonitorHistoryOccurrence, formatMonitorLatestOccurrence, formatMonitorSource, formatVariableTimestamp, getHotelDateTimeParts, getHotelTimeFormatter, normalizeMonitorReason } from './WiredCreatorTools.helpers';
|
||||
import { HotelDateTimeParts, InspectionElementButton, InspectionElementType, InspectionFurniLiveState, InspectionFurniSelection, InspectionUserLiveState, InspectionUserSelection, InspectionUserTeamData, InspectionVariable, ManagedHolderVariableEntry, MonitorLog, MonitorLogDetails, MonitorSnapshot, MonitorStat, ParsedWallLocation, TeamEffectData, VariableDefinition, VariableHighlightOverlay, VariableHighlightTarget, VariableManageEntry, VariableTextValue, VariablesElementButton, VariablesElementType, WiredToolsTab } from './WiredCreatorTools.types';
|
||||
import { WiredToolsSettingsTabView } from './WiredToolsSettingsTabView';
|
||||
|
||||
type WiredToolsTab = 'monitor' | 'variables' | 'inspection' | 'chests' | 'settings';
|
||||
type InspectionElementType = 'furni' | 'user' | 'global';
|
||||
type VariablesElementType = InspectionElementType | 'context';
|
||||
|
||||
interface InspectionElementButton
|
||||
{
|
||||
key: InspectionElementType;
|
||||
label: string;
|
||||
icon: string;
|
||||
}
|
||||
|
||||
interface VariablesElementButton
|
||||
{
|
||||
key: VariablesElementType;
|
||||
label: string;
|
||||
icon: string;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
interface InspectionFurniSelection
|
||||
{
|
||||
objectId: number;
|
||||
category: number;
|
||||
info: AvatarInfoFurni;
|
||||
}
|
||||
|
||||
interface InspectionFurniLiveState
|
||||
{
|
||||
positionX: number;
|
||||
positionY: number;
|
||||
altitude: number;
|
||||
rotation: number;
|
||||
state: number;
|
||||
}
|
||||
|
||||
interface InspectionUserSelection
|
||||
{
|
||||
kind: 'user' | 'bot' | 'rentable_bot' | 'pet';
|
||||
roomIndex: number;
|
||||
name: string;
|
||||
figure: string;
|
||||
gender: string;
|
||||
userId: number;
|
||||
level: number;
|
||||
achievementScore: number;
|
||||
isHC: boolean;
|
||||
hasRights: boolean;
|
||||
isOwner: boolean;
|
||||
favouriteGroupId: number;
|
||||
roomEntryMethod: string;
|
||||
roomEntryTeleportId: number;
|
||||
posture?: string;
|
||||
}
|
||||
|
||||
interface InspectionUserLiveState
|
||||
{
|
||||
positionX: number;
|
||||
positionY: number;
|
||||
altitude: number;
|
||||
direction: number;
|
||||
}
|
||||
|
||||
interface MonitorStat
|
||||
{
|
||||
label: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
interface MonitorLog
|
||||
{
|
||||
type: string;
|
||||
category: string;
|
||||
amount: string;
|
||||
latest: string;
|
||||
latestReason: string;
|
||||
latestSourceId: number;
|
||||
latestSourceLabel: string;
|
||||
}
|
||||
|
||||
interface MonitorSnapshot
|
||||
{
|
||||
usageCurrentWindow: number;
|
||||
usageLimitPerWindow: number;
|
||||
isHeavy: boolean;
|
||||
delayedEventsPending: number;
|
||||
delayedEventsLimit: number;
|
||||
averageExecutionMs: number;
|
||||
peakExecutionMs: number;
|
||||
recursionDepthCurrent: number;
|
||||
recursionDepthLimit: number;
|
||||
killedRemainingSeconds: number;
|
||||
usageWindowMs: number;
|
||||
overloadAverageThresholdMs: number;
|
||||
overloadPeakThresholdMs: number;
|
||||
heavyUsageThresholdPercent: number;
|
||||
heavyConsecutiveWindowsThreshold: number;
|
||||
overloadConsecutiveWindowsThreshold: number;
|
||||
heavyDelayedThresholdPercent: number;
|
||||
logs: Array<{
|
||||
amount: number;
|
||||
latestOccurrenceSeconds: number;
|
||||
latestReason: string;
|
||||
latestSourceId: number;
|
||||
latestSourceLabel: string;
|
||||
severity: string;
|
||||
type: string;
|
||||
}>;
|
||||
history: Array<{
|
||||
occurredAtSeconds: number;
|
||||
reason: string;
|
||||
sourceId: number;
|
||||
sourceLabel: string;
|
||||
severity: string;
|
||||
type: string;
|
||||
}>;
|
||||
}
|
||||
|
||||
interface MonitorLogDetails
|
||||
{
|
||||
amount?: string;
|
||||
latest?: string;
|
||||
occurredAt?: string;
|
||||
reason: string;
|
||||
severity: string;
|
||||
sourceId: number;
|
||||
sourceLabel: string;
|
||||
type: string;
|
||||
}
|
||||
|
||||
interface InspectionVariable
|
||||
{
|
||||
key: string;
|
||||
value: string;
|
||||
editable?: boolean;
|
||||
valueClassName?: string;
|
||||
}
|
||||
|
||||
interface VariableDefinition
|
||||
{
|
||||
key: string;
|
||||
itemId?: number;
|
||||
target: 'Furni' | 'User' | 'Global' | 'Context';
|
||||
type: string;
|
||||
hasValue: boolean;
|
||||
isReadOnly?: boolean;
|
||||
availability: string;
|
||||
canWriteTo: boolean;
|
||||
canCreateDelete: boolean;
|
||||
canIntercept: boolean;
|
||||
hasCreationTime: boolean;
|
||||
hasUpdateTime: boolean;
|
||||
isTextConnected: boolean;
|
||||
isAlwaysAvailable?: boolean;
|
||||
}
|
||||
|
||||
interface VariableTextValue
|
||||
{
|
||||
value: string;
|
||||
text: string;
|
||||
}
|
||||
|
||||
interface VariableManageEntry
|
||||
{
|
||||
categoryLabel: string;
|
||||
createdAt: number;
|
||||
entityId: number;
|
||||
entityName: string;
|
||||
manageLabel: string;
|
||||
updatedAt: number;
|
||||
value: number | null;
|
||||
}
|
||||
|
||||
interface VariableHighlightTarget
|
||||
{
|
||||
category: number;
|
||||
hasValue: boolean;
|
||||
objectId: number;
|
||||
value: number | null;
|
||||
}
|
||||
|
||||
interface VariableHighlightOverlay extends VariableHighlightTarget
|
||||
{
|
||||
key: string;
|
||||
x: number;
|
||||
y: number;
|
||||
}
|
||||
|
||||
interface ManagedHolderVariableEntry
|
||||
{
|
||||
availability: string;
|
||||
createdAt: number;
|
||||
hasValue: boolean;
|
||||
name: string;
|
||||
isReadOnly?: boolean;
|
||||
updatedAt: number;
|
||||
value: number | null;
|
||||
variableItemId: number;
|
||||
}
|
||||
|
||||
interface InspectionUserTeamData
|
||||
{
|
||||
colorId: number;
|
||||
typeId: number;
|
||||
score: number;
|
||||
}
|
||||
|
||||
interface TeamEffectData
|
||||
{
|
||||
colorId: number;
|
||||
typeId: number;
|
||||
}
|
||||
|
||||
interface ParsedWallLocation
|
||||
{
|
||||
width: number;
|
||||
height: number;
|
||||
localX: number;
|
||||
localY: number;
|
||||
direction: string;
|
||||
}
|
||||
|
||||
interface HotelDateTimeParts
|
||||
{
|
||||
year: number;
|
||||
month: number;
|
||||
day: number;
|
||||
hour: number;
|
||||
minute: number;
|
||||
second: number;
|
||||
millisecond: number;
|
||||
}
|
||||
|
||||
const TABS: Array<{ key: WiredToolsTab; label: string; }> = [
|
||||
{ key: 'monitor', label: 'Monitor' },
|
||||
{ key: 'variables', label: 'Variables' },
|
||||
{ key: 'inspection', label: 'Inspection' },
|
||||
{ key: 'chests', label: 'Chests' },
|
||||
{ key: 'settings', label: 'Settings' }
|
||||
];
|
||||
|
||||
const MONITOR_LOG_ORDER: string[] = [
|
||||
'EXECUTION_CAP',
|
||||
'DELAYED_EVENTS_CAP',
|
||||
'EXECUTOR_OVERLOAD',
|
||||
'MARKED_AS_HEAVY',
|
||||
'KILLED',
|
||||
'RECURSION_TIMEOUT'
|
||||
];
|
||||
|
||||
const WIRED_MONITOR_ACTION_FETCH = 0;
|
||||
const WIRED_MONITOR_ACTION_CLEAR_LOGS = 1;
|
||||
const WIRED_MONITOR_POLL_MS = 50;
|
||||
const WIRED_VARIABLES_POLL_MS = 50;
|
||||
const WIRED_INSPECTION_REFRESH_MS = 50;
|
||||
const WIRED_CLOCK_REFRESH_MS = 50;
|
||||
|
||||
const MONITOR_ERROR_INFO: Record<string, { description: string[]; severity: string; title: string; }> = {
|
||||
EXECUTION_CAP: {
|
||||
title: 'EXECUTION_CAP',
|
||||
severity: 'ERROR',
|
||||
description: [
|
||||
'This error occurs when the maximum Wired usage limit is about to be exceeded by a Wired execution.',
|
||||
'When this happens, the current execution is cancelled so the room never goes over the configured usage budget.',
|
||||
'If this happens too often, it usually means the setup is too complex for the amount of triggers firing in a short time.'
|
||||
]
|
||||
},
|
||||
DELAYED_EVENTS_CAP: {
|
||||
title: 'DELAYED_EVENTS_CAP',
|
||||
severity: 'ERROR',
|
||||
description: [
|
||||
'Delayed Wired events happen when effects are scheduled to run later.',
|
||||
'There is a limit to how many delayed events can be pending at the same time. Once the limit is reached, new delayed executions are refused.',
|
||||
'If this appears often, the setup is likely relying too heavily on delayed effects and should be simplified.'
|
||||
]
|
||||
},
|
||||
EXECUTOR_OVERLOAD: {
|
||||
title: 'EXECUTOR_OVERLOAD',
|
||||
severity: 'ERROR',
|
||||
description: [
|
||||
'This error occurs when the Wired engine is receiving a lot of instructions and the room cannot keep up with the execution time.',
|
||||
'This can be a sign of server pressure or of a setup that is too expensive to evaluate repeatedly.',
|
||||
'If the room is also marked as heavy, it is a good sign that the setup should be reduced or optimized.'
|
||||
]
|
||||
},
|
||||
MARKED_AS_HEAVY: {
|
||||
title: 'MARKED_AS_HEAVY',
|
||||
severity: 'WARNING',
|
||||
description: [
|
||||
'The room is being considered heavy because its Wired usage stays high across multiple monitor windows.',
|
||||
'This is not a fatal error by itself, but it means the room is consuming a significant portion of the execution budget.',
|
||||
'If the room is not intentionally complex, it is worth reviewing the setup before it starts triggering harder limits.'
|
||||
]
|
||||
},
|
||||
KILLED: {
|
||||
title: 'KILLED',
|
||||
severity: 'ERROR',
|
||||
description: [
|
||||
'This happens when the room is temporarily halted by the protection layer because the Wired flow looks abusive or unstable.',
|
||||
'While the room is killed, Wired execution is paused for a cooldown period.',
|
||||
'This is usually caused by loops, event spam, or repeated limit violations.'
|
||||
]
|
||||
},
|
||||
RECURSION_TIMEOUT: {
|
||||
title: 'RECURSION_TIMEOUT',
|
||||
severity: 'ERROR',
|
||||
description: [
|
||||
'Recursive Wired events happen when signals keep re-triggering other stacks in the same room.',
|
||||
'When the recursion depth limit is reached, execution is stopped to prevent runaway loops.',
|
||||
'In most cases this means two or more stacks are indirectly calling each other too many times.'
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
const INSPECTION_ELEMENTS: InspectionElementButton[] = [
|
||||
{ key: 'furni', label: 'Furni', icon: furniInspectionIcon },
|
||||
{ key: 'user', label: 'User', icon: userInspectionIcon },
|
||||
{ key: 'global', label: 'Global', icon: globalInspectionIcon }
|
||||
];
|
||||
|
||||
const VARIABLES_ELEMENTS: VariablesElementButton[] = [
|
||||
{ key: 'furni', label: 'Furni', icon: furniInspectionIcon },
|
||||
{ key: 'user', label: 'User', icon: userInspectionIcon },
|
||||
{ key: 'global', label: 'Global', icon: globalInspectionIcon },
|
||||
{ key: 'context', label: 'Context', icon: contextInspectionIcon }
|
||||
];
|
||||
|
||||
const EDITABLE_FURNI_VARIABLES: string[] = [ '@position_x', '@position_y', '@rotation', '@altitude', '@state', '@wallitem_offset' ];
|
||||
const EDITABLE_USER_VARIABLES: string[] = [ '@position_x', '@position_y', '@direction' ];
|
||||
const createVariableDefinition = (key: string, target: 'Furni' | 'User' | 'Global' | 'Context', availability: string = 'Always', canWriteTo = false): VariableDefinition =>
|
||||
({
|
||||
key,
|
||||
target,
|
||||
type: 'Internal',
|
||||
hasValue: true,
|
||||
availability,
|
||||
canWriteTo,
|
||||
canCreateDelete: false,
|
||||
canIntercept: false,
|
||||
hasCreationTime: false,
|
||||
hasUpdateTime: false,
|
||||
isTextConnected: false,
|
||||
isAlwaysAvailable: (availability === 'Always')
|
||||
});
|
||||
const VARIABLE_DEFINITIONS: Record<VariablesElementType, VariableDefinition[]> = {
|
||||
furni: [
|
||||
createVariableDefinition('~teleport.target_id', 'Furni', 'Conditional'),
|
||||
createVariableDefinition('@id', 'Furni'),
|
||||
createVariableDefinition('@class_id', 'Furni'),
|
||||
createVariableDefinition('@height', 'Furni'),
|
||||
createVariableDefinition('@state', 'Furni', 'Always', true),
|
||||
createVariableDefinition('@position_x', 'Furni', 'Always', true),
|
||||
createVariableDefinition('@position_y', 'Furni', 'Always', true),
|
||||
createVariableDefinition('@rotation', 'Furni', 'Always', true),
|
||||
createVariableDefinition('@altitude', 'Furni', 'Always', true),
|
||||
createVariableDefinition('@is_invisible', 'Furni', 'Conditional'),
|
||||
createVariableDefinition('@wallitem_offset', 'Furni', 'Conditional', true),
|
||||
createVariableDefinition('@type', 'Furni'),
|
||||
createVariableDefinition('@can_sit_on', 'Furni', 'Conditional'),
|
||||
createVariableDefinition('@can_lay_on', 'Furni', 'Conditional'),
|
||||
createVariableDefinition('@can_stand_on', 'Furni', 'Conditional'),
|
||||
createVariableDefinition('@is_stackable', 'Furni', 'Conditional'),
|
||||
createVariableDefinition('@dimensions.x', 'Furni'),
|
||||
createVariableDefinition('@dimensions.y', 'Furni'),
|
||||
createVariableDefinition('@owner_id', 'Furni')
|
||||
],
|
||||
user: [
|
||||
createVariableDefinition('@index', 'User'),
|
||||
createVariableDefinition('@type', 'User'),
|
||||
createVariableDefinition('@gender', 'User'),
|
||||
createVariableDefinition('@level', 'User'),
|
||||
createVariableDefinition('@achievement_score', 'User'),
|
||||
createVariableDefinition('@is_hc', 'User', 'Conditional'),
|
||||
createVariableDefinition('@has_rights', 'User', 'Conditional'),
|
||||
createVariableDefinition('@is_owner', 'User', 'Conditional'),
|
||||
createVariableDefinition('@is_group_admin', 'User', 'Conditional'),
|
||||
createVariableDefinition('@is_muted', 'User', 'Conditional'),
|
||||
createVariableDefinition('@is_trading', 'User', 'Conditional'),
|
||||
createVariableDefinition('@is_frozen', 'User', 'Conditional'),
|
||||
createVariableDefinition('@effect_id', 'User', 'Conditional'),
|
||||
createVariableDefinition('@team_score', 'User', 'Conditional'),
|
||||
createVariableDefinition('@team_color', 'User', 'Conditional'),
|
||||
createVariableDefinition('@team_type', 'User', 'Conditional'),
|
||||
createVariableDefinition('@sign', 'User', 'Conditional'),
|
||||
createVariableDefinition('@dance', 'User', 'Conditional'),
|
||||
createVariableDefinition('@is_idle', 'User', 'Conditional'),
|
||||
createVariableDefinition('@handitem_id', 'User', 'Conditional'),
|
||||
createVariableDefinition('@position_x', 'User', 'Always', true),
|
||||
createVariableDefinition('@position_y', 'User', 'Always', true),
|
||||
createVariableDefinition('@direction', 'User', 'Always', true),
|
||||
createVariableDefinition('@altitude', 'User'),
|
||||
createVariableDefinition('@favourite_group_id', 'User', 'Conditional'),
|
||||
createVariableDefinition('@room_entry.method', 'User', 'Conditional'),
|
||||
createVariableDefinition('@room_entry.teleport_id', 'User', 'Conditional'),
|
||||
createVariableDefinition('@user_id', 'User', 'Conditional'),
|
||||
createVariableDefinition('@bot_id', 'User', 'Conditional'),
|
||||
createVariableDefinition('@pet_id', 'User', 'Conditional'),
|
||||
createVariableDefinition('@pet_owner_id', 'User', 'Conditional')
|
||||
],
|
||||
global: [
|
||||
createVariableDefinition('@furni_count', 'Global'),
|
||||
createVariableDefinition('@user_count', 'Global'),
|
||||
createVariableDefinition('@wired_timer', 'Global'),
|
||||
createVariableDefinition('@team_red_score', 'Global'),
|
||||
createVariableDefinition('@team_green_score', 'Global'),
|
||||
createVariableDefinition('@team_blue_score', 'Global'),
|
||||
createVariableDefinition('@team_yellow_score', 'Global'),
|
||||
createVariableDefinition('@team_red_size', 'Global'),
|
||||
createVariableDefinition('@team_green_size', 'Global'),
|
||||
createVariableDefinition('@team_blue_size', 'Global'),
|
||||
createVariableDefinition('@team_yellow_size', 'Global'),
|
||||
createVariableDefinition('@room_id', 'Global'),
|
||||
createVariableDefinition('@group_id', 'Global'),
|
||||
createVariableDefinition('@timezone_server', 'Global'),
|
||||
createVariableDefinition('@timezone_client', 'Global'),
|
||||
createVariableDefinition('@current_time', 'Global'),
|
||||
createVariableDefinition('@current_time.millisecond_of_second', 'Global'),
|
||||
createVariableDefinition('@current_time.seconds_of_minute', 'Global'),
|
||||
createVariableDefinition('@current_time.minute_of_hour', 'Global'),
|
||||
createVariableDefinition('@current_time.hour_of_day', 'Global'),
|
||||
createVariableDefinition('@current_time.day_of_week', 'Global'),
|
||||
createVariableDefinition('@current_time.day_of_month', 'Global'),
|
||||
createVariableDefinition('@current_time.day_of_year', 'Global'),
|
||||
createVariableDefinition('@current_time.week_of_year', 'Global'),
|
||||
createVariableDefinition('@current_time.month_of_year', 'Global'),
|
||||
createVariableDefinition('@current_time.year', 'Global')
|
||||
],
|
||||
context: [
|
||||
createVariableDefinition('@selector_furni_count', 'Context', 'Conditional'),
|
||||
createVariableDefinition('@selector_user_count', 'Context', 'Conditional'),
|
||||
createVariableDefinition('@signal_furni_count', 'Context', 'Conditional'),
|
||||
createVariableDefinition('@signal_user_count', 'Context', 'Conditional'),
|
||||
createVariableDefinition('@antenna_id', 'Context', 'Conditional'),
|
||||
createVariableDefinition('@chat_type', 'Context', 'Conditional'),
|
||||
createVariableDefinition('@chat_style', 'Context', 'Conditional')
|
||||
]
|
||||
};
|
||||
const WIRED_FREEZE_EFFECT_IDS: Set<number> = new Set([ 218, 12, 11, 53, 163 ]);
|
||||
const TEAM_COLOR_NAMES: Record<number, string> = {
|
||||
1: 'red',
|
||||
2: 'green',
|
||||
3: 'blue',
|
||||
4: 'yellow'
|
||||
};
|
||||
const WEEKDAY_NAMES: string[] = [ 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday' ];
|
||||
const MONTH_NAMES: string[] = [ 'January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December' ];
|
||||
const DIRECTION_NAMES: string[] = [ 'North', 'North-East', 'East', 'South-East', 'South', 'South-West', 'West', 'North-West' ];
|
||||
const HOTEL_TIME_FORMATTERS: Map<string, Intl.DateTimeFormat> = new Map();
|
||||
|
||||
const createEmptyMonitorSnapshot = (): MonitorSnapshot =>
|
||||
({
|
||||
usageCurrentWindow: 0,
|
||||
usageLimitPerWindow: 0,
|
||||
isHeavy: false,
|
||||
delayedEventsPending: 0,
|
||||
delayedEventsLimit: 0,
|
||||
averageExecutionMs: 0,
|
||||
peakExecutionMs: 0,
|
||||
recursionDepthCurrent: 0,
|
||||
recursionDepthLimit: 0,
|
||||
killedRemainingSeconds: 0,
|
||||
usageWindowMs: 0,
|
||||
overloadAverageThresholdMs: 0,
|
||||
overloadPeakThresholdMs: 0,
|
||||
heavyUsageThresholdPercent: 0,
|
||||
heavyConsecutiveWindowsThreshold: 0,
|
||||
overloadConsecutiveWindowsThreshold: 0,
|
||||
heavyDelayedThresholdPercent: 0,
|
||||
logs: [],
|
||||
history: []
|
||||
});
|
||||
|
||||
const getHotelTimeFormatter = (timeZone: string): Intl.DateTimeFormat =>
|
||||
{
|
||||
const formatterTimeZone = (timeZone || 'UTC');
|
||||
const existingFormatter = HOTEL_TIME_FORMATTERS.get(formatterTimeZone);
|
||||
|
||||
if(existingFormatter) return existingFormatter;
|
||||
|
||||
let formatter: Intl.DateTimeFormat = null;
|
||||
|
||||
try
|
||||
{
|
||||
formatter = new Intl.DateTimeFormat('en-GB', {
|
||||
timeZone: formatterTimeZone,
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
second: '2-digit',
|
||||
hourCycle: 'h23'
|
||||
});
|
||||
}
|
||||
catch
|
||||
{
|
||||
formatter = new Intl.DateTimeFormat('en-GB', {
|
||||
timeZone: 'UTC',
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
second: '2-digit',
|
||||
hourCycle: 'h23'
|
||||
});
|
||||
}
|
||||
|
||||
HOTEL_TIME_FORMATTERS.set(formatterTimeZone, formatter);
|
||||
|
||||
return formatter;
|
||||
};
|
||||
|
||||
const getHotelDateTimeParts = (epochMs: number, timeZone: string): HotelDateTimeParts =>
|
||||
{
|
||||
const normalizedEpochMs = Number.isFinite(epochMs) ? epochMs : Date.now();
|
||||
const date = new Date(normalizedEpochMs);
|
||||
const formatter = getHotelTimeFormatter(timeZone);
|
||||
const formattedParts = formatter.formatToParts(date);
|
||||
const partsMap = new Map<string, string>();
|
||||
|
||||
for(const part of formattedParts)
|
||||
{
|
||||
if(part.type === 'literal') continue;
|
||||
|
||||
partsMap.set(part.type, part.value);
|
||||
}
|
||||
|
||||
return {
|
||||
year: Number(partsMap.get('year') ?? date.getUTCFullYear()),
|
||||
month: Number(partsMap.get('month') ?? (date.getUTCMonth() + 1)),
|
||||
day: Number(partsMap.get('day') ?? date.getUTCDate()),
|
||||
hour: Number(partsMap.get('hour') ?? date.getUTCHours()),
|
||||
minute: Number(partsMap.get('minute') ?? date.getUTCMinutes()),
|
||||
second: Number(partsMap.get('second') ?? date.getUTCSeconds()),
|
||||
millisecond: (((normalizedEpochMs % 1000) + 1000) % 1000)
|
||||
};
|
||||
};
|
||||
|
||||
const formatMonitorLatestOccurrence = (latestOccurrenceSeconds: number, nowMs: number): string =>
|
||||
{
|
||||
if(latestOccurrenceSeconds <= 0) return '/';
|
||||
|
||||
const diffMs = Math.max(0, (nowMs - (latestOccurrenceSeconds * 1000)));
|
||||
const diffSeconds = Math.floor(diffMs / 1000);
|
||||
|
||||
if(diffSeconds < 5) return 'Just now';
|
||||
if(diffSeconds < 60) return `${ diffSeconds }s ago`;
|
||||
|
||||
const diffMinutes = Math.floor(diffSeconds / 60);
|
||||
|
||||
if(diffMinutes < 60) return `${ diffMinutes }m ago`;
|
||||
|
||||
const diffHours = Math.floor(diffMinutes / 60);
|
||||
|
||||
if(diffHours < 24) return `${ diffHours }h ago`;
|
||||
|
||||
const diffDays = Math.floor(diffHours / 24);
|
||||
|
||||
return `${ diffDays }d ago`;
|
||||
};
|
||||
|
||||
const formatMonitorHistoryOccurrence = (occurredAtSeconds: number): string =>
|
||||
{
|
||||
if(occurredAtSeconds <= 0) return '/';
|
||||
|
||||
return new Date(occurredAtSeconds * 1000).toLocaleString('en-GB');
|
||||
};
|
||||
|
||||
const formatVariableTimestamp = (timestamp: number): string =>
|
||||
{
|
||||
if(!timestamp || (timestamp <= 0)) return '/';
|
||||
|
||||
return new Date(timestamp * 1000).toLocaleString('en-GB');
|
||||
};
|
||||
|
||||
const formatMonitorSource = (sourceLabel: string, sourceId: number): string =>
|
||||
{
|
||||
const normalizedLabel = (sourceLabel || '').trim();
|
||||
|
||||
if(!normalizedLabel && !(sourceId > 0)) return 'Room monitor';
|
||||
if(sourceId > 0) return `${ normalizedLabel || 'wired' } (#${ sourceId })`;
|
||||
|
||||
return normalizedLabel;
|
||||
};
|
||||
|
||||
const normalizeMonitorReason = (reason: string): string =>
|
||||
{
|
||||
const normalizedReason = (reason || '').trim();
|
||||
|
||||
return normalizedReason || 'No detailed reason was recorded for this entry.';
|
||||
};
|
||||
|
||||
export const WiredCreatorToolsView: FC<{}> = () =>
|
||||
{
|
||||
const [ isVisible, setIsVisible ] = useState(false);
|
||||
|
||||
Reference in New Issue
Block a user