Auto-Save

Auto-Save Feature (changeable Polling and debounce time)

Tendrás que instalar una extensión para tu navegador como Tampermonkey, Greasemonkey o Violentmonkey si quieres utilizar este script.

You will need to install an extension such as Tampermonkey to install this script.

Tendrás que instalar una extensión como Tampermonkey o Violentmonkey para instalar este script.

Necesitarás instalar una extensión como Tampermonkey o Userscripts para instalar este script.

Tendrás que instalar una extensión como Tampermonkey antes de poder instalar este script.

Necesitarás instalar una extensión para administrar scripts de usuario si quieres instalar este script.

(Ya tengo un administrador de scripts de usuario, déjame instalarlo)

Tendrás que instalar una extensión como Stylus antes de poder instalar este script.

Tendrás que instalar una extensión como Stylus antes de poder instalar este script.

Tendrás que instalar una extensión como Stylus antes de poder instalar este script.

Para poder instalar esto tendrás que instalar primero una extensión de estilos de usuario.

Para poder instalar esto tendrás que instalar primero una extensión de estilos de usuario.

Para poder instalar esto tendrás que instalar primero una extensión de estilos de usuario.

(Ya tengo un administrador de estilos de usuario, déjame instalarlo)

// ==UserScript==
// @name         Auto-Save
// @namespace    bennoghg
// @match        https://map-making.app/maps/*
// @grant        none
// @version      1.0
// @author       BennoGHG
// @license      MIT
// @description  Auto-Save Feature (changeable Polling and debounce time)
// ==/UserScript==

