Initial commit

This commit is contained in:
2026-07-03 16:31:37 +07:00
commit 899c7c637d
1939 changed files with 641750 additions and 0 deletions

View File

@@ -0,0 +1,111 @@
// File Explorer JavaScript functions
let escapeKeyHandler = null;
export function registerEscapeKeyHandler(dotNetHelper) {
// Remove existing handler if any
if (escapeKeyHandler) {
document.removeEventListener('keydown', escapeKeyHandler);
}
// Create new handler
escapeKeyHandler = (e) => {
if (e.key === 'Escape' || e.key === 'Esc') {
dotNetHelper.invokeMethodAsync('HandleEscapeKey');
}
};
// Register handler
document.addEventListener('keydown', escapeKeyHandler);
}
export function unregisterEscapeKeyHandler() {
if (escapeKeyHandler) {
document.removeEventListener('keydown', escapeKeyHandler);
escapeKeyHandler = null;
}
}
export function init(treeContainerRef) {
// Initialize file explorer if needed
}
export function updateFolderBadges(folderPath, hasWarnings, hasErrors, isModified, warningCount, errorCount, folderName) {
// Update folder badges via DOM manipulation
const folderElement = document.querySelector(`[data-folder-path="${folderPath}"]`);
if (folderElement) {
const warningBadge = folderElement.querySelector('[data-badge="warning"]');
const errorBadge = folderElement.querySelector('[data-badge="error"]');
const modifiedBadge = folderElement.querySelector('[data-badge="modified"]');
if (warningBadge) {
warningBadge.style.display = hasWarnings ? 'inline' : 'none';
warningBadge.textContent = warningCount;
warningBadge.setAttribute('data-count', warningCount);
}
if (errorBadge) {
errorBadge.style.display = hasErrors ? 'inline' : 'none';
errorBadge.textContent = errorCount;
errorBadge.setAttribute('data-count', errorCount);
}
if (modifiedBadge) {
modifiedBadge.style.display = isModified ? 'inline' : 'none';
}
}
}
export function updateFileBadges(filePath, hasWarnings, hasErrors, isModified, warningCount, errorCount, fileName) {
// Update file badges via DOM manipulation
const fileElement = document.querySelector(`[data-file-path="${filePath}"]`);
if (fileElement) {
const warningBadge = fileElement.querySelector('[data-badge="warning"]');
const errorBadge = fileElement.querySelector('[data-badge="error"]');
const modifiedBadge = fileElement.querySelector('[data-badge="modified"]');
if (warningBadge) {
warningBadge.style.display = hasWarnings ? 'inline' : 'none';
warningBadge.textContent = warningCount;
warningBadge.setAttribute('data-count', warningCount);
}
if (errorBadge) {
errorBadge.style.display = hasErrors ? 'inline' : 'none';
errorBadge.textContent = errorCount;
errorBadge.setAttribute('data-count', errorCount);
}
if (modifiedBadge) {
modifiedBadge.style.display = isModified ? 'inline' : 'none';
}
}
}
export function UncheckRadioByName(radioName) {
const radios = document.querySelectorAll(`input[type="radio"][name="${radioName}"]`);
radios.forEach(radio => {
radio.checked = false;
});
}
export function CheckRadioById(itemId) {
const radio = document.getElementById(itemId);
if (radio) {
radio.checked = true;
}
}
export function setElementPosition(elementId, x, y) {
const element = document.getElementById(elementId);
if (element) {
element.style.position = 'fixed';
element.style.left = x + 'px';
element.style.top = y + 'px';
element.style.width = '1px';
element.style.height = '1px';
element.style.pointerEvents = 'none';
element.style.zIndex = '-1';
}
}

View File

