// ==UserScript==
// @name Runcity
// @namespace http://tampermonkey.net/
// @version 2024-11-18
// @desc try to take over the world!
// @author You
// @match https://rst.runcity.org/*
// @icon https://www.google.com/s2/favicons?sz=64&domain=runcity.org
// @grant none
// @require https://code.jquery.com/jquery-3.7.1.min.js
// @require https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/js/select2.min.js
// @require https://cdn.jsdelivr.net/npm/swiper@11/swiper-bundle.min.js
// ==/UserScript==
(function () {
'use strict';
let fileInputs = {
"attachment1": "legend_photo",
"attachment4": "legend_files",
"attachment2": "admin_photo",
"attachment3": "admin_files"
}
const localStorageItems = {
NEED_UPDATE_ID: "needUpdateId",
JUST_CREATED: "justCreated",
PREV_CREATED: "prevCreated",
LATTITUDE: "lattitude",
LONGITUDE: "longitude",
ALWAYS_PRETTIFY: "always",
REDIRECT_EXIT: "redirectExit"
}
const ZOOM = 17
const METERS = 510
let removedFilesLinks = []
class Property {
id;
name;
content;
desc;
constructor(id, row) {
this.id = id
let columns = [...row.querySelectorAll('td, th')].map(el => el.cloneNode(true))
if (columns.length === 1) {
this.content = columns[0].innerHTML.trim()
}
else if (columns.length >= 2) {
this.name = columns[0].innerHTML.trim()
let elementsToRemove = columns[1].querySelector("input[name=\"read_gps2\"]")
if (elementsToRemove) {
columns[1].removeChild(elementsToRemove)
}
this.content = columns[1].innerHTML
.replaceAll("
", "")
.replaceAll(/\s*Перейти к легенде.*$/gs, "")
.replaceAll("для легенды (\"l\")", "")
.replaceAll("общие (\"c\")", "")
.replaceAll("для ист. справки (\"h\")", "")
.replaceAll("Получить координаты КП из картинки", "")
.trim()
}
if (columns.length === 3) {
this.desc = columns[2].innerHTML.replaceAll("
", "").replaceAll(/(\n)\s*/g, "$1").trim()
}
}
toDiv(altName = null, altDesc = null) {
let div = document.createElement('div')
let desc = altDesc ?? this.desc
desc = desc ? "
" +
"

" +
"
" : ""
div.innerHTML = "" + "" + this.content + "
" +
desc
return div
}
}
function createFrom(rows, classList, data) {
let container = document.createElement('div')
container.classList.add(...classList.split(" "))
for (const options of data) {
let prop = new Property(options.index, rows[options.index])
container.appendChild(prop.toDiv(options.name, options.desc))
}
return container
}
function createFromMulti(rows, classList, data) {
let rowData = []
let toIndex = data.to ?? rows.length - 1
for (let i = data.from; i <= toIndex; i++) {
rowData.push({ index: i })
}
return createFrom(rows, classList, rowData)
}
function addCss(link) {
let css = document.createElement("link")
css.href = link
css.rel = "stylesheet"
document.head.appendChild(css)
}
function addJs(link) {
let script = document.createElement("script")
script.src = link
document.head.appendChild(script)
}
const sleep = ms => new Promise(res => setTimeout(res, ms));
function isEditCpPage() {
let params = new URLSearchParams(document.location.search)
return params.get("action") === "edit"
}
function isDeleteCpPage() {
let params = new URLSearchParams(document.location.search)
return params.get("action") === "delete"
}
async function updatePoint(formData) {
return await fetch(`https://runcity.geo.rictum.ru/api/competitions/${window.location.pathname.split('/')[1]}/update`, {
method: 'POST',
body: formData
})
}
async function deletePoint() {
return await fetch(`https://runcity.geo.rictum.ru/api/points/${new URLSearchParams(location.search).get("cp_id")}`, {
method: 'DELETE'
})
}
function copyCoordinates() {
let copyButton = document.createElement("button")
copyButton.addEventListener("click", async event => {
event.preventDefault();
let lat = document.querySelector(`input[name="cp[lattitude]"]`).value
let lon = document.querySelector(`input[name="cp[longitude]"]`).value
const text = new Blob([`${lat}, ${lon}`], { type: "text/plain" });
const data = new ClipboardItem({ "text/plain": text });
await navigator.clipboard.write([data]);
})
copyButton.classList.add("copy-button")
let copyImage = document.createElement("img")
copyImage.src = "https://upload.wikimedia.org/wikipedia/commons/a/aa/Bw_copy_icon_320x320.svg"
copyButton.appendChild(copyImage)
return copyButton
}
function yandexMaps(lat, lon, zoom) {
return `https://yandex.ru/maps/213/moscow/?ll=${lon}%2C${lat}&z=${zoom}`
}
function googleMaps(lat, lon, meters) {
return `https://www.google.ru/maps/@${lat},${lon},${meters}m`
}
function pastvu(lat, lon, zoom) {
return `https://pastvu.com/?g=${lat},${lon}&z=${zoom}&s=yandex&t=scheme&type=1`
}
function twoGis(lat, lon, zoom) {
return `https://2gis.ru/moscow?m=${lon}%2C${lat}%2F${zoom}`
}
function makeRef(linkCallback, iconSrc, zoom) {
let ref = document.createElement("a")
ref.href = linkCallback(lat, lon, zoom)
ref.setAttribute('target', "_blank")
ref.addEventListener("click", function (e) {
let lat = document.querySelector(`input[name="cp[lattitude]"]`).value
let lon = document.querySelector(`input[name="cp[longitude]"]`).value
this.href = linkCallback(lat, lon, zoom)
})
let icon = document.createElement("img")
icon.src = iconSrc
icon.classList.add("map-icon")
ref.appendChild(icon)
return ref
}
let makeCoordinatesLinks = (function() {
var executed = false
return function () {
if (executed) return
executed = true
let linksContainer = document.createElement("div")
linksContainer.appendChild(copyCoordinates())
linksContainer.appendChild(makeRef(yandexMaps, "https://upload.wikimedia.org/wikipedia/commons/7/72/Yandex_Maps_icon.svg", ZOOM))
linksContainer.appendChild(makeRef(googleMaps, "https://upload.wikimedia.org/wikipedia/commons/a/aa/Google_Maps_icon_%282020%29.svg", METERS))
linksContainer.appendChild(makeRef(pastvu, "https://pastvu.com/coast-icon.png", ZOOM))
linksContainer.appendChild(makeRef(twoGis, "https://d-assets.2gis.ru/favicon.png", ZOOM))
document.querySelector(`div:has(> div > input[name="cp[longitude]"])`).after(linksContainer)
}
})()
function makeDownloadLink(name, href = null) {
let downloadLink = document.createElement("a")
downloadLink.setAttribute("download", name)
downloadLink.textContent = "Скачать"
downloadLink.setAttribute("target", "_blank")
if (href)
downloadLink.href = href
return downloadLink
}
async function sendFileDeleted(inputName) {
let formData = new FormData()
let normalInputName = fileInputs[inputName]
formData.set(`cp[id]`, document.querySelector("input[name='cp[id]']").value)
formData.set(`cp[${normalInputName}]`, "-1")
let result = await updatePoint(formData)
try {
console.log(result.ok)
}
catch (e) {
console.log(e)
}
}
function addDisplayedFile(inputName, type, file, preview, removeLink) {
if (!inputName || !file || !removeLink) return
let input = document.querySelector(`input[name="${inputName}\[\]"]`)
let fileListContainer = input.parentElement.parentElement.querySelector(".file-list-container")
let fileContainer = document.createElement("div")
fileContainer.classList.add("file-container")
const displayedFile = document.createElement(type ?? "a")
displayedFile.classList.add("preview-small")
if (type == "img") {
displayedFile.src = preview
displayedFile.dataset.origin = file
displayedFile.addEventListener("click", () => {
let dialog = document.querySelector("dialog")
makeSwiper(dialog, fileListContainer, file)
dialog.showModal()
})
}
else if (type == "audio") {
displayedFile.src = file
displayedFile.setAttribute("controls", "")
}
else {
displayedFile.href = file
displayedFile.textContent = "Файл"
}
let fileButtonsContainer = document.createElement("div")
fileButtonsContainer.classList.add("file-buttons-container")
fileButtonsContainer.appendChild(makeDownloadLink(/[^/]*$/.exec(new URL(file).pathname)[0], file))
let deleteButton = document.createElement("button")
deleteButton.type = "button"
deleteButton.classList.add("button-delete")
deleteButton.textContent = "x"
deleteButton.addEventListener("click", async e => {
if (!confirm("Точно?")) return
await fetch(removeLink)
await sendFileDeleted(inputName)
removedFilesLinks.push(removeLink)
fileListContainer.removeChild(fileContainer)
})
fileButtonsContainer.appendChild(deleteButton)
fileContainer.appendChild(displayedFile)
fileContainer.appendChild(fileButtonsContainer)
fileListContainer.appendChild(fileContainer)
}
function dataURLtoFile(dataurl, filename) {
let arr = dataurl.split(',')
let mime = arr[0].match(/:(.*?);/)[1]
let bstr = atob(arr[arr.length - 1])
let n = bstr.length
let u8arr = new Uint8Array(n)
while (n--) {
u8arr[n] = bstr.charCodeAt(n)
}
return new File([u8arr], filename, {type:mime})
}
function getHtmlElementByFileType(file) {
const htmlElementsForTypes = {
"image/": "img",
"audio/": "audio"
}
let res = "div"
for (const [type, el] of Object.entries(htmlElementsForTypes)) {
if (file.type.startsWith(type)) {
res = el
break
}
}
let el = document.createElement(res)
if (res == "audio") {
el.setAttribute("controls", "")
}
return el
}
function makeHandleFilesFunc() {
let storedFiles = []
return function() {
let dt = new DataTransfer()
if (storedFiles.length) {
for (const oldFile of storedFiles) {
let oldFileObj = dataURLtoFile(oldFile.data, oldFile.name)
dt.items.add(oldFileObj)
}
}
for (let i = 0; i < this.files.length; i++) {
const file = this.files[i]
dt.items.add(file)
let fileListContainer = this.parentElement.parentElement.querySelector(".file-list-container")
let fileContainer = document.createElement("div")
fileContainer.classList.add("file-container")
const displayedFile = getHtmlElementByFileType(file)
displayedFile.classList.add("preview")
displayedFile.file = file
displayedFile.addEventListener("click", () => {
let dialog = document.querySelector("dialog")
makeSwiper(dialog, fileListContainer, displayedFile.src)
dialog.showModal()
})
let fileButtonsContainer = document.createElement("div")
fileButtonsContainer.classList.add("file-buttons-container")
let downloadLink = makeDownloadLink(file.name)
fileButtonsContainer.appendChild(downloadLink)
let deleteButton = document.createElement("button")
deleteButton.classList.add("button-delete")
deleteButton.textContent = "x"
deleteButton.type = "button"
deleteButton.addEventListener("click", e => {
let index = [...fileListContainer.children].indexOf(fileContainer)
if (!confirm("Точно?")) return
storedFiles.splice(storedFiles.length - this.files.length + index, 1)
let rdt = new DataTransfer()
for (const file of this.files) {
rdt.items.add(file)
}
rdt.items.remove(index)
this.files = rdt.files
fileListContainer.removeChild(fileContainer)
})
fileButtonsContainer.appendChild(deleteButton)
fileContainer.appendChild(displayedFile)
fileContainer.appendChild(fileButtonsContainer)
fileListContainer.appendChild(fileContainer)
const reader = new FileReader()
reader.onload = (e) => {
displayedFile.src = e.target.result
downloadLink.setAttribute("href", displayedFile.src)
storedFiles.push({data: displayedFile.src, name: file.name})
}
reader.readAsDataURL(file)
}
this.files = dt.files
}
}
let prettifyFiles = (function() {
var executed = false
return function(insertedFileRows) {
if (executed) return
executed = true
document.querySelector("#new").querySelectorAll("input[type=file]").forEach((element, index) => {
element.id = `input-file-${index}`
element.setAttribute("multiple", "multiple")
element.dataset.index = index
let pseudoInput = document.createElement("label")
pseudoInput.classList.add("custom-file-upload")
pseudoInput.setAttribute("for", element.id)
pseudoInput.textContent = "+"
element.parentElement.insertBefore(pseudoInput, element)
let fileListContainer = document.createElement("div")
fileListContainer.classList.add("file-list-container")
element.parentElement.parentElement.appendChild(fileListContainer)
element.addEventListener("change", makeHandleFilesFunc(), false)
})
moveFileInputValues(insertedFileRows)
}
})()
function saveInputValues(from) {
let fromInputs = [...from.querySelectorAll("input, textarea")]
let result = new Map()
for (const fromEl of fromInputs) {
result.set(fromEl, fromEl.type == "radio" ? fromEl.checked : fromEl.value)
}
return result
}
function setInputValues(to, values) {
let toInputs = [...to.querySelectorAll("input, textarea")]
for (const [fromEl, value] of values) {
let currentToInput = toInputs.find(toEl =>
toEl.type !== 'file' &&
toEl.type === fromEl.type &&
toEl.name === fromEl.name &&
toEl.id === fromEl.id &&
(toEl.type !== "radio" || toEl.value === fromEl.value)
)
if (currentToInput) {
currentToInput.checked = fromEl.checked
currentToInput.value = fromEl.value
}
}
}
function moveFileInputValues(insertedFileRows) {
for (const [inputRowIndex, rows] of insertedFileRows) {
for (const row of rows) {
let fileInput = null, type = null, fileUrl = null, deleteUrl = null, preview = null
if (row.querySelector("img")) {
type = "img"
fileUrl = row.querySelector("td:first-child a").href
preview = row.querySelector("img").src
}
else if (row.querySelector("audio")) {
type = "audio"
fileUrl = row.querySelector("audio").src
}
else {
fileUrl = row.querySelector("td:first-child a").href
}
deleteUrl = row.querySelector("td:nth-child(2) a").href
if (inputRowIndex == 23) {
fileInput = "attachment1"
}
else if (inputRowIndex == 27) {
fileInput = "attachment4"
}
else if (inputRowIndex == 31) {
fileInput = "attachment2"
}
else if (inputRowIndex == 35) {
fileInput = "attachment3"
}
else {
console.log(inputRowIndex)
return
}
addDisplayedFile(fileInput, type, fileUrl, preview, deleteUrl)
}
}
}
function removeDeletedFilesFromUglyVersion(removeLink) {
let shortHref = removeLink.split("/").at(-1)
document.querySelector(`tr:has(a[href="${shortHref}"])`).remove()
}
function moveInputValues(form, was, became) {
let inputValues = saveInputValues(was)
form.removeChild(was)
form.appendChild(became)
setInputValues(became, inputValues)
createSendButtons()
}
function prettify(form, was, became, insertedFileRows) {
moveInputValues(form, was, became)
prettifyFiles(insertedFileRows)
$("#cps_main").select2()
makeCoordinatesLinks()
}
function uglify(form, was, became) {
moveInputValues(form, was, became)
for (const removedFileLink of removedFilesLinks) {
removeDeletedFilesFromUglyVersion(removedFileLink)
}
removedFilesLinks = []
}
function createAlwaysPrettifyInput(index) {
let alwaysPrettify = document.createElement("div")
alwaysPrettify.classList.add("always-prettify-container")
let alwaysPrettifyLabel = document.createElement("label")
alwaysPrettifyLabel.setAttribute("for", "always-prettify-" + index)
alwaysPrettifyLabel.textContent = "Всегда"
alwaysPrettify.appendChild(alwaysPrettifyLabel)
let alwaysPrettifyCheckbox = document.createElement("input")
alwaysPrettifyCheckbox.type = "checkbox"
alwaysPrettifyCheckbox.id = "always-prettify-" + index
alwaysPrettifyCheckbox.name = "always-prettify-" + index
alwaysPrettifyCheckbox.addEventListener("change", function() {
let otherCheckboxes = document.querySelectorAll(`input[name^="always-prettify-"]`)
for (let checkbox of otherCheckboxes) {
if (checkbox.id !== this.id) {
checkbox.checked = this.checked
}
}
if (this.checked)
localStorage.setItem(localStorageItems.ALWAYS_PRETTIFY, "+")
else
localStorage.removeItem(localStorageItems.ALWAYS_PRETTIFY)
})
alwaysPrettify.appendChild(alwaysPrettifyCheckbox)
i++
return alwaysPrettify
}
function hide(elList) {
elList.forEach(el => el.classList.add("hidden"))
}
function show(elList) {
elList.forEach(el => el.classList.remove("hidden"))
}
function makeSwiper(dialog, fileListContainer, src) {
let files = [...fileListContainer.querySelectorAll(`:is(.preview, .preview-small)`)]
let swiperDiv = document.createElement("div")
swiperDiv.classList.add("swiper")
let swiperWrapper = document.createElement("div")
swiperWrapper.classList.add("swiper-wrapper")
for (const file of files) {
let swiperSlide = document.createElement("div")
swiperSlide.classList.add("swiper-slide")
let downloadLink = document.createElement("a")
downloadLink.classList.add("swiper-download-link")
downloadLink.href = file.dataset.origin
downloadLink.text = "Скачать"
downloadLink.setAttribute("target", "_blank")
swiperSlide.appendChild(downloadLink)
let swiperFile = file.cloneNode(true)
if (swiperFile.dataset.origin)
swiperFile.src = swiperFile.dataset.origin
swiperSlide.appendChild(swiperFile)
swiperWrapper.appendChild(swiperSlide)
}
swiperDiv.appendChild(swiperWrapper)
let prevButton = document.createElement("div")
prevButton.classList.add("swiper-button-prev")
swiperDiv.appendChild(prevButton)
let nextButton = document.createElement("div")
nextButton.classList.add("swiper-button-next")
swiperDiv.appendChild(nextButton)
dialog.appendChild(swiperDiv)
new Swiper('.swiper', {
initialSlide: files.findIndex(el => el.dataset.origin && el.dataset.origin === src || el.src === src),
navigation: {
nextEl: '.swiper-button-next',
prevEl: '.swiper-button-prev',
},
})
bindArrowsForGallery('.swiper-button-prev', '.swiper-button-next')
}
function bindArrowsForGallery(leftButtonQuery, rightButtonQuery) {
document.addEventListener("keydown", function(e) {
if (!document.querySelector("dialog").open) return
switch(e.key) {
case "ArrowLeft":
$(leftButtonQuery).click()
break
case "ArrowRight":
$(rightButtonQuery).click()
break
default: return
}
e.preventDefault()
})
}
async function sendForm(onSend) {
let formData = new FormData(document.querySelector("form"))
let fileContainers = [...document.querySelectorAll(".file-list-container")]
let fileInputNames = ["legend_photo", "legend_files", "admin_photo", "admin_files"]
for (let [i, fileContainer] of fileContainers.entries()) {
formData.set(`cp[${fileInputNames[i]}]`, fileContainer.children.length)
}
if (formData.get("cp[id]") == '') {
localStorage.setItem(localStorageItems.NEED_UPDATE_ID, true)
}
let props = document.querySelectorAll("input[name^=\"prop_\"]");
for (let prop of props) {
let parent = prop.parentElement
let propName
if (parent.tagName === "DIV") {
propName = parent.parentElement.querySelector("label").textContent
}
else if (parent.tagName === "TD") {
propName = parent.parentElement.querySelector("td:first-child").textContent
}
formData.set(`propname[${prop.name}]`, propName)
}
let result = await updatePoint(formData)
try {
console.log(result.ok)
}
catch (e) {
console.log(e)
}
finally {
onSend()
}
}
function createSaveAndNewButton() {
let saveAndNewButton = document.createElement("button")
saveAndNewButton.textContent = "+"
saveAndNewButton.type = "button"
saveAndNewButton.classList.add("safe-action")
saveAndNewButton.addEventListener("click", () => {
sendForm(() => {
let cpNumber = document.querySelector(`input[name="cp[number]"]`).value
let lattitude = document.querySelector(`input[name="cp[lattitude]"]`).value
let longitude = document.querySelector(`input[name="cp[longitude]"]`).value
localStorage.setItem(localStorageItems.JUST_CREATED, cpNumber)
localStorage.setItem(localStorageItems.LATTITUDE, lattitude)
localStorage.setItem(localStorageItems.LONGITUDE, longitude)
document.querySelector(`input[name="save_go"]`).click()
})
})
return saveAndNewButton
}
function createSendButtons() {
if (document.querySelector(".pseudo-save")) return
let saveAndStayButtons = document.querySelectorAll(`input[name="save_go"]`)
let saveAndExitButtons = document.querySelectorAll(`input[name="save_exit"]`)
let saveButtons = [...saveAndStayButtons, ...saveAndExitButtons]
for (let saveButton of saveButtons) {
let pseudoSaveButton = document.createElement("button")
pseudoSaveButton.type = "button"
pseudoSaveButton.textContent = saveButton.value
pseudoSaveButton.classList.add("safe-action", "pseudo-save")
pseudoSaveButton.addEventListener("click", async () => await sendForm(() => {
if (document.querySelector(`input[name="cp[id]"]`).value != '') {
saveButton.click()
return
}
if (saveButton.name == "save_exit") {
localStorage.setItem(localStorageItems.REDIRECT_EXIT, true)
}
saveAndStayButtons[0].click()
}))
saveButton.style.display = "none"
saveButton.parentElement.insertBefore(pseudoSaveButton, saveButton)
}
saveAndStayButtons.forEach(el => el.after(createSaveAndNewButton()))
}
function addUglyDeleteListener() {
let links = [...document.querySelectorAll("a[href*='del_stored']")]
for (const link of links) {
let shortHref = link.href.split("/").at(-1)
let fileInput = document.querySelector(`tr:has(a[href$="${shortHref}"]) ~ tr:has(input[type="file"]) input[type="file"]`)
link.addEventListener("click", async e => {
e.preventDefault()
if (!confirm("Точно?")) return
await sendFileDeleted(fileInput.name.split("[")[0])
location.href = link.href
})
}
}
function addFullscreenButton() {
$(async () => {
if (document.querySelector("#map") !== null) {
while (L.Control.Fullscreen === undefined) {
await sleep(500)
}
map.addControl(new L.Control.Fullscreen())
}
})
}
let styles = `
.large-menu {
position: sticky;
top: 0;
z-index: 10;
background: white;
}
#content-wrapper {
display: flex;
gap: 20px;
#props {
width: unset !important;
}
form {
width: 50%;
}
#map-wrapper {
width: 50%;
}
#map {
width: unset !important;
}
#new {
display: flex;
flex-direction: column;
& > :not(:first-child) {
padding-top: 5px;
border-top: 1px solid #ddd;
}
& > :not(:last-child) {
padding-bottom: 5px;
}
.map-icon {
width: 15px;
height: 15px;
}
.copy-button {
all: unset;
cursor: pointer;
}
.copy-button img {
width: 15px;
heigth: 15px;
}
input[type=radio], input[type=checkbox] {
margin: 0;
}
input[type="file"] {
display: none;
}
div > input[type="checkbox"] {
display: flex;
align-items: center;
}
input#cp_number {
width: 3em;
}
select#cps_main {
width: 6em;
}
input:is(#lat, #lng) {
width: 5.5em;
}
input[name="cp[name_int]"] {
width: 34em;
}
}
#cps_main {
width: 150px;
}
input[type=submit] {
width: fit-content;
}
.desc-icon-container {
display: flex;
align-items: center;
}
.desc-icon {
width: 15px;
height: 15px;
cursor: help;
}
button, input[type=submit] {
cursor: pointer;
}
.buttons-row__content-wrapper {
display: flex;
justify-content: center;
gap: 10px;
font-weight: normal;
}
.custom-file-upload {
display: block;
width: fit-content;
border: 1px solid black;
padding: 5px 14px;
border-radius: 5px;
box-shadow: 0 1px 3px #ddd;
background: linear-gradient(white, #ddd);
}
.buttons > * > * {
display: flex;
gap: 10px;
}
.always-prettify-container {
display: flex;
align-items: center;
gap: 5px;
}
.header > * {
display: flex;
gap: 5px;
}
:is(.header, .options) input {
width: unset;
}
.header {
display: flex;
gap: 30px;
align-items: start;
}
.options {
display: flex;
flex-wrap: wrap;
gap: 10px;
}
.options > * {
display: flex;
gap: 5px;
align-items: center;
}
.legend-container {
display: flex;
}
.legend-container > :not(:last-child) {
padding-right: 15px;
border-right: 1px solid #ddd;
}
.legend-container > :not(:first-child) {
padding-left: 15px;
}
.legend-container > * {
display: flex;
flex-direction: column;
flex: 0 1 48%;
gap: 5px;
}
.legend-container__desc-header {
display: flex;
gap: 10px;
align-items: center;
justify-content: end;
}
.legend-desc {
display: flex;
flex-direction: column;
gap: 5px;
}
.collapsed, .hidden {
display: none;
}
.collapse-button::after {
content: "Свернуть"
}
.collapsed + * > .collapse-button::after {
content: "Развернуть"
}
:is(.comment, .legend-desc) > * {
display: flex;
gap: 5px;
}
:is(.comment, .legend-desc) label {
display: block;
width: 10em;
}
:is(.comment, .legend-desc) > * > :nth-child(2) {
flex: 1 1 auto;
}
textarea {
width: 100%;
}
.files-container input[type=radio] {
display: none;
}
.file-list-container {
display: flex;
flex-wrap: wrap;
gap: 10px;
margin-top: 5px;
}
.file-container {
display: flex;
flex-direction: column;
gap: 5px;
}
.file-container img.preview {
width: 120px;
}
.file-container img.preview-small {
width: 60px;
}
.file-container img:is(.preview, .preview-small) {
cursor: zoom-in;
}
dialog .preview,
.swiper {
max-width: 1000px;
width: 100%;
max-height: 500px;
height: 100%;
}
.swiper-button-next,
.swiper-button-prev {
user-select: none;
-webkit-user-select: none;
}
.swiper-wrapper {
align-items: center;
}
.swiper-wrapper > .swiper-slide {
width: 100%;
max-height: 500px;
height: 100%;
}
.swiper-slide img {
width: 100%;
max-height: 500px;
height: 100%;
object-fit: contain;
object-position: center;
}
.swiper-download-link {
display: block;
position: absolute;
top: 0;
right: 0;
background: white;
padding: 5px 10px;
margin: 5px;
border: 1px solid #ccc;
border-radius: 7.5px;
}
button.button-delete {
padding: 0;
border: 0;
margin: 0;
background: none;
font-size: 18px;
}
.admin-files-container {
display: flex;
}
.admin-files-container > :not(:last-child) {
padding-right: 10px;
border-right: 1px solid #ddd;
}
.admin-files-container > :not(:first-child) {
padding-left: 10px;
}
dialog img.preview {
width: auto;
}
.file-buttons-container {
display: flex;
align-items: center;
gap: 5px;
}
}
`
/* REDIRECTS */
let justCreated = localStorage.getItem(localStorageItems.JUST_CREATED)
let prevCreated = localStorage.getItem(localStorageItems.PREV_CREATED)
let needUpdateId = localStorage.getItem(localStorageItems.NEED_UPDATE_ID)
let exit = localStorage.getItem(localStorageItems.REDIRECT_EXIT)
if (needUpdateId) {
let formData = new FormData()
formData.set("cp[id]", new URLSearchParams(document.location.search).get("cp_id"))
formData.set("cp[number]", document.querySelector(`input[name="cp[number]"]`).value)
formData.set("update_id", true)
updatePoint(formData).then(() => localStorage.removeItem(localStorageItems.NEED_UPDATE_ID))
}
if (exit) {
localStorage.removeItem(localStorageItems.REDIRECT_EXIT)
location.href = location.href.replace(location.search, '')
return
}
if (justCreated !== null) {
localStorage.setItem(localStorageItems.PREV_CREATED, justCreated)
localStorage.removeItem(localStorageItems.JUST_CREATED)
location.href = location.href.replace(location.search, "?action=edit")
return
}
if (prevCreated !== null) {
document.querySelector(`input[name="cp[number]"]`).value = parseInt(localStorage.getItem(localStorageItems.PREV_CREATED)) + 1
document.querySelector(`input[name="cp[lattitude]"]`).value = localStorage.getItem(localStorageItems.LATTITUDE)
document.querySelector(`input[name="cp[longitude]"]`).value = localStorage.getItem(localStorageItems.LONGITUDE)
localStorage.removeItem(localStorageItems.PREV_CREATED)
localStorage.removeItem(localStorageItems.LATTITUDE)
localStorage.removeItem(localStorageItems.LONGITUDE)
}
/* HEAD */
let styleSheet = document.createElement("style")
styleSheet.textContent = styles
document.head.appendChild(styleSheet)
/* SWITCH FOR DIFFERENT PAGES */
if (isDeleteCpPage()) {
let deleteButton = document.querySelector(`input[name="delete_go"]`)
let pseudoDeleteButton = document.createElement("button")
pseudoDeleteButton.type = "button"
pseudoDeleteButton.textContent = deleteButton.value
pseudoDeleteButton.classList.add("unsafe-action", "pseudo-save")
pseudoDeleteButton.addEventListener("click", async () => {
await deletePoint()
deleteButton.click()
})
deleteButton.style.display = "none"
deleteButton.parentElement.insertBefore(pseudoDeleteButton, deleteButton)
return
}
addJs('https://api.mapbox.com/mapbox.js/plugins/leaflet-fullscreen/v1.0.1/Leaflet.fullscreen.min.js')
addCss('https://api.mapbox.com/mapbox.js/plugins/leaflet-fullscreen/v1.0.1/leaflet.fullscreen.css')
addFullscreenButton()
if (!isEditCpPage()) {
return
}
/* action=edit HEAD */
addCss("https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/css/select2.min.css")
addCss("https://cdn.jsdelivr.net/npm/swiper@11/swiper-bundle.min.css")
/* NEW DEFALUT VALUES */
document.querySelector(`#props input[name="new_file_type1"][value="l"]`).click()
document.querySelector(`#props input[name="new_file_type4"][value="l"]`).click()
/* CONTAINER */
let form = document.querySelector('form')
let rows = [...document.querySelectorAll('#props > tbody > tr')]
let insertedFileRows = new Map()
let i = 0
while (i < rows.length) {
if ([23, 27, 31, 35].includes(i) && rows[i].querySelector(`input[type="file"]`) == null) {
if (!insertedFileRows.has(i)) {
insertedFileRows.set(i, [rows[i]])
}
else {
let savedRows = insertedFileRows.get(i)
savedRows.push(rows[i])
insertedFileRows.set(i, savedRows)
}
rows.splice(i, 1)
}
else {
i++
}
}
let container = document.createElement('div')
container.id = "new"
let oldTable = document.querySelector("#props")
/* HEADER */
let headerContainer = createFrom(rows, "header", [
{ index: 2, name: "№", desc: "" },
{ index: 8, name: "Название" }
])
let copyLink = document.createElement("div")
copyLink.innerHTML = new Property(2, rows[2]).desc
headerContainer.append(copyLink)
/* TOP BUTTONS */
let topButtonsContainer = createFrom(rows, "buttons", [
{ index: 0 }
])
let bottomButtonsContainer = topButtonsContainer.cloneNode(true)
document.querySelectorAll("#props tr:is(:first-child, :last-child) th").forEach((el, index) => {
let rowContentWrapper = document.createElement("div")
rowContentWrapper.classList.add("buttons-row__content-wrapper")
while ([...el.children].length > 0) {
let child = el.firstChild
rowContentWrapper.appendChild(child)
}
let prettifyButton = document.createElement("button")
prettifyButton.type = "button"
prettifyButton.textContent = "Сделать красиво"
prettifyButton.addEventListener("click", () => {
prettify(form, oldTable, container, insertedFileRows)
})
rowContentWrapper.appendChild(prettifyButton)
rowContentWrapper.appendChild(createAlwaysPrettifyInput(index))
el.appendChild(rowContentWrapper)
})
;[topButtonsContainer, bottomButtonsContainer].forEach((el, index) => {
let unglifyButton = document.createElement("button")
unglifyButton.type = "button"
unglifyButton.textContent = "Сделать некрасиво"
unglifyButton.addEventListener("click", () => {
uglify(form, container, oldTable)
})
let topRowContentWrapper = el.querySelector("div > div > div")
topRowContentWrapper.appendChild(unglifyButton)
topRowContentWrapper.appendChild(createAlwaysPrettifyInput(index))
})
addUglyDeleteListener()
createSendButtons()
/* OPTIONS */
let firstContainer = createFrom(rows, "options", [
{ index: 3 },
{ index: 4, name: "КП-загадка" },
{ index: 16, name: "Этапник" },
{ index: 7, name: "Пиктограмма" },
{ index: 12, name: "Нужна ИС" },
{ index: 17, name: "Спрятать ИС" },
{ index: 14, name: "Основной КП" },
{ index: 10, name: "Широта" },
{ index: 11, name: "Долгота" },
{ index: 5, name: "Старт" },
{ index: 6, name: "Финиш" },
{ index: 15, name: "Знак" }
])
let commentContainer = createFrom(rows, "comment", [
{ index: 9, desc: "" }
])
/* LEGEND */
let legendContainer = document.createElement("div")
legendContainer.classList.add("legend-container")
/* LEGEND DESC */
let legendDescContainer = document.createElement("div")
legendDescContainer.classList.add("legend-container__desc")
let legendDescHeader = document.createElement("div")
legendDescHeader.classList.add("legend-container__desc-header")
const LEGEND_RU_LABEL = "Русский"
const LEGEND_EN_LABEL = "Английский"
let legendLang = document.createElement("div")
legendLang.classList.add("legend-desc__lang")
legendLang.textContent = LEGEND_RU_LABEL
legendDescHeader.appendChild(legendLang)
let legendEnSwitchContainer = createFrom(rows, "legend-switch-container", [
{ index: 48 }
])
legendEnSwitchContainer.addEventListener("click", event => {
hide([legendRuDescContainer, legendRuHiddenDescContainer, legendEnSwitchContainer])
show([legendEnDescContainer, legendEnHiddenDescContainer, legendRuSwitchContainer])
legendLang.textContent = LEGEND_EN_LABEL
})
legendDescHeader.appendChild(legendEnSwitchContainer)
let copyDescButton = document.createElement("button")
copyDescButton.type = "button"
copyDescButton.textContent = "Копировать"
copyDescButton.addEventListener("click", () => {
let ruInputs = [...container.querySelectorAll(":is(input, textarea)[name^=\"cp_strings\[ru\]\"]")]
let enInputs = [...container.querySelectorAll(":is(input, textarea)[name^=\"cp_strings\[en\]\"]")]
for (const [i, enInput] of enInputs.entries()) {
enInput.value = ruInputs[i].value
}
})
let legendRuSwitchContainer = createFrom(rows, "legend-switch-container hidden", [
{ index: 39 }
])
legendRuSwitchContainer.addEventListener("click", event => {
hide([legendEnDescContainer, legendEnHiddenDescContainer, legendRuSwitchContainer])
show([legendRuDescContainer, legendRuHiddenDescContainer, legendEnSwitchContainer])
legendLang.textContent = LEGEND_RU_LABEL
})
legendDescHeader.appendChild(legendRuSwitchContainer)
legendDescHeader.appendChild(copyDescButton)
let legendRuDescContainer = createFrom(rows, "legend-desc", [
{ index: 40, desc: "" },
{ index: 41, desc: "" },
{ index: 42, desc: "" },
{ index: 43, desc: "" }
])
let legendRuHiddenDescContainer = createFrom(rows, "legend-desc collapsible collapsed", [
{ index: 44, desc: "" },
{ index: 45, desc: "" },
{ index: 46, desc: "" },
{ index: 47, desc: "" },
])
let legendEnDescContainer = createFrom(rows, "legend-desc hidden", [
{ index: 49, desc: "" },
{ index: 50, desc: "" },
{ index: 51, desc: "" },
{ index: 52, desc: "" }
])
let legendEnHiddenDescContainer = createFrom(rows, "legend-desc collapsible collapsed hidden", [
{ index: 53, desc: "" },
{ index: 54, desc: "" },
{ index: 55, desc: "" },
{ index: 56, desc: "" }
])
let hider = document.createElement("div")
let hiderButton = document.createElement("button")
hiderButton.classList.add("collapse-button")
hiderButton.setAttribute("type", "button")
hiderButton.addEventListener("click", event => {
container.querySelectorAll(".legend-desc.collapsible").forEach(element => {
element.classList.toggle("collapsed")
})
})
hider.appendChild(hiderButton)
legendDescContainer.appendChild(legendDescHeader)
legendDescContainer.appendChild(legendRuDescContainer)
legendDescContainer.appendChild(legendRuHiddenDescContainer)
legendDescContainer.appendChild(legendEnDescContainer)
legendDescContainer.appendChild(legendEnHiddenDescContainer)
legendDescContainer.appendChild(hider)
legendContainer.appendChild(legendDescContainer)
/* LEGEND FILES */
let legendFilesContainer = document.createElement("div")
legendFilesContainer.classList.add("legend-container__files")
let imagesForLegendContainer = createFrom(rows, "files-container", [
{ index: 23, name: "Фото в легенде" }
])
legendFilesContainer.appendChild(imagesForLegendContainer)
let audioForLegendContainer = createFrom(rows, "files-container", [
{ index: 27, name: "Файлы в легенде" }
])
legendFilesContainer.appendChild(audioForLegendContainer)
legendContainer.appendChild(legendFilesContainer)
let adminFilesContainer = document.createElement('div')
adminFilesContainer.classList.add('admin-files-container')
let imagesForAdminContainer = createFrom(rows, "files-container", [
{ index: 31, name: "Фото в админке" }
])
adminFilesContainer.appendChild(imagesForAdminContainer)
let audioForAdminContainer = createFrom(rows, "files-container", [
{ index: 35, name: "Файлы в админке" }
])
adminFilesContainer.appendChild(audioForAdminContainer)
/* BOTTOM OPTIONS */
let bottomOptionsContainer = createFromMulti(rows, "options bottom-options", {
from: 59,
to: rows.length - 3
})
/* APPEND ALL */
container.appendChild(topButtonsContainer)
container.appendChild(headerContainer)
container.appendChild(firstContainer)
container.appendChild(commentContainer)
container.appendChild(legendContainer)
container.appendChild(adminFilesContainer)
container.appendChild(bottomOptionsContainer)
container.appendChild(bottomButtonsContainer)
/* MAP */
if (map !== undefined && L !== undefined) {
let content = document.querySelector("#content")
let contentWrapper = document.createElement("div")
contentWrapper.id = "content-wrapper"
contentWrapper.appendChild(document.querySelector("form"))
contentWrapper.appendChild(document.querySelector("#map-wrapper"))
content.appendChild(contentWrapper)
let panToCenter = document.createElement("button")
panToCenter.type = "button"
panToCenter.textContent = "В центр"
panToCenter.addEventListener("click", () => {
let lat = document.querySelector("input[name=\"cp\[lattitude\]\"").value
let lon = document.querySelector("input[name=\"cp\[longitude\]\"").value
map.setView(new L.LatLng(parseFloat(lat), parseFloat(lon)), 16)
})
document.querySelector("#map_controls").appendChild(panToCenter)
}
/* DIALOG */
let dialog = document.createElement("dialog")
dialog.id = "dialog"
document.body.appendChild(dialog)
dialog.addEventListener("click", e => {
if (e.target == dialog) {
e.target.close()
}
})
dialog.addEventListener("close", e => {
e.target.innerHTML = ""
})
/* PRETTIFY CHECKBOX */
if (localStorage.getItem(localStorageItems.ALWAYS_PRETTIFY)) {
document.querySelector(`input[name^="always-prettify-0"]`).click()
prettify(form, oldTable, container, insertedFileRows)
}
})();