您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Modal framework for Idle-Pixel with a Settings manager, modal optional for settings
此脚本不应直接安装。它是供其他脚本使用的外部库,要使用该库请加入元指令 // @require https://update.greasyfork.icu/scripts/500003/1408507/IdlePixel%2B%20Zlef%27s%20Modal%20%20Settings%20Manager.js
// ==UserScript== // @name IdlePixel+ Zlef's Modal & Settings Manager // @namespace com.zlef.modallibrary // @version 1.0.2 // @description Modal framework for Idle-Pixel with a Settings manager, modal optional for settings // @author Zlef // @match *://idle-pixel.com/* // @grant none // @license MIT // ==/UserScript== (function() { 'use strict'; if(window.ZlefsModal ) { return; } class ZlefsModal { constructor(pluginName) { this.pluginName = pluginName; this.modals = []; this.overlay = null; this.closeCallbacks = []; this.initCustomCSS(); } initCustomCSS() { const css = ` .zlefs-modal { position: absolute; /* Ensure it works with draggable */ background-color: #fff; border-radius: 8px; box-shadow: 0 0 10px rgba(0, 0, 0, 0.5); max-height: 80vh; max-width: 80vh; display: flex; flex-direction: column; overflow: hidden; /* Ensures rounded corners are visible */ } .zlefs-modal-header { cursor: move; padding-bottom: 40px; margin-bottom: 10px; background-color: #f1f1f1; border-bottom: 1px solid #ccc; position: sticky; top: 0; left: 0; right: 0; width: 100%; box-sizing: border-box; border-top-left-radius: 8px; /* Rounded corners */ border-top-right-radius: 8px; /* Rounded corners */ z-index: 10; /* Ensure header is above the content */ } .zlefs-modal-header-text { position: absolute; top: 50%; left: 10px; transform: translateY(-50%); pointer-events: none; height: 20px; font-weight: bold; } .zlefs-modal-content { padding: 0 20px 20px 20px; overflow-y: auto; flex-grow: 1; /* Allows the content to grow and fill the remaining space */ } .zlefs-close-button { position: absolute; top: 10px; right: 10px; background: none; border: none; cursor: pointer; z-index: 11; /* Ensure close button is above the header */ } #zlefs-modal-overlay { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(0, 0, 0, 0.7); z-index: 1000; display: flex; justify-content: center; align-items: center; } .zlefs-section { border: 1px solid black; background-color: white; padding: 10px 10px 0px 10px; margin-bottom: 10px; } .zlefs-section-title { margin-bottom: 10px; margin-left: 2px; font-weight: bold; cursor: pointer; } .zlefs-section-content { display: none; } .zlefs-section-content.zlefs-hidden { display: block; } .zlefs-item { width: 100%; display: flex; justify-content: space-between; align-items: center; margin-bottom: 5px; } .zlefs-checkbox-container { display: flex; justify-content: space-between; align-items: center; width: 100%; max-width: 400px; } .zlefs-checkbox-label { flex: 1; text-align: left; margin-right: 10px; } .zlefs-checkbox-input { flex: 0; text-align: right; } .zlefs-vertical-divider { width: 1px; background-color: #ccc; padding: 0; display: flex; } `; this.addCSS(css) } addCSS(cssString) { const style = document.createElement('style'); style.type = 'text/css'; style.appendChild(document.createTextNode(cssString)); document.head.appendChild(style); } createOverlay() { if (this.overlay) return; this.overlay = document.createElement('div'); this.overlay.id = 'zlefs-modal-overlay'; this.overlay.addEventListener('click', (event) => { if (event.target === this.overlay) { this.closeTopModal(); } }); document.body.appendChild(this.overlay); } addModal(content, name, width = 'auto', height = 'auto', closeCallback = null) { this.createOverlay(); const modalBox = document.createElement('div'); modalBox.className = 'zlefs-modal'; modalBox.style.width = typeof width === 'number' ? `${width}px` : width; modalBox.style.height = typeof height === 'number' ? `${height}px` : height; const closeButton = document.createElement('button'); closeButton.className = 'zlefs-close-button'; closeButton.textContent = '✖'; closeButton.addEventListener('click', () => { this.closeModal(modalBox); if (closeCallback) closeCallback(); }); const header = document.createElement('div'); header.className = 'zlefs-modal-header'; const nameSpan = document.createElement('span'); nameSpan.textContent = name; nameSpan.className = 'zlefs-modal-header-text'; header.appendChild(nameSpan); const contentWrapper = document.createElement('div'); contentWrapper.className = 'zlefs-modal-content'; contentWrapper.appendChild(content); modalBox.appendChild(header); modalBox.appendChild(closeButton); modalBox.appendChild(contentWrapper); this.overlay.appendChild(modalBox); this.modals.push(modalBox); this.closeCallbacks.push(closeCallback); this.makeDraggable(modalBox, header); // Ensure modal is fully rendered before centering setTimeout(() => { this.centreModal(modalBox); }, 0); } centreModal(modalBox) { if (!modalBox) { console.log("modalBox is not defined. If you're seeing this good luck lol."); return; } const rect = modalBox.getBoundingClientRect(); // Window dimensions const windowHeight = window.innerHeight; const windowWidth = window.innerWidth; console.log(`windowHeight: ${windowHeight}`); console.log(`windowWidth: ${windowWidth}`); console.log(`modalBox height: ${rect.height}`); console.log(`modalBox width: ${rect.width}`); const newModalTop = (windowHeight - rect.height) / 2; const newModalLeft = (windowWidth - rect.width) / 2; console.log(`newModalTop: ${Math.floor(newModalTop)}`); console.log(`newModalLeft: ${Math.floor(newModalLeft)}`); // Set the top and left positions modalBox.style.position = 'absolute'; // Ensure the positioning context modalBox.style.top = `${Math.floor(newModalTop)}px`; modalBox.style.left = `${Math.floor(newModalLeft)}px`; // Set min width as moving the modal against the side can squish it. Should probably solve that behavior instead but here we are. modalBox.style.minWidth = `${rect.width}px`; // Additional log to check the final style console.log(`modalBox final top: ${modalBox.style.top}`); console.log(`modalBox final left: ${modalBox.style.left}`); console.log(`modalBox final minWidth: ${modalBox.style.minWidth}`); } closeModal(modal) { const modalIndex = this.modals.indexOf(modal); if (modalIndex !== -1) { this.modals.splice(modalIndex, 1); this.closeCallbacks.splice(modalIndex, 1); } if (modal && modal.parentElement) { this.overlay.removeChild(modal); } if (this.modals.length === 0 && this.overlay) { document.body.removeChild(this.overlay); this.overlay = null; } } closeTopModal() { if (this.modals.length > 0) { const topmodal = this.modals[this.modals.length - 1]; const topCallback = this.closeCallbacks[this.closeCallbacks.length - 1]; this.closeModal(topmodal); if (topCallback) topCallback(); } } makeDraggable(modalBox, header) { let offsetX = 0, offsetY = 0, startX = 0, startY = 0; const onMouseDown = (e) => { e.preventDefault(); startX = e.clientX; startY = e.clientY; offsetX = modalBox.offsetLeft; offsetY = modalBox.offsetTop; document.addEventListener('mousemove', onMouseMove); document.addEventListener('mouseup', onMouseUp); }; const onMouseMove = (e) => { e.preventDefault(); let dx = e.clientX - startX; let dy = e.clientY - startY; let newLeft = offsetX + dx; let newTop = offsetY + dy; modalBox.style.left = `${newLeft}px`; modalBox.style.top = `${newTop}px`; }; const onMouseUp = (e) => { const rect = modalBox.getBoundingClientRect(); const corners = { topLeft: { left: rect.left, top: rect.top }, bottomRight: { left: rect.right, top: rect.bottom } }; if (rect.left < 0) { modalBox.style.left = '0px'; } if (rect.top < 0) { modalBox.style.top = '0px'; } if (rect.right > window.innerWidth) { modalBox.style.left = (window.innerWidth - rect.width) + 'px'; } if (rect.bottom > window.innerHeight) { modalBox.style.top = (window.innerHeight - rect.height) + 'px'; } document.removeEventListener('mousemove', onMouseMove); document.removeEventListener('mouseup', onMouseUp); }; header.addEventListener('mousedown', onMouseDown); } repositionModal(modalBox) { if (!modalBox) return; const rect = modalBox.getBoundingClientRect(); const corners = { topLeft: { left: rect.left, top: rect.top }, bottomRight: { left: rect.right, top: rect.bottom } }; if (rect.left < 0) { modalBox.style.left = (rect.width) + 'px'; } if (rect.top < 0) { modalBox.style.top = (rect.height) + 'px'; } if (rect.right > window.innerWidth) { modalBox.style.left = (window.innerWidth - (rect.width)) + 'px'; } if (rect.bottom > window.innerHeight) { modalBox.style.top = (window.innerHeight - (rect.height)) + 'px'; } } titleCaseUnderscore(input) { const words = input.split('_'); const convertedText = words.map(word => word.charAt(0).toUpperCase() + word.slice(1)).join(' '); return convertedText; } addTitle(parent, text, level = 2, textAlign = 'left') { const title = document.createElement(`h${level}`); title.textContent = text; title.style.textAlign = textAlign; parent.appendChild(title); return title } addSection(parent, sectionTitleText) { const sectionDiv = document.createElement('div'); sectionDiv.className = 'zlefs-section'; const sectionTitle = document.createElement('div'); sectionTitle.className = 'zlefs-section-title'; sectionTitle.textContent = sectionTitleText; const sectionContent = document.createElement('div'); sectionContent.className = 'zlefs-section-content'; sectionTitle.addEventListener('click', () => { const modal = this.findParentmodal(sectionDiv); const originalTop = modal.getBoundingClientRect().top; const originalScrollTop = document.documentElement.scrollTop || document.body.scrollTop; sectionContent.classList.toggle('zlefs-hidden'); const newTop = modal.getBoundingClientRect().top; const deltaHeight = parseInt((originalTop - newTop)); const currentTop = parseInt(window.getComputedStyle(modal).top); modal.style.top = `${(currentTop + deltaHeight)}px`; this.repositionModal(modal); }); sectionDiv.appendChild(sectionTitle); sectionDiv.appendChild(sectionContent); parent.appendChild(sectionDiv); return sectionContent; } findParentmodal(element) { while (element && !element.classList.contains('zlefs-modal')) { element = element.parentElement; } return element; } addDiv(parent) { const div = document.createElement('div'); parent.appendChild(div); return div; } addContainer(parent) { const container = document.createElement('div'); container.className = 'container'; parent.appendChild(container); return container; } addRow(parent) { const row = document.createElement('div'); row.className = 'row'; parent.appendChild(row); return row; } addCol(parent, size = '', align = 'left') { const col = document.createElement('div'); col.className = size ? `col-${size}` : 'col'; col.style.textAlign = align === 'centre' ? 'center' : align; parent.appendChild(col); return col; } addDivider(parent, margin = 5) { const divider = document.createElement('hr'); divider.style.width = `calc(100% - ${2 * margin}px)`; divider.style.marginLeft = `${margin}px`; divider.style.marginRight = `${margin}px`; parent.appendChild(divider); return divider; } addVDivider(parent) { const divider = document.createElement('div'); divider.className = 'zlefs-vertical-divider'; parent.appendChild(divider); } addInput(parent, type, value, placeholder, min, max, onChange, label = undefined) { const inputContainer = document.createElement('div'); inputContainer.style.display = 'flex'; inputContainer.style.alignItems = 'center'; inputContainer.style.marginBottom = '10px'; if (label){ const inputLabel = document.createElement('label'); inputLabel.textContent = this.titleCaseUnderscore(label); inputLabel.style.marginRight = '10px'; inputLabel.style.flex = '1'; inputLabel.style.cursor = 'pointer'; inputContainer.appendChild(inputLabel); } const input = document.createElement('input'); input.type = type; input.value = value; input.style.flex = '0'; if (placeholder) input.placeholder = placeholder; if (min !== undefined) input.min = min; if (max !== undefined) input.max = max; input.addEventListener('input', (event) => { let inputValue = event.target.value; if (type === 'number') { if (inputValue < min) inputValue = min; if (inputValue > max) inputValue = max; event.target.value = inputValue; } onChange(inputValue); }); inputContainer.appendChild(input); parent.appendChild(inputContainer); } addCheckbox(parent, checked, onChange, label = undefined) { const checkboxContainer = document.createElement('div'); checkboxContainer.className = 'zlefs-checkbox-container'; checkboxContainer.style.display = 'flex'; checkboxContainer.style.alignItems = 'center'; checkboxContainer.style.marginBottom = '10px'; const checkbox = document.createElement('input'); checkbox.type = 'checkbox'; checkbox.checked = checked; checkbox.className = 'zlefs-checkbox-input'; checkbox.style.flex = '0'; checkbox.style.cursor = 'pointer'; checkbox.addEventListener('change', (event) => onChange(event.target.checked)); if (label){ const checkboxLabel = document.createElement('label'); checkboxLabel.textContent = this.titleCaseUnderscore(label); checkboxLabel.className = 'zlefs-checkbox-label'; checkboxLabel.style.flex = '1'; checkboxLabel.style.cursor = 'pointer'; checkboxContainer.appendChild(checkboxLabel); checkboxLabel.addEventListener('click', () => checkbox.click()); } checkboxContainer.appendChild(checkbox); parent.appendChild(checkboxContainer); } addButton(parent, text, onClick, className = 'btn') { const button = document.createElement('button'); button.textContent = text; button.className = className; button.addEventListener('click', onClick); parent.appendChild(button); return button; } addCombobox(parent, options, selectedValue, onChange, label = undefined) { const comboboxContainer = document.createElement('div'); comboboxContainer.style.display = 'flex'; comboboxContainer.style.alignItems = 'center'; comboboxContainer.style.marginBottom = '10px'; if (label) { const comboboxLabel = document.createElement('label'); comboboxLabel.textContent = this.titleCaseUnderscore(label); comboboxLabel.style.marginRight = '10px'; comboboxLabel.style.flex = '1'; comboboxLabel.style.cursor = 'pointer'; comboboxContainer.appendChild(comboboxLabel); } const combobox = document.createElement('select'); combobox.style.flex = '0'; options.forEach(option => { const optionElement = document.createElement('option'); optionElement.value = option; optionElement.text = option; if (option === selectedValue) optionElement.selected = true; combobox.appendChild(optionElement); }); combobox.addEventListener('change', (event) => onChange(event.target.value)); comboboxContainer.appendChild(combobox); parent.appendChild(comboboxContainer); return comboboxContainer; } } class ZlefsSettingsManager { constructor(prefix, settings) { this.prefix = prefix; this.defaultSettings = JSON.parse(JSON.stringify(settings)); this.settings = settings; this.modalContent = null; this.loadSettings(); } saveSettings() { const settingsToSave = {}; const processSettings = (settings, saveObj) => { for (const key in settings) { const setting = settings[key]; if (setting.type === 'button') { continue; // Skip button types } if (setting.type === 'section') { saveObj[key] = { type: 'section', name: setting.name, settings: {} }; processSettings(setting.settings, saveObj[key].settings); } else if (setting.type === 'multicheckbox') { saveObj[key] = { type: 'multicheckbox', values: {} }; for (const subKey in setting.values) { saveObj[key].values[subKey] = setting.values[subKey]; } } else if (setting.type === 'combobox') { saveObj[key] = { type: 'combobox', options: setting.options, value: setting.value }; } else { saveObj[key] = { type: setting.type, value: setting.value }; } } }; processSettings(this.settings, settingsToSave); localStorage.setItem(`${this.prefix}_settings`, JSON.stringify(settingsToSave)); } loadSettings() { const savedSettings = JSON.parse(localStorage.getItem(`${this.prefix}_settings`)); if (savedSettings) { const processLoadedSettings = (loadedSettings, currentSettings) => { for (const key in currentSettings) { if (loadedSettings[key] !== undefined) { const setting = currentSettings[key]; if (setting.type === 'button') { continue; // Skip button types } if (setting.type === 'section') { processLoadedSettings(loadedSettings[key].settings, setting.settings); } else if (setting.type === 'multicheckbox') { for (const subKey in setting.values) { setting.values[subKey] = loadedSettings[key].values[subKey] !== undefined ? loadedSettings[key].values[subKey] : setting.values[subKey]; } } else { setting.value = loadedSettings[key].value; } } } for (const key in loadedSettings) { if (currentSettings[key] === undefined) { delete loadedSettings[key]; } } }; processLoadedSettings(savedSettings, this.settings); localStorage.setItem(`${this.prefix}_settings`, JSON.stringify(this.settings)); } } resetSettings() { this.settings = JSON.parse(JSON.stringify(this.defaultSettings)); this.saveSettings(); } deleteSettings() { localStorage.removeItem(`${this.prefix}_settings`); } getSettings() { return this.settings; } settingsChanged(fullKey, value, subKey = null) { const keys = fullKey.split('.'); let currentSettings = this.settings; for (let i = 0; i < keys.length - 1; i++) { if (currentSettings[keys[i]].type === 'section') { currentSettings = currentSettings[keys[i]].settings; } else if (currentSettings[keys[i]].type === 'multicheckbox' && subKey) { currentSettings = currentSettings[keys[i]].values; } else { currentSettings = currentSettings[keys[i]]; } } const finalKey = keys[keys.length - 1]; if (!currentSettings[finalKey]) { console.error(`settingsChanged - Key ${finalKey} not found in settings`); return; } if (subKey) { currentSettings[finalKey].values[subKey] = value; } else { currentSettings[finalKey].value = value; } this.saveSettings(); } createSettingsModal(modalFramework, width = 'auto', height = 'auto', closeCallback = null) { return () => { const content = document.createElement('div'); const buildSettings = (settings, parent) => { if (!parent) { console.error('Parent element is null'); return; } for (const key in settings) { const setting = settings[key]; const fullKey = key; if (setting.type === 'section') { const sectionContent = modalFramework.addSection(parent, setting.name); buildSettings(setting.settings, sectionContent); } else if (setting.type === 'multicheckbox') { const sectionContent = modalFramework.addSection(parent, setting.name); for (const subKey in setting.values) { modalFramework.addCheckbox(sectionContent, setting.values[subKey], (value) => { this.settingsChanged(`${fullKey}.${subKey}`, value, subKey); }, subKey); } } else if (setting.type === 'checkbox') { modalFramework.addCheckbox(parent, setting.value, (value) => { this.settingsChanged(fullKey, value); }, key); } else if (setting.type === 'numinput') { modalFramework.addInput(parent, 'number', setting.value, '', setting.minValue, setting.maxValue, (value) => { this.settingsChanged(fullKey, value); }, key); } else if (setting.type === 'text') { modalFramework.addInput(parent, 'text', setting.value, setting.placeholder, undefined, undefined, (value) => { this.settingsChanged(fullKey, value); }, key); } else if (setting.type === 'combobox') { modalFramework.addCombobox(parent, setting.options, setting.value, (value) => { this.settingsChanged(fullKey, value); }, key); } else if (setting.type === 'button') { modalFramework.addButton(parent, setting.name, setting.function, 'btn'); } } }; buildSettings(this.settings, content); modalFramework.addModal(content, `${this.prefix} Settings`, width, height, closeCallback); }; } } window.ZlefsModal = ZlefsModal; window.ZlefsSettingsManager = ZlefsSettingsManager; console.log(`Zlef's Modal and Settings Manager version ${GM_info.script.version} loaded.`); })();