(function() {
  'use strict';

  // ═══ Configuration & State Management ═══
  const STORAGE_KEY = 'mapmaking-autosave-settings';
  const MIN_POLLING = 200, MAX_POLLING = 10000;
  const MIN_DEBOUNCE = 200, MAX_DEBOUNCE = 30000;

  function loadSettings() {
    try {
      const saved = localStorage.getItem(STORAGE_KEY);
      if (saved) {
        const settings = JSON.parse(saved);
        return {
          pollingInterval: Math.max(MIN_POLLING, Math.min(MAX_POLLING, settings.pollingInterval || 1000)),
          debounceMs: Math.max(MIN_DEBOUNCE, Math.min(MAX_DEBOUNCE, settings.debounceMs || 3000)),
          autosaveEnabled: settings.autosaveEnabled !== false
        };
      }
    } catch (error) {
      console.warn('[AutoSave] Failed to load settings:', error);
    }
    return { pollingInterval: 1000, debounceMs: 3000, autosaveEnabled: true };
  }

  function saveSettings() {
    try {
      const settings = { pollingInterval, debounceMs, autosaveEnabled };
      localStorage.setItem(STORAGE_KEY, JSON.stringify(settings));
    } catch (error) {
      console.warn('[AutoSave] Failed to save settings:', error);
    }
  }

  const initialSettings = loadSettings();
  let pollingInterval = initialSettings.pollingInterval;
  let debounceMs = initialSettings.debounceMs;
  let autosaveEnabled = initialSettings.autosaveEnabled;
  let lastCount = null;
  let saveTimer = null;
  let pollTimer = null;

  function findSaveButton() {
    return Array.from(document.querySelectorAll('button'))
      .find(btn => btn.textContent.trim() === 'Save');
  }

  function findCountsElement() {
    return document.querySelector('span.map-meta__count');
  }

  function triggerSave() {
    const saveBtn = findSaveButton();
    if (saveBtn && !saveBtn.disabled) {
      saveBtn.click();
      console.log('[AutoSave] Save triggered');
    }
  }

  function scheduleSave() {
    clearTimeout(saveTimer);
    saveTimer = setTimeout(() => {
      if (autosaveEnabled) triggerSave();
    }, debounceMs);
  }

  // Main polling function - checks for changes
  function checkForChanges() {
    if (!autosaveEnabled) return;

    const countsElement = findCountsElement();
    if (!countsElement) {
      lastCount = null;
      return;
    }

    const currentCount = countsElement.textContent.trim();
    if (lastCount === null || currentCount !== lastCount) {
      lastCount = currentCount;
      scheduleSave();
    }
  }

  // Start the polling loop
  function startPolling() {
    clearInterval(pollTimer);
    pollTimer = setInterval(checkForChanges, pollingInterval);
  }

  // ═══ UI Creation and Management ═══
  function createUI() {
    if (document.querySelector('.autosave-ui')) return; // Already exists

    const saveBtn = findSaveButton();
    if (!saveBtn) return; // Save button not ready yet

    const toolbar = saveBtn.parentElement;
    toolbar.style.display = 'flex';
    toolbar.style.alignItems = 'center';

    const ui = document.createElement('div');
    ui.className = 'autosave-ui';
    ui.innerHTML = `
      <div class="autosave-toggle">
        <input type="checkbox" id="autosave-checkbox" ${autosaveEnabled?'checked':''}/>
        <label for="autosave-checkbox" class="toggle-switch">
          <span class="toggle-slider"></span>
        </label>
        <span class="toggle-label">Auto Save</span>
      </div>
      <button class="settings-btn" type="button">
        <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
          <circle cx="12" cy="12" r="3"></circle>
          <path d="M12 1v6m0 10v6m11-7h-6m-10 0H1m15.5-6.5L19 4.5m-14 14 2.5-2.5m0-11L4.5 19.5m14-14L16.5 7.5"></path>
        </svg>
      </button>
      <div class="settings-panel">
        <div class="settings-content">
          <div class="setting-group">
            <label>Polling Interval</label>
            <div class="input-group">
              <input type="number" value="${pollingInterval}" min="${MIN_POLLING}" max="${MAX_POLLING}" step="100" class="poll-input"/>
              <span class="unit">ms</span>
            </div>
          </div>
          <div class="setting-group">
            <label>Debounce Delay</label>
            <div class="input-group">
              <input type="number" value="${debounceMs}" min="${MIN_DEBOUNCE}" max="${MAX_DEBOUNCE}" step="100" class="debounce-input"/>
              <span class="unit">ms</span>
            </div>
          </div>
        </div>
      </div>
    `;
    toolbar.insertBefore(ui, saveBtn);

    // Only inject styles once
    if (!document.getElementById('autosave-ui-styles')) {
      injectStyles();
    }

    setupEventHandlers(ui);
  }

  // Inject CSS styles for the UI
  function injectStyles() {
    const style = document.createElement('style');
    style.id = 'autosave-ui-styles';
    style.textContent = `
        .autosave-ui {
          position: relative;
          display: flex;
          align-items: center;
          gap: 6px;
          margin: 0 4px;
          padding: 4px 8px;
          height: 32px;
          background: rgba(255, 255, 255, 0.05);
          backdrop-filter: blur(10px);
          border: 1px solid rgba(255, 255, 255, 0.1);
          border-radius: 6px;
          font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
          user-select: none;
        }

        .autosave-toggle {
          display: flex;
          align-items: center;
          gap: 4px;
        }

        .autosave-toggle input[type="checkbox"] {
          display: none;
        }

        .toggle-switch {
          position: relative;
          width: 28px;
          height: 14px;
          background: rgba(255, 255, 255, 0.2);
          border-radius: 14px;
          cursor: pointer;
          transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
        }

        .toggle-slider {
          position: absolute;
          top: 1px;
          left: 1px;
          width: 12px;
          height: 12px;
          background: white;
          border-radius: 50%;
          transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
          box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
        }

        .autosave-toggle input:checked + .toggle-switch {
          background: #10b981;
        }

        .autosave-toggle input:checked + .toggle-switch .toggle-slider {
          transform: translateX(14px);
        }

        .toggle-label {
          color: rgba(255, 255, 255, 0.9);
          font-size: 12px;
          font-weight: 500;
        }

        .settings-btn {
          display: flex;
          align-items: center;
          justify-content: center;
          width: 20px;
          height: 20px;
          background: rgba(255, 255, 255, 0.1);
          border: none;
          border-radius: 4px;
          color: rgba(255, 255, 255, 0.7);
          cursor: pointer;
          transition: all 0.2s ease;
        }

        .settings-btn:hover {
          background: rgba(255, 255, 255, 0.15);
          color: rgba(255, 255, 255, 0.9);
          transform: translateY(-1px);
        }

        .settings-panel {
          position: absolute;
          bottom: calc(100% + 8px);
          right: 0;
          width: 240px;
          background: rgba(20, 20, 20, 0.95);
          backdrop-filter: blur(20px);
          border: 1px solid rgba(255, 255, 255, 0.1);
          border-radius: 12px;
          padding: 0;
          opacity: 0;
          visibility: hidden;
          transform: translateY(10px) scale(0.95);
          transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
          box-shadow: 0 20px 40px rgba(0, 0, 0, 0.4);
          z-index: 1000;
        }

        .autosave-ui.open .settings-panel {
          opacity: 1;
          visibility: visible;
          transform: translateY(0) scale(1);
        }

        .settings-content {
          padding: 16px;
        }

        .setting-group {
          margin-bottom: 20px;
        }

        .setting-group:last-child {
          margin-bottom: 0;
        }

        .setting-group label {
          display: block;
          color: rgba(255, 255, 255, 0.9);
          font-size: 13px;
          font-weight: 600;
          margin-bottom: 8px;
          text-transform: uppercase;
          letter-spacing: 0.5px;
        }

        .input-group {
          display: flex;
          align-items: center;
          background: rgba(255, 255, 255, 0.05);
          border: 1px solid rgba(255, 255, 255, 0.1);
          border-radius: 10px;
          padding: 0 12px;
          transition: all 0.2s ease;
        }

        .input-group:focus-within {
          border-color: #10b981;
          box-shadow: 0 0 0 3px rgba(16, 185, 129, 0.1);
        }

        .input-group input {
          flex: 1;
          background: none;
          border: none;
          color: white;
          font-size: 14px;
          font-weight: 500;
          padding: 12px 0;
          outline: none;
        }

        .input-group input::-webkit-outer-spin-button,
        .input-group input::-webkit-inner-spin-button {
          -webkit-appearance: none;
          margin: 0;
        }

        .input-group input[type=number] {
          -moz-appearance: textfield;
        }

        .unit {
          color: rgba(255, 255, 255, 0.5);
          font-size: 12px;
          font-weight: 500;
          margin-left: 8px;
        }

        /* Click outside to close */
        .settings-panel::before {
          content: '';
          position: fixed;
          top: 0;
          left: 0;
          right: 0;
          bottom: 0;
          z-index: -1;
        }
      `;
    document.head.appendChild(style);
  }

  // Set up all event handlers for the UI
  function setupEventHandlers(ui) {
    const checkbox = ui.querySelector('#autosave-checkbox');
    const settingsBtn = ui.querySelector('.settings-btn');
    const panel = ui.querySelector('.settings-panel');
    const pollInput = ui.querySelector('.poll-input');
    const debounceInput = ui.querySelector('.debounce-input');

    // Toggle auto-save on/off
    checkbox.addEventListener('change', () => {
      autosaveEnabled = checkbox.checked;
      saveSettings();
    });

    // Show/hide settings panel
    settingsBtn.addEventListener('click', (event) => {
      event.stopPropagation();
      ui.classList.toggle('open');
    });

    // Close panel when clicking outside
    document.addEventListener('click', (event) => {
      if (!ui.contains(event.target)) {
        ui.classList.remove('open');
      }
    });

    // Update polling interval
    pollInput.addEventListener('input', () => {
      const value = parseInt(pollInput.value, 10);
      if (value >= MIN_POLLING && value <= MAX_POLLING) {
        pollingInterval = value;
        startPolling();
        saveSettings();
      }
    });

    // Update debounce delay
    debounceInput.addEventListener('input', () => {
      const value = parseInt(debounceInput.value, 10);
      if (value >= MIN_DEBOUNCE && value <= MAX_DEBOUNCE) {
        debounceMs = value;
        saveSettings();
      }
    });
  }

  // ═══ Initialization ═══
  function initialize() {
    // Try to inject UI every 500ms until toolbar is ready
    const uiInjectionInterval = setInterval(() => {
      createUI();
      // Stop trying after UI is successfully created
      if (document.querySelector('.autosave-ui')) {
        clearInterval(uiInjectionInterval);
      }
    }, 500);

    // Start polling for changes
    startPolling();

    console.log('[AutoSave] v2.1 initialized - Waiting for toolbar...');
  }
  // Start the script
  initialize();

})();