This commit is contained in:
Marces Zastrow
2025-01-17 13:27:12 +01:00
parent f65c71619e
commit 0e888ca258
6 changed files with 1109 additions and 72 deletions
Binary file not shown.
+191 -11
View File
@@ -6,14 +6,20 @@ const bodyParser = require('body-parser');
const crypto = require('crypto'); const crypto = require('crypto');
const multer = require('multer'); // Add this to your dependencies const multer = require('multer'); // Add this to your dependencies
const storage = multer.memoryStorage(); const storage = multer.memoryStorage();
const upload = multer({ storage: storage }); const upload = multer({
storage: storage,
limits: {
fileSize: 50 * 1024 * 1024 // 50MB limit
}
});
const app = express(); const app = express();
const port = 5000; const port = 5000;
// Middleware // Middleware
app.use(cors()); app.use(cors());
app.use(bodyParser.json()); app.use(bodyParser.json({limit: '50mb'})); // Increase JSON payload limit
app.use(bodyParser.urlencoded({limit: '50mb', extended: true})); // Increase URL-encoded payload limit
// SQLite database setup // SQLite database setup
const db = new sqlite3.Database('./database.db', (err) => { const db = new sqlite3.Database('./database.db', (err) => {
@@ -195,31 +201,46 @@ app.get('/games/:gameId/playerchars', (req, res) => {
// Fetch game NPCs // Fetch game NPCs
app.get('/games/:gameId/npcs', (req, res) => { app.get('/games/:gameId/npcs', (req, res) => {
const gameId = req.params.gameId; const gameId = req.params.gameId;
db.all('SELECT * FROM NPC WHERE GameID = ?', [gameId], (err, row) => { db.all('SELECT * FROM NPC WHERE GameID = ?', [gameId], (err, rows) => {
if (err) { if (err) {
return res.status(500).json({ error: 'Internal server error' }); return res.status(500).json({ error: 'Internal server error' });
} }
res.json(row); // Process each row and convert BLOB to base64 if image exists
const processedRows = rows.map(row => {
if (row.Img) {
row.Img = `data:image/jpeg;base64,${row.Img.toString('base64')}`;
}
return row;
}); });
}) res.json(processedRows);
});
});
// Fetch game Items // Fetch game Items
app.get('/games/:gameId/items', (req, res) => { app.get('/games/:gameId/items', (req, res) => {
const gameId = req.params.gameId; const gameId = req.params.gameId;
db.all(` db.all(`
SELECT Item.*, PlayerCharacter.CharName as OwnerName SELECT i.*,
FROM Item COALESCE(pc.CharName, n.Name) as OwnerName
LEFT JOIN PlayerCharacter ON Item.OwnerID = PlayerCharacter.CharID FROM Item i
WHERE Item.GameID = ?`, LEFT JOIN PlayerCharacter pc ON i.OwnerID = pc.CharID
LEFT JOIN NPC n ON i.OwnerID = n.NPCID
WHERE i.GameID = ?`,
[gameId], [gameId],
(err, rows) => { (err, rows) => {
if (err) { if (err) {
return res.status(500).json({ error: 'Internal server error' }); return res.status(500).json({ error: 'Internal server error' });
} }
res.json(rows); const processedRows = rows.map(row => {
if (row.img) {
row.Img = `data:image/jpeg;base64,${row.img.toString('base64')}`;
}
return row;
});
res.json(processedRows);
} }
); );
}) });
// Player Part // Player Part
@@ -398,6 +419,165 @@ app.post('/games/:charId/:itemId/owner', (req, res) => {
}); });
}) })
// Update Item details including owner
app.put('/games/item/:itemId', async (req, res) => {
const itemId = req.params.itemId;
const {
ItemName, Type, Art, Rarity, MaxDurability, CurrentDurability,
GoldValue, Abilities, OwnerID, AP
} = req.body;
const stmt = db.prepare(`
UPDATE Item
SET ItemName = ?, Type = ?, Art = ?, Rarity = ?,
MaxDurability = ?, CurrentDurability = ?, GoldValue = ?,
Abilities = ?, OwnerID = ?, AP = ?
WHERE ItemID = ?
`);
stmt.run([
ItemName, Type, Art, Rarity, MaxDurability, CurrentDurability,
GoldValue, Abilities, OwnerID, AP, itemId
], function(err) {
if (err) {
console.error('Database error:', err);
return res.status(500).json({ error: 'Internal server error' });
}
if (this.changes === 0) {
return res.status(404).json({ error: 'Item not found' });
}
res.json({ message: 'Item updated successfully!' });
});
stmt.finalize();
});
//NPC part
// Create NPC
app.post('/games/npc/create', upload.single('image'), (req, res) => {
const {
Name, Race, Sex, Age, Job, Description,
MaxHealth, MaxMana, Strength, Dexterity,
Agility, Endurance, Allied, Level, GameID
} = req.body;
const imageBuffer = req.file ? req.file.buffer : null;
const stmt = db.prepare(`
INSERT INTO NPC (
GameID, Name, Race, Sex, Age, Job, Description,
MaxHealth, CurrentHealth, MaxMana, CurrentMana,
Strength, Dexterity, Agility, Endurance,
Level, Allied, Img
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
`);
stmt.run([
GameID, Name, Race, Sex, Age, Job, Description,
MaxHealth, MaxHealth, MaxMana, MaxMana,
Strength, Dexterity, Agility, Endurance,
Level, Allied, imageBuffer
], function(err) {
if (err) {
console.error('Database error:', err);
return res.status(500).json({ error: 'Internal server error' });
}
res.status(201).json({
message: 'NPC created successfully!',
npcId: this.lastID
});
});
stmt.finalize();
});
// Update NPC details
app.put('/games/npc/:npcId', upload.single('image'), (req, res) => {
const npcId = req.params.npcId;
const {
Name, Race, Sex, Age, Job, Description,
MaxHealth, CurrentHealth, MaxMana, CurrentMana,
Strength, Dexterity, Agility, Endurance,
Level, Allied
} = req.body;
let updateQuery = `
UPDATE NPC SET
Name = ?, Race = ?, Sex = ?, Age = ?, Job = ?,
Description = ?, MaxHealth = ?, CurrentHealth = ?,
MaxMana = ?, CurrentMana = ?, Strength = ?,
Dexterity = ?, Agility = ?, Endurance = ?,
Level = ?, Allied = ?
`;
let params = [
Name, Race, Sex, Age, Job, Description,
MaxHealth, CurrentHealth, MaxMana, CurrentMana,
Strength, Dexterity, Agility, Endurance,
Level, Allied
];
// If new image is uploaded, add it to the update
if (req.file) {
updateQuery += `, Img = ?`;
params.push(req.file.buffer);
}
updateQuery += ` WHERE NPCID = ?`;
params.push(npcId);
const stmt = db.prepare(updateQuery);
stmt.run(params, function(err) {
if (err) {
console.error('Database error:', err);
return res.status(500).json({ error: 'Internal server error' });
}
if (this.changes === 0) {
return res.status(404).json({ error: 'NPC not found' });
}
res.json({ message: 'NPC updated successfully!' });
});
stmt.finalize();
});
// Get NPC details
app.get('/games/npc/:npcId', (req, res) => {
const npcId = req.params.npcId;
db.get('SELECT * FROM NPC WHERE NPCID = ?', [npcId], (err, row) => {
if (err) {
return res.status(500).json({ error: 'Internal server error' });
}
if (!row) {
return res.status(404).json({ error: 'NPC not found' });
}
// Convert BLOB to base64 string if image exists
if (row.Img) {
row.Img = `data:image/jpeg;base64,${row.Img.toString('base64')}`;
}
res.json(row);
});
});
// Update NPC image
app.put('/games/npc/:npcId/image', upload.single('image'), (req, res) => {
const npcId = req.params.npcId;
const imageBuffer = req.file.buffer;
const stmt = db.prepare('UPDATE NPC SET Img = ? WHERE NPCID = ?');
stmt.run([imageBuffer, npcId], function(err) {
if (err) {
console.error('Database error:', err);
return res.status(500).json({ error: 'Internal server error' });
}
if (this.changes === 0) {
return res.status(404).json({ error: 'NPC not found' });
}
res.json({ message: 'NPC image updated successfully!' });
});
stmt.finalize();
});
+5
View File
@@ -13,6 +13,7 @@ import Games from './pages/games.jsx';
import GameMasterPage from './pages/gameMasterPage.jsx'; import GameMasterPage from './pages/gameMasterPage.jsx';
import CreateItem from './pages/createItem.jsx'; import CreateItem from './pages/createItem.jsx';
import CreateCharacter from './pages/createCharacter.jsx'; import CreateCharacter from './pages/createCharacter.jsx';
import CreateNpc from './pages/createNpc.jsx';
import { UserProvider } from './context/UserContext.jsx'; import { UserProvider } from './context/UserContext.jsx';
function App() { function App() {
@@ -79,6 +80,10 @@ function App() {
path='/create-item' path='/create-item'
element={<CreateItem isLoggedIn={isLoggedIn} />} element={<CreateItem isLoggedIn={isLoggedIn} />}
/> />
<Route
path='/create-npc'
element={<CreateNpc isLoggedIn={isLoggedIn} />}
/>
</Routes> </Routes>
</div> </div>
+12
View File
@@ -89,6 +89,18 @@ const CreateItem = () => {
return ( return (
<Box sx={{ p: 3, background: 'rgba(30, 30, 47, 0.9)', borderRadius: '8px', marginTop: '40px' }}> <Box sx={{ p: 3, background: 'rgba(30, 30, 47, 0.9)', borderRadius: '8px', marginTop: '40px' }}>
<Button
onClick={() => navigate(`/games/${gameId}/master`)}
variant="contained"
sx={{
backgroundColor: '#764ACB',
'&:hover': { backgroundColor: '#9865f7' },
mb: 3
}}
>
Back to Game
</Button>
<form onSubmit={handleSubmit}> <form onSubmit={handleSubmit}>
<Grid2 container spacing={3}> <Grid2 container spacing={3}>
{/* Left Column - Image and Basic Info */} {/* Left Column - Image and Basic Info */}
+417
View File
@@ -0,0 +1,417 @@
import React, { useState, useContext } from 'react';
import { useNavigate, useLocation } from 'react-router-dom';
import { UserContext } from '../context/UserContext';
import axios from 'axios';
import { Box, TextField, Button, Typography, Select, MenuItem, FormControl, InputLabel, Grid2, Card, CardContent, CardMedia } from '@mui/material';
// Import the same icons as games.jsx
import PaidIcon from '@mui/icons-material/Paid';
import CalendarTodayIcon from '@mui/icons-material/CalendarToday';
import PetsIcon from '@mui/icons-material/Pets';
import WcIcon from '@mui/icons-material/Wc';
import WorkIcon from '@mui/icons-material/Work';
import HeartIcon from '@mui/icons-material/Favorite';
import WaterDropIcon from '@mui/icons-material/WaterDrop';
import KeyboardDoubleArrowUpIcon from '@mui/icons-material/KeyboardDoubleArrowUp';
import SecurityIcon from '@mui/icons-material/Security';
import defaultCharacterImage from '../assets/default-character.png';
const CreateNpc = () => {
const navigate = useNavigate();
const location = useLocation();
const { userId } = useContext(UserContext);
const gameId = new URLSearchParams(location.search).get('gameId');
const [formData, setFormData] = useState({
Name: '',
Race: '',
Sex: '',
Age: '',
Job: '',
Description: '',
MaxHealth: 100,
MaxMana: 100,
Strength: 10,
Dexterity: 10,
Agility: 10,
Endurance: 10,
Allied: 0, // 0 = Allied, 1 = Neutral, 2 = Enemy
Level: 1,
GameID: gameId
});
const [selectedImage, setSelectedImage] = useState(null);
const [imagePreview, setImagePreview] = useState(null);
const handleChange = (e) => {
setFormData({
...formData,
[e.target.name]: e.target.value
});
};
const handleImageChange = (e) => {
const file = e.target.files[0];
if (file) {
setSelectedImage(file);
setImagePreview(URL.createObjectURL(file));
}
};
const handleSubmit = async (e) => {
e.preventDefault();
const formDataToSend = new FormData();
Object.keys(formData).forEach(key => {
formDataToSend.append(key, formData[key]);
});
if (selectedImage) {
formDataToSend.append('image', selectedImage);
}
try {
const response = await axios.post(
'http://localhost:5000/games/npc/create',
formDataToSend,
{
headers: {
'Content-Type': 'multipart/form-data'
}
}
);
if (response.status === 201) {
navigate(`/games/${gameId}/master`);
}
} catch (error) {
console.error('Error creating NPC:', error);
}
};
const inputStyles = {
'& .MuiOutlinedInput-root': {
'& fieldset': { borderColor: '#444' },
'&:hover fieldset': { borderColor: '#764ACB' },
'&.Mui-focused fieldset': { borderColor: '#764ACB' },
},
'& .MuiInputLabel-root': { color: '#fff' },
'& .MuiInputBase-input': { color: '#fff' }
};
return (
<Box sx={{ p: 3, background: 'rgba(30, 30, 47, 0.9)', borderRadius: '8px', marginTop: '40px' }}>
<form onSubmit={handleSubmit}>
<Grid2 container spacing={3}>
{/* NPC Image and Basic Info */}
<Grid2 item xs={12} md={4}>
<Box sx={{ border: '1px solid #444', borderRadius: '3px', p: 2, backgroundColor: '#2e2e3f' }}>
<Card sx={{ width: '400px', backgroundColor: '#1e1e2f', color: '#fff', height: '635px' }}>
{/* Image Upload Section - Similar to CreateCharacter */}
{imagePreview ? (
<Box component="label" htmlFor="image-upload" sx={{ cursor: 'pointer', position: 'relative' }}>
<input
accept="image/*"
type="file"
id="image-upload"
style={{ display: 'none' }}
onChange={handleImageChange}
/>
<CardMedia
component="img"
height="300"
image={imagePreview}
alt="NPC Preview"
sx={{ borderRadius: '3px', objectFit: 'contain', margin: '0 auto', borderBottom: '1px solid #444' }}
/>
</Box>
) : (
<Box
component="label"
htmlFor="image-upload"
sx={{
height: 300,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
backgroundColor: '#2e2e3f',
borderRadius: '3px',
cursor: 'pointer'
}}
>
<input
accept="image/*"
type="file"
id="image-upload"
style={{ display: 'none' }}
onChange={handleImageChange}
/>
<Button
variant="contained"
component="span"
sx={{
backgroundColor: '#764ACB',
'&:hover': { backgroundColor: '#9865f7' }
}}
>
Bild Hochladen
</Button>
</Box>
)}
<CardContent sx={{ p: 3 }}>
<TextField
fullWidth
label="Name des NPCs"
name="Name"
value={formData.Name}
onChange={handleChange}
required
sx={{ ...inputStyles, mb: 2, width: '347px'}}
/>
<Grid2 container spacing={2}>
<Grid2 item xs={6}>
<TextField
fullWidth
label="Alter"
name="Age"
type="number"
value={formData.Age}
onChange={handleChange}
required
sx={{ ...inputStyles, width: '165px' }}
/>
</Grid2>
<Grid2 item xs={6}>
<TextField
fullWidth
label="Rasse"
name="Race"
value={formData.Race}
onChange={handleChange}
required
sx={{ ...inputStyles, width: '165px' }}
/>
</Grid2>
</Grid2>
<Grid2 container spacing={2} sx={{ mt: 1 }}>
<Grid2 item xs={6}>
<FormControl sx={{ width: '165px' }}>
<InputLabel sx={{ color: '#fff' }}>Geschlecht</InputLabel>
<Select
name="Sex"
value={formData.Sex}
onChange={handleChange}
required
sx={{
color: '#fff',
'& .MuiOutlinedInput-notchedOutline': { borderColor: '#444' },
'&:hover .MuiOutlinedInput-notchedOutline': { borderColor: '#764ACB' },
}}
>
<MenuItem value="Male">Männlich</MenuItem>
<MenuItem value="Female">Weiblich</MenuItem>
<MenuItem value="Other">Divers</MenuItem>
</Select>
</FormControl>
</Grid2>
<Grid2 item xs={6}>
<FormControl sx={{ width: '165px' }}>
<InputLabel sx={{ color: '#fff' }}>Status</InputLabel>
<Select
name="Allied"
value={formData.Allied}
onChange={handleChange}
required
sx={{
color: '#fff',
'& .MuiOutlinedInput-notchedOutline': { borderColor: '#444' },
'&:hover .MuiOutlinedInput-notchedOutline': { borderColor: '#764ACB' },
}}
>
<MenuItem value={0}>Verbündet</MenuItem>
<MenuItem value={1}>Neutral</MenuItem>
<MenuItem value={2}>Feindlich</MenuItem>
</Select>
</FormControl>
</Grid2>
</Grid2>
<Grid2 container spacing={2} sx={{ mt: 1 }}>
<Grid2 item xs={6}>
<TextField
fullWidth
label="Beruf/Klasse"
name="Job"
value={formData.Job}
onChange={handleChange}
required
sx={{ ...inputStyles, width: '165px' }}
/>
</Grid2>
<Grid2 item xs={6}>
<TextField
fullWidth
label="Level"
name="Level"
type="number"
value={formData.Level}
onChange={handleChange}
required
sx={{ ...inputStyles, width: '165px' }}
/>
</Grid2>
</Grid2>
</CardContent>
</Card>
</Box>
</Grid2>
{/* Right Column - Stats and Description */}
<Grid2 item xs={12} md={8}>
{/* Health/Mana Bars */}
<Box sx={{ display: 'flex', gap: 2, mb: 4, width: '810px' }}>
<Box sx={{ flex: 1, backgroundColor: '#2e2e3f', borderRadius: '8px', p: 2, border: '1px solid #444' }}>
<Typography variant="body1" sx={{ display: 'flex', justifyContent: 'space-between', color: '#fff' }}>
<HeartIcon sx={{ color: "red" }} />
<span>Maximale Gesundheit</span>
</Typography>
<TextField
fullWidth
name="MaxHealth"
type="number"
value={formData.MaxHealth}
onChange={handleChange}
required
sx={inputStyles}
/>
</Box>
<Box sx={{ flex: 1, backgroundColor: '#2e2e3f', borderRadius: '8px', p: 2, border: '1px solid #444' }}>
<Typography variant="body1" sx={{ display: 'flex', justifyContent: 'space-between', color: '#fff' }}>
<WaterDropIcon sx={{ color: 'blue' }} />
<span>Maximales Mana</span>
</Typography>
<TextField
fullWidth
name="MaxMana"
type="number"
value={formData.MaxMana}
onChange={handleChange}
required
sx={inputStyles}
/>
</Box>
</Box>
{/* Description */}
<Box sx={{ mb: 4, width: '810px' }}>
<Typography variant="h5" sx={{ color: '#fff', mb: 2 }}>Beschreibung</Typography>
<Box sx={{ backgroundColor: '#2e2e3f', p: 2, borderRadius: '8px', border: '1px solid #444' }}>
<TextField
fullWidth
multiline
rows={4}
name="Description"
value={formData.Description}
onChange={handleChange}
sx={inputStyles}
/>
</Box>
</Box>
{/* Stats */}
<Box sx={{ mb: 4, width: '810px' }}>
<Typography variant="h5" sx={{ color: '#fff', mb: 2 }}>Attribute</Typography>
<Box sx={{
backgroundColor: '#2e2e3f',
p: 2,
borderRadius: '8px',
border: '1px solid #444'
}}>
<Grid2 container spacing={2} justifyContent="space-between">
<Grid2 item>
<TextField
label="Stärke"
name="Strength"
type="number"
value={formData.Strength}
onChange={handleChange}
sx={{ ...inputStyles, width: '180px' }}
/>
</Grid2>
<Grid2 item>
<TextField
label="Geschicklichkeit"
name="Dexterity"
type="number"
value={formData.Dexterity}
onChange={handleChange}
sx={{ ...inputStyles, width: '180px' }}
/>
</Grid2>
<Grid2 item>
<TextField
label="Beweglichkeit"
name="Agility"
type="number"
value={formData.Agility}
onChange={handleChange}
sx={{ ...inputStyles, width: '180px' }}
/>
</Grid2>
<Grid2 item>
<TextField
label="Ausdauer"
name="Endurance"
type="number"
value={formData.Endurance}
onChange={handleChange}
sx={{ ...inputStyles, width: '180px' }}
/>
</Grid2>
</Grid2>
</Box>
</Box>
{/* Button Container */}
<Box sx={{
display: 'flex',
justifyContent: 'space-between',
gap: 2,
width: '800px'
}}>
<Button
onClick={() => navigate(`/games/${gameId}/master`)}
variant="contained"
sx={{
backgroundColor: '#764ACB',
'&:hover': { backgroundColor: '#9865f7' },
width: '200px'
}}
>
Zurück ohne Speichern
</Button>
<Button
type="submit"
variant="contained"
sx={{
backgroundColor: '#764ACB',
'&:hover': { backgroundColor: '#9865f7' },
width: '580px'
}}
>
NPC Erstellen
</Button>
</Box>
</Grid2>
</Grid2>
</form>
</Box>
);
};
export default CreateNpc;
+437 -14
View File
@@ -2,7 +2,7 @@ import React, { useState, useEffect, useContext } from 'react';
import { Link, useParams } from 'react-router-dom'; import { Link, useParams } from 'react-router-dom';
import { UserContext } from '../context/UserContext'; import { UserContext } from '../context/UserContext';
import axios from 'axios'; import axios from 'axios';
import { Box, Typography, Grid2, Card, CardContent, CardMedia, Button, IconButton } from '@mui/material'; import { Box, Typography, Grid2, Card, CardContent, CardMedia, Button, IconButton, Dialog, DialogTitle, DialogContent, DialogActions, TextField, FormControl, InputLabel, Select, MenuItem, Tooltip } from '@mui/material';
import AddIcon from '@mui/icons-material/Add'; import AddIcon from '@mui/icons-material/Add';
import defaultCharacterImage from '../assets/default-character.png'; import defaultCharacterImage from '../assets/default-character.png';
@@ -14,6 +14,12 @@ const GameMasterPage = () => {
const [playerCharacters, setPlayerCharacters] = useState([]); const [playerCharacters, setPlayerCharacters] = useState([]);
const [npcs, setNpcs] = useState([]); const [npcs, setNpcs] = useState([]);
const [items, setItems] = useState([]); const [items, setItems] = useState([]);
const [editModalOpen, setEditModalOpen] = useState(false);
const [selectedItem, setSelectedItem] = useState(null);
const [editType, setEditType] = useState(''); // 'item', 'npc', or 'character'
const [isEditing, setIsEditing] = useState(false);
const [allOwners, setAllOwners] = useState([]);
const [formData, setFormData] = useState(null);
// Fix useEffect data fetching // Fix useEffect data fetching
useEffect(() => { useEffect(() => {
@@ -39,10 +45,400 @@ const GameMasterPage = () => {
}; };
fetchData(); fetchData();
const interval = setInterval(fetchData, 5000); let interval;
return () => clearInterval(interval); if (!isEditing && !editModalOpen) { // Add editModalOpen check
interval = setInterval(fetchData, 10000);
}
return () => {
if (interval) clearInterval(interval);
};
}, [gameId, isEditing, editModalOpen]); // Add editModalOpen dependency
useEffect(() => {
const fetchOwners = async () => {
try {
// Only fetch player characters
const pcsResponse = await axios.get(`http://localhost:5000/games/${gameId}/playerchars`);
const pcs = pcsResponse.data.map(pc => ({
id: pc.CharID,
name: pc.CharName,
type: 'Player Character'
}));
setAllOwners([
{ id: null, name: 'Unassigned', type: 'None' },
...pcs
]);
} catch (error) {
console.error('Error fetching potential owners:', error);
}
};
fetchOwners();
}, [gameId]); }, [gameId]);
const handleItemClick = (item) => {
setSelectedItem(item);
setFormData({...item});
setEditType('item');
setEditModalOpen(true);
setIsEditing(true);
};
const handleNpcClick = (npc) => {
setSelectedItem(npc);
setFormData({...npc});
setEditType('npc');
setEditModalOpen(true);
setIsEditing(true);
};
const handleCharacterClick = (character) => {
setSelectedItem(character);
setFormData({...character});
setEditType('character');
setEditModalOpen(true);
setIsEditing(true);
};
const handleUpdate = async () => {
try {
let response;
if (editType === 'item') {
response = await axios.put(`http://localhost:5000/games/item/${selectedItem.ItemID}`, formData);
} else if (editType === 'npc') {
response = await axios.put(`http://localhost:5000/games/npc/${selectedItem.NPCID}`, formData);
} else if (editType === 'character') {
response = await axios.put(`http://localhost:5000/games/character/${selectedItem.CharID}`, formData);
}
if (response.status === 200) {
setEditModalOpen(false);
setIsEditing(false);
setFormData(null);
fetchData();
}
} catch (error) {
console.error('Error updating:', error);
}
};
const handleModalClose = () => {
setEditModalOpen(false);
setIsEditing(false);
setFormData(null);
};
// First, define the common input styles at component level
const commonStyles = {
dialog: {
'& .MuiPaper-root': {
backgroundColor: '#1e1e2f',
color: '#fff',
border: '1px solid #444',
borderRadius: '8px',
minWidth: '500px'
}
},
input: {
'& .MuiOutlinedInput-root': {
color: '#fff',
'& fieldset': { borderColor: '#444' },
'&:hover fieldset': { borderColor: '#764ACB' },
'&.Mui-focused fieldset': { borderColor: '#764ACB' },
},
'& .MuiInputLabel-root': {
color: '#fff',
'&.Mui-focused': {
color: '#764ACB'
}
},
'& .MuiInputBase-input': { color: '#fff' }
},
select: {
color: '#fff',
'.MuiInputLabel-root': {
color: '#fff',
'&.Mui-focused': {
color: '#764ACB'
}
},
'.MuiSelect-select': {
color: '#fff',
backgroundColor: '#1e1e2f'
},
'.MuiOutlinedInput-root': {
color: '#fff',
backgroundColor: '#1e1e2f',
'& fieldset': { borderColor: '#444' },
'&:hover fieldset': { borderColor: '#764ACB' },
'&.Mui-focused fieldset': { borderColor: '#764ACB' }
},
'.MuiSelect-icon': { color: '#fff' }
},
menuItem: {
backgroundColor: '#1e1e2f',
color: '#fff',
'&:hover': {
backgroundColor: '#2e2e3f'
},
'&.Mui-selected': {
backgroundColor: '#764ACB',
'&:hover': {
backgroundColor: '#9865f7'
}
}
}
};
// Update the EditModal component
const EditModal = () => {
const nonEditableFields = [
'GameID', 'CharID', 'ItemID', 'NPCID', 'Img', 'img',
'GameId', 'NpcID', 'itemID', 'PlayerID', 'OwnerName'
];
const renderField = (key) => {
if (key === 'Allied') {
return (
<FormControl fullWidth key={key} sx={{ mb: 2, ...commonStyles.select }}>
<InputLabel>Status</InputLabel>
<Select
value={formData[key] || 0}
label="Status"
onChange={(e) => setFormData({
...formData,
[key]: e.target.value
})}
>
<MenuItem value={0}>Allied</MenuItem>
<MenuItem value={1}>Neutral</MenuItem>
<MenuItem value={2}>Enemy</MenuItem>
</Select>
</FormControl>
);
}
if (key === 'Sex') {
return (
<FormControl fullWidth key={key} sx={{ mb: 2, ...commonStyles.select }}>
<InputLabel>Sex</InputLabel>
<Select
value={formData[key] || ''}
label="Sex"
onChange={(e) => setFormData({
...formData,
[key]: e.target.value
})}
>
<MenuItem value="Male">Male</MenuItem>
<MenuItem value="Female">Female</MenuItem>
<MenuItem value="Other">Other</MenuItem>
</Select>
</FormControl>
);
}
if (key === 'Rarity') {
return (
<FormControl fullWidth key={key} sx={{ mb: 2, ...commonStyles.select }}>
<InputLabel>Rarity</InputLabel>
<Select
value={formData[key] || 1}
label="Rarity"
onChange={(e) => setFormData({
...formData,
[key]: e.target.value
})}
>
<MenuItem value={1}>Poor</MenuItem>
<MenuItem value={2}>Common</MenuItem>
<MenuItem value={3}>Uncommon</MenuItem>
<MenuItem value={4}>Rare</MenuItem>
<MenuItem value={5}>Epic</MenuItem>
<MenuItem value={6}>Legendary</MenuItem>
<MenuItem value={7}>Artifact</MenuItem>
</Select>
</FormControl>
);
}
if (key === 'Type') {
return (
<FormControl fullWidth key={key} sx={{ mb: 2, ...commonStyles.select }}>
<InputLabel>Type</InputLabel>
<Select
value={formData[key] || ''}
label="Type"
onChange={(e) => setFormData({
...formData,
[key]: e.target.value
})}
>
<MenuItem value="Weapon">Weapon</MenuItem>
<MenuItem value="Armor">Armor</MenuItem>
<MenuItem value="Consumable">Consumable</MenuItem>
<MenuItem value="Quest">Quest Item</MenuItem>
<MenuItem value="Other">Other</MenuItem>
</Select>
</FormControl>
);
}
if (key === 'Art') {
return (
<FormControl fullWidth key={key} sx={{ mb: 2, ...commonStyles.select }}>
<InputLabel>Art</InputLabel>
<Select
value={formData[key] || ''}
label="Art"
onChange={(e) => setFormData({
...formData,
[key]: e.target.value
})}
>
<MenuItem value="Sword">Sword</MenuItem>
<MenuItem value="Axe">Axe</MenuItem>
<MenuItem value="Bow">Bow</MenuItem>
<MenuItem value="Shield">Shield</MenuItem>
<MenuItem value="Staff">Staff</MenuItem>
<MenuItem value="Potion">Potion</MenuItem>
<MenuItem value="Other">Other</MenuItem>
</Select>
</FormControl>
);
}
if (key === 'OwnerID') {
return (
<FormControl fullWidth key={key} sx={{ mb: 2, ...commonStyles.select }}>
<InputLabel sx={{ color: '#fff' }}>Owner</InputLabel>
<Select
value={formData[key] || ''}
label="Owner"
onChange={(e) => setFormData({
...formData,
[key]: e.target.value
})}
sx={{
color: '#fff',
'& .MuiOutlinedInput-notchedOutline': { borderColor: '#444' },
'&:hover .MuiOutlinedInput-notchedOutline': { borderColor: '#764ACB' },
}}
>
{allOwners.map((owner) => (
<MenuItem
key={owner.id}
value={owner.id || ''}
sx={{
...commonStyles.menuItem,
display: 'flex',
justifyContent: 'space-between'
}}
>
<span>{owner.name}</span>
{owner.type !== 'None' && (
<Typography
variant="caption"
sx={{
ml: 2,
color: '#764ACB'
}}
>
{owner.type}
</Typography>
)}
</MenuItem>
))}
</Select>
</FormControl>
);
}
return (
<TextField
key={key}
fullWidth
label={key}
value={formData[key] || ''}
onChange={(e) => setFormData({
...formData,
[key]: e.target.value
})}
sx={{ ...commonStyles.input, mb: 2 }}
/>
);
};
return (
<Dialog
open={editModalOpen}
onClose={handleModalClose}
sx={commonStyles.dialog}
>
<DialogTitle sx={{
borderBottom: '1px solid #444',
color: '#fff',
backgroundColor: '#2e2e3f',
padding: '16px 24px'
}}>
{editType === 'item' ? 'Edit Item' : editType === 'npc' ? 'Edit NPC' : 'Edit Character'}
</DialogTitle>
<DialogContent sx={{
p: 3,
backgroundColor: '#1e1e2f'
}}>
{formData && (
<Box sx={{ p: 2 }}>
{Object.keys(formData)
.filter(key => !nonEditableFields.includes(key))
.map(key => renderField(key))}
</Box>
)}
</DialogContent>
<DialogActions sx={{
borderTop: '1px solid #444',
p: 2,
backgroundColor: '#2e2e3f',
'& .MuiButton-root': {
textTransform: 'none',
fontSize: '1rem',
padding: '6px 16px'
}
}}>
<Button
onClick={handleModalClose}
variant="outlined"
sx={{
color: '#fff',
borderColor: '#444',
'&:hover': {
borderColor: '#764ACB',
backgroundColor: 'rgba(118, 74, 203, 0.1)'
}
}}
>
Cancel
</Button>
<Button
onClick={handleUpdate}
variant="contained"
sx={{
backgroundColor: '#764ACB',
color: '#fff',
'&:hover': {
backgroundColor: '#9865f7'
}
}}
>
Save Changes
</Button>
</DialogActions>
</Dialog>
);
};
const Section = ({ title, items, createPath, createText }) => ( const Section = ({ title, items, createPath, createText }) => (
<Box sx={{ <Box sx={{
mb: 4, mb: 4,
@@ -70,12 +466,21 @@ const GameMasterPage = () => {
<Grid2 container spacing={2}> <Grid2 container spacing={2}>
{items.map((item, index) => ( {items.map((item, index) => (
<Grid2 item xs={12} sm={6} md={3} key={index}> <Grid2 item xs={12} sm={6} md={3} key={index}>
<Card sx={{ <Tooltip title={item.Abilities || 'No description available'} arrow>
<Card
onClick={() => handleItemClick(item)}
sx={{
backgroundColor: '#1e1e2f', backgroundColor: '#1e1e2f',
color: '#fff', color: '#fff',
height: '95%', // Reduced height
border: '1px solid #444', border: '1px solid #444',
}}> height: '95%',
cursor: 'pointer',
transition: 'border-color 0.2s',
'&:hover': {
borderColor: '#764ACB'
}
}}
>
<Box sx={{ <Box sx={{
display: 'flex', display: 'flex',
justifyContent: 'center', justifyContent: 'center',
@@ -85,7 +490,7 @@ const GameMasterPage = () => {
<CardMedia <CardMedia
component="img" component="img"
height="135" // Reduced from 140 height="135" // Reduced from 140
image={item.Img || defaultItemImage} image={item.img || defaultItemImage} // Changed from item.img to item.Img
alt={item.ItemName} alt={item.ItemName}
className={`rarity-${item.Rarity} rarity-image`} className={`rarity-${item.Rarity} rarity-image`}
sx={{ sx={{
@@ -113,6 +518,7 @@ const GameMasterPage = () => {
</Typography> </Typography>
</CardContent> </CardContent>
</Card> </Card>
</Tooltip>
</Grid2> </Grid2>
))} ))}
</Grid2> </Grid2>
@@ -147,12 +553,20 @@ const GameMasterPage = () => {
<Grid2 container spacing={2}> <Grid2 container spacing={2}>
{playerCharacters.map((character, index) => ( {playerCharacters.map((character, index) => (
<Grid2 item xs={12} sm={6} md={3} key={index}> <Grid2 item xs={12} sm={6} md={3} key={index}>
<Card sx={{ <Card
onClick={() => handleCharacterClick(character)}
sx={{
backgroundColor: '#1e1e2f', backgroundColor: '#1e1e2f',
color: '#fff', color: '#fff',
border: '1px solid #444', border: '1px solid #444',
height: '100%' height: '100%',
}}> cursor: 'pointer',
transition: 'border-color 0.2s',
'&:hover': {
borderColor: '#764ACB'
}
}}
>
<Box sx={{ <Box sx={{
display: 'flex', display: 'flex',
justifyContent: 'center', justifyContent: 'center',
@@ -215,12 +629,20 @@ const GameMasterPage = () => {
<Grid2 container spacing={2}> <Grid2 container spacing={2}>
{npcs.map((npc, index) => ( {npcs.map((npc, index) => (
<Grid2 item xs={12} sm={6} md={3} key={index}> <Grid2 item xs={12} sm={6} md={3} key={index}>
<Card sx={{ <Card
onClick={() => handleNpcClick(npc)}
sx={{
backgroundColor: '#1e1e2f', backgroundColor: '#1e1e2f',
color: '#fff', color: '#fff',
border: '1px solid #444', border: '1px solid #444',
height: '100%' height: '100%',
}}> cursor: 'pointer',
transition: 'border-color 0.2s',
'&:hover': {
borderColor: '#764ACB'
}
}}
>
<Box sx={{ <Box sx={{
display: 'flex', display: 'flex',
justifyContent: 'center', justifyContent: 'center',
@@ -231,7 +653,7 @@ const GameMasterPage = () => {
component="img" component="img"
height="140" height="140"
image={npc.Img || defaultCharacterImage} image={npc.Img || defaultCharacterImage}
alt={npc.CharName} alt={npc.Name || 'NPC'}
sx={{ sx={{
objectFit: 'contain', objectFit: 'contain',
width: '140px', width: '140px',
@@ -276,6 +698,7 @@ const GameMasterPage = () => {
createPath="/create-item" createPath="/create-item"
createText="Create Item" createText="Create Item"
/> />
<EditModal />
</Box> </Box>
); );
}; };