Auto-Save

Auto-Save Feature (changeable Polling and debounce time)

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==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();

})();