@@ -0,0 +1,292 @@
// Layout handlers cho ScriptEditor - xử lý tất cả logic layout bằng JavaScript
let sidebarResizer = null;
let consoleResizer = null;
let isResizingSidebar = false;
let isResizingConsole = false;
let accordionHeaders = [];
let activeTab = null;
export function initializeLayout() {
initializeResizeHandlers();
initializeAccordion();
}
function initializeAccordion() {
// Reset state
activeTab = null;
// Tìm tất cả accordion headers
accordionHeaders = document.querySelectorAll('.sidebar-accordion-header');
accordionHeaders.forEach(header => {
header.addEventListener('click', handleAccordionClick);
});
// Mở tab đầu tiên mặc định (workspace)
const firstTab = document.querySelector('[data-tab="workspace"]');
if (firstTab) {
toggleAccordionTab(firstTab, true);
activeTab = 'workspace'; // Quan trọng: set activeTab để đồng bộ với UI
}
}
function handleAccordionClick(e) {
const header = e.currentTarget;
const accordionItem = header.closest('.sidebar-accordion-item');
if (!accordionItem) return;
// Kiểm tra nếu click vào accordion-header-actions hoặc các phần tử con của nó thì không toggle
const actionsContainer = header.querySelector('.accordion-header-actions');
if (actionsContainer && (actionsContainer.contains(e.target) || actionsContainer === e.target)) {
return; // Không xử lý toggle nếu click vào actions container
}
const tabName = accordionItem.getAttribute('data-tab');
const isCurrentlyActive = activeTab === tabName;
// Nếu click vào tab đang active thì đóng nó, ngược lại mở tab mới
if (isCurrentlyActive) {
toggleAccordionTab(accordionItem, false);
activeTab = null;
} else {
// Đóng tab cũ nếu có
if (activeTab) {
const oldTab = document.querySelector(`[data-tab="${activeTab}"]`);
if (oldTab) {
toggleAccordionTab(oldTab, false);
}
}
// Mở tab mới
toggleAccordionTab(accordionItem, true);
activeTab = tabName;
}
}
function toggleAccordionTab(accordionItem, expand) {
const header = accordionItem.querySelector('.sidebar-accordion-header');
const content = accordionItem.querySelector('.sidebar-accordion-content');
const arrow = accordionItem.querySelector('.accordion-arrow');
if (!header || !content || !arrow) return;
if (expand) {
header.classList.add('active');
content.classList.remove('collapsed');
content.classList.add('expanded');
arrow.textContent = '▼';
} else {
header.classList.remove('active');
content.classList.remove('expanded');
content.classList.add('collapsed');
arrow.textContent = '▶';
}
}
function initializeResizeHandlers() {
sidebarResizer = document.querySelector('.sidebar-resizer');
consoleResizer = document.querySelector('.vertical-resizer');
// Khởi tạo chiều cao mặc định cho editor và console
const container = document.querySelector('.editor-console-container');
const editorArea = document.querySelector('#editor-area');
const consolePanel = document.querySelector('#console-panel');
if (container && editorArea && consolePanel) {
const containerHeight = container.clientHeight;
const consoleHeight = 200; // Chiều cao khởi tạo ban đầu cho console
const editorHeight = containerHeight - consoleHeight - 4; // 4px cho resizer
editorArea.style.height = editorHeight + 'px';
consolePanel.style.height = consoleHeight + 'px';
}
if (sidebarResizer) {
sidebarResizer.addEventListener('mousedown', (e) => {
isResizingSidebar = true;
document.addEventListener('mousemove', handleSidebarResize);
document.addEventListener('mouseup', stopSidebarResize);
e.preventDefault();
});
}
if (consoleResizer) {
consoleResizer.addEventListener('mousedown', (e) => {
isResizingConsole = true;
// Thêm class resizing ngay khi bắt đầu để disable transition
const consolePanel = document.querySelector('#console-panel');
const editorArea = document.querySelector('#editor-area');
if (consolePanel) consolePanel.classList.add('resizing');
if (editorArea) editorArea.classList.add('resizing');
document.addEventListener('mousemove', handleConsoleResize);
document.addEventListener('mouseup', stopConsoleResize);
e.preventDefault();
});
}
// Touch support
if (sidebarResizer) {
sidebarResizer.addEventListener('touchstart', (e) => {
isResizingSidebar = true;
document.addEventListener('touchmove', handleSidebarResizeTouch);
document.addEventListener('touchend', stopSidebarResize);
e.preventDefault();
});
}
if (consoleResizer) {
consoleResizer.addEventListener('touchstart', (e) => {
isResizingConsole = true;
// Thêm class resizing ngay khi bắt đầu để disable transition
const consolePanel = document.querySelector('#console-panel');
const editorArea = document.querySelector('#editor-area');
if (consolePanel) consolePanel.classList.add('resizing');
if (editorArea) editorArea.classList.add('resizing');
document.addEventListener('touchmove', handleConsoleResizeTouch);
document.addEventListener('touchend', stopConsoleResize);
e.preventDefault();
});
}
}
function handleSidebarResize(e) {
if (!isResizingSidebar) return;
const container = document.querySelector('.script-editor-container');
const sidebar = document.querySelector('.sidebar-container');
if (!container || !sidebar) return;
const containerRect = container.getBoundingClientRect();
const newWidth = e.clientX - containerRect.left;
const minWidth = 200;
const maxWidth = Math.min(600, containerRect.width * 0.5);
if (newWidth >= minWidth && newWidth <= maxWidth) {
sidebar.style.width = newWidth + 'px';
}
}
function handleSidebarResizeTouch(e) {
if (!isResizingSidebar || !e.touches || e.touches.length === 0) return;
const container = document.querySelector('.script-editor-container');
const sidebar = document.querySelector('.sidebar-container');
if (!container || !sidebar) return;
const containerRect = container.getBoundingClientRect();
const newWidth = e.touches[0].clientX - containerRect.left;
const minWidth = 200;
const maxWidth = Math.min(600, containerRect.width * 0.5);
if (newWidth >= minWidth && newWidth <= maxWidth) {
sidebar.style.width = newWidth + 'px';
}
}
function stopSidebarResize() {
isResizingSidebar = false;
document.removeEventListener('mousemove', handleSidebarResize);
document.removeEventListener('mouseup', stopSidebarResize);
document.removeEventListener('touchmove', handleSidebarResizeTouch);
document.removeEventListener('touchend', stopSidebarResize);
}
function handleConsoleResize(e) {
if (!isResizingConsole) return;
const container = document.querySelector('.editor-console-container');
const editorArea = document.querySelector('#editor-area');
const consolePanel = document.querySelector('#console-panel');
if (!container || !editorArea || !consolePanel) return;
const containerRect = container.getBoundingClientRect();
const containerHeight = containerRect.height;
const newConsoleHeight = containerRect.bottom - e.clientY;
const minConsoleHeight = 100;
const maxConsoleHeight = containerHeight * 0.8;
if (newConsoleHeight >= minConsoleHeight && newConsoleHeight <= maxConsoleHeight) {
const newEditorHeight = containerHeight - newConsoleHeight - 4; // 4px cho resizer
editorArea.style.height = newEditorHeight + 'px';
consolePanel.style.height = newConsoleHeight + 'px';
}
}
function handleConsoleResizeTouch(e) {
if (!isResizingConsole || !e.touches || e.touches.length === 0) return;
const container = document.querySelector('.editor-console-container');
const editorArea = document.querySelector('#editor-area');
const consolePanel = document.querySelector('#console-panel');
if (!container || !editorArea || !consolePanel) return;
const containerRect = container.getBoundingClientRect();
const containerHeight = containerRect.height;
const newConsoleHeight = containerRect.bottom - e.touches[0].clientY;
const minConsoleHeight = 100;
const maxConsoleHeight = containerHeight * 0.8;
if (newConsoleHeight >= minConsoleHeight && newConsoleHeight <= maxConsoleHeight) {
const newEditorHeight = containerHeight - newConsoleHeight - 4; // 4px cho resizer
editorArea.style.height = newEditorHeight + 'px';
consolePanel.style.height = newConsoleHeight + 'px';
}
}
function stopConsoleResize() {
isResizingConsole = false;
// Remove resizing class để restore transition
const consolePanel = document.querySelector('#console-panel');
const editorArea = document.querySelector('#editor-area');
if (consolePanel) {
consolePanel.classList.remove('resizing');
}
if (editorArea) {
editorArea.classList.remove('resizing');
}
document.removeEventListener('mousemove', handleConsoleResize);
document.removeEventListener('mouseup', stopConsoleResize);
document.removeEventListener('touchmove', handleConsoleResizeTouch);
document.removeEventListener('touchend', stopConsoleResize);
}
export function cleanupLayout() {
// Cleanup accordion handlers
if (accordionHeaders && accordionHeaders.length > 0) {
accordionHeaders.forEach(header => {
header.removeEventListener('click', handleAccordionClick);
});
}
accordionHeaders = [];
// Reset tất cả tabs về trạng thái collapsed
const allTabs = document.querySelectorAll('.sidebar-accordion-item');
allTabs.forEach(tab => {
const header = tab.querySelector('.sidebar-accordion-header');
const content = tab.querySelector('.sidebar-accordion-content');
const arrow = tab.querySelector('.accordion-arrow');
if (header && content && arrow) {
header.classList.remove('active');
content.classList.remove('expanded');
content.classList.add('collapsed');
arrow.textContent = '▶';
}
});
activeTab = null;
// Cleanup resize handlers
stopSidebarResize();
stopConsoleResize();
}

