Feat: Server Props Search

Feat: Add custom prop adds
Feat: Add Removing Props
This commit is contained in:
MCHost
2025-06-26 02:39:42 -04:00
parent 728ea10e80
commit 375d1400d6
2 changed files with 416 additions and 245 deletions

View File

@ -42,6 +42,8 @@
--spacing: 0.25rem; --spacing: 0.25rem;
--container-md: 28rem; --container-md: 28rem;
--container-xl: 36rem; --container-xl: 36rem;
--text-xs: 0.75rem;
--text-xs--line-height: calc(1 / 0.75);
--text-sm: 0.875rem; --text-sm: 0.875rem;
--text-sm--line-height: calc(1.25 / 0.875); --text-sm--line-height: calc(1.25 / 0.875);
--text-lg: 1.125rem; --text-lg: 1.125rem;
@ -224,6 +226,9 @@
.inset-0 { .inset-0 {
inset: calc(var(--spacing) * 0); inset: calc(var(--spacing) * 0);
} }
.z-50 {
z-index: 50;
}
.container { .container {
width: 100%; width: 100%;
@media (width >= 40rem) { @media (width >= 40rem) {
@ -251,6 +256,9 @@
.mt-4 { .mt-4 {
margin-top: calc(var(--spacing) * 4); margin-top: calc(var(--spacing) * 4);
} }
.mr-2 {
margin-right: calc(var(--spacing) * 2);
}
.mb-1 { .mb-1 {
margin-bottom: calc(var(--spacing) * 1); margin-bottom: calc(var(--spacing) * 1);
} }
@ -278,6 +286,9 @@
.inline-block { .inline-block {
display: inline-block; display: inline-block;
} }
.table {
display: table;
}
.h-24 { .h-24 {
height: calc(var(--spacing) * 24); height: calc(var(--spacing) * 24);
} }
@ -296,9 +307,15 @@
.min-h-full { .min-h-full {
min-height: 100%; min-height: 100%;
} }
.w-1 {
width: calc(var(--spacing) * 1);
}
.w-1\/3 { .w-1\/3 {
width: calc(1/3 * 100%); width: calc(1/3 * 100%);
} }
.w-2 {
width: calc(var(--spacing) * 2);
}
.w-2\/3 { .w-2\/3 {
width: calc(2/3 * 100%); width: calc(2/3 * 100%);
} }
@ -320,9 +337,15 @@
.flex-1 { .flex-1 {
flex: 1; flex: 1;
} }
.flex-shrink {
flex-shrink: 1;
}
.flex-grow { .flex-grow {
flex-grow: 1; flex-grow: 1;
} }
.border-collapse {
border-collapse: collapse;
}
.transform { .transform {
transform: var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,); transform: var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,);
} }
@ -518,6 +541,10 @@
font-size: var(--text-xl); font-size: var(--text-xl);
line-height: var(--tw-leading, var(--text-xl--line-height)); line-height: var(--tw-leading, var(--text-xl--line-height));
} }
.text-xs {
font-size: var(--text-xs);
line-height: var(--tw-leading, var(--text-xs--line-height));
}
.leading-relaxed { .leading-relaxed {
--tw-leading: var(--leading-relaxed); --tw-leading: var(--leading-relaxed);
line-height: var(--leading-relaxed); line-height: var(--leading-relaxed);
@ -552,6 +579,9 @@
.text-white { .text-white {
color: var(--color-white); color: var(--color-white);
} }
.underline {
text-decoration-line: underline;
}
.opacity-50 { .opacity-50 {
opacity: 50%; opacity: 50%;
} }
@ -559,6 +589,10 @@
--tw-shadow: 0 10px 15px -3px var(--tw-shadow-color, rgb(0 0 0 / 0.1)), 0 4px 6px -4px var(--tw-shadow-color, rgb(0 0 0 / 0.1)); --tw-shadow: 0 10px 15px -3px var(--tw-shadow-color, rgb(0 0 0 / 0.1)), 0 4px 6px -4px var(--tw-shadow-color, rgb(0 0 0 / 0.1));
box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow); box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
} }
.outline {
outline-style: var(--tw-outline-style);
outline-width: 1px;
}
.filter { .filter {
filter: var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,); filter: var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,);
} }
@ -634,6 +668,13 @@
} }
} }
} }
.hover\:text-red-700 {
&:hover {
@media (hover: hover) {
color: var(--color-red-700);
}
}
}
.sm\:w-\[90\%\] { .sm\:w-\[90\%\] {
@media (width >= 40rem) { @media (width >= 40rem) {
width: 90%; width: 90%;
@ -1188,6 +1229,11 @@
inherits: false; inherits: false;
initial-value: 0 0 #0000; initial-value: 0 0 #0000;
} }
@property --tw-outline-style {
syntax: "*";
inherits: false;
initial-value: solid;
}
@property --tw-blur { @property --tw-blur {
syntax: "*"; syntax: "*";
inherits: false; inherits: false;
@ -1296,6 +1342,7 @@
--tw-ring-offset-width: 0px; --tw-ring-offset-width: 0px;
--tw-ring-offset-color: #fff; --tw-ring-offset-color: #fff;
--tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-shadow: 0 0 #0000;
--tw-outline-style: solid;
--tw-blur: initial; --tw-blur: initial;
--tw-brightness: initial; --tw-brightness: initial;
--tw-contrast: initial; --tw-contrast: initial;

View File

@ -1451,6 +1451,15 @@ document.addEventListener('DOMContentLoaded', () => {
searchContainer.appendChild(searchLabel); searchContainer.appendChild(searchLabel);
searchContainer.appendChild(searchInput); searchContainer.appendChild(searchInput);
// Add custom property button (mini style)
const addButton = document.createElement('button');
addButton.textContent = 'Add Property';
addButton.type = 'button';
addButton.className = 'mt-2 bg-blue-600 hover:bg-blue-700 text-white px-2 py-1 rounded text-xs';
addButton.addEventListener('click', showCustomPropertyForm);
searchContainer.appendChild(addButton);
fieldsContainer.appendChild(searchContainer); fieldsContainer.appendChild(searchContainer);
// Add input event listener // Add input event listener
@ -1480,32 +1489,147 @@ document.addEventListener('DOMContentLoaded', () => {
elements.searchInput.value = ''; elements.searchInput.value = '';
renderPropertiesList(displayProperties, ''); renderPropertiesList(displayProperties, '');
elements.editPropertiesModal.classList.add('hidden'); elements.editPropertiesModal.classList.add('hidden');
hideCustomPropertyForm();
}); });
closeButton.dataset.closeHandlerAdded = 'true'; closeButton.dataset.closeHandlerAdded = 'true';
} }
} }
function showCustomPropertyForm() {
let formContainer = elements.propertiesFields.querySelector('#customPropertyForm');
if (formContainer) {
formContainer.remove();
}
formContainer = document.createElement('div');
formContainer.id = 'customPropertyForm';
formContainer.className = 'mb-4 p-4 bg-gray-800 rounded';
const form = document.createElement('form');
form.addEventListener('submit', (e) => e.preventDefault());
const keyInput = document.createElement('input');
keyInput.type = 'text';
keyInput.placeholder = 'Property name';
keyInput.className = 'bg-gray-700 px-4 py-2 rounded text-white w-full mb-2';
const valueInput = document.createElement('input');
valueInput.type = 'text';
valueInput.placeholder = 'Property value';
valueInput.className = 'bg-gray-700 px-4 py-2 rounded text-white w-full mb-2';
const addButton = document.createElement('button');
addButton.type = 'button';
addButton.textContent = 'Add';
addButton.className = 'bg-green-600 hover:bg-green-700 text-white px-2 py-1 rounded text-xs mr-2';
addButton.addEventListener('click', () => {
const key = keyInput.value.trim();
const value = valueInput.value.trim();
if (key && value) {
displayProperties[key] = value;
allProperties[key] = value;
renderPropertiesList(displayProperties, elements.searchInput.value);
formContainer.remove();
} else {
showNotification('Please enter both property name and value', 'error', 'custom-property-error');
}
});
const cancelButton = document.createElement('button');
cancelButton.type = 'button';
cancelButton.textContent = 'Cancel';
cancelButton.className = 'bg-gray-600 hover:bg-gray-700 text-white px-2 py-1 rounded text-xs';
cancelButton.addEventListener('click', hideCustomPropertyForm);
form.appendChild(keyInput);
form.appendChild(valueInput);
form.appendChild(addButton);
form.appendChild(cancelButton);
formContainer.appendChild(form);
elements.propertiesFields.insertBefore(formContainer, elements.propertiesFields.querySelector('#propertiesList'));
}
function hideCustomPropertyForm() {
const formContainer = elements.propertiesFields.querySelector('#customPropertyForm');
if (formContainer) {
formContainer.remove();
}
}
function showDeleteConfirmationModal(key) {
const modal = document.createElement('div');
modal.id = 'deleteConfirmationModal';
modal.className = 'absolute inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50';
const modalContent = document.createElement('div');
modalContent.className = 'bg-gray-800 p-6 rounded-lg max-w-md w-full';
const header = document.createElement('h3');
header.className = 'text-lg font-medium text-white mb-4';
header.textContent = 'Confirm Deletion';
const message = document.createElement('p');
message.className = 'text-white mb-6';
message.textContent = `Are you sure you want to delete the property "${key}"? This action cannot be undone.`;
const buttonContainer = document.createElement('div');
buttonContainer.className = 'flex justify-end space-x-2';
const cancelButton = document.createElement('button');
cancelButton.type = 'button';
cancelButton.className = 'bg-gray-600 hover:bg-gray-700 text-white px-4 py-2 rounded';
cancelButton.textContent = 'Cancel';
cancelButton.addEventListener('click', () => modal.remove());
const deleteButton = document.createElement('button');
deleteButton.type = 'button';
deleteButton.className = 'bg-red-600 hover:bg-red-700 text-white px-4 py-2 rounded';
deleteButton.textContent = 'Delete';
deleteButton.addEventListener('click', () => {
delete displayProperties[key];
delete allProperties[key];
renderPropertiesList(displayProperties, elements.searchInput.value);
modal.remove();
showNotification(`Property "${key}" deleted`, 'success', 'delete-property-success');
});
buttonContainer.appendChild(cancelButton);
buttonContainer.appendChild(deleteButton);
modalContent.appendChild(header);
modalContent.appendChild(message);
modalContent.appendChild(buttonContainer);
modal.appendChild(modalContent);
elements.editPropertiesModal.appendChild(modal);
}
function renderPropertiesList(properties, filter = '') { function renderPropertiesList(properties, filter = '') {
const propertiesList = elements.propertiesFields.querySelector('#propertiesList'); const propertiesList = elements.propertiesFields.querySelector('#propertiesList');
propertiesList.innerHTML = ''; propertiesList.innerHTML = '';
// Filter properties based on search input
const filteredProperties = Object.entries(properties).filter(([key]) => const filteredProperties = Object.entries(properties).filter(([key]) =>
key.toLowerCase().includes(filter.toLowerCase()) key.toLowerCase().includes(filter.toLowerCase())
); );
// Render filtered properties
filteredProperties.forEach(([key, value]) => { filteredProperties.forEach(([key, value]) => {
if (filteredSettings.includes(key)) { if (filteredSettings.includes(key)) {
return; return;
} }
console.log(`Rendering field for ${key}: ${value}`); // Debug log console.log(`Rendering field for ${key}: ${value}`);
const fieldDiv = document.createElement('div'); const fieldDiv = document.createElement('div');
fieldDiv.className = 'flex items-center space-x-2'; fieldDiv.className = 'flex items-center space-x-2';
fieldDiv.style.display = 'flex'; fieldDiv.style.display = 'flex';
const deleteButton = document.createElement('button');
deleteButton.type = 'button';
deleteButton.className = 'text-red-500 hover:text-red-700';
deleteButton.innerHTML = '✕';
deleteButton.title = `Delete ${key}`;
deleteButton.addEventListener('click', () => showDeleteConfirmationModal(key));
let inputType = 'text'; let inputType = 'text';
let isBoolean = value.toLowerCase() === 'true' || value.toLowerCase() === 'false'; let isBoolean = value.toLowerCase() === 'true' || value.toLowerCase() === 'false';
if (isBoolean) { if (isBoolean) {
@ -1520,7 +1644,6 @@ document.addEventListener('DOMContentLoaded', () => {
label.setAttribute('for', `prop-${key}`); label.setAttribute('for', `prop-${key}`);
if (inputType === 'switch') { if (inputType === 'switch') {
// Hidden text input for accessibility and form association
const hiddenInput = document.createElement('input'); const hiddenInput = document.createElement('input');
hiddenInput.type = 'text'; hiddenInput.type = 'text';
hiddenInput.id = `prop-${key}`; hiddenInput.id = `prop-${key}`;
@ -1556,7 +1679,6 @@ document.addEventListener('DOMContentLoaded', () => {
switchHandle.style.position = 'absolute'; switchHandle.style.position = 'absolute';
switchHandle.style.zIndex = '11'; switchHandle.style.zIndex = '11';
// Handle click and keyboard events
const toggleSwitch = () => { const toggleSwitch = () => {
const currentValue = hiddenInput.value === 'true'; const currentValue = hiddenInput.value === 'true';
const newValue = !currentValue; const newValue = !currentValue;
@ -1576,6 +1698,7 @@ document.addEventListener('DOMContentLoaded', () => {
switchContainer.appendChild(switchTrack); switchContainer.appendChild(switchTrack);
switchContainer.appendChild(switchHandle); switchContainer.appendChild(switchHandle);
fieldDiv.appendChild(deleteButton);
fieldDiv.appendChild(label); fieldDiv.appendChild(label);
fieldDiv.appendChild(hiddenInput); fieldDiv.appendChild(hiddenInput);
fieldDiv.appendChild(switchContainer); fieldDiv.appendChild(switchContainer);
@ -1584,11 +1707,12 @@ document.addEventListener('DOMContentLoaded', () => {
input.id = `prop-${key}`; input.id = `prop-${key}`;
input.name = key; input.name = key;
input.className = 'bg-gray-700 px-4 py-2 rounded text-white w-2/3'; input.className = 'bg-gray-700 px-4 py-2 rounded text-white w-2/3';
input.type = inputType; input.type = 'number';
input.value = value; input.value = value;
if (inputType === 'number') { if (inputType === 'number') {
input.min = '0'; input.min = '0';
} }
fieldDiv.appendChild(deleteButton);
fieldDiv.appendChild(label); fieldDiv.appendChild(label);
fieldDiv.appendChild(input); fieldDiv.appendChild(input);
} }
@ -1635,7 +1759,7 @@ document.addEventListener('DOMContentLoaded', () => {
const key = `action-save-properties`; const key = `action-save-properties`;
const notification = showNotification('Saving server properties...', 'loading', key); const notification = showNotification('Saving server properties...', 'loading', key);
const properties = {}; const properties = {};
const inputs = elements.propertiesFields.querySelectorAll('input:not(#propertiesSearch)'); const inputs = elements.propertiesFields.querySelectorAll('input:not(#propertiesSearch):not([id="customPropertyKey"]):not([id="customPropertyValue"])');
// Collect modified properties // Collect modified properties
inputs.forEach(input => { inputs.forEach(input => {