feat: Add new commands for Discord bot - Implemented "Everyone" command that replies to the user when executed. - Added "sdfsdfsdf" command with an alias "dfsf" that also replies to the user. - Enhanced the command structure with type definitions for better type safety.
722 lines
18 KiB
Vue
722 lines
18 KiB
Vue
<script setup lang="ts">
|
||
import { ref, onMounted, onUnmounted } from "vue";
|
||
import { invoke } from "@tauri-apps/api/core";
|
||
import Sidebar from "./components/Sidebar.vue";
|
||
import MonacoEditor from "./components/MonacoEditor.vue";
|
||
import CommandCreator from "./components/CommandCreator.vue";
|
||
import EventCreator from "./components/EventCreator.vue";
|
||
import ProjectSelector from "./components/ProjectSelector.vue";
|
||
import CommandPalette from "./components/CommandPalette.vue";
|
||
import SkeletonLoader from "./components/SkeletonLoader.vue";
|
||
import DatabaseViewer from "./components/DatabaseViewer.vue";
|
||
import EnvManager from "./components/EnvManager.vue";
|
||
import type { ProjectStats, FileInfo, Command, Event } from "./types/bot";
|
||
|
||
// Estado de la aplicación
|
||
const projectRoot = ref<string>("");
|
||
const showProjectSelector = ref(false);
|
||
const showCommandPalette = ref(false);
|
||
const devUltraMode = ref(false);
|
||
const initialLoading = ref(true);
|
||
const stats = ref<ProjectStats>({
|
||
messageCommands: 0,
|
||
slashCommands: 0,
|
||
standardEvents: 0,
|
||
customEvents: 0,
|
||
totalCommands: 0,
|
||
totalEvents: 0,
|
||
});
|
||
const commands = ref<FileInfo[]>([]);
|
||
const events = ref<FileInfo[]>([]);
|
||
const allFiles = ref<FileInfo[]>([]);
|
||
const selectedFile = ref<FileInfo | null>(null);
|
||
const fileContent = ref<string>("");
|
||
const currentView = ref<"editor" | "command-creator" | "event-creator" | "database" | "env-manager">("editor");
|
||
const loading = ref(false);
|
||
const errorMsg = ref<string>("");
|
||
const schemaContent = ref<string>("");
|
||
|
||
// Deshabilitar F12 y habilitar Ctrl+Q
|
||
const handleF12 = (e: KeyboardEvent) => {
|
||
if (e.key === 'F12') {
|
||
e.preventDefault();
|
||
e.stopPropagation();
|
||
return false;
|
||
}
|
||
};
|
||
|
||
const handleCtrlQ = (e: KeyboardEvent) => {
|
||
if (e.ctrlKey && e.key === 'q') {
|
||
e.preventDefault();
|
||
e.stopPropagation();
|
||
showCommandPalette.value = !showCommandPalette.value;
|
||
}
|
||
};
|
||
|
||
onMounted(() => {
|
||
window.addEventListener('keydown', handleF12);
|
||
window.addEventListener('keydown', handleCtrlQ);
|
||
|
||
// Inicializar Discord RPC
|
||
initDiscordRPC();
|
||
|
||
// Cargar proyecto inicial
|
||
loadProjectData();
|
||
});
|
||
|
||
onUnmounted(() => {
|
||
window.removeEventListener('keydown', handleF12);
|
||
window.removeEventListener('keydown', handleCtrlQ);
|
||
|
||
// Desconectar Discord RPC al cerrar
|
||
disconnectDiscordRPC();
|
||
});
|
||
|
||
// Funciones Discord RPC
|
||
async function initDiscordRPC() {
|
||
try {
|
||
await invoke('init_discord_rpc');
|
||
console.log('✅ Discord RPC inicializado');
|
||
} catch (error) {
|
||
console.warn('⚠️ No se pudo inicializar Discord RPC:', error);
|
||
}
|
||
}
|
||
|
||
async function updateDiscordRPC(details: string, state: string, fileName?: string) {
|
||
try {
|
||
await invoke('update_discord_rpc', { details, state, fileName });
|
||
} catch (error) {
|
||
console.warn('⚠️ Error actualizando Discord RPC:', error);
|
||
}
|
||
}
|
||
|
||
async function disconnectDiscordRPC() {
|
||
try {
|
||
await invoke('disconnect_discord_rpc');
|
||
} catch (error) {
|
||
console.warn('⚠️ Error desconectando Discord RPC:', error);
|
||
}
|
||
}
|
||
|
||
// Cargar datos del proyecto
|
||
async function loadProjectData() {
|
||
loading.value = true;
|
||
errorMsg.value = "";
|
||
|
||
try {
|
||
// Si no hay projectRoot, intentar cargar desde localStorage o mostrar selector
|
||
if (!projectRoot.value) {
|
||
const savedPath = localStorage.getItem('amayo-project-path');
|
||
if (savedPath) {
|
||
// Validar ruta guardada
|
||
const isValid = await invoke<boolean>('validate_project_path', {
|
||
path: savedPath,
|
||
});
|
||
|
||
if (isValid) {
|
||
projectRoot.value = savedPath;
|
||
} else {
|
||
// Ruta inválida, mostrar selector
|
||
showProjectSelector.value = true;
|
||
initialLoading.value = false;
|
||
loading.value = false;
|
||
return;
|
||
}
|
||
} else {
|
||
// No hay ruta guardada, mostrar selector
|
||
showProjectSelector.value = true;
|
||
initialLoading.value = false;
|
||
loading.value = false;
|
||
return;
|
||
}
|
||
}
|
||
|
||
// Cargar estadísticas
|
||
stats.value = await invoke<ProjectStats>("get_project_stats", {
|
||
projectRoot: projectRoot.value
|
||
});
|
||
console.log("📊 Stats cargadas:", stats.value);
|
||
|
||
// Cargar comandos
|
||
commands.value = await invoke<FileInfo[]>("scan_commands", {
|
||
projectRoot: projectRoot.value
|
||
});
|
||
console.log("💬 Comandos cargados:", commands.value.length, commands.value);
|
||
|
||
// Cargar eventos
|
||
events.value = await invoke<FileInfo[]>("scan_events", {
|
||
projectRoot: projectRoot.value
|
||
});
|
||
console.log("📡 Eventos cargados:", events.value.length, events.value);
|
||
|
||
// Cargar schema.prisma si existe
|
||
try {
|
||
schemaContent.value = await invoke<string>("read_file_content", {
|
||
filePath: `${projectRoot.value}/prisma/schema.prisma`
|
||
});
|
||
} catch {
|
||
schemaContent.value = "// Schema no encontrado";
|
||
}
|
||
|
||
// Actualizar Discord RPC
|
||
updateDiscordRPC(
|
||
"Navegando proyecto",
|
||
`${stats.value.totalCommands} comandos | ${stats.value.totalEvents} eventos`
|
||
);
|
||
|
||
} catch (error: any) {
|
||
errorMsg.value = `Error cargando proyecto: ${error}`;
|
||
console.error("Error:", error);
|
||
} finally {
|
||
loading.value = false;
|
||
// Pequeño delay para mostrar el skeleton
|
||
setTimeout(() => {
|
||
initialLoading.value = false;
|
||
}, 800);
|
||
}
|
||
}
|
||
|
||
// Seleccionar archivo
|
||
async function selectFile(file: FileInfo) {
|
||
try {
|
||
selectedFile.value = file;
|
||
fileContent.value = await invoke<string>("read_file_content", {
|
||
filePath: file.path
|
||
});
|
||
currentView.value = "editor";
|
||
|
||
// Actualizar Discord RPC
|
||
const fileType = file.commandType
|
||
? `Comando ${file.commandType}`
|
||
: file.eventType
|
||
? `Evento ${file.eventType}`
|
||
: "Archivo";
|
||
updateDiscordRPC(`Editando ${fileType}`, file.name);
|
||
} catch (error: any) {
|
||
errorMsg.value = `Error leyendo archivo: ${error}`;
|
||
console.error("Error:", error);
|
||
}
|
||
}
|
||
|
||
// Guardar archivo
|
||
async function saveFile(content: string) {
|
||
if (!selectedFile.value) return;
|
||
|
||
try {
|
||
await invoke("write_file_content", {
|
||
filePath: selectedFile.value.path,
|
||
content: content
|
||
});
|
||
|
||
// Mostrar notificación de éxito
|
||
showNotification("✅ Archivo guardado correctamente");
|
||
|
||
// Recargar estadísticas
|
||
await loadProjectData();
|
||
} catch (error: any) {
|
||
errorMsg.value = `Error guardando archivo: ${error}`;
|
||
console.error("Error:", error);
|
||
}
|
||
}
|
||
|
||
// Crear nuevo comando
|
||
function showCommandCreator() {
|
||
currentView.value = "command-creator";
|
||
updateDiscordRPC("Creando comando nuevo", "En el wizard de comandos");
|
||
}
|
||
|
||
// Crear nuevo evento
|
||
function showEventCreator() {
|
||
currentView.value = "event-creator";
|
||
updateDiscordRPC("Creando evento nuevo", "En el wizard de eventos");
|
||
}
|
||
|
||
// Guardar nuevo comando
|
||
async function saveCommand(_command: Command, code: string, savePath: string) {
|
||
try {
|
||
const fullPath = `${projectRoot.value}/${savePath}`;
|
||
await invoke("write_file_content", {
|
||
filePath: fullPath,
|
||
content: code
|
||
});
|
||
|
||
showNotification("✅ Comando creado correctamente");
|
||
currentView.value = "editor";
|
||
await loadProjectData();
|
||
|
||
} catch (error: any) {
|
||
errorMsg.value = `Error creando comando: ${error}`;
|
||
console.error("Error:", error);
|
||
}
|
||
}
|
||
|
||
// Guardar nuevo evento
|
||
async function saveEvent(_event: Event, code: string, savePath: string) {
|
||
try {
|
||
const fullPath = `${projectRoot.value}/${savePath}`;
|
||
await invoke("write_file_content", {
|
||
filePath: fullPath,
|
||
content: code
|
||
});
|
||
|
||
showNotification("✅ Evento creado correctamente");
|
||
currentView.value = "editor";
|
||
await loadProjectData();
|
||
|
||
} catch (error: any) {
|
||
errorMsg.value = `Error creando evento: ${error}`;
|
||
console.error("Error:", error);
|
||
}
|
||
}
|
||
|
||
// Cerrar creadores
|
||
function closeCreator() {
|
||
currentView.value = "editor";
|
||
}
|
||
|
||
// Mostrar notificación temporal
|
||
function showNotification(message: string, type: 'success' | 'error' | 'info' = 'info') {
|
||
const notification = document.createElement("div");
|
||
notification.className = `notification notification-${type}`;
|
||
notification.textContent = message;
|
||
document.body.appendChild(notification);
|
||
|
||
setTimeout(() => {
|
||
notification.remove();
|
||
}, 3000);
|
||
}
|
||
|
||
// Manejar selección de proyecto
|
||
function handleProjectPathSelected(path: string) {
|
||
projectRoot.value = path;
|
||
showProjectSelector.value = false;
|
||
loadProjectData();
|
||
}
|
||
|
||
// Cambiar directorio del proyecto
|
||
function changeProjectDirectory() {
|
||
showProjectSelector.value = true;
|
||
}
|
||
|
||
// Toggle modo Dev Ultra
|
||
async function toggleDevUltra() {
|
||
devUltraMode.value = !devUltraMode.value;
|
||
if (devUltraMode.value) {
|
||
showNotification("⚡ Modo Dev Ultra Activado - Cargando archivos...");
|
||
// Cargar todos los archivos del proyecto
|
||
try {
|
||
allFiles.value = await invoke<FileInfo[]>("scan_all_files", {
|
||
projectRoot: projectRoot.value
|
||
});
|
||
} catch (error: any) {
|
||
errorMsg.value = `Error cargando archivos: ${error}`;
|
||
console.error("Error:", error);
|
||
}
|
||
} else {
|
||
showNotification("🔒 Modo Dev Ultra Desactivado");
|
||
allFiles.value = [];
|
||
}
|
||
}
|
||
|
||
// Toggle Database Viewer
|
||
function toggleDatabase() {
|
||
if (currentView.value === 'database') {
|
||
currentView.value = 'editor';
|
||
updateDiscordRPC("Navegando proyecto", "En el editor");
|
||
} else {
|
||
currentView.value = 'database';
|
||
updateDiscordRPC("Editando base de datos", "Visualizando schema.prisma");
|
||
}
|
||
}
|
||
|
||
// Toggle Env Manager
|
||
function toggleEnvManager() {
|
||
if (currentView.value === 'env-manager') {
|
||
currentView.value = 'editor';
|
||
updateDiscordRPC("Navegando proyecto", "En el editor");
|
||
} else {
|
||
currentView.value = 'env-manager';
|
||
updateDiscordRPC("Configurando variables", "Gestionando .env");
|
||
}
|
||
}
|
||
|
||
// Guardar schema de base de datos
|
||
async function saveSchema(content: string) {
|
||
try {
|
||
await invoke("write_file_content", {
|
||
filePath: `${projectRoot.value}/prisma/schema.prisma`,
|
||
content: content
|
||
});
|
||
schemaContent.value = content;
|
||
showNotification("✅ Schema guardado correctamente");
|
||
} catch (error: any) {
|
||
errorMsg.value = `Error guardando schema: ${error}`;
|
||
console.error("Error:", error);
|
||
}
|
||
}
|
||
|
||
// Manejar comandos del palette
|
||
function handlePaletteCommand(commandId: string) {
|
||
switch (commandId) {
|
||
case 'new-command':
|
||
showCommandCreator();
|
||
break;
|
||
case 'new-event':
|
||
showEventCreator();
|
||
break;
|
||
case 'refresh':
|
||
loadProjectData();
|
||
break;
|
||
case 'change-project':
|
||
changeProjectDirectory();
|
||
break;
|
||
case 'database':
|
||
toggleDatabase();
|
||
break;
|
||
case 'env-manager':
|
||
toggleEnvManager();
|
||
break;
|
||
case 'toggle-dev-ultra':
|
||
toggleDevUltra();
|
||
break;
|
||
case 'save':
|
||
if (selectedFile.value && fileContent.value) {
|
||
saveFile(fileContent.value);
|
||
}
|
||
break;
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<template>
|
||
<div class="app-container">
|
||
<!-- Skeleton Loader -->
|
||
<SkeletonLoader v-if="initialLoading" />
|
||
|
||
<!-- Command Palette -->
|
||
<CommandPalette
|
||
:isOpen="showCommandPalette"
|
||
@close="showCommandPalette = false"
|
||
@command="handlePaletteCommand"
|
||
/>
|
||
|
||
<!-- Selector de proyecto -->
|
||
<ProjectSelector
|
||
v-if="showProjectSelector && !initialLoading"
|
||
@path-selected="handleProjectPathSelected"
|
||
/>
|
||
|
||
<template v-if="!showProjectSelector && !initialLoading">
|
||
<Sidebar
|
||
:stats="stats"
|
||
:commands="commands"
|
||
:events="events"
|
||
:allFiles="allFiles"
|
||
:selectedFile="selectedFile"
|
||
:projectRoot="projectRoot"
|
||
:devUltraMode="devUltraMode"
|
||
@new-command="showCommandCreator"
|
||
@new-event="showEventCreator"
|
||
@refresh="loadProjectData"
|
||
@select-file="selectFile"
|
||
@change-directory="changeProjectDirectory"
|
||
@toggle-dev-ultra="toggleDevUltra"
|
||
@toggle-database="toggleDatabase"
|
||
@toggle-env-manager="toggleEnvManager"
|
||
@notify="showNotification"
|
||
/>
|
||
|
||
<div class="main-content">
|
||
<div v-if="loading" class="loading-overlay">
|
||
<div class="loading-spinner">⏳ Cargando proyecto...</div>
|
||
</div>
|
||
|
||
<div v-else-if="errorMsg" class="error-banner">
|
||
❌ {{ errorMsg }}
|
||
<button @click="errorMsg = ''" class="close-error">✕</button>
|
||
</div>
|
||
|
||
<!-- Editor Monaco -->
|
||
<MonacoEditor
|
||
v-if="currentView === 'editor' && selectedFile"
|
||
:fileInfo="selectedFile"
|
||
:content="fileContent"
|
||
@save="saveFile"
|
||
/>
|
||
|
||
<!-- Command Creator -->
|
||
<CommandCreator
|
||
v-if="currentView === 'command-creator'"
|
||
@save="saveCommand"
|
||
@close="closeCreator"
|
||
/>
|
||
|
||
<!-- Event Creator -->
|
||
<EventCreator
|
||
v-if="currentView === 'event-creator'"
|
||
@save="saveEvent"
|
||
@close="closeCreator"
|
||
/>
|
||
|
||
<!-- Database Viewer -->
|
||
<DatabaseViewer
|
||
v-if="currentView === 'database'"
|
||
:schemaContent="schemaContent"
|
||
:projectRoot="projectRoot"
|
||
@save="saveSchema"
|
||
/>
|
||
|
||
<!-- Environment Manager -->
|
||
<EnvManager
|
||
v-if="currentView === 'env-manager'"
|
||
:projectRoot="projectRoot"
|
||
@close="() => currentView = 'editor'"
|
||
@notify="showNotification"
|
||
/>
|
||
|
||
<!-- Welcome Screen -->
|
||
<div v-if="currentView === 'editor' && !selectedFile" class="welcome-screen">
|
||
<div class="welcome-content">
|
||
<h1>🤖 Amayo Bot Editor</h1>
|
||
<p>Editor estilo VS Code para tu bot de Discord</p>
|
||
<div class="welcome-stats">
|
||
<div class="welcome-stat">
|
||
<div class="stat-number">{{ stats.totalCommands }}</div>
|
||
<div class="stat-label">Comandos Totales</div>
|
||
</div>
|
||
<div class="welcome-stat">
|
||
<div class="stat-number">{{ stats.totalEvents }}</div>
|
||
<div class="stat-label">Eventos Totales</div>
|
||
</div>
|
||
</div>
|
||
<div class="welcome-actions">
|
||
<button @click="showCommandCreator" class="welcome-btn primary">
|
||
➕ Crear Comando
|
||
</button>
|
||
<button @click="showEventCreator" class="welcome-btn primary">
|
||
➕ Crear Evento
|
||
</button>
|
||
</div>
|
||
<p class="welcome-hint">
|
||
💡 <strong>Tip:</strong> Selecciona un archivo del panel izquierdo para editarlo
|
||
</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
</div>
|
||
</template>
|
||
|
||
<style scoped>
|
||
.app-container {
|
||
display: flex;
|
||
height: 100vh;
|
||
overflow: hidden;
|
||
background-color: #1e1e1e;
|
||
}
|
||
|
||
.main-content {
|
||
flex: 1;
|
||
display: flex;
|
||
flex-direction: column;
|
||
position: relative;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.loading-overlay {
|
||
position: absolute;
|
||
top: 0;
|
||
left: 0;
|
||
right: 0;
|
||
bottom: 0;
|
||
background-color: rgba(30, 30, 30, 0.95);
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
z-index: 1000;
|
||
}
|
||
|
||
.loading-spinner {
|
||
color: #ffffff;
|
||
font-size: 18px;
|
||
font-weight: 500;
|
||
}
|
||
|
||
.error-banner {
|
||
background-color: #f14c4c;
|
||
color: #ffffff;
|
||
padding: 12px 20px;
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
font-size: 13px;
|
||
font-weight: 500;
|
||
}
|
||
|
||
.close-error {
|
||
background: none;
|
||
border: none;
|
||
color: #ffffff;
|
||
font-size: 18px;
|
||
cursor: pointer;
|
||
padding: 4px 8px;
|
||
line-height: 1;
|
||
}
|
||
|
||
.close-error:hover {
|
||
background-color: rgba(255, 255, 255, 0.1);
|
||
border-radius: 4px;
|
||
}
|
||
|
||
.welcome-screen {
|
||
flex: 1;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
background-color: #1e1e1e;
|
||
}
|
||
|
||
.welcome-content {
|
||
text-align: center;
|
||
max-width: 600px;
|
||
padding: 40px;
|
||
}
|
||
|
||
.welcome-content h1 {
|
||
color: #ffffff;
|
||
font-size: 48px;
|
||
margin: 0 0 16px 0;
|
||
font-weight: 700;
|
||
}
|
||
|
||
.welcome-content > p {
|
||
color: #cccccc;
|
||
font-size: 18px;
|
||
margin: 0 0 40px 0;
|
||
}
|
||
|
||
.welcome-stats {
|
||
display: flex;
|
||
gap: 40px;
|
||
justify-content: center;
|
||
margin-bottom: 40px;
|
||
}
|
||
|
||
.welcome-stat {
|
||
text-align: center;
|
||
}
|
||
|
||
.stat-number {
|
||
font-size: 48px;
|
||
font-weight: 700;
|
||
color: #4ec9b0;
|
||
margin-bottom: 8px;
|
||
}
|
||
|
||
.stat-label {
|
||
font-size: 14px;
|
||
color: #cccccc;
|
||
font-weight: 500;
|
||
}
|
||
|
||
.welcome-actions {
|
||
display: flex;
|
||
gap: 16px;
|
||
justify-content: center;
|
||
margin-bottom: 40px;
|
||
}
|
||
|
||
.welcome-btn {
|
||
padding: 12px 24px;
|
||
border: none;
|
||
border-radius: 6px;
|
||
cursor: pointer;
|
||
font-size: 15px;
|
||
font-weight: 600;
|
||
transition: all 0.2s;
|
||
}
|
||
|
||
.welcome-btn.primary {
|
||
background-color: #0e639c;
|
||
color: #ffffff;
|
||
}
|
||
|
||
.welcome-btn.primary:hover {
|
||
background-color: #1177bb;
|
||
transform: translateY(-2px);
|
||
}
|
||
|
||
.welcome-hint {
|
||
color: #858585;
|
||
font-size: 14px;
|
||
margin: 0;
|
||
padding: 16px;
|
||
background-color: #252526;
|
||
border-radius: 6px;
|
||
border-left: 3px solid #0e639c;
|
||
}
|
||
|
||
.welcome-hint strong {
|
||
color: #4ec9b0;
|
||
}
|
||
</style>
|
||
|
||
<style>
|
||
* {
|
||
margin: 0;
|
||
padding: 0;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
body {
|
||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu,
|
||
Cantarell, 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif;
|
||
-webkit-font-smoothing: antialiased;
|
||
-moz-osx-font-smoothing: grayscale;
|
||
overflow: hidden;
|
||
}
|
||
|
||
#app {
|
||
width: 100vw;
|
||
height: 100vh;
|
||
overflow: hidden;
|
||
}
|
||
|
||
/* Notificaciones */
|
||
.notification {
|
||
position: fixed;
|
||
top: 20px;
|
||
right: 20px;
|
||
color: #ffffff;
|
||
padding: 12px 20px;
|
||
border-radius: 6px;
|
||
font-size: 14px;
|
||
font-weight: 500;
|
||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
|
||
z-index: 10000;
|
||
animation: slideIn 0.3s ease-out;
|
||
}
|
||
|
||
.notification-success {
|
||
background-color: #4ec9b0;
|
||
}
|
||
|
||
.notification-error {
|
||
background-color: #f48771;
|
||
}
|
||
|
||
.notification-info {
|
||
background-color: #007acc;
|
||
}
|
||
|
||
@keyframes slideIn {
|
||
from {
|
||
transform: translateX(400px);
|
||
opacity: 0;
|
||
}
|
||
to {
|
||
transform: translateX(0);
|
||
opacity: 1;
|
||
}
|
||
}
|
||
</style> |