您需要先安装一款用户样式管理器扩展(如 Stylus)后才能安装此样式。
您需要先安装一款用户样式管理器扩展(如 Stylus)后才能安装此样式。
您需要先安装一款用户样式管理器扩展(如 Stylus)后才能安装此样式。
您需要先安装一款用户样式管理器扩展后才能安装此样式。
您需要先安装一款用户样式管理器扩展后才能安装此样式。
您需要先安装一款用户样式管理器扩展后才能安装此样式。
(我已经安装了用户样式管理器,让我安装!)
// ==UserScript==
// @name * Personaler
// @namespace bos-ernie.leitstellenspiel.de
// @version 1.1.0
// @license BSD-3-Clause
// @author BOS-Ernie
// @description Überprüft das Personalsoll und die Werbephasen in allen Gebäuden
// @match https://www.leitstellenspiel.de/
// @icon https://www.google.com/s2/favicons?sz=64&domain=leitstellenspiel.de
// @run-at document-idle
// @grant none
// ==/UserScript==
/* global $ */
(function () {
"use strict";
const defaultSettings = {
"building-type-0": 250,
"building-type-2": 80,
"building-type-6": 300,
"building-type-9": 140,
"building-type-11": 215,
"building-type-12": 34,
"building-type-13": 400,
"building-type-18": 250,
"building-type-19": 300,
"building-type-20": 80,
};
const buildingsWithPersonal = [0, 2, 6, 9, 11, 12, 13, 18, 19, 20];
function addStyle() {
const style =
".loader{width:100px;height:100px;border-radius:100%;position:relative;margin:0 auto;top:40px;left:-2.5px}.loader span{display:inline-block;width:5px;height:20px;background-color:#c9302c}.loader span:first-child{animation:1s ease-in-out infinite grow}.loader span:nth-child(2){animation:1s ease-in-out .15s infinite grow}.loader span:nth-child(3){animation:1s ease-in-out .3s infinite grow}.loader span:nth-child(4){animation:1s ease-in-out .45s infinite grow}@keyframes grow{0%,100%{-webkit-transform:scaleY(1);-ms-transform:scaleY(1);-o-transform:scaleY(1);transform:scaleY(1)}50%{-webkit-transform:scaleY(1.8);-ms-transform:scaleY(1.8);-o-transform:scaleY(1.8);transform:scaleY(1.8)}}";
const styleElement = document.createElement("style");
styleElement.innerHTML = style;
document.head.appendChild(styleElement);
}
function addMenuEntry() {
const profileMenu = document.getElementById("logout_button").parentElement.parentElement;
const divider = document.createElement("li");
divider.setAttribute("class", "divider");
divider.setAttribute("role", "presentation");
profileMenu.append(divider);
const bedIcon = document.createElement("span");
bedIcon.setAttribute("class", "glyphicon glyphicon-user");
const button = document.createElement("a");
button.setAttribute("href", "javascript: void(0)");
button.setAttribute("id", "personaler-button");
button.append(bedIcon);
button.append(" Personaler");
button.addEventListener("click", menuEntryClick);
const li = document.createElement("li");
li.appendChild(button);
profileMenu.append(li);
}
function addModal() {
const modal = document.createElement("div");
modal.className = "modal fade";
modal.id = "personaler-modal";
modal.setAttribute("tabindex", "-1");
modal.setAttribute("role", "dialog");
modal.setAttribute("aria-labelledby", "personaler-modal-label");
modal.setAttribute("aria-hidden", "true");
modal.style.zIndex = "5000";
modal.innerHTML = `<div class="modal-dialog modal-lg" role="document" style="width: 1280px;">
<div class="modal-content">
<div class="modal-header">
<h1 class="modal-title" id="personaler-modal-label">
<span class="glyphicon glyphicon-user" aria-hidden="true"></span> Personaler
</h1>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">×</span>
</button>
</div>
<div class="modal-body" style="max-height: calc(100vh - 212px);overflow-y: auto;">
<ul class="nav nav-tabs" role="tablist" style="margin-bottom: 10px">
<li role="presentation" class="active">
<a
href="#tab-buildings-with-incorrect-personal-count-target"
aria-controls="tab-buildings-with-incorrect-personal-count-target"
role="tab"
data-toggle="tab"
>
Gebäude mit falschem Personal (Soll)
</a>
</li>
<li role="presentation">
<a
href="#tab-buildings-without-automatic-hiring"
aria-controls="tab-buildings-without-automatic-hiring"
role="tab"
data-toggle="tab"
>
Gebäude ohne automatische Werbung
</a>
</li>
<li role="presentation">
<a href="#tab-settings" aria-controls="tab-settings" role="tab" data-toggle="tab">
Einstellungen
</a>
</li>
</ul>
<div>
<div class="tab-content">
<div role="tabpanel" class="tab-pane active" id="tab-buildings-with-incorrect-personal-count-target">
<div id="buildings-with-incorrect-personal-count-target">
<div class="row">
<div class="col-md-12 bg">
<div class="loader">
<span></span>
<span></span>
<span></span>
<span></span>
</div>
</div>
</div>
</div>
</div>
<div role="tabpanel" class="tab-pane" id="tab-buildings-without-automatic-hiring">
<div id="buildings-without-automatic-hiring">
<div class="row">
<div class="col-md-12 bg">
<div class="loader">
<span></span>
<span></span>
<span></span>
<span></span>
</div>
</div>
</div>
</div>
</div>
<div role="tabpanel" class="tab-pane" id="tab-settings">
<div id="settings">
<h2>Personal (Soll)</h2>
<form id="settings" class="form-horizontal">
<div class="form-group">
<label for="building-type-0" class="col-sm-4 control-label">
Feuerwache
</label>
<div class="col-sm-1">
<input type="number" class="form-control" id="building-type-0" min="0" max="400" />
</div>
</div>
<div class="form-group">
<label for="building-type-18" class="col-sm-4 control-label">
Feuerwache (Kleinwache)
</label>
<div class="col-sm-1">
<input type="number" class="form-control" id="building-type-18" min="0" max="400" />
</div>
</div>
<div class="form-group">
<label for="building-type-2" class="col-sm-4 control-label">
Rettungswache
</label>
<div class="col-sm-1">
<input type="number" class="form-control" id="building-type-2" min="0" max="400" />
</div>
</div>
<div class="form-group">
<label for="building-type-20" class="col-sm-4 control-label">
Rettungswache (Kleinwache)
</label>
<div class="col-sm-1">
<input type="number" class="form-control" id="building-type-20" min="0" max="400" />
</div>
</div>
<div class="form-group">
<label for="building-type-6" class="col-sm-4 control-label">
Polizeiwache
</label>
<div class="col-sm-1">
<input type="number" class="form-control" id="building-type-6" min="0" max="400" />
</div>
</div>
<div class="form-group">
<label for="building-type-19" class="col-sm-4 control-label">
Polizeiwache (Kleinwache)
</label>
<div class="col-sm-1">
<input type="number" class="form-control" id="building-type-19" min="0" max="400" />
</div>
</div>
<div class="form-group">
<label for="building-type-11" class="col-sm-4 control-label">
Bereitschaftspolizei
</label>
<div class="col-sm-1">
<input type="number" class="form-control" id="building-type-11" min="0" max="400" />
</div>
</div>
<div class="form-group">
<label for="building-type-13" class="col-sm-4 control-label">
Polizeihubschrauberstation
</label>
<div class="col-sm-1">
<input type="number" class="form-control" id="building-type-13" min="0" max="400" />
</div>
</div>
<div class="form-group">
<label for="building-type-12" class="col-sm-4 control-label">
Schnelleinsatzgruppe (SEG)
</label>
<div class="col-sm-1">
<input type="number" class="form-control" id="building-type-12" min="0" max="400" />
</div>
</div>
<div class="form-group">
<label for="building-type-9" class="col-sm-4 control-label">
THW
</label>
<div class="col-sm-1">
<input type="number" class="form-control" id="building-type-9" min="0" max="400" />
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-2">
<button id="save-settings" type="submit" class="btn btn-success">
Speichern
</button>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
`;
document.body.appendChild(modal);
}
function menuEntryClick(event) {
event.preventDefault();
render();
$("#personaler-modal").modal("show");
}
function getSettings() {
return JSON.parse(localStorage.getItem("personalerSettings"));
}
function fillForm() {
if (localStorage.getItem("personalerSettings") === null) {
localStorage.setItem("personalerSettings", JSON.stringify(defaultSettings));
}
let settings = getSettings();
for (let key in settings) {
document.getElementById(key).value = settings[key];
}
}
function saveSettings(event) {
event.preventDefault();
const saveButton = document.getElementById("save-settings");
saveButton.disabled = true;
saveButton.innerHTML = `
<span class="glyphicon glyphicon-refresh glyphicon-refresh-animate"></span>
Speichern...`;
const settings = {};
const inputs = document.querySelectorAll("#settings input");
for (let i = 0; i < inputs.length; i++) {
settings[inputs[i].id] = inputs[i].value;
}
localStorage.setItem("personalerSettings", JSON.stringify(settings));
setTimeout(() => {
saveButton.disabled = false;
saveButton.innerHTML = '<span class="glyphicon glyphicon-ok"></span> Speichern';
}, 250);
}
function getPersonalCountTarget(building_type) {
const settings = getSettings();
return settings["building-type-" + building_type];
}
async function getBuildings() {
if (
!sessionStorage.aBuildings ||
JSON.parse(sessionStorage.aBuildings).lastUpdate < new Date().getTime() - 5 * 1000 * 60
) {
const buildings = await fetch("/api/buildings.json").then(response => response.json());
try {
sessionStorage.setItem("aBuildings", JSON.stringify({ lastUpdate: new Date().getTime(), value: buildings }));
} catch (e) {
return buildings;
}
}
return JSON.parse(sessionStorage.aBuildings).value;
}
async function getBuildingsWithIncorrectPersonalCountTarget() {
const buildings = await getBuildings();
return buildings.filter(
building =>
buildingsWithPersonal.includes(building.building_type) &&
building.personal_count_target !== getPersonalCountTarget(building.building_type),
);
}
async function getBuildingsWithoutAutomaticHiring() {
const buildings = await getBuildings();
return buildings.filter(
building => buildingsWithPersonal.includes(building.building_type) && building.hiring_automatic !== true,
);
}
async function render() {
const saveButton = document.getElementById("save-settings");
saveButton.addEventListener("click", saveSettings);
fillForm();
const buildingsWithIncorrectPersonalCountTarget = await getBuildingsWithIncorrectPersonalCountTarget();
if (buildingsWithIncorrectPersonalCountTarget.length > 0) {
const tabPersonalCountTarget = document.getElementById("buildings-with-incorrect-personal-count-target");
tabPersonalCountTarget.innerHTML = `
<table class="table table-striped table-hover">
<thead>
<tr>
<th>Gebäude</th>
<th>Ist: Personal (Soll)</th>
<th>Soll: Personal (Soll)</th>
</tr>
</thead>
<tbody>
${buildingsWithIncorrectPersonalCountTarget
.sort((a, b) => {
if (a.caption < b.caption) {
return -1;
}
if (a.caption > b.caption) {
return 1;
}
return 0;
})
.map(
building => `
<tr>
<td><a href="/buildings/${building.id}">${building.caption}</a></td>
<td>${building.personal_count_target}</td>
<td>${getPersonalCountTarget(building.building_type)}</td>
</tr>
`,
)
.join("")}
</tbody>
</table>
`;
} else {
const tabPersonalCountTarget = document.getElementById("buildings-with-incorrect-personal-count-target");
tabPersonalCountTarget.innerHTML = `
<div class="row">
<div class="col-md-12 bg">
<div class="alert alert-success" role="alert">
<span class="glyphicon glyphicon-ok" aria-hidden="true"></span> Alle Gebäude haben das richtige Soll-Personal
</div>
</div>
</div>
`;
}
const tabHiring = document.getElementById("buildings-without-automatic-hiring");
const buildingsWithoutAutomaticHiring = await getBuildingsWithoutAutomaticHiring();
if (buildingsWithoutAutomaticHiring.length > 0) {
tabHiring.innerHTML = `
<table class="table table-striped table-hover">
<thead>
<tr>
<th>Gebäude</th>
</tr>
</thead>
<tbody>
${buildingsWithoutAutomaticHiring
.sort((a, b) => {
if (a.caption < b.caption) {
return -1;
}
if (a.caption > b.caption) {
return 1;
}
return 0;
})
.map(
building => `
<tr>
<td><a href="/buildings/${building.id}">${building.caption}</a></td>
</tr>
`,
)
.join("")}
</tbody>
</table>
`;
} else {
tabHiring.innerHTML = `
<div class="row">
<div class="col-md-12 bg">
<div class="alert alert-success" role="alert">
<span class="glyphicon glyphicon-ok" aria-hidden="true"></span> Alle Gebäude haben automatische Werbung aktiviert
</div>
</div>
</div>
`;
}
}
async function main() {
addStyle();
addMenuEntry();
addModal();
}
main();
})();