191 lines
9.7 KiB
TypeScript
191 lines
9.7 KiB
TypeScript
import { create } from 'zustand';
|
|
|
|
export interface FileNode {
|
|
id: string;
|
|
name: string;
|
|
type: 'file' | 'folder';
|
|
path: string;
|
|
children?: FileNode[];
|
|
platform?: 'roblox' | 'web' | 'mobile' | 'desktop' | 'shared';
|
|
content?: string;
|
|
}
|
|
|
|
export interface OpenFile {
|
|
id: string;
|
|
name: string;
|
|
path: string;
|
|
content: string;
|
|
language: string;
|
|
isDirty: boolean;
|
|
}
|
|
|
|
interface EditorStore {
|
|
files: FileNode[];
|
|
openFiles: OpenFile[];
|
|
activeFileId: string | null;
|
|
isSaving: boolean;
|
|
|
|
setFiles: (files: FileNode[]) => void;
|
|
openFile: (file: FileNode) => void;
|
|
closeFile: (fileId: string) => void;
|
|
setActiveFile: (fileId: string) => void;
|
|
updateFileContent: (fileId: string, content: string) => void;
|
|
saveFile: (fileId: string) => void;
|
|
saveAllFiles: () => void;
|
|
}
|
|
|
|
export const useEditorStore = create<EditorStore>((set, get) => ({
|
|
files: [
|
|
{
|
|
id: 'roblox',
|
|
name: 'roblox',
|
|
type: 'folder',
|
|
path: '/roblox',
|
|
platform: 'roblox',
|
|
children: [
|
|
{ id: 'roblox-main', name: 'main.lua', type: 'file', path: '/roblox/main.lua', platform: 'roblox', content: '-- Roblox Main Script\nprint("Hello from Roblox!")\n\n-- Player joined event\ngame.Players.PlayerAdded:Connect(function(player)\n print(player.Name .. " joined the game!")\nend)' },
|
|
{ id: 'roblox-controller', name: 'player-controller.lua', type: 'file', path: '/roblox/player-controller.lua', platform: 'roblox', content: '-- Player Controller\nlocal UserInputService = game:GetService("UserInputService")\n\nlocal function onInput(input, gameProcessed)\n if gameProcessed then return end\n -- Handle input\nend\n\nUserInputService.InputBegan:Connect(onInput)' },
|
|
{ id: 'roblox-datastore', name: 'datastore.lua', type: 'file', path: '/roblox/datastore.lua', platform: 'roblox', content: '-- DataStore Manager\nlocal DataStoreService = game:GetService("DataStoreService")\nlocal playerData = DataStoreService:GetDataStore("PlayerData")\n\nlocal function savePlayerData(player, data)\n -- Save data logic\nend' },
|
|
],
|
|
},
|
|
{
|
|
id: 'web',
|
|
name: 'web',
|
|
type: 'folder',
|
|
path: '/web',
|
|
platform: 'web',
|
|
children: [
|
|
{ id: 'web-index', name: 'index.html', type: 'file', path: '/web/index.html', platform: 'web', content: '<!DOCTYPE html>\n<html>\n<head>\n <title>AeThex Game</title>\n <link rel="stylesheet" href="style.css">\n</head>\n<body>\n <canvas id="game-canvas"></canvas>\n <script src="game.js"></script>\n</body>\n</html>' },
|
|
{ id: 'web-game', name: 'game.js', type: 'file', path: '/web/game.js', platform: 'web', content: '// Web Game Entry Point\nconst canvas = document.getElementById("game-canvas");\nconst ctx = canvas.getContext("2d");\n\nfunction gameLoop() {\n // Update game state\n // Render game\n requestAnimationFrame(gameLoop);\n}\n\ngameLoop();' },
|
|
{ id: 'web-style', name: 'style.css', type: 'file', path: '/web/style.css', platform: 'web', content: 'body {\n margin: 0;\n padding: 0;\n overflow: hidden;\n background: #000;\n}\n\n#game-canvas {\n display: block;\n width: 100%;\n height: 100vh;\n}' },
|
|
],
|
|
},
|
|
{
|
|
id: 'mobile',
|
|
name: 'mobile',
|
|
type: 'folder',
|
|
path: '/mobile',
|
|
platform: 'mobile',
|
|
children: [
|
|
{ id: 'mobile-app', name: 'App.tsx', type: 'file', path: '/mobile/App.tsx', platform: 'mobile', content: 'import React from "react";\nimport { GameScreen } from "./GameScreen";\n\nexport default function App() {\n return <GameScreen />;\n}' },
|
|
{ id: 'mobile-game', name: 'GameScreen.tsx', type: 'file', path: '/mobile/GameScreen.tsx', platform: 'mobile', content: 'import React from "react";\nimport { View, Text, StyleSheet } from "react-native";\n\nexport function GameScreen() {\n return (\n <View style={styles.container}>\n <Text>Mobile Game</Text>\n </View>\n );\n}\n\nconst styles = StyleSheet.create({\n container: { flex: 1, justifyContent: "center", alignItems: "center" },\n});' },
|
|
{ id: 'mobile-config', name: 'config.json', type: 'file', path: '/mobile/config.json', platform: 'mobile', content: '{\n "name": "AeThexGame",\n "displayName": "AeThex Game",\n "version": "1.0.0",\n "platforms": ["ios", "android"]\n}' },
|
|
],
|
|
},
|
|
{
|
|
id: 'desktop',
|
|
name: 'desktop',
|
|
type: 'folder',
|
|
path: '/desktop',
|
|
platform: 'desktop',
|
|
children: [
|
|
{ id: 'desktop-main', name: 'main.js', type: 'file', path: '/desktop/main.js', platform: 'desktop', content: '// Electron Main Process\nconst { app, BrowserWindow } = require("electron");\n\nfunction createWindow() {\n const win = new BrowserWindow({\n width: 1280,\n height: 720,\n webPreferences: {\n preload: __dirname + "/preload.js"\n }\n });\n win.loadFile("index.html");\n}\n\napp.whenReady().then(createWindow);' },
|
|
{ id: 'desktop-preload', name: 'preload.js', type: 'file', path: '/desktop/preload.js', platform: 'desktop', content: '// Electron Preload Script\nconst { contextBridge } = require("electron");\n\ncontextBridge.exposeInMainWorld("electronAPI", {\n platform: "desktop",\n version: "1.0.0"\n});' },
|
|
],
|
|
},
|
|
{
|
|
id: 'shared',
|
|
name: 'shared',
|
|
type: 'folder',
|
|
path: '/shared',
|
|
platform: 'shared',
|
|
children: [
|
|
{ id: 'shared-nexus', name: 'nexus-engine.ts', type: 'file', path: '/shared/nexus-engine.ts', platform: 'shared', content: '// Nexus Engine - Cross-Platform State Sync\nexport class NexusEngine {\n private state: Map<string, any> = new Map();\n private listeners: Set<Function> = new Set();\n\n setState(key: string, value: any) {\n this.state.set(key, value);\n this.broadcast(key, value);\n }\n\n getState(key: string) {\n return this.state.get(key);\n }\n\n private broadcast(key: string, value: any) {\n this.listeners.forEach(listener => listener(key, value));\n }\n}' },
|
|
{ id: 'shared-passport', name: 'passport-auth.ts', type: 'file', path: '/shared/passport-auth.ts', platform: 'shared', content: '// Passport - Unified Authentication\nexport class PassportAuth {\n private token: string | null = null;\n\n async login(username: string, password: string) {\n // Mock login\n this.token = "mock-token-" + username;\n return { success: true, token: this.token };\n }\n\n async logout() {\n this.token = null;\n }\n\n isAuthenticated() {\n return this.token !== null;\n }\n}' },
|
|
{ id: 'shared-config', name: 'gameforge-config.json', type: 'file', path: '/shared/gameforge-config.json', platform: 'shared', content: '{\n "gameforge": {\n "antiCheat": {\n "enabled": true,\n "serverAuthoritative": true\n },\n "rules": [\n {\n "type": "speed-check",\n "maxSpeed": 100\n },\n {\n "type": "position-validation",\n "enabled": true\n }\n ]\n }\n}' },
|
|
],
|
|
},
|
|
{
|
|
id: 'readme',
|
|
name: 'README.md',
|
|
type: 'file',
|
|
path: '/README.md',
|
|
content: '# AeThex Studio Project\n\nThis is a cross-platform game project built with AeThex Studio.\n\n## Platforms\n\n- **Roblox**: Multiplayer game experience\n- **Web**: Browser-based version\n- **Mobile**: iOS & Android apps\n- **Desktop**: Native Windows/Mac/Linux\n\n## Features\n\n- Nexus Engine for real-time state synchronization\n- Passport authentication for unified identity\n- GameForge anti-cheat protection\n\n## Getting Started\n\n1. Select a file from the explorer\n2. Edit your code\n3. Click "Run All Platforms" to test\n4. Deploy when ready!\n',
|
|
},
|
|
],
|
|
openFiles: [],
|
|
activeFileId: null,
|
|
isSaving: false,
|
|
|
|
setFiles: (files) => set({ files }),
|
|
|
|
openFile: (file) => {
|
|
const { openFiles } = get();
|
|
if (openFiles.find(f => f.id === file.id)) {
|
|
set({ activeFileId: file.id });
|
|
return;
|
|
}
|
|
|
|
const language = file.name.endsWith('.lua') ? 'lua'
|
|
: file.name.endsWith('.ts') || file.name.endsWith('.tsx') ? 'typescript'
|
|
: file.name.endsWith('.js') || file.name.endsWith('.jsx') ? 'javascript'
|
|
: file.name.endsWith('.html') ? 'html'
|
|
: file.name.endsWith('.css') ? 'css'
|
|
: file.name.endsWith('.json') ? 'json'
|
|
: file.name.endsWith('.md') ? 'markdown'
|
|
: 'plaintext';
|
|
|
|
const newFile: OpenFile = {
|
|
id: file.id,
|
|
name: file.name,
|
|
path: file.path,
|
|
content: file.content || '',
|
|
language,
|
|
isDirty: false,
|
|
};
|
|
|
|
set({
|
|
openFiles: [...openFiles, newFile],
|
|
activeFileId: file.id,
|
|
});
|
|
},
|
|
|
|
closeFile: (fileId) => {
|
|
const { openFiles, activeFileId } = get();
|
|
const newOpenFiles = openFiles.filter(f => f.id !== fileId);
|
|
const newActiveId = activeFileId === fileId
|
|
? (newOpenFiles.length > 0 ? newOpenFiles[newOpenFiles.length - 1].id : null)
|
|
: activeFileId;
|
|
|
|
set({
|
|
openFiles: newOpenFiles,
|
|
activeFileId: newActiveId,
|
|
});
|
|
},
|
|
|
|
setActiveFile: (fileId) => set({ activeFileId: fileId }),
|
|
|
|
updateFileContent: (fileId, content) => {
|
|
const { openFiles } = get();
|
|
set({
|
|
openFiles: openFiles.map(f =>
|
|
f.id === fileId ? { ...f, content, isDirty: true } : f
|
|
),
|
|
});
|
|
},
|
|
|
|
saveFile: (fileId) => {
|
|
const { openFiles } = get();
|
|
set({ isSaving: true });
|
|
|
|
// Simulate save
|
|
setTimeout(() => {
|
|
set({
|
|
openFiles: openFiles.map(f =>
|
|
f.id === fileId ? { ...f, isDirty: false } : f
|
|
),
|
|
isSaving: false,
|
|
});
|
|
}, 500);
|
|
},
|
|
|
|
saveAllFiles: () => {
|
|
set({ isSaving: true });
|
|
setTimeout(() => {
|
|
set({
|
|
openFiles: get().openFiles.map(f => ({ ...f, isDirty: false })),
|
|
isSaving: false,
|
|
});
|
|
}, 500);
|
|
},
|
|
}));
|