WME Layer Saver

Save the state of different combinations of layer display settings.settings

이 스크립트를 설치하려면 Tampermonkey, Greasemonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램을 설치해야 합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Userscripts와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 유저 스크립트 관리자 확장 프로그램이 필요합니다.

(이미 유저 스크립트 관리자가 설치되어 있습니다. 설치를 진행합니다!)

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

(이미 유저 스타일 관리자가 설치되어 있습니다. 설치를 진행합니다!)

// ==UserScript==
// @name               WME Layer Saver
// @author             HBiede
// @namespace          hbiede.com
// @description        Save the state of different combinations of layer display settings.settings
// @match              https://www.waze.com/editor*
// @match              https://beta.waze.com/editor*
// @match              https://www.waze.com/*/editor*
// @match              https://beta.waze.com/*/editor*
// @require            https://greatest.deepsurf.us/scripts/24851-wazewrap/code/WazeWrap.js
// @version            2026.01.01.004
// @grant              none
// @copyright          2026 HBiede
// ==/UserScript==

/* global W */
/* global WazeWrap */

const DEBUG = true;
const UPDATE_DESCRIPTION =
  "<h4 style='margin-bottom: 5px;'>Bug Fixes:</h4><ul><li>Rework loading</li></ul>";
const DEFAULT_SETTINGS = { settings: [] };
const SCRIPT_STRING = "LSaver";
const SCRIPT_NAME = "Layer Saver";
const LAYER_SELECTION_TYPES = "wz-toggle-switch,wz-checkbox";
const LAYER_CONTAINER = "layer-switcher-region";
const DELIMITER = "::";
const settings = DEFAULT_SETTINGS;

let sdk = null;

const logPrint = (...message) => {
  if (DEBUG) console.log(...message);
};

// clear the alert text
function setAlertParagraph(message) {
  if (typeof message === "string")
    document.getElementById("LSaverAlertText").innerText = message;
}

// load the settings.settings from the local and server sources
async function loadSettings() {
  let local;
  try {
    local = JSON.parse(localStorage.getItem(SCRIPT_STRING));
  } catch (e) {
    local = DEFAULT_SETTINGS;
  }
  try {
    let returnValue = { ...DEFAULT_SETTINGS, ...(local ?? {}) };

    const serverSettings = await WazeWrap.Remote.RetrieveSettings(
      SCRIPT_STRING
    );
    if (serverSettings && serverSettings.time > returnValue.time) {
      returnValue = { ...returnValue, ...(serverSettings ?? {}) }
    }

    return returnValue;
  } catch (e) {
    logPrint(`${e.message}`);
    return DEFAULT_SETTINGS;
  }
}

// save settings.settings locally/to the server
function saveSettings() {
  settings.time = Date.now();
  WazeWrap.Remote.SaveSettings(SCRIPT_STRING, settings);
  localStorage.setItem(SCRIPT_STRING, JSON.stringify(settings));
}

// load all the settings.settings groups
async function loadLayerSaverSettings() {
  const returnValue = await loadSettings(SCRIPT_STRING);
  logPrint(returnValue);
  if (returnValue.settings.every((s) => s.includes(`${DELIMITER}[`))) {
    settings.settings = returnValue.settings;
  } else {
    settings.settings = returnValue.settings.map((s) => {
      const [title, string] = s.split(DELIMITER);
      const list = string
        .split("layer-switcher-group_")
        .map((t) => `layer-switcher-group_${t}`);
      return `${title}${DELIMITER}${JSON.stringify(list)}`;
    });
  }
  saveSettings(SCRIPT_STRING);
  logPrint(await WazeWrap.Remote.RetrieveSettings(SCRIPT_STRING));
}

// save all the settings groups
function saveLayerSaverSettings() {
  setAlertParagraph("");
  let arrayBuilder = [];
  const currentSettings = document.getElementById("LSaverSelector").children;
  for (let i = 0; i < currentSettings.length; i++) {
    arrayBuilder = arrayBuilder.concat([
      `${currentSettings[i].textContent}${DELIMITER}${JSON.stringify(
        currentSettings[i].settingsList
      )}`,
    ]);
    logPrint(`${currentSettings[i].textContent}`);
  }
  settings.settings = arrayBuilder;
  logPrint(settings.settings);
  saveSettings(SCRIPT_STRING);
}

