* THW Personalbeschaffer

Wirbt benötigtes Personal für eine THW-Wache an.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name        * THW Personalbeschaffer
// @namespace   bos-ernie.leitstellenspiel.de
// @version     1.1.0
// @license     BSD-3-Clause
// @author      BOS-Ernie edited by gonscher
// @description Wirbt benötigtes Personal für eine THW-Wache an.
// @match       https://www.leitstellenspiel.de/buildings/*
// @match       https://polizei.leitstellenspiel.de/buildings/*
// @icon        https://www.google.com/s2/favicons?sz=64&domain=leitstellenspiel.de
// @run-at      document-idle
// @grant       none
// @resource    https://forum.leitstellenspiel.de/index.php?thread/26995-script-thw-personalbeschaffer/
// ==/UserScript==

/* global $, loadedBuildings */

(function () {
  "use strict";

  const minimalRemainingPersonnelInTHW = 50;

  // https://api.lss-manager.de/de_DE/schoolings
  const personnelSettingsInternal = [
    {
      caption: "Ohne Ausbildung",
      key: null,
      numberOfRequiredPersonnel: 50,
      numberOfSelectedPersonnel: 0,
    },
    {
      caption: "Zugtrupp",
      key: "thw_zugtrupp",
      numberOfRequiredPersonnel: 8,
      numberOfSelectedPersonnel: 0,
    },
    {
      caption: "FG Räumen",
      key: "thw_raumen",
      numberOfRequiredPersonnel: 9,
      numberOfSelectedPersonnel: 0,
    },
    {
      caption: "FG Bergungstaucher",
      key: "gw_taucher",
      numberOfRequiredPersonnel: 2,
      numberOfSelectedPersonnel: 0,
    },
    {
      caption: "FG Rettungshundeführer",
      key: "thw_rescue_dogs",
      numberOfRequiredPersonnel: 10,
      numberOfSelectedPersonnel: 0,
    },
    {
      caption: "FG Wasserschaden/Pumpen",
      key: "water_damage_pump",
      numberOfRequiredPersonnel: 10,
      numberOfSelectedPersonnel: 0,
    },
    {
      caption: "FG SB",
      key: "heavy_rescue",
      numberOfRequiredPersonnel: 9,
      numberOfSelectedPersonnel: 0,
    },
    {
      caption: "FG Elektr.Vers.",
      key: "thw_energy_supply",
      numberOfRequiredPersonnel: 3,
      numberOfSelectedPersonnel: 0,
    },
    {
      caption: "Trupp Unbemannte Luftfahrtsysteme",
      key: "thw_drone",
      numberOfRequiredPersonnel: 4,
      numberOfSelectedPersonnel: 0,
    },
    {
      caption: "FZ FüKom",
      key: "thw_command",
      numberOfRequiredPersonnel: 22,
      numberOfSelectedPersonnel: 0,
    },

  ];

  const personnelSettingsProxy = personnelSettingsInternal.map(setting => {
    return new Proxy(setting, {
      set: function (target, key, value) {
        target[key] = value;
        updateFooter(target.key, target.numberOfSelectedPersonnel);
        return true;
      },
    });
  });

  function initPanelBodies() {
    const elements = document.getElementsByClassName("panel-body");
    for (let i = 0; i < elements.length; i++) {
      elements[i].classList.add("hidden");
    }
  }
  function removePanelHeadingClickEvent() {
    const elements = document.getElementsByClassName("personal-select-heading");
    for (let i = 0; i < elements.length; i++) {
      elements[i].replaceWith(elements[i].cloneNode(true));
      elements[i].addEventListener("click", panelHeadingClickEvent);
    }
  }

  function addFooter() {
    const wrapper = document.createElement("div");
    wrapper.style = "display: flex; flex-wrap: wrap; flex-direction: row; column-gap: 15px";

    const list = document.createElement("ul");
    list.classList.add("list-inline");
    list.style = "color: #fff;padding-top: 8px;";

    for (let i = 0; i < personnelSettingsProxy.length; i++) {
      const setting = personnelSettingsProxy[i];

      list.appendChild(createTotalSummaryElement(setting));
    }

    wrapper.appendChild(list);

    const nav = document.querySelector(".navbar.navbar-default.navbar-fixed-bottom");

    nav.children[0].children[0].insertAdjacentElement("afterend", wrapper);
  }

  function updateFooter(key, selectedPersonnel) {
    document.getElementById("number-of-selected-personnel-" + key).innerHTML = selectedPersonnel;

    const requiredPersonnel = personnelSettingsProxy.find(setting => setting.key === key).numberOfRequiredPersonnel;

    const labelClass = selectedPersonnel === requiredPersonnel ? "label-success" : "label-warning";

    const spanPersonnel = document.getElementById("personnel-" + key);

    spanPersonnel.classList.remove("label-success", "label-warning");
    spanPersonnel.classList.add(labelClass);
  }

  function addClickEventHandlerToCheckboxes() {
    const inputElements = document.getElementsByClassName("schooling_checkbox");

    for (let i = 0; i < inputElements.length; i++) {
      inputElements[i].addEventListener("change", updateNumberOfSelectedPersonnel);
    }
  }

  function updateNumberOfSelectedPersonnel(event) {
    const attributes = Object.keys(event.target.attributes);

    let attributeIndex = attributes.find(key => event.target.attributes[key].value === "true");
    let key = null;
    if (attributeIndex !== undefined) {
      key = event.target.attributes[attributeIndex].name;
    }

    const setting = personnelSettingsProxy.find(setting => setting.key === key);

    if (event.target.checked) {
      setting.numberOfSelectedPersonnel = setting.numberOfSelectedPersonnel + 1;
    } else {
      setting.numberOfSelectedPersonnel = setting.numberOfSelectedPersonnel - 1;
    }

    const buildingId = event.target.getAttribute("building_id");

    const panelHeading = document.querySelector(".personal-select-heading[building_id='" + buildingId + "']");
  }

  function addPersonnelSelector() {
    let elements = document.getElementsByClassName("panel-heading personal-select-heading");
    for (let i = 0; i < elements.length; i++) {
      const element = elements[i];
      const buildingId = element.getAttribute("building_id");
      element.children[1].prepend(createPersonnelSelector(buildingId));
    }
  }

  function createPersonnelSelector(buildingId) {
    const trashIcon = document.createElement("span");
    trashIcon.classList.add("glyphicon", "glyphicon-trash");

    const resetButton = document.createElement("button");
    resetButton.classList.add("btn", "btn-xs", "btn-default", "personnel-reset-button");
    resetButton.setAttribute("type", "button");
    resetButton.setAttribute("data-building-id", buildingId);
    resetButton.addEventListener("click", resetPersonnelClick);
    resetButton.appendChild(trashIcon);

    const buttonGroup = document.createElement("div");
    buttonGroup.classList.add("btn-group", "btn-group-xs");
    buttonGroup.setAttribute("role", "group");
    buttonGroup.appendChild(resetButton);
    buttonGroup.appendChild(createSelectButton(buildingId));

    return buttonGroup;
  }

  function createSelectButton(buildingId) {
    const userIcon = document.createElement("span");
    userIcon.classList.add("glyphicon", "glyphicon-user");

    const button = document.createElement("button");
    button.classList.add("btn", "btn-xs", "btn-default", "personnel-select-button");
    button.setAttribute("id", "personnel-select-button-" + buildingId);
    button.setAttribute("type", "button");
    button.setAttribute("data-building-id", buildingId);
    button.addEventListener("click", selectPersonnelClick);
    button.innerText = " Personal auswählen";
    button.prepend(userIcon);

    return button;
  }

  function createTotalSummaryElement(setting) {
    const listItem = document.createElement("li");

    const spanCaption = document.createElement("span");
    spanCaption.innerHTML = setting.caption + ": ";

    const spanSelected = document.createElement("span");
    spanSelected.setAttribute("id", "number-of-selected-personnel-" + setting.key);
    spanSelected.innerHTML = "0";

    const spanRequired = document.createElement("span");
    spanRequired.setAttribute("id", "number-of-required-personnel-" + setting.key);
    spanRequired.innerHTML = setting.numberOfRequiredPersonnel;

    const spanPersonnel = document.createElement("span");
    spanPersonnel.setAttribute("id", "personnel-" + setting.key);
    spanPersonnel.classList.add("label", "label-warning");
    spanPersonnel.appendChild(spanSelected);
    spanPersonnel.appendChild(document.createTextNode("/"));
    spanPersonnel.appendChild(spanRequired);

    listItem.appendChild(spanCaption);
    listItem.appendChild(spanPersonnel);

    return listItem;
  }

  async function selectPersonnelClick(event) {
    event.preventDefault();

    const button = event.target.closest("button");

    button.disabled = true;
    button.classList.remove("btn-default");
    button.classList.add("btn-success");

    const okIcon = document.createElement("span");
    okIcon.classList.add("glyphicon", "glyphicon-ok");
    button.replaceChild(okIcon, button.children[0]);

    const buildingId = button.dataset.buildingId;
    await selectPersonnel(buildingId);

    const panelBody = getPanelBody(buildingId);
    const numberOfSelectedPersonnel = panelBody.querySelectorAll("input:checked").length;

    button.innerHTML = button.innerHTML + " (" + numberOfSelectedPersonnel + ")";
  }

  async function resetPersonnelClick(event) {
    event.preventDefault();

    const resetButton = event.target.closest("button");
    const buildingId = resetButton.dataset.buildingId;

    const selectButton = createSelectButton(buildingId);

    document.getElementById("personnel-select-button-" + buildingId).replaceWith(selectButton);

    await resetPersonnel(buildingId);
  }

  async function selectPersonnel(buildingId) {
    await panelHeadingClick(buildingId);

    for (let i = personnelSettingsProxy.length - 1; i >= 0; i--) {
      const setting = personnelSettingsProxy[i];

      const panelBody = getPanelBody(buildingId);
      let inputElements = [];
      if (setting.key === null) {
        const schoolingCells = panelBody.querySelectorAll("td[id^='school_personal_education_']");
        for (let j = 0; j < schoolingCells.length; j++) {
          const schoolingCell = schoolingCells[j];
          // Personnel with anything but whitespace in schooling column, have a schooling and should not be selected
          if (schoolingCell.innerHTML.replace(/\s/g, "").length > 0) {
            continue;
          }

          inputElements.push(schoolingCell.parentElement.children[0].children[0]);
        }
      } else {
        inputElements = panelBody.querySelectorAll("input[" + setting.key + "='true']");
      }

      inputElements = Array.from(inputElements).filter(function (element) {
        if (typeof element === "undefined") {
          return false;
        }

        return element.parentElement.parentElement.children[3].innerHTML.replace(/\s/g, "").length === 0;
      });

      let j = inputElements.length - 1;
      while (setting.numberOfSelectedPersonnel < setting.numberOfRequiredPersonnel && j >= 0) {
        inputElements[j].click();
        --j;

        if (
          setting.key === null &&
          inputElements.length - setting.numberOfSelectedPersonnel <= minimalRemainingPersonnelInTHW
        ) {
          break;
        }
      }
    }
  }

  function resetPersonnel(buildingId) {
    const panelBody = getPanelBody(buildingId);
    const inputElements = panelBody.querySelectorAll("input:checked");

    for (let i = 0; i < inputElements.length; i++) {
      inputElements[i].click();
    }
  }

  async function panelHeadingClickEvent(event) {
    // Skip redundant panelHeadingClick call which is handled by button click event
    if (
      event.target.classList.contains("personnel-select-button") ||
      event.target.classList.contains("glyphicon-trash")
    ) {
      return;
    }

    let buildingIdElement = event.target.outerHTML.match(/building_id="(\d+)"/);
    if (buildingIdElement === null) {
      buildingIdElement =
        event.target.parentElement.parentElement.parentElement.parentElement.outerHTML.match(/building_id="(\d+)"/);
    }

    await panelHeadingClick(buildingIdElement[1], true);
  }

  async function panelHeadingClick(buildingId, toggle = false) {
    const panelHeading = getPanelHeading(buildingId);
    const panelBody = getPanelBody(buildingId);
    const href = panelHeading.outerHTML.match(/href="([^"]+)"/)[1];

    if (loadedBuildings.indexOf(href) > -1) {
      if (toggle) {
        togglePanelBody(panelBody);
      }

      return;
    }

    loadedBuildings.push(href);
    await $.get(href, function (data) {
      panelBody.innerHTML = data;
    });

    const schoolingSelectAvailableButtons = panelBody.getElementsByClassName("schooling_select_available");
    for (let i = 0; i < schoolingSelectAvailableButtons.length; i++) {
      schoolingSelectAvailableButtons[i].parentElement.remove();
    }

    addClickEventHandlerToCheckboxes();

    if (toggle) {
      showPanelBody(panelBody);
    }
  }

  function togglePanelBody(panelBody) {
    if (panelBody.classList.contains("hidden")) {
      panelBody.classList.remove("hidden");
    } else {
      panelBody.classList.add("hidden");
    }
  }

  function showPanelBody(panelBody) {
    if (panelBody.classList.contains("hidden")) {
      panelBody.classList.remove("hidden");
    }
  }

  function getPanelHeading(buildingId) {
    return document.querySelector(".personal-select-heading[building_id='" + buildingId + "']");
  }

  function getPanelBody(buildingId) {
    return document.querySelector(".panel-body[building_id='" + buildingId + "']");
  }

  function main() {
    if (!window.location.href.match(/\/buildings\/\d+\/hire/)) {
      return;
    }

    const h1 = document.querySelector("h1[building_type]");
    if (!h1 || h1.getAttribute("building_type") !== "9") {
      return;
    }

    initPanelBodies();
    removePanelHeadingClickEvent();
    addPersonnelSelector();
    addFooter();
  }

  main();
})();