Compare commits

...

7 Commits

4 changed files with 260 additions and 41 deletions

View File

@@ -4,37 +4,43 @@
display: flex;
flex-direction: column;
overflow: hidden;
background: #2c2c2c;
}
.view-controls {
display: flex;
gap: 0.5rem;
padding: 0.75rem;
background: #1e293b;
border-bottom: 2px solid #334155;
background: #3b3b3b;
border-bottom: 3px solid #1a1a1a;
box-shadow: inset 0 -2px 0 #555;
}
.view-controls button {
padding: 0.5rem 1rem;
border: none;
background: #334155;
color: #e2e8f0;
border-radius: 0.375rem;
border: 2px solid #1a1a1a;
background: #6b6b6b;
color: #e0e0e0;
border-radius: 0;
font-size: 0.875rem;
font-weight: 600;
font-weight: 700;
cursor: pointer;
transition: all 0.2s;
transition: all 0.1s;
font-family: 'Silkscreen', 'Courier New', monospace;
text-shadow: 1px 1px 0 #1a1a1a;
box-shadow: inset 0 -2px 0 #444, inset 0 2px 0 #888;
}
.view-controls button:hover {
background: #475569;
transform: translateY(-1px);
background: #7b7b7b;
box-shadow: inset 0 -2px 0 #555, inset 0 2px 0 #999;
}
.view-controls button.active {
background: #3b82f6;
background: #4a8c2a;
color: white;
box-shadow: 0 0 10px rgba(59, 130, 246, 0.5);
box-shadow: inset 0 2px 0 #6ab04c, inset 0 -2px 0 #2d6b1a;
border-color: #1a1a1a;
}
.app-content {
@@ -67,11 +73,11 @@
.map-container {
position: relative;
background: #0a0e1a;
background: #2c2c2c;
}
.panel-container {
background: #0f172a;
background: #2c2c2c;
overflow: hidden;
display: flex;
flex-direction: column;
@@ -81,34 +87,37 @@
display: flex;
gap: 0.25rem;
padding: 0.5rem;
background: #1e293b;
border-bottom: 2px solid #334155;
background: #3b3b3b;
border-bottom: 3px solid #1a1a1a;
overflow-x: auto;
flex-shrink: 0;
}
.panel-tabs button {
padding: 0.5rem 1rem;
border: none;
background: #334155;
color: #94a3b8;
border-radius: 0.375rem;
border: 2px solid #1a1a1a;
background: #5a5a5a;
color: #b0b0b0;
border-radius: 0;
font-size: 0.875rem;
font-weight: 600;
font-weight: 700;
cursor: pointer;
transition: all 0.2s;
transition: all 0.1s;
white-space: nowrap;
font-family: 'Silkscreen', 'Courier New', monospace;
text-shadow: 1px 1px 0 #1a1a1a;
box-shadow: inset 0 -2px 0 #444, inset 0 2px 0 #777;
}
.panel-tabs button:hover {
background: #475569;
background: #6b6b6b;
color: #e5e7eb;
}
.panel-tabs button.active {
background: #3b82f6;
background: #4a8c2a;
color: white;
box-shadow: 0 0 8px rgba(59, 130, 246, 0.4);
box-shadow: inset 0 2px 0 #6ab04c, inset 0 -2px 0 #2d6b1a;
}
.panel-content-wrapper {
@@ -125,16 +134,16 @@
}
::-webkit-scrollbar-track {
background: #1e293b;
background: #2c2c2c;
}
::-webkit-scrollbar-thumb {
background: #475569;
border-radius: 4px;
background: #5a5a5a;
border: 1px solid #1a1a1a;
}
::-webkit-scrollbar-thumb:hover {
background: #64748b;
background: #6b6b6b;
}
/* Mobile-Responsive Design */

View File

@@ -79,6 +79,21 @@ function TurtleCard({ turtle, isSelected, onSelect }) {
function TurtleDetails({ turtle }) {
const sendCommand = useTurtleStore((state) => state.sendCommand);
const setTurtleState = useTurtleStore((state) => state.setTurtleState);
const renameTurtle = useTurtleStore((state) => state.renameTurtle);
const equipLeft = useTurtleStore((state) => state.equipLeft);
const equipRight = useTurtleStore((state) => state.equipRight);
const sortInventory = useTurtleStore((state) => state.sortInventory);
const selectSlot = useTurtleStore((state) => state.selectSlot);
const dropItems = useTurtleStore((state) => state.dropItems);
const suckItems = useTurtleStore((state) => state.suckItems);
const connectToInventory = useTurtleStore((state) => state.connectToInventory);
const updateTurtleConfig = useTurtleStore((state) => state.updateTurtleConfig);
const exploreTurtle = useTurtleStore((state) => state.exploreTurtle);
const gpsLocateTurtle = useTurtleStore((state) => state.gpsLocateTurtle);
const [renameValue, setRenameValue] = useState('');
const [showConfig, setShowConfig] = useState(false);
const [configValues, setConfigValues] = useState({ maxDistance: 200, autoRefuel: true });
if (!turtle) {
return (
@@ -96,11 +111,66 @@ function TurtleDetails({ turtle }) {
setTurtleState(turtle.turtleID, stateName, data);
};
const handleRename = async () => {
if (renameValue.trim()) {
await renameTurtle(turtle.turtleID, renameValue.trim());
setRenameValue('');
}
};
const handleSlotClick = async (slotIndex) => {
await selectSlot(turtle.turtleID, slotIndex + 1);
};
const handleConfigSave = async () => {
await updateTurtleConfig(turtle.turtleID, configValues);
setShowConfig(false);
};
const activeState = turtle.state || turtle.mode || 'idle';
const displayName = turtle.label || `Turtle ${turtle.turtleID}`;
return (
<div className="turtle-details">
<h2>Turtle {turtle.turtleID} Control</h2>
<h2>{displayName} <span style={{color: '#6b7280', fontSize: '0.8em'}}>#{turtle.turtleID}</span></h2>
{/* Rename + Config bar */}
<div className="detail-section" style={{ display: 'flex', gap: '8px', alignItems: 'center', flexWrap: 'wrap' }}>
<input
type="text"
value={renameValue}
onChange={(e) => setRenameValue(e.target.value)}
onKeyDown={(e) => e.key === 'Enter' && handleRename()}
placeholder="Rename turtle..."
className="rename-input"
style={{ flex: 1, minWidth: '120px', padding: '4px 8px', borderRadius: '4px', border: '1px solid #4b5563', background: '#1f2937', color: '#fff' }}
/>
<button onClick={handleRename} className="command-btn" style={{ padding: '4px 12px' }}>📝 Rename</button>
<button onClick={() => setShowConfig(!showConfig)} className="command-btn" style={{ padding: '4px 12px' }}> Config</button>
<button onClick={() => gpsLocateTurtle(turtle.turtleID)} className="command-btn" style={{ padding: '4px 12px' }}>📡 GPS</button>
<button onClick={() => exploreTurtle(turtle.turtleID)} className="command-btn" style={{ padding: '4px 12px' }}>🔎 Inspect</button>
</div>
{/* Config Modal */}
{showConfig && (
<div className="detail-section" style={{ background: '#1e293b', padding: '12px', borderRadius: '8px', border: '1px solid #334155' }}>
<h3> Configuration</h3>
<div style={{ display: 'flex', flexDirection: 'column', gap: '8px', marginTop: '8px' }}>
<label style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<span>Max Distance:</span>
<input type="number" value={configValues.maxDistance} onChange={(e) => setConfigValues({...configValues, maxDistance: parseInt(e.target.value)})} style={{ width: '80px', padding: '2px 6px', borderRadius: '4px', border: '1px solid #4b5563', background: '#0f172a', color: '#fff' }} />
</label>
<label style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<span>Auto Refuel:</span>
<input type="checkbox" checked={configValues.autoRefuel} onChange={(e) => setConfigValues({...configValues, autoRefuel: e.target.checked})} />
</label>
<div style={{ display: 'flex', gap: '8px', justifyContent: 'flex-end' }}>
<button onClick={() => setShowConfig(false)} className="command-btn" style={{ padding: '4px 12px' }}>Cancel</button>
<button onClick={handleConfigSave} className="command-btn explore" style={{ padding: '4px 12px' }}>💾 Save</button>
</div>
</div>
</div>
)}
<div className="detail-section">
<h3>Status</h3>
@@ -417,17 +487,86 @@ function TurtleDetails({ turtle }) {
</div>
</div>
{/* Equipment & Inventory Actions */}
<div className="detail-section">
<h3>Equipment & Inventory</h3>
<div className="action-grid">
<button
className="action-btn"
onClick={() => equipLeft(turtle.turtleID)}
title="Equip selected item on left side"
>
🛡 Equip L
</button>
<button
className="action-btn"
onClick={() => equipRight(turtle.turtleID)}
title="Equip selected item on right side"
>
🗡 Equip R
</button>
<button
className="action-btn"
onClick={() => sortInventory(turtle.turtleID)}
title="Sort and compact inventory"
>
📋 Sort
</button>
<button
className="action-btn"
onClick={() => dropItems(turtle.turtleID, 'front')}
title="Drop items forward"
>
📤 Drop
</button>
<button
className="action-btn"
onClick={() => dropItems(turtle.turtleID, 'up')}
title="Drop items upward"
>
Drop Up
</button>
<button
className="action-btn"
onClick={() => dropItems(turtle.turtleID, 'down')}
title="Drop items downward"
>
Drop Down
</button>
<button
className="action-btn"
onClick={() => suckItems(turtle.turtleID, 'front')}
title="Suck items from front"
>
📥 Suck
</button>
<button
className="action-btn"
onClick={() => {
const side = prompt('Enter peripheral side (front/top/bottom/left/right):');
if (side) connectToInventory(turtle.turtleID, side);
}}
title="Read adjacent inventory peripheral contents"
>
📦 Read Inv
</button>
</div>
</div>
{turtle.inventory && turtle.inventory.length > 0 && (
<div className="detail-section">
<h3>Inventory ({turtle.inventoryCount || turtle.inventory.length}/16)</h3>
<h3>Inventory ({turtle.inventoryCount || turtle.inventory.length}/16) Slot: {turtle.selectedSlot || 1}</h3>
<div className="inventory-grid">
{Array.from({ length: 16 }, (_, slotIndex) => {
const item = turtle.inventory[slotIndex];
const isSelected = (turtle.selectedSlot || 1) === (slotIndex + 1);
return (
<div
key={slotIndex}
className={`inventory-slot ${item ? 'filled' : 'empty'}`}
title={item ? `${item.name.replace('minecraft:', '').replace(/_/g, ' ')} (${item.count})` : 'Empty'}
className={`inventory-slot ${item ? 'filled' : 'empty'} ${isSelected ? 'selected-slot' : ''}`}
title={item ? `${item.name.replace('minecraft:', '').replace(/_/g, ' ')} (${item.count}) — Click to select` : `Slot ${slotIndex + 1} — Click to select`}
onClick={() => handleSlotClick(slotIndex)}
style={isSelected ? { outline: '2px solid #60a5fa', outlineOffset: '-2px' } : {}}
>
{item ? (
<>

View File

@@ -1,3 +1,5 @@
@import url('https://fonts.googleapis.com/css2?family=Silkscreen:wght@400;700&display=swap');
* {
margin: 0;
padding: 0;
@@ -5,13 +7,12 @@
}
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;
background: #0a0e1a;
color: #e0e0e0;
font-family: 'Silkscreen', 'Courier New', monospace;
-webkit-font-smoothing: none;
-moz-osx-font-smoothing: unset;
image-rendering: pixelated;
background: #2c2c2c;
color: #d4d4d4;
overflow: hidden;
}
@@ -21,5 +22,5 @@ body {
}
code {
font-family: 'Courier New', monospace;
font-family: 'Silkscreen', 'Courier New', monospace;
}

View File

@@ -272,6 +272,44 @@ local function drawDetail()
sendCommand(turtle.turtleID, "setHome")
end, colors.blue)
btnY = h - 7
addButton(1, btnY, 8, 2, "EQUIP L", function()
-- Send eval to equip left
modem.transmit(CHANNEL_SEND, CHANNEL_RECEIVE, {
type = "eval",
uuid = tostring(math.random(100000, 999999)),
code = "return turtle.equipLeft()",
target = turtle.turtleID
})
end, colors.purple)
addButton(10, btnY, 8, 2, "EQUIP R", function()
modem.transmit(CHANNEL_SEND, CHANNEL_RECEIVE, {
type = "eval",
uuid = tostring(math.random(100000, 999999)),
code = "return turtle.equipRight()",
target = turtle.turtleID
})
end, colors.purple)
addButton(19, btnY, 7, 2, "RENAME", function()
term.setBackgroundColor(colors.black)
term.clear()
term.setCursorPos(1, 1)
term.setTextColor(colors.yellow)
print("Enter new name:")
term.setTextColor(colors.white)
local name = read()
if name and #name > 0 then
modem.transmit(CHANNEL_SEND, CHANNEL_RECEIVE, {
type = "rename",
name = name,
target = turtle.turtleID
})
end
draw()
end, colors.lightBlue)
addButton(1, h - 1, 12, 2, "< BACK", function()
viewMode = "overview"
end, colors.gray)
@@ -363,6 +401,38 @@ local function drawManual()
sendCommand(turtle.turtleID, "refuel")
end, colors.lime)
addButton(19, h - 3, 7, 2, "SORT", function()
modem.transmit(CHANNEL_SEND, CHANNEL_RECEIVE, {
type = "eval",
uuid = tostring(math.random(100000, 999999)),
code = [[
local moved = 0
for slot = 1, 16 do
local item = turtle.getItemDetail(slot)
if item then
for target = 1, slot - 1 do
local ti = turtle.getItemDetail(target)
if not ti then
turtle.select(slot)
turtle.transferTo(target)
moved = moved + 1
break
elseif ti.name == item.name and ti.count < 64 then
turtle.select(slot)
turtle.transferTo(target)
moved = moved + 1
if turtle.getItemCount(slot) == 0 then break end
end
end
end
end
turtle.select(1)
return moved
]],
target = turtle.turtleID
})
end, colors.cyan)
addButton(19, h - 3, 7, 2, "INFO", function()
sendCommand(turtle.turtleID, "status")
end, colors.lightBlue)