// load the selected settings.settings group
async function loadLayerSettings() {
  setAlertParagraph("");
  const settingsList = JSON.parse(
    document.getElementById("LSaverSelector").selectedOptions[0].settingsList
  );
  const selected = new Set(settingsList);
  logPrint(`Loading according to: ${JSON.stringify([...selected])}`);

  const toggleSelectorList = (toggles) => {
    let hasChanged = false;
    for (let i = 0; i < toggles.length; i++) {
      const id = identifierForToggle(toggles[i]);
      // if the input is in the group and not checked, or not in the group and checked, click the input
      if ((id && selected.has(id)) !== toggles[i].checked) {
        logPrint(`Toggling ${id}`);
        toggles[i].click();
        hasChanged = true;
      }
    }
    return hasChanged;
  };

  LAYER_SELECTION_TYPES.split(",").forEach((type) => {
    return toggleSelectorList(
      document.getElementById(LAYER_CONTAINER).querySelectorAll(type)
    );
  });

  logPrint(
      `Loaded Group: ${
        document.getElementById("LSaverSelector").selectedOptions[0].textContent
      }`
    );
}

// delete the selected settings.settings group
function deleteLayerSettings() {
  setAlertParagraph("");
  const name =
    document.getElementById("LSaverSelector").children[
      document.getElementById("LSaverSelector").selectedIndex
    ].textContent;
  document
    .getElementById("LSaverSelector")
    .children[document.getElementById("LSaverSelector").selectedIndex].remove();
  saveLayerSaverSettings();
  logPrint(`Deleted Group: ${name}`);
  updateDisabledStates();
}

function identifierForToggle(toggle) {
  return toggle.id || toggle.name || toggle.innerText;
}

// turn the currently selected inputs into a usable string
function getCurrentLayerSettings() {
  const toggles = document
    .getElementById(LAYER_CONTAINER)
    .querySelectorAll(LAYER_SELECTION_TYPES);
  const res = [...toggles].filter((t) => t.checked).map(identifierForToggle);
  logPrint(res);
  return res;
}

// save the selected settings.settings group
function saveLayerSettings() {
  setAlertParagraph("");
  const layerSettingSelector = document.createElement("option");
  layerSettingSelector.textContent = prompt(
    "Name Your New Layer Settings Group",
    ""
  );
  if (layerSettingSelector.textContent != null) {
    layerSettingSelector.settingsList = JSON.stringify(
      getCurrentLayerSettings()
    );
    document.getElementById("LSaverSelector").appendChild(layerSettingSelector);
    saveLayerSaverSettings();
    logPrint(`Created Group: ${layerSettingSelector.textContent}`);
    updateDisabledStates();
    return;
  }
  logPrint("Save Aborted");
}

function populateSelector() {
  // build the selector options
  if (settings.settings.length > 0) {
    for (let i = 0; i < settings.settings.length; i++) {
      const setting = settings.settings[i].split(DELIMITER);
      const layerSettingSelector = document.createElement("option");
      layerSettingSelector.textContent = setting[0];
      layerSettingSelector.value = setting[0];
      layerSettingSelector.settingsList = setting[1] || "";
      document
        .getElementById("LSaverSelector")
        .appendChild(layerSettingSelector);
    }
  }
  updateDisabledStates();
}

// build the selector on the script tab
function selectorInit() {
  console.log("Loading Layer Settings");
  populateSelector();

  // add button listeners
  document
    .getElementById("LSaverLoadBtn")
    .addEventListener("click", async () => {
      await loadLayerSettings();
    });
  document.getElementById("LSaverDeleteBtn").addEventListener("click", () => {
    deleteLayerSettings();
  });
  document.getElementById("LSaverSaveBtn").addEventListener("click", () => {
    saveLayerSettings();
  });
  document
    .getElementById("LSaverSetDefaultBtn")
    .addEventListener("click", () => {
      document
        .getElementById("LSaverSelector")
        .add(
          document.getElementById("LSaverSelector").children[
            document.getElementById("LSaverSelector").selectedIndex
          ],
          0
        );
      saveLayerSaverSettings();
    });
  document.getElementById("LSaverImportBtn").addEventListener("click", () => {
    importSettingsList();
  });
  document.getElementById("LSaverExportBtn").addEventListener("click", () => {
    exportSettingsList();
  });
  document
    .getElementById("LSaverExportAllBtn")
    .addEventListener("click", () => {
      exportAllSettingsString();
    });
  console.log("Layer Settings Loaded");
}

// import a settings array in the from of a base64 encoded stringified version of the settings array
function importSettingsList() {
  try {
    const [title, settingsList] = window
      .atob(prompt("Import settings text:", ""))
      .split(DELIMITER);
    if (settingsList) {
      const importedArray = JSON.parse(settingsList);
      if (!Array.isArray(importedArray)) {
        setAlertParagraph("Invalid Input String");
        return;
      }
      settings.settings = settings.settings.concat(`${title}${DELIMITER}${JSON.stringify(settingsList)}`);
      saveSettings("LSaver");

      const selector = document.getElementById("LSaverSelector");
      while (selector.firstChild) {
        selector.removeChild(selector.firstChild);
      }
      populateSelector();

      setAlertParagraph("Loaded");
    }
  } catch (e) {
    setAlertParagraph(e.message);
  }
}

