mirror of
https://github.com/duckietm/Nitro-V3.git
synced 2026-06-20 15:36:18 +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 { 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 { WiredMonitorDataEvent, WiredMonitorRequestComposer } from '@nitrots/nitro-renderer';
|
||||||
import { FC, KeyboardEvent, useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
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 wiredGlobalPlaceholderImage from '../../assets/images/wiredtools/wired_global_placeholder.png';
|
||||||
import wiredMonitorImage from '../../assets/images/wiredtools/wired_monitor.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 { Button, DraggableWindowPosition, LayoutAvatarImageView, LayoutPetImageView, LayoutRoomObjectImageView, NitroCardContentView, NitroCardHeaderView, NitroCardTabsItemView, NitroCardTabsView, NitroCardView, Text } from '../../common';
|
||||||
import { useInventoryTrade, useMessageEvent, useNotification, useObjectSelectedEvent, useRoom, useWiredTools } from '../../hooks';
|
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';
|
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<{}> = () =>
|
export const WiredCreatorToolsView: FC<{}> = () =>
|
||||||
{
|
{
|
||||||
const [ isVisible, setIsVisible ] = useState(false);
|
const [ isVisible, setIsVisible ] = useState(false);
|
||||||
|
|||||||
Reference in New Issue
Block a user