Add SettingsPanel component for managing dropper nicknames
This commit is contained in:
145
web/client/src/components/SettingsPanel.jsx
Normal file
145
web/client/src/components/SettingsPanel.jsx
Normal file
@@ -0,0 +1,145 @@
|
|||||||
|
import React, { useState, useCallback } from 'react';
|
||||||
|
import { useInventoryStore } from '../store/inventoryStore';
|
||||||
|
import './SettingsPanel.css';
|
||||||
|
|
||||||
|
function SettingsPanel({ isOpen, onClose }) {
|
||||||
|
const droppers = useInventoryStore((state) => state.inventory.droppers) || [];
|
||||||
|
const dropperNicknames = useInventoryStore((state) => state.dropperNicknames) || {};
|
||||||
|
const setDropperNickname = useInventoryStore((state) => state.setDropperNickname);
|
||||||
|
|
||||||
|
const [editingDropper, setEditingDropper] = useState(null);
|
||||||
|
const [editValue, setEditValue] = useState('');
|
||||||
|
const [saving, setSaving] = useState(false);
|
||||||
|
|
||||||
|
const startEditing = useCallback((dropperName) => {
|
||||||
|
setEditingDropper(dropperName);
|
||||||
|
setEditValue(dropperNicknames[dropperName] || '');
|
||||||
|
}, [dropperNicknames]);
|
||||||
|
|
||||||
|
const saveNickname = useCallback(async () => {
|
||||||
|
if (!editingDropper) return;
|
||||||
|
setSaving(true);
|
||||||
|
await setDropperNickname(editingDropper, editValue);
|
||||||
|
setSaving(false);
|
||||||
|
setEditingDropper(null);
|
||||||
|
setEditValue('');
|
||||||
|
}, [editingDropper, editValue, setDropperNickname]);
|
||||||
|
|
||||||
|
const removeNickname = useCallback(async (dropperName) => {
|
||||||
|
setSaving(true);
|
||||||
|
await setDropperNickname(dropperName, '');
|
||||||
|
setSaving(false);
|
||||||
|
}, [setDropperNickname]);
|
||||||
|
|
||||||
|
const handleKeyDown = useCallback((e) => {
|
||||||
|
if (e.key === 'Enter') saveNickname();
|
||||||
|
if (e.key === 'Escape') {
|
||||||
|
setEditingDropper(null);
|
||||||
|
setEditValue('');
|
||||||
|
}
|
||||||
|
}, [saveNickname]);
|
||||||
|
|
||||||
|
if (!isOpen) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="settings-overlay" onClick={onClose}>
|
||||||
|
<div className="settings-panel" onClick={(e) => e.stopPropagation()}>
|
||||||
|
<div className="settings-header">
|
||||||
|
<h2>⚙️ Settings</h2>
|
||||||
|
<button className="settings-close" onClick={onClose}>✕</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="settings-body">
|
||||||
|
<div className="settings-section">
|
||||||
|
<h3>🏷️ Dropper Nicknames</h3>
|
||||||
|
<p className="settings-hint">
|
||||||
|
Give your dispensers friendly names so they're easier to identify.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
{droppers.length === 0 ? (
|
||||||
|
<div className="settings-empty">
|
||||||
|
No droppers detected. Connect the bridge to discover droppers.
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="dropper-nickname-list">
|
||||||
|
{droppers.map((d) => {
|
||||||
|
const nickname = dropperNicknames[d.name];
|
||||||
|
const isEditing = editingDropper === d.name;
|
||||||
|
const shortName = d.name.replace(/^minecraft:/, '');
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div key={d.name} className="dropper-nickname-row">
|
||||||
|
<div className="dropper-nickname-info">
|
||||||
|
<span className="dropper-raw-name">
|
||||||
|
{shortName}
|
||||||
|
{d.isDefault && <span className="dropper-badge default">default</span>}
|
||||||
|
{d.clientId && <span className="dropper-badge client">client {d.clientId}</span>}
|
||||||
|
</span>
|
||||||
|
{nickname && !isEditing && (
|
||||||
|
<span className="dropper-current-nick">"{nickname}"</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className="dropper-nickname-actions">
|
||||||
|
{isEditing ? (
|
||||||
|
<>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
className="nickname-input"
|
||||||
|
value={editValue}
|
||||||
|
onChange={(e) => setEditValue(e.target.value)}
|
||||||
|
onKeyDown={handleKeyDown}
|
||||||
|
placeholder="Enter nickname..."
|
||||||
|
maxLength={32}
|
||||||
|
autoFocus
|
||||||
|
disabled={saving}
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
className="mc-btn green nick-btn"
|
||||||
|
onClick={saveNickname}
|
||||||
|
disabled={saving}
|
||||||
|
>
|
||||||
|
✓
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className="mc-btn nick-btn"
|
||||||
|
onClick={() => { setEditingDropper(null); setEditValue(''); }}
|
||||||
|
disabled={saving}
|
||||||
|
>
|
||||||
|
✕
|
||||||
|
</button>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<button
|
||||||
|
className="mc-btn nick-btn"
|
||||||
|
onClick={() => startEditing(d.name)}
|
||||||
|
title="Edit nickname"
|
||||||
|
>
|
||||||
|
✏️
|
||||||
|
</button>
|
||||||
|
{nickname && (
|
||||||
|
<button
|
||||||
|
className="mc-btn red nick-btn"
|
||||||
|
onClick={() => removeNickname(d.name)}
|
||||||
|
title="Remove nickname"
|
||||||
|
disabled={saving}
|
||||||
|
>
|
||||||
|
🗑️
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default SettingsPanel;
|
||||||
Reference in New Issue
Block a user