View File

@@ -0,0 +1,247 @@
window.robotnet = window.robotnet || {};
robotnet.console = {
init: function (dotNetRef) {
// Initialization if needed
},
addMessage: function (level, message, autoScroll) {
const container = document.getElementById('console-content');
if (!container) return;
const messageDiv = document.createElement('div');
messageDiv.className = `console-message console-message-${level.toLowerCase()}`;
const timestamp = new Date().toLocaleTimeString();
const levelSpan = document.createElement('span');
levelSpan.className = 'console-level';
levelSpan.textContent = level;
const timeSpan = document.createElement('span');
timeSpan.className = 'console-time';
timeSpan.textContent = timestamp;
const messageSpan = document.createElement('span');
messageSpan.className = 'console-text';
messageSpan.textContent = message;
messageDiv.appendChild(levelSpan);
messageDiv.appendChild(timeSpan);
messageDiv.appendChild(messageSpan);
container.appendChild(messageDiv);
if (autoScroll) {
container.scrollTop = container.scrollHeight;
}
},
clear: function () {
const container = document.getElementById('console-content');
if (!container) return;
// Xóa tất cả messages bằng cách set innerHTML rỗng
container.innerHTML = '';
},
toggleCollapse: function () {
const container = document.getElementById('console-content');
const consolePanel = document.getElementById('console-panel');
const consoleHeader = document.querySelector('.console-header');
const verticalResizer = document.querySelector('.vertical-resizer');
const toggleButton = document.querySelector('.console-container .btn-toggle');
const iconSpan = toggleButton?.querySelector('span');
if (!container || !consolePanel || !toggleButton || !iconSpan) return;
// Check trạng thái hiện tại trước khi toggle
const wasCollapsed = container.classList.contains('collapsed');
// Toggle collapsed class trên cả console-content và console-panel
container.classList.toggle('collapsed');
consolePanel.classList.toggle('collapsed');
// Tính toán và set height cho console-panel sau khi toggle
if (!wasCollapsed) {
// Đang collapse - set height về chỉ còn header
if (consoleHeader) {
const headerHeight = consoleHeader.offsetHeight;
consolePanel.style.height = headerHeight + 'px';
} else {
consolePanel.style.height = '40px'; // Fallback height
}
// Ẩn vertical resizer khi collapsed
if (verticalResizer) {
verticalResizer.style.display = 'none';
}
// Update icon
iconSpan.className = 'mdi mdi-chevron-up';
toggleButton.title = 'Expand Console';
} else {
// Đang expand - restore về height mặc định
consolePanel.style.height = '200px';
// Hiện vertical resizer khi expanded
if (verticalResizer) {
verticalResizer.style.display = '';
}
// Update icon
iconSpan.className = 'mdi mdi-chevron-down';
toggleButton.title = 'Collapse Console';
}
}
};
robotnet.monaco = {
CSharpLanguageRegisterSignatureHelpProvider: (dotnetRef, getSignatureHelpMethod) => {
return monaco.languages.registerSignatureHelpProvider("csharp", {
signatureHelpTriggerCharacters: ['('],
provideSignatureHelp: async (model, position, token, context) => {
let signature = await dotnetRef.invokeMethodAsync(getSignatureHelpMethod, model.uri.toString(), position.lineNumber, position.column);
if (!signature) {
return undefined;
}
return {
value: signature.value,
dispose: () => { },
}
}
});
},
}
robotnet.blazor = {
getResourcePath: function (logicalName) {
// In .NET 10, try to get resource path from Blazor runtime
// Strategy 1: Try to access via window.Blazor or Blazor._internal
if (window.Blazor && window.Blazor._internal) {
try {
// Try to get resource mapping from Blazor internal APIs
var resources = window.Blazor._internal.resources;
if (resources && resources.assembly) {
var assemblyName = logicalName.replace('.wasm', '');
if (resources.assembly[assemblyName]) {
return '/_framework/' + resources.assembly[assemblyName];
}
}
} catch (e) {
// Fallback to direct path
}
}
// Strategy 2: Try direct path (NET 10+)
// In .NET 10, resources might be directly accessible
return '/_framework/' + logicalName;
}
}
window.fileExplorer = {
init: function (element) {
// Initialize file explorer interactions
// Currently no initialization needed as we use MudMenu for context menus
// This function is kept for future use if needed
},
updateFileBadges: function (fileId, showWarning, showError, showModified, warningCount, errorCount, fileName) {
if (!fileId) return;
// Update name
const nameElement = document.querySelector(`[data-file-id="${fileId}"][data-name="name"]`);
if (nameElement) {
nameElement.textContent = fileName;
}
// Update warning badge
const warningBadge = document.querySelector(`[data-file-id="${fileId}"][data-badge="warning"]`);
if (warningBadge) {
warningBadge.textContent = warningCount;
warningBadge.setAttribute('data-count', warningCount);
warningBadge.setAttribute('title', `${warningCount} warning(s)`);
if (showWarning) {
warningBadge.classList.remove('hidden');
} else {
warningBadge.classList.add('hidden');
}
}
// Update error badge
const errorBadge = document.querySelector(`[data-file-id="${fileId}"][data-badge="error"]`);
if (errorBadge) {
errorBadge.textContent = errorCount;
errorBadge.setAttribute('data-count', errorCount);
errorBadge.setAttribute('title', `${errorCount} error(s)`);
if (showError) {
errorBadge.classList.remove('hidden');
} else {
errorBadge.classList.add('hidden');
}
}
// Update modified badge
const modifiedBadge = document.querySelector(`[data-file-id="${fileId}"][data-badge="modified"]`);
if (modifiedBadge) {
if (showModified) {
modifiedBadge.classList.remove('hidden');
} else {
modifiedBadge.classList.add('hidden');
}
}
},
CheckRadioById: function (id) {
const radio = document.getElementById(id);
if (radio) {
radio.checked = true;
// Trigger change event to ensure CSS :has(:checked) selector works
radio.dispatchEvent(new Event('change', { bubbles: true }));
}
},
UncheckRadioByName: function (name) {
const radios = document.querySelectorAll(`input[type="radio"][name="${name}"]`);
radios.forEach(radio => radio.checked = false);
},
updateFolderBadges: function (folderPath, showWarning, showError, showModified, warningCount, errorCount, folderName) {
if (!folderPath) return;
// Update name
const nameElement = document.querySelector(`[data-folder-path="${folderPath}"][data-name="name"]`);
if (nameElement) {
nameElement.textContent = folderName;
}
// Update warning badge
const warningBadge = document.querySelector(`[data-folder-path="${folderPath}"][data-badge="warning"]`);
if (warningBadge) {
warningBadge.textContent = warningCount;
warningBadge.setAttribute('data-count', warningCount);
warningBadge.setAttribute('title', `${warningCount} warning(s)`);
if (showWarning) {
warningBadge.classList.remove('hidden');
} else {
warningBadge.classList.add('hidden');
}
}
// Update error badge
const errorBadge = document.querySelector(`[data-folder-path="${folderPath}"][data-badge="error"]`);
if (errorBadge) {
errorBadge.textContent = errorCount;
errorBadge.setAttribute('data-count', errorCount);
errorBadge.setAttribute('title', `${errorCount} error(s)`);
if (showError) {
errorBadge.classList.remove('hidden');
} else {
errorBadge.classList.add('hidden');
}
}
// Update modified badge
const modifiedBadge = document.querySelector(`[data-folder-path="${folderPath}"][data-badge="modified"]`);
if (modifiedBadge) {
if (showModified) {
modifiedBadge.classList.remove('hidden');
} else {
modifiedBadge.classList.add('hidden');
}
}
}
};