function copyToClipboard(text) {
  if (text === "") {
    setAlertParagraph("Cannot export no settings.");
    return;
  }

  const ta = document.createElement("textarea");
  ta.value = text;
  document.body.appendChild(ta);
  ta.select();
  document.execCommand("copy");
  document.body.removeChild(ta);
  setAlertParagraph("Copied all groups' settings text to clipboard");
}

// export the selected settings string
function exportSettingsList() {
  const selectedSetting =
    document.getElementById("LSaverSelector").selectedOptions[0];
  if (selectedSetting) {
    copyToClipboard(
      window.btoa(
        JSON.stringify([
          `${selectedSetting.textContent}${DELIMITER}${JSON.stringify(
            selectedSetting.settingsList
          )}`,
        ])
      )
    );
  } else {
    setAlertParagraph("Select a group");
  }
}

// export a settings array in the form of a base64 encoded stringified version of the settings array
function exportAllSettingsString() {
  copyToClipboard(window.btoa(JSON.stringify(settings.settings)));
}

function updateDisabledStates() {
  const hasSettings = settings.settings.length > 0;
  const hasSelection =
    hasSettings && !!document.getElementById("LSaverSelector").value;

  document.getElementById("LSaverLoadBtn").disabled = !hasSelection;
  document.getElementById("LSaverDeleteBtn").disabled = !hasSelection;
  document.getElementById("LSaverSetDefaultBtn").disabled = !hasSelection;
  document.getElementById("LSaverExportBtn").disabled = !hasSelection;
  document.getElementById("LSaverExportAllBtn").disabled = !hasSettings;
}

// Create the tab in the sidebar via WazeWrap
function createTab() {
  sdk.Sidebar.registerScriptTab().then(({ tabLabel, tabPane }) => {
    tabLabel.innerText = "Layer Saver";

    tabPane.innerHTML = `<h3><b>WME Layer Saver</b></h3><p><i>${GM_info.script.version} by ${GM_info.script.author}</i></p><div id="LSaverSelectorDiv"><label>Groups</label><br><select id="LSaverSelector" style="width:90%; margin-bottom: 8px;"></select></div><div id="LSaverInteractionDiv"></div><button class="btn btn-primary" id="LSaverLoadBtn" title="Load" style="margin: 8px 8px auto auto;">Load</button><button class="btn btn-primary" id="LSaverDeleteBtn" title="Delete Selected" style="margin: 8px 8px auto auto;">Delete Selected</button><br><button class="btn btn-primary" id="LSaverSaveBtn" title="Save New Group" style="margin: 8px 8px auto auto;">Save New Group</button><br><button class="btn btn-primary" id="LSaverSetDefaultBtn" title="Set As Default" style="margin: 8px 8px auto auto;">Set As Default</button><br><button class="btn btn-primary" id="LSaverImportBtn" title="Import" style="margin: 8px 8px auto auto;">Import</button><button class="btn btn-primary" id="LSaverExportBtn" title="Export" style="margin: 8px 8px auto auto;">Export</button><button class="btn btn-primary" id="LSaverExportAllBtn" title="Export All" style="margin: 8px 8px auto auto;">Export All</button><p style="padding-top: 10px; font-weight: bold;" id="LSaverAlertText"></p></div>`;
    tabPane.id = "WMELayerSaver";

    selectorInit();
  });
}

// main function
async function initLayerSaver(attempts = 1) {
  sdk = window.getWmeSdk({ scriptId: SCRIPT_STRING, scriptName: SCRIPT_NAME });

  if (attempts <= 1000) {
    if (
      !WazeWrap.Ready ||
      typeof W === "undefined" ||
      typeof W.map === "undefined" ||
      typeof W.loginManager === "undefined" ||
      !document.getElementById(LAYER_CONTAINER)
    ) {
      logPrint("Layer Saver: retry");
      setTimeout(() => {
        initLayerSaver(attempts++);
      }, 800);
    } else {
      logPrint("Starting Layer Saver");
      await loadLayerSaverSettings();
      createTab();
      WazeWrap.Interface.ShowScriptUpdate(
        GM_info.script.name,
        GM_info.script.version,
        UPDATE_DESCRIPTION,
        "https://greatest.deepsurf.us/en/scripts/383384-wme-layer-saver",
        "https://www.waze.com/forum/viewtopic.php?f=819&t=283513"
      );
    }
  }
}

// start
window.SDK_INITIALIZED.then(() => {
  setTimeout(initLayerSaver, 1000);
});