feat: UI Overhaul
This commit is contained in:
65
extension/modules/ui/components/automation.js
Normal file
65
extension/modules/ui/components/automation.js
Normal file
@@ -0,0 +1,65 @@
|
||||
import { Styles, createElement } from '../ui_styles.js';
|
||||
|
||||
export const Automation = {
|
||||
init(container) {
|
||||
const MB = window.MagicBot;
|
||||
|
||||
const wrapper = createElement('div', Styles.panel);
|
||||
|
||||
// Header
|
||||
const header = createElement('div', Styles.flexBetween + 'margin-bottom: 5px; cursor: pointer; user-select: none;', {
|
||||
onclick: () => {
|
||||
const section = wrapper.querySelector('#section-auto');
|
||||
const arrow = wrapper.querySelector('#arrow-auto');
|
||||
if (section.style.display === 'none') {
|
||||
section.style.display = 'block';
|
||||
arrow.textContent = '▼';
|
||||
} else {
|
||||
section.style.display = 'none';
|
||||
arrow.textContent = '▶';
|
||||
}
|
||||
}
|
||||
});
|
||||
header.innerHTML = `
|
||||
<label style="${Styles.label} cursor: pointer;">Automation</label>
|
||||
<span id="arrow-auto" style="font-size: 10px; color: #aaa;">▼</span>
|
||||
`;
|
||||
|
||||
const content = createElement('div', 'display: flex; flex-direction: column; gap: 8px;', { id: 'section-auto' });
|
||||
|
||||
const createToggle = (label, prop, tooltip = '') => {
|
||||
const row = createElement('label', Styles.flexBetween + 'cursor: pointer;', { title: tooltip });
|
||||
const txt = createElement('span', '', { textContent: label });
|
||||
const chk = createElement('input', 'cursor: pointer;', {
|
||||
type: 'checkbox',
|
||||
onchange: (e) => {
|
||||
if (MB.automation) MB.automation[prop] = e.target.checked;
|
||||
}
|
||||
});
|
||||
|
||||
// Set initial state if available
|
||||
if (MB.automation && MB.automation[prop]) chk.checked = true;
|
||||
|
||||
row.appendChild(txt);
|
||||
row.appendChild(chk);
|
||||
return row;
|
||||
};
|
||||
|
||||
content.appendChild(createToggle('Auto Plant', 'autoPlant'));
|
||||
content.appendChild(createToggle('Auto Harvest', 'autoHarvest'));
|
||||
content.appendChild(createToggle('Auto Sell', 'autoSell'));
|
||||
content.appendChild(createToggle('Auto Feed Pets', 'autoFeed'));
|
||||
|
||||
// Smart Harvest Special
|
||||
const div = createElement('div', 'border-top: 1px solid #444; margin: 4px 0;');
|
||||
content.appendChild(div);
|
||||
|
||||
const smartToggle = createToggle('Smart Harvest', 'smartHarvest', 'Waits for optimal mutations (Gold->Frozen, Long->Wet)');
|
||||
smartToggle.querySelector('span').style.color = Styles.colors.accent;
|
||||
content.appendChild(smartToggle);
|
||||
|
||||
wrapper.appendChild(header);
|
||||
wrapper.appendChild(content);
|
||||
container.appendChild(wrapper);
|
||||
}
|
||||
};
|
||||
119
extension/modules/ui/components/diet.js
Normal file
119
extension/modules/ui/components/diet.js
Normal file
@@ -0,0 +1,119 @@
|
||||
import { Styles, createElement } from '../ui_styles.js';
|
||||
|
||||
export const Diet = {
|
||||
init(container) {
|
||||
const MB = window.MagicBot;
|
||||
|
||||
const wrapper = createElement('div', Styles.panel);
|
||||
|
||||
// Header
|
||||
const header = createElement('div', Styles.flexBetween + 'margin-bottom: 5px; cursor: pointer; user-select: none;', {
|
||||
onclick: () => {
|
||||
const section = wrapper.querySelector('#section-diet');
|
||||
const arrow = wrapper.querySelector('#arrow-diet');
|
||||
if (section.style.display === 'none') {
|
||||
section.style.display = 'block';
|
||||
arrow.textContent = '▼';
|
||||
} else {
|
||||
section.style.display = 'none';
|
||||
arrow.textContent = '▶';
|
||||
}
|
||||
}
|
||||
});
|
||||
header.innerHTML = `
|
||||
<label style="${Styles.label} cursor: pointer;">Pet Diet</label>
|
||||
<span id="arrow-diet" style="font-size: 10px; color: #aaa;">▼</span>
|
||||
`;
|
||||
|
||||
const content = createElement('div', '', { id: 'section-diet' }); // Default open? or closed? Display logic handles it.
|
||||
|
||||
// Controls
|
||||
const controls = createElement('div', 'display: flex; gap: 5px; margin-bottom: 5px;');
|
||||
const createCtrl = (txt, fn) => {
|
||||
return createElement('button', Styles.button + 'background: #444; font-size: 10px; padding: 2px 6px; flex: 1;', {
|
||||
textContent: txt,
|
||||
onclick: fn
|
||||
});
|
||||
};
|
||||
|
||||
controls.appendChild(createCtrl('All', () => {
|
||||
const list = document.getElementById('diet-list');
|
||||
if (!list) return;
|
||||
list.querySelectorAll('input').forEach(cb => { if (!cb.checked) cb.click(); });
|
||||
}));
|
||||
|
||||
controls.appendChild(createCtrl('None', () => {
|
||||
const list = document.getElementById('diet-list');
|
||||
if (!list) return;
|
||||
list.querySelectorAll('input').forEach(cb => { if (cb.checked) cb.click(); });
|
||||
}));
|
||||
|
||||
content.appendChild(controls);
|
||||
|
||||
// List Container
|
||||
const listContainer = createElement('div', 'max-height: 150px; overflow-y: auto; background: rgba(0,0,0,0.2); padding: 5px; border-radius: 4px;', {
|
||||
id: 'diet-list'
|
||||
});
|
||||
content.appendChild(listContainer);
|
||||
|
||||
wrapper.appendChild(header);
|
||||
wrapper.appendChild(content);
|
||||
container.appendChild(wrapper);
|
||||
|
||||
// Initial Populate
|
||||
this.populate(listContainer);
|
||||
|
||||
// Subscribe to state updates to refresh list
|
||||
if (MB.on) {
|
||||
MB.on('state_updated', () => {
|
||||
if (listContainer.children.length === 0) this.populate(listContainer);
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
populate(list) {
|
||||
const MB = window.MagicBot;
|
||||
if (!list) return;
|
||||
|
||||
// Preserve checks if re-populating not fully implemented but okay for now to clear
|
||||
list.innerHTML = '';
|
||||
|
||||
let crops = ["Carrot", "Tomato", "Potato", "Corn", "Wheat", "Strawberry", "Blueberry", "Pumpkin", "Watermelon", "Radish", "Cabbage", "Lettuce", "Pepper", "Apple", "Orange", "Banana", "Coconut", "Grape", "Lemon", "Lime", "Peach", "Pear", "Plum", "Cherry", "Mango", "Pineapple", "Aloe", "Daffodil", "OrangeTulip"];
|
||||
|
||||
// Try to merge with shop data if present
|
||||
if (MB && MB.state && MB.state.shops && MB.state.shops.seed && MB.state.shops.seed.inventory) {
|
||||
const shopCrops = MB.state.shops.seed.inventory.map(i => i.species);
|
||||
crops = [...new Set([...crops, ...shopCrops])];
|
||||
} else if (MB.state && MB.state.inventory && MB.state.inventory.items) {
|
||||
// Also scan inventory for seeds
|
||||
MB.state.inventory.items.forEach(i => {
|
||||
if (i.itemType === 'Seed' && i.species) crops.push(i.species);
|
||||
});
|
||||
crops = [...new Set(crops)];
|
||||
}
|
||||
|
||||
crops.sort();
|
||||
|
||||
crops.forEach(crop => {
|
||||
const label = createElement('label', 'display: flex; align-items: center; gap: 4px; font-size: 10px; cursor: pointer; user-select: none; padding: 2px 0;');
|
||||
|
||||
const checkbox = createElement('input', 'cursor: pointer;', { type: 'checkbox' });
|
||||
|
||||
if (MB.automation && MB.automation.petDiet && MB.automation.petDiet.has(crop)) {
|
||||
checkbox.checked = true;
|
||||
}
|
||||
|
||||
checkbox.onchange = (e) => {
|
||||
if (!MB.automation) return;
|
||||
if (!MB.automation.petDiet) MB.automation.petDiet = new Set();
|
||||
|
||||
if (e.target.checked) MB.automation.petDiet.add(crop);
|
||||
else MB.automation.petDiet.delete(crop);
|
||||
};
|
||||
|
||||
label.appendChild(checkbox);
|
||||
label.appendChild(document.createTextNode(crop));
|
||||
list.appendChild(label);
|
||||
});
|
||||
}
|
||||
};
|
||||
92
extension/modules/ui/components/harvest.js
Normal file
92
extension/modules/ui/components/harvest.js
Normal file
@@ -0,0 +1,92 @@
|
||||
import { Styles, createElement } from '../ui_styles.js';
|
||||
|
||||
export const Harvest = {
|
||||
init(container) {
|
||||
const MB = window.MagicBot;
|
||||
|
||||
const wrapper = createElement('div', Styles.panel);
|
||||
|
||||
// Header
|
||||
const header = createElement('div', Styles.flexBetween + 'margin-bottom: 5px; cursor: pointer; user-select: none;', {
|
||||
onclick: () => {
|
||||
const section = wrapper.querySelector('#section-hv');
|
||||
const arrow = wrapper.querySelector('#arrow-hv');
|
||||
if (section.style.display === 'none') {
|
||||
section.style.display = 'block';
|
||||
arrow.textContent = '▼';
|
||||
} else {
|
||||
section.style.display = 'none';
|
||||
arrow.textContent = '▶';
|
||||
}
|
||||
}
|
||||
});
|
||||
header.innerHTML = `
|
||||
<label style="${Styles.label} cursor: pointer;">Harvest</label>
|
||||
<span id="arrow-hv" style="font-size: 10px; color: #aaa;">▼</span>
|
||||
`;
|
||||
|
||||
const content = createElement('div', '', { id: 'section-hv' });
|
||||
|
||||
// Range Inputs
|
||||
const rangeGrid = createElement('div', 'display: grid; grid-template-columns: 1fr 1fr; gap: 8px; margin-bottom: 8px;');
|
||||
|
||||
const createInput = (label, id, val) => {
|
||||
const div = createElement('div');
|
||||
div.appendChild(createElement('span', 'font-size: 10px; color: #888;', { textContent: label }));
|
||||
div.appendChild(createElement('input', Styles.input, { type: 'number', id, value: val }));
|
||||
return div;
|
||||
};
|
||||
|
||||
rangeGrid.appendChild(createInput('Start Slot', 'hv-start', '140'));
|
||||
rangeGrid.appendChild(createInput('End Slot', 'hv-end', '160'));
|
||||
content.appendChild(rangeGrid);
|
||||
|
||||
// Options Inputs
|
||||
const optGrid = createElement('div', 'display: grid; grid-template-columns: 1fr 1fr; gap: 8px; margin-bottom: 10px;');
|
||||
optGrid.appendChild(createInput('Iter. (Count)', 'hv-count', '1'));
|
||||
optGrid.appendChild(createInput('Delay (ms)', 'hv-delay', '20'));
|
||||
content.appendChild(optGrid);
|
||||
|
||||
// Quick Buttons for Range
|
||||
const quickRange = createElement('div', 'display: flex; gap: 4px; margin-bottom: 8px;');
|
||||
const addQuickBtn = (txt, s, e) => {
|
||||
quickRange.appendChild(createElement('button', Styles.button + 'background: #444; font-size: 9px; padding: 2px 6px; flex: 1;', {
|
||||
textContent: txt,
|
||||
onclick: () => {
|
||||
document.getElementById('hv-start').value = s;
|
||||
document.getElementById('hv-end').value = e;
|
||||
}
|
||||
}));
|
||||
};
|
||||
addQuickBtn('All (0-199)', 0, 199);
|
||||
addQuickBtn('Left (0-99)', 0, 99);
|
||||
addQuickBtn('Right (100-199)', 100, 199);
|
||||
content.appendChild(quickRange);
|
||||
|
||||
// Actions
|
||||
const btnRun = createElement('button', Styles.button + 'background: ' + Styles.colors.success + '; width: 100%;', {
|
||||
textContent: 'Run Harvest',
|
||||
onclick: () => {
|
||||
const s = parseInt(document.getElementById('hv-start').value);
|
||||
const e = parseInt(document.getElementById('hv-end').value);
|
||||
const c = parseInt(document.getElementById('hv-count').value);
|
||||
const d = parseInt(document.getElementById('hv-delay').value);
|
||||
if (MB && MB.harvestLoop) MB.harvestLoop(s, e, c, d);
|
||||
}
|
||||
});
|
||||
content.appendChild(btnRun);
|
||||
|
||||
// Sell All (moved here from root)
|
||||
const btnSell = createElement('button', Styles.button + 'background: ' + Styles.colors.warning + '; width: 100%; margin-top: 10px;', {
|
||||
textContent: 'Sell All Crops',
|
||||
onclick: () => {
|
||||
if (MB && MB.sellAll) MB.sellAll();
|
||||
}
|
||||
});
|
||||
content.appendChild(btnSell);
|
||||
|
||||
wrapper.appendChild(header);
|
||||
wrapper.appendChild(content);
|
||||
container.appendChild(wrapper);
|
||||
}
|
||||
};
|
||||
78
extension/modules/ui/components/teleport.js
Normal file
78
extension/modules/ui/components/teleport.js
Normal file
@@ -0,0 +1,78 @@
|
||||
import { Styles, createElement } from '../ui_styles.js';
|
||||
|
||||
export const Teleport = {
|
||||
init(container) {
|
||||
const MB = window.MagicBot;
|
||||
|
||||
const wrapper = createElement('div', Styles.panel);
|
||||
|
||||
// Header
|
||||
const header = createElement('div', Styles.flexBetween + 'margin-bottom: 5px; cursor: pointer; user-select: none;', {
|
||||
onclick: () => {
|
||||
const section = wrapper.querySelector('#section-tp');
|
||||
const arrow = wrapper.querySelector('#arrow-tp');
|
||||
if (section.style.display === 'none') {
|
||||
section.style.display = 'block';
|
||||
arrow.textContent = '▼';
|
||||
} else {
|
||||
section.style.display = 'none';
|
||||
arrow.textContent = '▶';
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
header.innerHTML = `
|
||||
<label style="${Styles.label} cursor: pointer;">Teleport</label>
|
||||
<span id="arrow-tp" style="font-size: 10px; color: #aaa;">▼</span>
|
||||
`;
|
||||
|
||||
// Content
|
||||
const content = createElement('div', '', { id: 'section-tp' });
|
||||
|
||||
const inputsDiv = createElement('div', 'display: flex; gap: 8px;');
|
||||
|
||||
const inpX = createElement('input', Styles.input, { type: 'number', placeholder: 'X', value: '15', id: 'tp-x' });
|
||||
const inpY = createElement('input', Styles.input, { type: 'number', placeholder: 'Y', value: '15', id: 'tp-y' });
|
||||
|
||||
const btnGo = createElement('button', Styles.button + 'background: ' + Styles.colors.primary, {
|
||||
textContent: 'Go',
|
||||
onclick: () => {
|
||||
const x = parseInt(inpX.value);
|
||||
const y = parseInt(inpY.value);
|
||||
if (MB && MB.teleport) MB.teleport(x, y);
|
||||
}
|
||||
});
|
||||
|
||||
inputsDiv.appendChild(inpX);
|
||||
inputsDiv.appendChild(inpY);
|
||||
inputsDiv.appendChild(btnGo);
|
||||
|
||||
content.appendChild(inputsDiv);
|
||||
|
||||
// Pre-defined Locations (Bonus feature)
|
||||
const quickLocs = createElement('div', 'display: flex; gap: 5px; margin-top: 5px;');
|
||||
const locs = [
|
||||
{ name: 'Home', x: 15, y: 15 },
|
||||
{ name: 'Market', x: 50, y: 50 } // Example coordinates, adjust if known
|
||||
];
|
||||
|
||||
locs.forEach(loc => {
|
||||
const btn = createElement('button', Styles.button + 'background: #333; font-size: 10px; padding: 4px 8px;', {
|
||||
textContent: loc.name,
|
||||
onclick: () => {
|
||||
inpX.value = loc.x;
|
||||
inpY.value = loc.y;
|
||||
if (MB && MB.teleport) MB.teleport(loc.x, loc.y);
|
||||
}
|
||||
});
|
||||
quickLocs.appendChild(btn);
|
||||
});
|
||||
|
||||
content.appendChild(quickLocs);
|
||||
|
||||
wrapper.appendChild(header);
|
||||
wrapper.appendChild(content);
|
||||
|
||||
container.appendChild(wrapper);
|
||||
}
|
||||
};
|
||||
169
extension/modules/ui/components/visualizers.js
Normal file
169
extension/modules/ui/components/visualizers.js
Normal file
@@ -0,0 +1,169 @@
|
||||
import { Styles, createElement } from '../ui_styles.js';
|
||||
import { WindowManager } from '../overlays/window_manager.js';
|
||||
|
||||
export const Visualizers = {
|
||||
init(container) {
|
||||
const MB = window.MagicBot;
|
||||
|
||||
this.createLogOverlay();
|
||||
|
||||
const wrapper = createElement('div', 'margin-top: 10px; border-top: 1px solid #444; padding-top: 10px;');
|
||||
|
||||
// Logs and HUDs
|
||||
const btnRow = createElement('div', 'display: flex; gap: 5px; margin-bottom: 5px;');
|
||||
|
||||
const btnLogs = createElement('button', Styles.button + 'flex: 1; background: #5c6bc0;', {
|
||||
textContent: 'Logs',
|
||||
onclick: () => {
|
||||
const el = document.getElementById('magic-bot-logs');
|
||||
if (el) el.style.display = el.style.display === 'none' ? 'flex' : 'none';
|
||||
}
|
||||
});
|
||||
|
||||
const btnState = createElement('button', Styles.button + 'flex: 1; background: #ab47bc;', {
|
||||
textContent: 'All HUDs',
|
||||
onclick: () => {
|
||||
if (window.MagicBot_Overlays) {
|
||||
window.MagicBot_Overlays.openAll();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
btnRow.appendChild(btnLogs);
|
||||
btnRow.appendChild(btnState);
|
||||
wrapper.appendChild(btnRow);
|
||||
|
||||
// Individual toggles for specific useful overlays
|
||||
const extraRow = createElement('div', 'display: flex; gap: 5px;');
|
||||
|
||||
const btnPets = createElement('button', Styles.button + 'flex: 1; background: #ffa726; color: #222;', {
|
||||
textContent: 'Pets',
|
||||
onclick: () => {
|
||||
if (window.MagicBot_Overlays && window.MagicBot_Overlays.openPets) {
|
||||
window.MagicBot_Overlays.openPets();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const btnModel = createElement('button', Styles.button + 'flex: 1; background: #26a69a; color: #eee;', {
|
||||
textContent: 'Model',
|
||||
onclick: () => {
|
||||
if (window.MagicBot_Overlays && window.MagicBot_Overlays.openModel) {
|
||||
window.MagicBot_Overlays.openModel();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
extraRow.appendChild(btnPets);
|
||||
extraRow.appendChild(btnModel);
|
||||
wrapper.appendChild(extraRow);
|
||||
|
||||
container.appendChild(wrapper);
|
||||
|
||||
// Bind Status Indicator
|
||||
MB.on('socket_connected', (connected) => {
|
||||
const el = document.getElementById('mb-status-ind');
|
||||
if (el) {
|
||||
el.textContent = connected ? '● Connected' : '● Disconnected';
|
||||
el.style.color = connected ? Styles.colors.success : Styles.colors.danger;
|
||||
}
|
||||
});
|
||||
|
||||
MB.on('log', (msg) => {
|
||||
this.logToOverlay(msg.type, msg.data);
|
||||
});
|
||||
},
|
||||
|
||||
createLogOverlay() {
|
||||
if (document.getElementById('magic-bot-logs')) return;
|
||||
|
||||
const logOverlay = createElement('div', `
|
||||
display: none;
|
||||
position: fixed;
|
||||
bottom: 20px;
|
||||
left: 20px;
|
||||
width: 600px;
|
||||
height: 300px;
|
||||
background: rgba(10, 10, 15, 0.95);
|
||||
border: 1px solid #444;
|
||||
border-radius: 8px;
|
||||
z-index: 9999999;
|
||||
font-family: 'Consolas', 'Monaco', monospace;
|
||||
font-size: 11px;
|
||||
color: #eee;
|
||||
flex-direction: column;
|
||||
box-shadow: 0 4px 15px rgba(0,0,0,0.6);
|
||||
resize: both;
|
||||
overflow: hidden;
|
||||
`, { id: 'magic-bot-logs' });
|
||||
|
||||
logOverlay.innerHTML = `
|
||||
<div style="padding: 5px 10px; background: #222; border-bottom: 1px solid #444; display: flex; justify-content: space-between; align-items: center; cursor: move;" id="log-header">
|
||||
<span style="font-weight: bold; color: #aaa;">Network Logs</span>
|
||||
<div style="display:flex; gap:10px; align-items:center;">
|
||||
<input id="log-filter-input" type="text" placeholder="Filter..." style="background:#333; border:1px solid #555; color:#eee; padding:2px 5px; border-radius:3px; font-size:10px; width:100px;">
|
||||
<button id="btn-clear-logs" style="background: none; border: none; color: #888; cursor: pointer;">Clear</button>
|
||||
<button style="background: none; border: none; color: #ff5252; cursor: pointer;" onclick="this.parentElement.parentElement.parentElement.style.display='none'">X</button>
|
||||
</div>
|
||||
</div>
|
||||
<div id="log-content" style="flex: 1; overflow-y: auto; padding: 5px; word-break: break-all;"></div>
|
||||
`;
|
||||
|
||||
document.body.appendChild(logOverlay);
|
||||
|
||||
const header = logOverlay.querySelector('#log-header');
|
||||
let isDragging = false, offX = 0, offY = 0;
|
||||
header.onmousedown = (e) => {
|
||||
if (e.target.tagName === 'INPUT' || e.target.tagName === 'BUTTON') return;
|
||||
isDragging = true; offX = e.clientX - logOverlay.offsetLeft; offY = e.clientY - logOverlay.offsetTop;
|
||||
};
|
||||
window.addEventListener('mousemove', (e) => { if (isDragging) { logOverlay.style.left = (e.clientX - offX) + 'px'; logOverlay.style.top = (e.clientY - offY) + 'px'; } });
|
||||
window.addEventListener('mouseup', () => isDragging = false);
|
||||
|
||||
document.getElementById('btn-clear-logs').onclick = () => {
|
||||
document.getElementById('log-content').innerHTML = '';
|
||||
};
|
||||
|
||||
// Re-filter on input
|
||||
document.getElementById('log-filter-input').addEventListener('input', (e) => {
|
||||
const term = e.target.value.toLowerCase();
|
||||
const lines = document.getElementById('log-content').children;
|
||||
for (let line of lines) {
|
||||
line.style.display = line.textContent.toLowerCase().includes(term) ? 'block' : 'none';
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
logToOverlay(type, data) {
|
||||
const container = document.getElementById('log-content');
|
||||
if (!container) return;
|
||||
|
||||
const filterInput = document.getElementById('log-filter-input');
|
||||
const filterTerm = filterInput ? filterInput.value.toLowerCase() : '';
|
||||
|
||||
const isAtBottom = (container.scrollHeight - container.scrollTop - container.clientHeight) < 50;
|
||||
const line = createElement('div', 'border-bottom: 1px solid #222; padding: 2px 0; cursor: pointer;');
|
||||
line.title = 'Click to copy';
|
||||
|
||||
const timestamp = new Date().toLocaleTimeString();
|
||||
const color = type === 'TX' ? Styles.colors.success : Styles.colors.primary;
|
||||
let content = data;
|
||||
try { if (typeof data === 'string' && (data.startsWith('{') || data.startsWith('['))) content = JSON.stringify(JSON.parse(data)); } catch (e) { }
|
||||
|
||||
line.innerHTML = `<span style="color: #666;">[${timestamp}]</span> <span style="color: ${color}; font-weight: bold;">${type}</span> <span style="color: #ccc;">${content}</span>`;
|
||||
|
||||
line.onclick = () => {
|
||||
navigator.clipboard.writeText(data);
|
||||
line.style.background = 'rgba(255,255,255,0.1)';
|
||||
setTimeout(() => line.style.background = 'transparent', 200);
|
||||
};
|
||||
|
||||
if (filterTerm && !line.textContent.toLowerCase().includes(filterTerm)) {
|
||||
line.style.display = 'none';
|
||||
}
|
||||
|
||||
container.appendChild(line);
|
||||
if (isAtBottom) container.scrollTop = container.scrollHeight;
|
||||
if (container.children.length > 500) container.removeChild(container.firstChild);
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user