View File

@@ -0,0 +1,70 @@
/* Console Message */
.console-message {
display: flex;
gap: 8px;
padding: 4px 0;
word-wrap: break-word;
border-left: 3px solid transparent;
padding-left: 8px;
margin-left: 4px;
}
.console-message:hover {
background-color: rgba(255, 255, 255, 0.05);
}
/* Console Level Badge */
.console-level {
font-weight: 600;
font-size: 10px;
text-transform: uppercase;
padding: 2px 6px;
border-radius: 3px;
min-width: 50px;
text-align: center;
flex-shrink: 0;
}
/* Console Time */
.console-time {
color: #858585;
font-size: 11px;
flex-shrink: 0;
min-width: 80px;
}
/* Console Text */
.console-text {
flex: 1;
color: #cccccc;
white-space: pre-wrap;
word-break: break-word;
}
/* Message Level Styles */
.console-message-info {
border-left-color: #007acc;
}
.console-message-info .console-level {
background-color: rgba(0, 122, 204, 0.2);
color: #4fc3f7;
}
.console-message-warn {
border-left-color: #ffa726;
}
.console-message-warn .console-level {
background-color: rgba(255, 167, 38, 0.2);
color: #ffb74d;
}
.console-message-error {
border-left-color: #f44336;
}
.console-message-error .console-level {
background-color: rgba(244, 67, 54, 0.2);
color: #ef5350;
}