Compare commits
7 Commits
d8f3d5d13c
...
5f6dbce277
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5f6dbce277 | ||
|
|
75ca027ab4 | ||
|
|
cfc891d164 | ||
|
|
5b89e0432e | ||
|
|
37bd17f26a | ||
|
|
c0865d5196 | ||
|
|
a0eaeb6712 |
@@ -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 */
|
||||
|
||||
@@ -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 ? (
|
||||
<>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user