fisrt init
|
|
@ -0,0 +1,2 @@
|
||||||
|
/data
|
||||||
|
/.git
|
||||||
|
|
@ -0,0 +1,94 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
require_once 'db_manager.php';
|
||||||
|
|
||||||
|
use function Catcher\recovery;
|
||||||
|
|
||||||
|
function gen_geojson($name, $features) {
|
||||||
|
return (object)[
|
||||||
|
"type" => "FeatureCollection",
|
||||||
|
"name" => $name,
|
||||||
|
"crs" => (object) [
|
||||||
|
"type" => "name",
|
||||||
|
"properties" => (object) [
|
||||||
|
"name" => "urn:ogc:def:crs:OGC:1.3:CRS84"
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"features" => $features
|
||||||
|
];
|
||||||
|
}
|
||||||
|
function gen_feature($settlement) {
|
||||||
|
$settlement = (object) $settlement;
|
||||||
|
$settlement->info_exist = ($settlement->info_exist == 1)? true : false;
|
||||||
|
if($settlement->slider)
|
||||||
|
$settlement->slider = json_decode($settlement->slider);
|
||||||
|
if($settlement->images)
|
||||||
|
$settlement->images = json_decode($settlement->images);
|
||||||
|
$latitude = $settlement->latitude;
|
||||||
|
$longitude = $settlement->longitude;
|
||||||
|
unset($settlement->latitude);
|
||||||
|
unset($settlement->longitude);
|
||||||
|
|
||||||
|
return (object) [
|
||||||
|
"type" => "Feature",
|
||||||
|
"properties" => $settlement,
|
||||||
|
"geometry" => (object) [
|
||||||
|
"type" => "Point",
|
||||||
|
"coordinates" => [$longitude, $latitude]
|
||||||
|
]
|
||||||
|
];
|
||||||
|
}
|
||||||
|
function gen_json($obj) :string {
|
||||||
|
return json_encode($obj, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
||||||
|
}
|
||||||
|
|
||||||
|
$body_raw = file_get_contents('php://input');
|
||||||
|
$body = json_decode($body_raw);
|
||||||
|
|
||||||
|
$mode = $body->mode;
|
||||||
|
|
||||||
|
$result = (object)[];
|
||||||
|
|
||||||
|
switch($mode) {
|
||||||
|
case 'geojsons':
|
||||||
|
$periods_geojson = (object) [];
|
||||||
|
$settlements = $db_manager->select_all();
|
||||||
|
foreach($settlements as $settlement) {
|
||||||
|
$period = ((object)$settlement)->period;
|
||||||
|
if(empty($periods_geojson->$period))
|
||||||
|
$periods_geojson->$period = [];
|
||||||
|
array_push($periods_geojson->$period, gen_feature($settlement));
|
||||||
|
};
|
||||||
|
|
||||||
|
foreach($periods_geojson as $name=>$features) {
|
||||||
|
$result->$name = gen_geojson($name, $features);
|
||||||
|
}
|
||||||
|
|
||||||
|
echo gen_json($result);
|
||||||
|
break;
|
||||||
|
case 'file':
|
||||||
|
$full_path = $body->path;
|
||||||
|
|
||||||
|
if (is_file($full_path)) {
|
||||||
|
// Определяем MIME-тип (например, для видео/mp4, изображений и т.д.)
|
||||||
|
$mime_type = mime_content_type($full_path);
|
||||||
|
|
||||||
|
// Устанавливаем заголовки
|
||||||
|
header('Content-Type: ' . $mime_type);
|
||||||
|
header('Content-Length: ' . filesize($full_path));
|
||||||
|
|
||||||
|
// Отдаём файл
|
||||||
|
readfile($full_path);
|
||||||
|
exit();
|
||||||
|
} else {
|
||||||
|
http_response_code(404);
|
||||||
|
echo json_encode(['error' => 'File not found']);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
echo gen_json((object)['msg' => "Не передан mode"]);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
|
@ -0,0 +1,33 @@
|
||||||
|
<?php
|
||||||
|
require_once './lib/php/DataBaseManager/DBManager.php';
|
||||||
|
require_once './lib/php/DataBaseManager/Entitie.php';
|
||||||
|
require_once './lib/php/Catcher/Catcher.php';
|
||||||
|
|
||||||
|
use DataBaseManager\Entitie\Entitie;
|
||||||
|
use DataBaseManager\DBManager\DBManager;
|
||||||
|
use function Catcher\recovery;
|
||||||
|
|
||||||
|
$config = (object) [
|
||||||
|
'driver' => 'mysql',
|
||||||
|
'host' => 'localhost',
|
||||||
|
'port' => 3306,
|
||||||
|
'dbname' => 'anodb',
|
||||||
|
'username' => 'root',
|
||||||
|
'password' => 'root'
|
||||||
|
];
|
||||||
|
|
||||||
|
$entitie = new Entitie([
|
||||||
|
'name varchar(32) not null',
|
||||||
|
'type varchar(16) not null',
|
||||||
|
'period varchar(16) not null',
|
||||||
|
'longitude double not null',
|
||||||
|
'latitude double not null',
|
||||||
|
'info_exist tinyint(1) not null',
|
||||||
|
'slider json',
|
||||||
|
'images json',
|
||||||
|
'video text',
|
||||||
|
'background text'
|
||||||
|
], 'settlements');
|
||||||
|
|
||||||
|
$db_manager = new DBManager($entitie, $config);
|
||||||
|
?>
|
||||||
|
|
@ -0,0 +1,189 @@
|
||||||
|
/**
|
||||||
|
* @typedef {Object} MarkerData
|
||||||
|
* @property {Blob} background - фон
|
||||||
|
* @property {Array<Blob>} images - изображения
|
||||||
|
* @property {Array<Blob>} slider - слайды
|
||||||
|
*/
|
||||||
|
|
||||||
|
/** Путь к php-прослойке, работающая с бд и внутренним хранилищем */
|
||||||
|
const db_get_filepath = '../db_get.php'
|
||||||
|
/** Дефолтный срок годности кэша : 2 часа */
|
||||||
|
const CACHE_DEFAULT_TTL_MS = 2 * 60 * 60 * 1000;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Излечь данные из ответа
|
||||||
|
* @param {string} type - тип извлекаемых данных
|
||||||
|
* @param {Response} response - ответ
|
||||||
|
* @returns {Promise<Object|Blob>}
|
||||||
|
*/
|
||||||
|
const extract_file = recovery(async (type, response) => {
|
||||||
|
switch(type) {
|
||||||
|
case 'file':
|
||||||
|
return await response.blob()
|
||||||
|
case 'json':
|
||||||
|
return await response.json()
|
||||||
|
default:
|
||||||
|
throw new Error('Не определен type')
|
||||||
|
}
|
||||||
|
}, (...e) => {throw Error(`Ошибки при извлечении (extract_file): ${e.join(', ')}`)} )
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} path - путь (либо локальный или https)
|
||||||
|
* @param {object} options - параметры fetch
|
||||||
|
* @param {int} timeout - секундомер
|
||||||
|
*/
|
||||||
|
const fetch_with_timeout = recovery(async (path, options = {}, timeout = 10 * 1000) => {
|
||||||
|
/** Штука для преждевременного завершения fetch */
|
||||||
|
const controller = new AbortController()
|
||||||
|
/** Как я понял, якорь для fetch */
|
||||||
|
const signal = controller.signal
|
||||||
|
|
||||||
|
/** Сработает завершение после истечения timeout */
|
||||||
|
const timeoutId = setTimeout(() => controller.abort(), timeout)
|
||||||
|
/** Запрос с привязкой якоря */
|
||||||
|
const response = await fetch(path, {...options, signal})
|
||||||
|
|
||||||
|
try {return response}
|
||||||
|
finally {clearTimeout(timeoutId)}
|
||||||
|
}, (e) => {
|
||||||
|
if(e.name === 'AbortError')
|
||||||
|
console.log('Запрос прерван по таймауту:', e)
|
||||||
|
else console.log(e)
|
||||||
|
}, (...e) => {throw Error(`Ошибки при запросе с таймером (fetch_with_timeout): ${e.join(', ')}`)} )
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Вернуть данные из ответа, кэшируя перед этим
|
||||||
|
* @param {string} type - тип извлекаемых данных
|
||||||
|
* @param {string} path - путь (либо локальный или https)
|
||||||
|
* @param {Object} body_req - тело запроса
|
||||||
|
* @returns {Promise<Object|Blob>}
|
||||||
|
*/
|
||||||
|
const return_fetch = recovery(async (type, path, cache_key, body_req) => {
|
||||||
|
const cache = await caches.open('cache')
|
||||||
|
|
||||||
|
/** Запрос по пути (если есть тело, то локальное обращение к файлу) */
|
||||||
|
const response = !body_req ? await fetch_with_timeout(path) : await fetch_with_timeout(path, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json'},
|
||||||
|
body: JSON.stringify(body_req)
|
||||||
|
})
|
||||||
|
|
||||||
|
/** Клонирование, т.к blob/json необратимый процесс. */
|
||||||
|
const cloned = response.clone()
|
||||||
|
const blob = await cloned.blob()
|
||||||
|
|
||||||
|
const headers = new Headers(cloned.headers)
|
||||||
|
/** Добавление даты создания в заголовок 'date' кешируемого ответа. */
|
||||||
|
headers.set('date', new Date().toUTCString())
|
||||||
|
|
||||||
|
/** Процесс кэширования */
|
||||||
|
await cache.put(
|
||||||
|
cache_key,
|
||||||
|
new Response(blob, {
|
||||||
|
status: cloned.status,
|
||||||
|
statusText: cloned.statusText,
|
||||||
|
headers
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
/** Возвращаем оригинальный (первый) ответ */
|
||||||
|
return await extract_file(type, response)
|
||||||
|
}, (...e) => {throw Error(`Ошибки при запросе и кэшировании (return_fetch): ${e.join(', ')}`)} )
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Получить данные из пути(локальный или https)
|
||||||
|
* @param {*} type - тип извлекаемых данных
|
||||||
|
* @param {*} path - путь (либо локальный или https)
|
||||||
|
* @param {*} body_req - тело запроса
|
||||||
|
* @returns {Promise<Object|Blob>}
|
||||||
|
*/
|
||||||
|
const get = recovery(async (type, path, body_req = null) => {
|
||||||
|
const cache = await caches.open('cache')
|
||||||
|
|
||||||
|
/** Ключ для добавления или получения кэш-данных. */
|
||||||
|
const cache_key = `${path}?type=${type}&file=${encodeURIComponent(JSON.stringify(body_req || {}))}`
|
||||||
|
|
||||||
|
/** Результат поиска ответа в кэше */
|
||||||
|
const cached_response = await cache.match(cache_key)
|
||||||
|
|
||||||
|
if (cached_response) {
|
||||||
|
/** Срок годности кэша (если задан) */
|
||||||
|
const expires_value = cached_response.headers.get('expires')
|
||||||
|
/** Дата создания кэша */
|
||||||
|
const date_value = cached_response.headers.get('date')
|
||||||
|
/** До какого числа годен */
|
||||||
|
const exp = expires_value && expires_value !== '-1'
|
||||||
|
? Date.parse(expires_value)
|
||||||
|
: Date.parse(date_value ?? '') + CACHE_DEFAULT_TTL_MS
|
||||||
|
/** Если exp больше, вернуть ответ из кэша, иначе удалить протухшие данные */
|
||||||
|
if (Date.now() < exp) {
|
||||||
|
return await extract_file(type, cached_response )
|
||||||
|
}
|
||||||
|
await cache.delete(cache_key)
|
||||||
|
}
|
||||||
|
/** Формирование нового кэша, т.к предыдущий протух или его нет */
|
||||||
|
return await return_fetch(type, path, cache_key, body_req)
|
||||||
|
}, (...e) => {throw Error(`Ошибки при получении (get): ${e.join(', ')}`)} )
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Получить данные на диске (абсолютный/относительный путь)
|
||||||
|
* @param {string} filepath - путь к файлу
|
||||||
|
* @returns {Promise<Blob>} - без json
|
||||||
|
*/
|
||||||
|
const get_local = recovery(async (filepath) => {
|
||||||
|
return await get('file', db_get_filepath, {
|
||||||
|
mode: 'file',
|
||||||
|
path: filepath
|
||||||
|
})
|
||||||
|
}, (...e) => {throw Error(`Ошибки при локальном получении (get_local): ${e.join(', ')}`)} )
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Получить данные без кэширования
|
||||||
|
* @param {string} type - тип извлекаемых данных
|
||||||
|
* @param {string} path - путь (либо локальный или https)
|
||||||
|
* @returns {Promise<Object|Blob>}
|
||||||
|
*/
|
||||||
|
const get_no_cached = recovery(async (type, path) => {
|
||||||
|
const file = await fetch_with_timeout(path)
|
||||||
|
return await extract_file(type, file)
|
||||||
|
}, (...e) => {throw Error(`Ошибки при получении без кэширования (get_no_cached): ${e.join(', ')}`)} )
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Получить метериалы поселения для инфо. страницы
|
||||||
|
* @param {MarkerData} properties
|
||||||
|
* - нужные свойства (пути) из свойств feature.
|
||||||
|
* @returns {Promise<{ background: Blob, images: Blob[], slider: Blob[]}>}
|
||||||
|
*/
|
||||||
|
const get_for_info_page = recovery(async (properties) => {
|
||||||
|
/** Фон инфо. страницы */
|
||||||
|
const background = await get_local(properties.background)
|
||||||
|
|
||||||
|
/** Фотографи инфо. страницы */
|
||||||
|
const images = await Promise.all(
|
||||||
|
(properties.images || []).map(i_path => get_local(i_path))
|
||||||
|
)
|
||||||
|
|
||||||
|
/** Слайды инфо. страницы (если есть) */
|
||||||
|
const slider = Array.isArray(properties.slider)
|
||||||
|
? await Promise.all(properties.slider.map(s_path => get_local(s_path)))
|
||||||
|
: []
|
||||||
|
|
||||||
|
return {
|
||||||
|
background,
|
||||||
|
images,
|
||||||
|
slider,
|
||||||
|
}
|
||||||
|
}, (...e) => {throw Error(`Ошибки при получении материалов для страницы (get_for_info_page): ${e.join(', ')}`)} )
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Получить иконки для стилизации страниц.
|
||||||
|
* @returns {Promise<Object|Blob>}
|
||||||
|
*/
|
||||||
|
const get_icons = recovery(async () => await get_no_cached ('json', './data/icons.json')
|
||||||
|
, (...e) => {throw Error(`Ошибки при получении данных для стилизации (get_icons): ${e.join(', ')}`)} )
|
||||||
|
/**
|
||||||
|
* Получить все geojson'ы из базы данных
|
||||||
|
* @returns {Promise<Object>}
|
||||||
|
*/
|
||||||
|
const get_all_geojsons = recovery(async () => await get('json', db_get_filepath, {'mode': 'geojsons'})
|
||||||
|
, (...e) => {throw Error(`Ошибки при получении geojson'ов из бд (get_all_geojsons): ${e.join(', ')}`)} )
|
||||||
|
After Width: | Height: | Size: 881 KiB |
|
After Width: | Height: | Size: 109 KiB |
|
After Width: | Height: | Size: 293 KiB |
|
After Width: | Height: | Size: 24 KiB |
|
After Width: | Height: | Size: 87 KiB |
|
After Width: | Height: | Size: 9.1 KiB |
|
After Width: | Height: | Size: 154 KiB |
|
|
@ -0,0 +1,19 @@
|
||||||
|
{
|
||||||
|
"markers": {
|
||||||
|
"Село": "./icons/markers/Selo.png",
|
||||||
|
"Деревня": "./icons/markers/Drevnya.png",
|
||||||
|
"Поселок": "./icons/markers/Poselok1.png",
|
||||||
|
"Юрта": "./icons/markers/Urta.png",
|
||||||
|
"Археообъект": "./icons/markers/Arch.png"
|
||||||
|
},
|
||||||
|
"others": {
|
||||||
|
"next": "./icons/style/next.png",
|
||||||
|
"previous": "./icons/style/previous.png",
|
||||||
|
"close": "./icons/style/close.svg",
|
||||||
|
"map": "./icons/func_btn/map_btn.jpg",
|
||||||
|
"slider": "./icons/func_btn/slider_btn.png",
|
||||||
|
|
||||||
|
"malaya_logo": "./icons/func_img/malaya_rodina.png",
|
||||||
|
"rmc_logo": "./icons/func_img/rmc_logo.png"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
After Width: | Height: | Size: 5.7 KiB |
|
After Width: | Height: | Size: 5.8 KiB |
|
After Width: | Height: | Size: 7.7 KiB |
|
After Width: | Height: | Size: 3.7 KiB |
|
After Width: | Height: | Size: 7.1 KiB |
|
After Width: | Height: | Size: 6.5 KiB |
|
|
@ -0,0 +1,9 @@
|
||||||
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||||
|
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Transformed by: SVG Repo Mixer Tools -->
|
||||||
|
<svg width="800px" height="800px" viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" fill="#ffffff" stroke="#ffffff">
|
||||||
|
<g id="SVGRepo_bgCarrier" stroke-width="0"/>
|
||||||
|
<g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<g id="SVGRepo_iconCarrier">
|
||||||
|
<path fill="#ffffff" d="M195.2 195.2a64 64 0 0 1 90.496 0L512 421.504 738.304 195.2a64 64 0 0 1 90.496 90.496L602.496 512 828.8 738.304a64 64 0 0 1-90.496 90.496L512 602.496 285.696 828.8a64 64 0 0 1-90.496-90.496L421.504 512 195.2 285.696a64 64 0 0 1 0-90.496z"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 747 B |
|
After Width: | Height: | Size: 22 KiB |
|
|
@ -0,0 +1,7 @@
|
||||||
|
<?xml version="1.0" ?>
|
||||||
|
|
||||||
|
<svg fill="#000000" width="800px" height="800px" viewBox="0 0 52 52" data-name="Layer 1" id="Layer_1" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g data-name="Group 132" id="Group_132">
|
||||||
|
<path d="M14,52a2,2,0,0,1-1.41-3.41L35.17,26,12.59,3.41a2,2,0,0,1,0-2.82,2,2,0,0,1,2.82,0l24,24a2,2,0,0,1,0,2.82l-24,24A2,2,0,0,1,14,52Z"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 359 B |
|
After Width: | Height: | Size: 22 KiB |
|
|
@ -0,0 +1,7 @@
|
||||||
|
<?xml version="1.0" ?>
|
||||||
|
|
||||||
|
<svg fill="#000000" width="800px" height="800px" viewBox="0 0 52 52" data-name="Layer 1" id="Layer_1" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g data-name="Group 132" id="Group_132">
|
||||||
|
<path d="M38,52a2,2,0,0,1-1.41-.59l-24-24a2,2,0,0,1,0-2.82l24-24a2,2,0,0,1,2.82,0,2,2,0,0,1,0,2.82L16.83,26,39.41,48.59A2,2,0,0,1,38,52Z"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 359 B |
|
|
@ -0,0 +1,103 @@
|
||||||
|
<!--
|
||||||
|
MetaInfo
|
||||||
|
Author of the reissue: Diller(Кутман)
|
||||||
|
Date of change: 19.05.2025
|
||||||
|
-->
|
||||||
|
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Document</title>
|
||||||
|
|
||||||
|
<!-- Подключение OL -->
|
||||||
|
<link rel="stylesheet" href="../lib/ol/ol.css">
|
||||||
|
<script src="../lib/ol/ol.js"></script>
|
||||||
|
|
||||||
|
<!-- Подключение самописных модулей -->
|
||||||
|
<script src="../lib/js/catcher.js"></script>
|
||||||
|
<script src="../lib/js/page-manager.js"></script>
|
||||||
|
<script src="../lib/js/logger.js"></script>
|
||||||
|
<script src="../lib/js/single-layer-manager.js"></script>
|
||||||
|
<script src="../lib/js/ref-manager.js"></script>
|
||||||
|
<script src="../lib/js/popup-manager.js"></script>
|
||||||
|
<script src="../lib/js/slider.js"></script>
|
||||||
|
|
||||||
|
<!-- Подключение основных файлов -->
|
||||||
|
<link rel="stylesheet" href="./style.css">
|
||||||
|
<script src="./main.js"></script>
|
||||||
|
<script src="./data-manager.js"></script>
|
||||||
|
|
||||||
|
<!-- Файлы инициалиазции страниц -->
|
||||||
|
<script src="./page/main-page.js"></script>
|
||||||
|
<script src="./page/info-page.js"></script>
|
||||||
|
<script src="./page/slider-page.js"></script>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<!-- Область для страниц -->
|
||||||
|
<div id="page-space">
|
||||||
|
<!-- Главная страница -->
|
||||||
|
<div id="main-page">
|
||||||
|
<!-- Карта -->
|
||||||
|
<div id="map"></div>
|
||||||
|
|
||||||
|
<!-- UI-компоненты -->
|
||||||
|
<div id="main">
|
||||||
|
<div id="left-div">
|
||||||
|
<div class="common" id="logos"></div>
|
||||||
|
</div>
|
||||||
|
<div id="center-div">
|
||||||
|
<div class="common" id="title">
|
||||||
|
<p>Деньщиковская волость @Е-ГЕО</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="right-div">
|
||||||
|
<div class="common" id="time-select">
|
||||||
|
</div>
|
||||||
|
<div class="common" id="legend"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Страница информации -->
|
||||||
|
<div id="info-page">
|
||||||
|
<div id="info-page-images"></div>
|
||||||
|
<video id="info-page-video" controls="true"></video>
|
||||||
|
<!-- Модальное окно -->
|
||||||
|
<div id="myModal" class="modal">
|
||||||
|
<span class="close">×</span>
|
||||||
|
<img class="modal-content" id="modalImg">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Страница с слайдером -->
|
||||||
|
<div id="slider-page">
|
||||||
|
<div id="slider-container">
|
||||||
|
<div class="side" id="left">
|
||||||
|
<img id="previous">
|
||||||
|
</div>
|
||||||
|
<div id="center">
|
||||||
|
<div id="slider"></div>
|
||||||
|
</div>
|
||||||
|
<div class="side" id="right">
|
||||||
|
<img id="next">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<img id="to-info-page">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Служебные компоненты (не отрисовываются при загрузке) -->
|
||||||
|
<div id="popup">
|
||||||
|
<div id="popup-header">
|
||||||
|
<a id="popup-title"></a>
|
||||||
|
<img id="popup-close">
|
||||||
|
</div>
|
||||||
|
<div id="popup-content"></div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
|
|
@ -0,0 +1,34 @@
|
||||||
|
/**
|
||||||
|
* @typedef {import ('./lib/js-lib/page-manager.js').PageManager} PageManager
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Основной элемент для управления страницами
|
||||||
|
* @type {PageManager}
|
||||||
|
*/
|
||||||
|
var page_manager
|
||||||
|
|
||||||
|
/** Основной метод */
|
||||||
|
const main = async () => {
|
||||||
|
page_manager = new PageManager('page-space',
|
||||||
|
{
|
||||||
|
element: 'main-page',
|
||||||
|
init: main_page_init,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
element: 'info-page',
|
||||||
|
init: info_page_init,
|
||||||
|
always_mode: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
element: 'slider-page',
|
||||||
|
init: slider_page_init,
|
||||||
|
always_mode: true
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
page_manager.set_page('main-page', {page_manager})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
window.onload = main
|
||||||
|
|
@ -0,0 +1,216 @@
|
||||||
|
/**
|
||||||
|
* @typedef {import ('/lib/ref-manager.js').RefManager} RefManager
|
||||||
|
* @typedef {import('/lib/single-layer-manager.js').SingleLayerManager} SingleLayerManager
|
||||||
|
* @typedef {import ('/lib/popup-manager.js').PopupManager} PopupManager
|
||||||
|
* @typedef {import ('/lib/page-manager.js').PageManager} PageManager
|
||||||
|
*
|
||||||
|
* @typedef {Object} RefContent
|
||||||
|
* @property {string} background
|
||||||
|
* @property {Array<string>} images
|
||||||
|
* @property {Array<string>} slider
|
||||||
|
*
|
||||||
|
* @typedef {Object} RefData
|
||||||
|
* @property {RefContent} refs - ссылки
|
||||||
|
* @property {RefManager} refs_manager - менеджер ссылок (устаревший, теперь null)
|
||||||
|
*/
|
||||||
|
|
||||||
|
/** Вствка изображений в панель */
|
||||||
|
const add_img = (div, img_ref, onclick = () => {}) => {
|
||||||
|
/** Создание dom-изображения */
|
||||||
|
const img = document.createElement('img')
|
||||||
|
|
||||||
|
/** Инъекция ссылки */
|
||||||
|
img.src = img_ref
|
||||||
|
|
||||||
|
/** Инъекция поведения */
|
||||||
|
img.onclick = onclick
|
||||||
|
|
||||||
|
/** Вставка */
|
||||||
|
div.appendChild(img)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Функция инициализации инфо. страницы
|
||||||
|
* @param {PageManager} page_manager
|
||||||
|
* @function
|
||||||
|
*/
|
||||||
|
const info_page_init = async ({page_manager, icon_refs_manager = null, properties}) => {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Инициалиазция изображений и их помещение в колонку
|
||||||
|
* @function
|
||||||
|
* @param {Array<string>} images_refs - изображения маркера
|
||||||
|
* @param {HTMLElement} modal
|
||||||
|
* @param {HTMLElement} modal_img
|
||||||
|
*/
|
||||||
|
const images_init = async (images_refs, modal, modal_img) => {
|
||||||
|
/** Панель изображений из info-page */
|
||||||
|
const images = document.getElementById('info-page-images')
|
||||||
|
|
||||||
|
/** Вставка изображений в панель */
|
||||||
|
images_refs.forEach(
|
||||||
|
img_ref => add_img(
|
||||||
|
images,
|
||||||
|
img_ref,
|
||||||
|
() => {
|
||||||
|
modal.style.display = 'flex'
|
||||||
|
modal_img.src = img_ref
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Инициалиазирует фон
|
||||||
|
* @function
|
||||||
|
* @param {string} background - ссылка на фон
|
||||||
|
*/
|
||||||
|
const background_init = (background) => {
|
||||||
|
const info_page = document.getElementById('info-page')
|
||||||
|
|
||||||
|
if (typeof background === 'string') {
|
||||||
|
// Заменяем \ на /
|
||||||
|
background = background.replace(/\\/g, '/')
|
||||||
|
|
||||||
|
// Убеждаемся, что путь начинается с /
|
||||||
|
if (!background.startsWith('/')) {
|
||||||
|
background = '/' + background
|
||||||
|
}
|
||||||
|
|
||||||
|
info_page.style.backgroundImage = `url(${background})`
|
||||||
|
} else {
|
||||||
|
info_page.style.backgroundImage = ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Функция очистки после инита страницы
|
||||||
|
* @function
|
||||||
|
*/
|
||||||
|
const clear = () => {
|
||||||
|
/** Очистка картинок */
|
||||||
|
const images = document.getElementById('info-page-images')
|
||||||
|
|
||||||
|
/** Видео элемент */
|
||||||
|
const video = document.getElementById('info-page-video')
|
||||||
|
|
||||||
|
/** Очистка внутренностей элементов */
|
||||||
|
images.innerHTML = ''
|
||||||
|
if (video) {
|
||||||
|
video.pause()
|
||||||
|
video.removeAttribute('src')
|
||||||
|
video.load()
|
||||||
|
video.innerHTML = ''
|
||||||
|
video.style.display = 'none'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Инициализация модальной функциональности
|
||||||
|
* @function
|
||||||
|
* @returns {{modal: HTMLElement, modal_img: HTMLElement}}
|
||||||
|
*/
|
||||||
|
const modal_init = () => {
|
||||||
|
const close = document.querySelector('.close')
|
||||||
|
const modal = document.getElementById('myModal')
|
||||||
|
const modal_img = document.getElementById('modalImg')
|
||||||
|
|
||||||
|
/** Условия закрытия модального окна */
|
||||||
|
close.onclick = function () {
|
||||||
|
modal.style.display = 'none'
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Тоже условие закрытия */
|
||||||
|
window.onclick = function (event) {
|
||||||
|
if (event.target == modal) {
|
||||||
|
modal.style.display = 'none'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {modal, modal_img}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Инициализация видео
|
||||||
|
* @function
|
||||||
|
* @param {string} ref - прямая ссылка на видеофайл
|
||||||
|
*/
|
||||||
|
const video_init = async (ref) => {
|
||||||
|
const video = document.getElementById('info-page-video')
|
||||||
|
video.style.display = 'block'
|
||||||
|
|
||||||
|
// Очистка предыдущих источников
|
||||||
|
video.innerHTML = ''
|
||||||
|
|
||||||
|
const source = document.createElement('source')
|
||||||
|
source.src = ref
|
||||||
|
source.type = 'video/mp4'
|
||||||
|
|
||||||
|
video.appendChild(source)
|
||||||
|
video.load()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Создание функциональных кнопок (назад, к слайдеру)
|
||||||
|
* @function
|
||||||
|
* @param {string} to_map - ссылка на иконку кнопки к карте (назад)
|
||||||
|
* @param {string} to_slider - ссылка на иконку кнопки слайдер
|
||||||
|
* @param {PageManager} page_manager - менеджер страниц
|
||||||
|
*/
|
||||||
|
const func_btn_init = (to_map, to_slider, page_manager, icon_refs_manager, properties) => {
|
||||||
|
const images = document.getElementById('info-page-images')
|
||||||
|
|
||||||
|
if (to_slider && typeof to_slider === 'string') {
|
||||||
|
add_img(images, to_slider, () => {
|
||||||
|
clear()
|
||||||
|
page_manager.set_page('slider-page', {
|
||||||
|
page_manager,
|
||||||
|
icon_refs_manager,
|
||||||
|
properties
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (to_map && typeof to_map === 'string') {
|
||||||
|
add_img(images, to_map, () => {
|
||||||
|
clear()
|
||||||
|
page_manager.set_page('main-page', {page_manager})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Защищенный вызов */
|
||||||
|
recovery(
|
||||||
|
async () => {
|
||||||
|
// /** Получение информации маркера (ссылки напрямую) */
|
||||||
|
// const data = await get_for_info_page(properties)
|
||||||
|
|
||||||
|
// /** Инициализация ref-менеджер и создание ссылок */
|
||||||
|
// const {refs} = await refs_data_init(data)
|
||||||
|
|
||||||
|
/** Элементы modal */
|
||||||
|
const {modal, modal_img} = modal_init()
|
||||||
|
|
||||||
|
background_init(properties.background)
|
||||||
|
|
||||||
|
images_init(properties.images, modal, modal_img)
|
||||||
|
|
||||||
|
if (properties.video && typeof properties.video === 'string') {
|
||||||
|
video_init(properties.video)
|
||||||
|
}
|
||||||
|
|
||||||
|
func_btn_init(
|
||||||
|
icon_refs_manager.get("map"),
|
||||||
|
properties.slider.length <= 0 ? null : icon_refs_manager.get("slider"),
|
||||||
|
page_manager,
|
||||||
|
icon_refs_manager,
|
||||||
|
properties
|
||||||
|
)
|
||||||
|
},
|
||||||
|
(...e) => {
|
||||||
|
console.log("Ошибка при инициалиазции main-page")
|
||||||
|
e.forEach(er => {throw er})
|
||||||
|
}
|
||||||
|
) ()
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,344 @@
|
||||||
|
/**
|
||||||
|
* @typedef {import ('/lib/js/ref-manager.js').RefManager} RefManager
|
||||||
|
* @typedef {import('/lib/js/single-layer-manager.js').SingleLayerManager} SingleLayerManager
|
||||||
|
* @typedef {import ('/lib/js/popup-manager.js').PopupManager} PopupManager
|
||||||
|
* @typedef {import ('/lib/js/page-manager.js').PageManager} PageManager
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Генерация функции для получения стилей маркера
|
||||||
|
* @function
|
||||||
|
* @param {Object<string, string>} marker_refs - объект с ссылками
|
||||||
|
* @returns {((feature) => ol.style.Style)}
|
||||||
|
*/
|
||||||
|
const gen_get_style = recovery(
|
||||||
|
marker_refs => feature => recovery(
|
||||||
|
() => {
|
||||||
|
/** Проверка feature */
|
||||||
|
if (!feature || typeof feature.getProperties !== 'function') {
|
||||||
|
throw new Error("Некорректный feature");
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Свойства feature */
|
||||||
|
const properties = feature.getProperties()
|
||||||
|
|
||||||
|
/** Ссылка к иконке */
|
||||||
|
const url = marker_refs[properties.type.toLowerCase()]
|
||||||
|
?? marker_refs['археообъект'.toLowerCase()]
|
||||||
|
|
||||||
|
/** Генерация стилей */
|
||||||
|
return new ol.style.Style({
|
||||||
|
image: new ol.style.Icon({
|
||||||
|
anchor: [0.5, 1],
|
||||||
|
scale: 0.08,
|
||||||
|
src: url
|
||||||
|
})
|
||||||
|
})
|
||||||
|
},
|
||||||
|
(...e) => {
|
||||||
|
console.log("Ошибка при получении стилей (gen_get_style->get_style)");
|
||||||
|
e.forEach(er => {throw er});
|
||||||
|
}
|
||||||
|
)(),
|
||||||
|
(...e) => {
|
||||||
|
console.log("Ошибка генерации функции стилей (gen_get_style)");
|
||||||
|
e.forEach(er => {throw er});
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Функция инициализирует карту
|
||||||
|
* @function
|
||||||
|
* @param {{coordinates: [number, number], target?: string}} arg
|
||||||
|
* @returns {{map: ol.Map, layer: ol.layer.Vector}}
|
||||||
|
*/
|
||||||
|
const create_map_and_layer = ({coordinates, target = "map"}, style_f) => {
|
||||||
|
/** Одиночный слой */
|
||||||
|
let layer = new ol.layer.VectorImage({
|
||||||
|
source: new ol.source.Vector({}),
|
||||||
|
style: (feature) => style_f(feature)
|
||||||
|
})
|
||||||
|
|
||||||
|
/** Создание карты и её привязка к dom-элементу */
|
||||||
|
const map = new ol.Map({
|
||||||
|
view: new ol.View({
|
||||||
|
center: ol.proj.fromLonLat(coordinates),
|
||||||
|
zoom: 10,
|
||||||
|
minZoom: 8
|
||||||
|
}),
|
||||||
|
layers: [
|
||||||
|
new ol.layer.Tile({
|
||||||
|
source: new ol.source.OSM(),
|
||||||
|
}),
|
||||||
|
layer
|
||||||
|
],
|
||||||
|
target: target
|
||||||
|
})
|
||||||
|
|
||||||
|
return {map, layer}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Активация стилей кнопки
|
||||||
|
* @function
|
||||||
|
*/
|
||||||
|
const active = (btn) => btn.classList.replace('not-selected', 'selected')
|
||||||
|
/**
|
||||||
|
* Деактивация стилей кнопки
|
||||||
|
* @function
|
||||||
|
*/
|
||||||
|
const deactive = (btn) => btn.classList.replace('selected', 'not-selected')
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Функция инициализации главой страницы
|
||||||
|
* @function
|
||||||
|
* @param {PageManager} page_manager
|
||||||
|
* @param {RefManager} [previous_refs_manager] - менеджер ссылок предыдущей страницы
|
||||||
|
*/
|
||||||
|
const main_page_init = async ({page_manager}) => {
|
||||||
|
/**
|
||||||
|
* Cоздание ссылок из загруженных фотографий
|
||||||
|
* @function
|
||||||
|
* @returns {RefManager}
|
||||||
|
*/
|
||||||
|
const icon_refs_manager_init = async () => {
|
||||||
|
/** Данные (иконки) из внешнего json */
|
||||||
|
const icons_data = (await get('json', './icons/icons.json'))
|
||||||
|
const now_icons_data = {...icons_data.markers, ...icons_data.others}
|
||||||
|
|
||||||
|
/** Менеджер иконок, заполненный ссылками */
|
||||||
|
let icon_ref_manager = new RefManager()
|
||||||
|
for (const [name, path] of Object.entries(now_icons_data))
|
||||||
|
icon_ref_manager.save({
|
||||||
|
key: name.toLowerCase(),
|
||||||
|
ref: URL.createObjectURL(await get('file', path))
|
||||||
|
})
|
||||||
|
|
||||||
|
return icon_ref_manager
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Создание и ининциализация карты
|
||||||
|
* @function
|
||||||
|
* @param {RefManager} icon_ref_manager - менеджер ссылок
|
||||||
|
* @returns {{map: ol.Map, layer: ol.layer.Vector}}
|
||||||
|
*/
|
||||||
|
const map_init = (icon_refs_manager) =>
|
||||||
|
create_map_and_layer(
|
||||||
|
{
|
||||||
|
coordinates: [70.008408, 60.001500],
|
||||||
|
target: "map"
|
||||||
|
},
|
||||||
|
gen_get_style(icon_refs_manager.get_all())
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Создание и заполнение элемента с кнопками периодов
|
||||||
|
* @function
|
||||||
|
* @param {Object} geojsons - объект, содержащий все geojson'ы
|
||||||
|
*/
|
||||||
|
const periods_init = (geojsons, single_layer_manager) => {
|
||||||
|
/** DOM-элемент, куда кнопки периодов загружаются */
|
||||||
|
const time_select = document.getElementById('time-select')
|
||||||
|
const periods = Object.keys(geojsons)
|
||||||
|
.sort((a, b) => parseInt(a) - parseInt(b))
|
||||||
|
|
||||||
|
/** Массив кнопок с период-кнопками */
|
||||||
|
const buttons_periods = periods.map(period => {
|
||||||
|
/** Период-кнопка */
|
||||||
|
const button = document.createElement('button')
|
||||||
|
button.dataset.period = period;
|
||||||
|
button.textContent = period;
|
||||||
|
|
||||||
|
/** Добавление стилей: неактивная кнопка */
|
||||||
|
button.classList.add('not-selected')
|
||||||
|
|
||||||
|
/** Добавление в панель */
|
||||||
|
time_select.appendChild(button)
|
||||||
|
return button
|
||||||
|
})
|
||||||
|
|
||||||
|
/** Добавление слушателей (Загрузка geojson + сохранение состояния) */
|
||||||
|
buttons_periods.forEach(bp => {
|
||||||
|
bp.addEventListener('click', async () => {
|
||||||
|
/** Изменение слоя на карте */
|
||||||
|
single_layer_manager.set(geojsons[bp.dataset.period])
|
||||||
|
|
||||||
|
/** Сохранение состояния */
|
||||||
|
localStorage.setItem('selected', bp.dataset.period);
|
||||||
|
|
||||||
|
/** Выключение всех остальных кнопок */
|
||||||
|
buttons_periods.forEach(deactive)
|
||||||
|
|
||||||
|
/** Включение текущей кнопки */
|
||||||
|
active(bp)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
/** Загрузка последнего сохраненного статуса-периода (Имитация выбора) */
|
||||||
|
const selected = localStorage.getItem('selected')
|
||||||
|
const button_select = buttons_periods.find(bp => bp.dataset.period === selected);
|
||||||
|
if (button_select) {
|
||||||
|
button_select.click()
|
||||||
|
} else
|
||||||
|
buttons_periods[0].click()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Создание и заполнение легенды (маркер - название)
|
||||||
|
* @function
|
||||||
|
* @param {RefManager} icon_refs_manager - менеджер ссылок
|
||||||
|
*/
|
||||||
|
const legend_init = (icon_refs_manager) => {
|
||||||
|
/** DOM-элемент легенды */
|
||||||
|
const legend = document.getElementById('legend')
|
||||||
|
|
||||||
|
for(const [name, ref] of Object.entries(icon_refs_manager.get_all())) {
|
||||||
|
/** Row в легенде */
|
||||||
|
const div = document.createElement('div')
|
||||||
|
|
||||||
|
/** Иконка - маркер */
|
||||||
|
const marker = document.createElement('img')
|
||||||
|
|
||||||
|
/** Текст - название */
|
||||||
|
const p = document.createElement('p');
|
||||||
|
|
||||||
|
/** Помещение данных в row */
|
||||||
|
marker.src = ref
|
||||||
|
p.innerHTML = name
|
||||||
|
|
||||||
|
/** Добавление в контейенер легенды */
|
||||||
|
div.appendChild(marker)
|
||||||
|
div.appendChild(p)
|
||||||
|
legend.appendChild(div)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Создание логотипов с переходом на другие страницы
|
||||||
|
* @function
|
||||||
|
* @param {RefManager} icon_refs_manager - менеджер ссылок
|
||||||
|
*/
|
||||||
|
const logos_init = (icon_refs_manager) => {
|
||||||
|
/** DOM-элемент для логотипов */
|
||||||
|
const logos = document.getElementById('logos')
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Линковка ссылок на изображения и onclick'ов
|
||||||
|
* @param {string} img_src
|
||||||
|
* @param {(() => {})} onclick
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
const logo_init = (img_src, onclick) => {
|
||||||
|
/** Создание dom-элемента */
|
||||||
|
const logo = document.createElement('img')
|
||||||
|
/** Инъекция аргументов */
|
||||||
|
logo.src = img_src
|
||||||
|
logo.onclick = onclick
|
||||||
|
|
||||||
|
return logo
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Создание и добавление логотипов */
|
||||||
|
[
|
||||||
|
logo_init(
|
||||||
|
icon_refs_manager.get('malaya_logo'),
|
||||||
|
() => window.location.href = 'https://vk.com/anomalaya_rodina'
|
||||||
|
),
|
||||||
|
logo_init(
|
||||||
|
icon_refs_manager.get('rmc_logo'),
|
||||||
|
() => window.location.href = 'https://vk.com/rcod_hmao'
|
||||||
|
)
|
||||||
|
].forEach(logo => logos.appendChild(logo));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Инициализация popup-manager
|
||||||
|
* @function
|
||||||
|
* @param {ol.Map} map - ol-карта
|
||||||
|
* @param {PageManager} page_manager - менеджер страниц
|
||||||
|
* @param {RefManager} refs_manager - менеджер ссылок
|
||||||
|
* @param {string} close_icon - икнонка крестика
|
||||||
|
* @returns {PopupManager}
|
||||||
|
*/
|
||||||
|
const popup_manager_init = (map, page_manager, refs_manager, close_icon) => {
|
||||||
|
/** Смещение popup */
|
||||||
|
const offset = [0, 10]
|
||||||
|
|
||||||
|
/** Контейнер popup */
|
||||||
|
const popup = document.getElementById('popup')
|
||||||
|
|
||||||
|
/** Закрывашка, привязка ссылки иконки*/
|
||||||
|
const popup_close = document.getElementById('popup-close')
|
||||||
|
popup_close.src = close_icon
|
||||||
|
|
||||||
|
/** Заголовок всплывающего окна */
|
||||||
|
const popup_title = document.getElementById('popup-title')
|
||||||
|
|
||||||
|
/** Контент всплывающего окна */
|
||||||
|
const popup_content = document.getElementById('popup-content')
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Поведение карты при нажатии на карту
|
||||||
|
* @param {ol.Feature} feature - объект маркера
|
||||||
|
*/
|
||||||
|
const map_on = feature => {
|
||||||
|
/** Формирование контента */
|
||||||
|
popup_title.innerHTML = feature.getProperties().name
|
||||||
|
popup_content.innerHTML = ''
|
||||||
|
|
||||||
|
/** Формирование кнопки 'подробнее' и его привязка */
|
||||||
|
if (feature.getProperties().info_exist) {
|
||||||
|
/** Создание ссылки 'подробнее' */
|
||||||
|
const content_description = document.createElement('a')
|
||||||
|
content_description.innerHTML = 'подробнее'
|
||||||
|
content_description.href = '#'
|
||||||
|
|
||||||
|
/** Привязка */
|
||||||
|
popup_content.appendChild(content_description)
|
||||||
|
|
||||||
|
/** Переход к info-page */
|
||||||
|
content_description.onclick = () =>
|
||||||
|
page_manager.set_page('info-page', {
|
||||||
|
page_manager: page_manager,
|
||||||
|
icon_refs_manager: refs_manager,
|
||||||
|
properties: feature.getProperties()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new PopupManager({
|
||||||
|
map: map,
|
||||||
|
popup_div: popup,
|
||||||
|
popup_close: popup_close,
|
||||||
|
offset: offset,
|
||||||
|
map_on: map_on
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Защищенный вызов */
|
||||||
|
await recovery(
|
||||||
|
async () => {
|
||||||
|
/** Получение всех иконок */
|
||||||
|
const icon_ref_manager = await icon_refs_manager_init()
|
||||||
|
|
||||||
|
/** Инициализация */
|
||||||
|
const {map, layer} = map_init(icon_ref_manager)
|
||||||
|
const single_layer_manager = new SingleLayerManager(layer)
|
||||||
|
|
||||||
|
/** Получение всех geojson'ов */
|
||||||
|
const geojsons = await get_all_geojsons()
|
||||||
|
|
||||||
|
periods_init(geojsons, single_layer_manager)
|
||||||
|
|
||||||
|
legend_init(icon_ref_manager)
|
||||||
|
logos_init(icon_ref_manager)
|
||||||
|
|
||||||
|
popup_manager_init(map, page_manager, icon_ref_manager, icon_ref_manager.get('close'))
|
||||||
|
},
|
||||||
|
(...e) => {
|
||||||
|
console.log("Ошибка при инициалиазции main-page");
|
||||||
|
e.forEach(er => {throw er});
|
||||||
|
}
|
||||||
|
)()
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,68 @@
|
||||||
|
/**
|
||||||
|
* @typedef {import('../lib/js-lib/slider.js').Slider} Slider
|
||||||
|
* @typedef {import('../lib/js-lib/page-manager.js').PageManager} PageManager
|
||||||
|
* @typedef {import('../lib/js-lib/ref-manager.js').RefManager} RefManager
|
||||||
|
*
|
||||||
|
* @typedef {Object} SliderPageArg
|
||||||
|
* @property {RefManager} slider_urls_manager - менеджер для слайдера
|
||||||
|
* @property {RefManager} icon_refs_manager - менеджер иконок
|
||||||
|
* @property {PageManager} page_manager - менеджер страниц
|
||||||
|
* @property {Object} properties - свойства маркера
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Инициализация slider-page
|
||||||
|
* @function
|
||||||
|
* @param {SliderPageArg} arg
|
||||||
|
*/
|
||||||
|
const slider_page_init = ({page_manager, icon_refs_manager, properties}) => {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Инициализация кнопки возврата на info_page
|
||||||
|
* @function
|
||||||
|
*/
|
||||||
|
const to_info_page_init = (slider) => {
|
||||||
|
const to_info_page = document.getElementById('to-info-page');
|
||||||
|
|
||||||
|
// Иконка кнопки
|
||||||
|
to_info_page.src = icon_refs_manager.get("map");
|
||||||
|
|
||||||
|
to_info_page.onclick = () => {
|
||||||
|
// Очистка
|
||||||
|
slider.clear?.();
|
||||||
|
|
||||||
|
page_manager.set_page('info-page', {
|
||||||
|
page_manager,
|
||||||
|
icon_refs_manager,
|
||||||
|
properties
|
||||||
|
});
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Безопасный вызов */
|
||||||
|
recovery(
|
||||||
|
() => {
|
||||||
|
// Получение изображений из менеджера
|
||||||
|
const images = properties.slider
|
||||||
|
|
||||||
|
// Создание слайдера
|
||||||
|
const slider = new Slider({
|
||||||
|
slider: {
|
||||||
|
main: document.getElementById('slider'),
|
||||||
|
next: document.getElementById('next'),
|
||||||
|
previous: document.getElementById('previous')
|
||||||
|
},
|
||||||
|
next_ref: icon_refs_manager.get('next'),
|
||||||
|
previous_ref: icon_refs_manager.get('previous'),
|
||||||
|
images
|
||||||
|
});
|
||||||
|
|
||||||
|
// Кнопка назад к info_page
|
||||||
|
to_info_page_init(slider);
|
||||||
|
},
|
||||||
|
(...e) => {
|
||||||
|
console.log("Ошибка при инициализации (slider-page)");
|
||||||
|
e.forEach(er => { throw er });
|
||||||
|
}
|
||||||
|
)();
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,316 @@
|
||||||
|
/** MetaInfo
|
||||||
|
* Author of the reissue: Diller(Кутман)
|
||||||
|
* Date of change: 19.05.2025
|
||||||
|
*/
|
||||||
|
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
background-color: #222;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal {
|
||||||
|
display: none;
|
||||||
|
position: fixed;
|
||||||
|
z-index: 1;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background-color: rgba(0, 0, 0, 0.7);
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-content {
|
||||||
|
margin: auto;
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
max-width: 1000px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.close {
|
||||||
|
position: absolute;
|
||||||
|
top: 15px;
|
||||||
|
right: 35px;
|
||||||
|
color: #fff;
|
||||||
|
font-size: 40px;
|
||||||
|
font-weight: bold;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
#page-space {
|
||||||
|
height: 100vh;
|
||||||
|
width: 100vw;
|
||||||
|
}
|
||||||
|
|
||||||
|
#main-page {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
#info-page {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
background-size: cover;
|
||||||
|
background-position: center;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
#info-page-images {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
width: 150px;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
#info-page-images img {
|
||||||
|
object-fit: cover;
|
||||||
|
background-position: center;
|
||||||
|
margin: 10px 10px 0px 10px;
|
||||||
|
|
||||||
|
max-height: 100px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#info-page-video {
|
||||||
|
display: none;
|
||||||
|
position: fixed;
|
||||||
|
height: 250px;
|
||||||
|
bottom: 10px;
|
||||||
|
left: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#map {
|
||||||
|
position: fixed;
|
||||||
|
width: 100vw;
|
||||||
|
height: 100vh;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
#main {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
width: 100vw;
|
||||||
|
height: 100vh;
|
||||||
|
z-index: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#left-div {
|
||||||
|
display: flex;
|
||||||
|
width: 20vw;
|
||||||
|
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
#logos {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
z-index: 2;
|
||||||
|
|
||||||
|
width: 75px;
|
||||||
|
height: 160px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#logos img {
|
||||||
|
margin: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#center-div {
|
||||||
|
display: flex;
|
||||||
|
flex-grow: 1;
|
||||||
|
|
||||||
|
justify-content: center;
|
||||||
|
align-items: start;
|
||||||
|
}
|
||||||
|
|
||||||
|
#title {
|
||||||
|
display: flex;
|
||||||
|
z-index: 2;
|
||||||
|
|
||||||
|
width: 600px;
|
||||||
|
height: 80px;
|
||||||
|
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
#title p {
|
||||||
|
margin: 0;
|
||||||
|
/* color: aliceblue */
|
||||||
|
font-size: 38px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#right-div {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: end;
|
||||||
|
|
||||||
|
width: 20vw;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#time-select {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
overflow: hidden;
|
||||||
|
border-radius: 10px;
|
||||||
|
z-index: 2;
|
||||||
|
background-color: rgba(99, 99, 99, 0);
|
||||||
|
|
||||||
|
width: 150px;
|
||||||
|
height: 250px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#time-select button {
|
||||||
|
background-color: rgba(99, 99, 99, 0.8);
|
||||||
|
border: 0;
|
||||||
|
height: 10%;
|
||||||
|
}
|
||||||
|
|
||||||
|
#time-select button:hover {
|
||||||
|
background-color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
#time-select button.selected {
|
||||||
|
background-color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
#legend {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
z-index: 2;
|
||||||
|
|
||||||
|
margin-bottom: 20px;
|
||||||
|
width: 170px;
|
||||||
|
height: 230px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#legend div {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
}
|
||||||
|
|
||||||
|
#legend div p {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
margin: 13px;
|
||||||
|
|
||||||
|
width: 80%;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#legend div img {
|
||||||
|
height: 26px;
|
||||||
|
margin: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.common {
|
||||||
|
background-color: rgba(99, 99, 99, 0.8);
|
||||||
|
border-radius: 10px;
|
||||||
|
opacity: 0.9;
|
||||||
|
margin: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Main End */
|
||||||
|
|
||||||
|
#popup {
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
background-color: rgba(6, 6, 6, 0.8);
|
||||||
|
border-radius: 10px;
|
||||||
|
z-index: 2;
|
||||||
|
|
||||||
|
width: 220px;
|
||||||
|
height: 100px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#popup-header {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: space-between;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
#popup-title {
|
||||||
|
display: flex;
|
||||||
|
margin: 10px 0px 0px 10px;
|
||||||
|
justify-content: center;
|
||||||
|
color: aliceblue;
|
||||||
|
font-size: x-large;
|
||||||
|
}
|
||||||
|
|
||||||
|
#popup-close {
|
||||||
|
position: relative;
|
||||||
|
right: 5px;
|
||||||
|
top: 5px;
|
||||||
|
right: 10px;
|
||||||
|
width: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#popup-content {
|
||||||
|
margin: 10px;
|
||||||
|
color: aliceblue;
|
||||||
|
font-size: medium;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Slider Start */
|
||||||
|
|
||||||
|
#slider-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
height: 100vh;
|
||||||
|
width: 100vw;
|
||||||
|
}
|
||||||
|
|
||||||
|
.side {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
height: 100%;
|
||||||
|
width: 10%;
|
||||||
|
min-width: 150px;
|
||||||
|
z-index: 10;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.side img {
|
||||||
|
height: 65px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#center {
|
||||||
|
display: flex;
|
||||||
|
flex-grow: 1;
|
||||||
|
overflow: hidden;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
#slider {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
height: 100%;
|
||||||
|
transition: transform 0.5s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slide {
|
||||||
|
height: 100%;
|
||||||
|
width: 80vw;
|
||||||
|
flex-shrink: 0;
|
||||||
|
object-fit: contain;
|
||||||
|
}
|
||||||
|
|
||||||
|
#to-info-page {
|
||||||
|
position: absolute;
|
||||||
|
left: 10px;
|
||||||
|
bottom: 10px;
|
||||||
|
|
||||||
|
height: 100px;
|
||||||
|
z-index: 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Slider End */
|
||||||
|
|
@ -0,0 +1,159 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
require_once 'db_manager.php';
|
||||||
|
|
||||||
|
use function Catcher\recovery;
|
||||||
|
|
||||||
|
$period_view = (object) [
|
||||||
|
'rArcheology' => '16 век и ранее',
|
||||||
|
'r1675' => '17 век',
|
||||||
|
'r1740' => '18 век',
|
||||||
|
'r1781' => '19 век',
|
||||||
|
'r1858' => '1901-1920',
|
||||||
|
'r1900' => '1921-1940',
|
||||||
|
'r1926' => '1941-1960',
|
||||||
|
'r1936' => '1961-1980',
|
||||||
|
'r1946' => '1981-2000',
|
||||||
|
'r200-now' => '2000 и н.в.'
|
||||||
|
];
|
||||||
|
|
||||||
|
$get_fs = null;
|
||||||
|
$get_fs = recovery(function (string $path) use (&$get_fs) {
|
||||||
|
$result = (object) ['files' => (object)[]];
|
||||||
|
|
||||||
|
$dirs = array_filter(scandir($path), function ($dir) {
|
||||||
|
return $dir !== '.' && $dir !== '..';
|
||||||
|
});
|
||||||
|
|
||||||
|
foreach($dirs as $dir) {
|
||||||
|
$new_path = $path . DIRECTORY_SEPARATOR . $dir;
|
||||||
|
if(is_dir($new_path))
|
||||||
|
$result->$dir = $get_fs($new_path);
|
||||||
|
else if(is_file($new_path)) {
|
||||||
|
$filename = pathinfo($new_path, PATHINFO_FILENAME);
|
||||||
|
$result->files->$filename = $new_path;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
}, function($e) {
|
||||||
|
throw $e;
|
||||||
|
});
|
||||||
|
|
||||||
|
$main = recovery(function (object $period_view) use (&$get_fs,&$db_manager)
|
||||||
|
{
|
||||||
|
$db_manager->delete_table();
|
||||||
|
$db_manager->create_table();
|
||||||
|
|
||||||
|
$fs = $get_fs('.' . DIRECTORY_SEPARATOR . 'data');
|
||||||
|
|
||||||
|
$removeLeadingDot = function(?string $path): ?string {
|
||||||
|
if ($path === null) return null;
|
||||||
|
return preg_replace('#^\.(?=[/\\\\])#', '', $path);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
$removeLeadingDotFromJsonArray = function(?string $json): ?string {
|
||||||
|
if ($json === null) return null;
|
||||||
|
$arr = json_decode($json);
|
||||||
|
if (!is_array($arr)) return $json;
|
||||||
|
$arr = array_map(fn($p) => preg_replace('#^\.(?=.*)#', '', $p), $arr);
|
||||||
|
return json_encode($arr, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
$settlements_data = [];
|
||||||
|
|
||||||
|
$material_path = $fs->material;
|
||||||
|
foreach ($fs->geojson->files as $geojson_path) {
|
||||||
|
if (!file_exists($geojson_path)) {
|
||||||
|
echo 'Невалидный geojson-path : ' . $geojson_path;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$geojson = json_decode(file_get_contents($geojson_path));
|
||||||
|
$features = $geojson->features;
|
||||||
|
$basename = pathinfo($geojson_path, PATHINFO_FILENAME);
|
||||||
|
$period = $period_view->$basename;
|
||||||
|
|
||||||
|
foreach($features as $feature) {
|
||||||
|
$properties = $feature->properties;
|
||||||
|
$coordinates = $feature->geometry->coordinates;
|
||||||
|
|
||||||
|
$en = $properties->en;
|
||||||
|
|
||||||
|
$info_exist = 0;
|
||||||
|
|
||||||
|
$background = null;
|
||||||
|
$images = null;
|
||||||
|
$slider = null;
|
||||||
|
$video = null;
|
||||||
|
|
||||||
|
if(!empty($en) && isset($material_path->$en)) {
|
||||||
|
$material = $material_path->$en;
|
||||||
|
|
||||||
|
$tmp = array_values((array)$material->background->files);
|
||||||
|
$background = end($tmp);
|
||||||
|
|
||||||
|
if (!empty($material->image) && isset($material->image)) {
|
||||||
|
$tmp = array_values((array)$material->image->files);
|
||||||
|
$images = json_encode($tmp, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) ?: null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($material->slider) && isset($material->slider)) {
|
||||||
|
$tmp = array_values((array)$material->slider->files);
|
||||||
|
$slider = json_encode($tmp, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) ?: null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($material->video) && isset($material->video)) {
|
||||||
|
$tmp = array_values((array)$material->video->files);
|
||||||
|
$video = end($tmp);
|
||||||
|
}
|
||||||
|
|
||||||
|
$info_exist = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (mb_strtolower($properties->Tupe)) {
|
||||||
|
case 'археология':
|
||||||
|
$type = 'археообъект';
|
||||||
|
break;
|
||||||
|
case 'юрты':
|
||||||
|
$type = 'юрта';
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
$type = $properties->Tupe;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Убираем точку в путях перед записью в массив
|
||||||
|
$background = $removeLeadingDot($background);
|
||||||
|
$images = $removeLeadingDotFromJsonArray($images);
|
||||||
|
$slider = $removeLeadingDotFromJsonArray($slider);
|
||||||
|
$video = $removeLeadingDot($video);
|
||||||
|
|
||||||
|
array_push($settlements_data, [
|
||||||
|
'name' => $properties->Name,
|
||||||
|
'type' => $type,
|
||||||
|
'period' => $period,
|
||||||
|
'longitude' => $coordinates[0],
|
||||||
|
'latitude' => $coordinates[1],
|
||||||
|
'info_exist' => $info_exist,
|
||||||
|
'slider' => $slider,
|
||||||
|
'images' => $images,
|
||||||
|
'video' => $video,
|
||||||
|
'background' => $background
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$settlements_arg = array_map(
|
||||||
|
fn($settlements) => array_values((array) $settlements),
|
||||||
|
$settlements_data
|
||||||
|
);
|
||||||
|
|
||||||
|
$db_manager->create_all($settlements_arg);
|
||||||
|
}, function($e) {
|
||||||
|
throw $e;
|
||||||
|
});
|
||||||
|
|
||||||
|
$main($period_view);
|
||||||
|
?>
|
||||||
|
|
@ -0,0 +1,36 @@
|
||||||
|
/** тип async function */
|
||||||
|
const AsyncFunction = Object.getPrototypeOf(async () => {}).constructor
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Безопасный запуск востановителя
|
||||||
|
* @param {(...errs: Error[]) => void} restorer
|
||||||
|
*/
|
||||||
|
const start_restorer = (restorer) => (...errs) => {
|
||||||
|
try {
|
||||||
|
return restorer(...errs)
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e)
|
||||||
|
throw e
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Функция отлова исключений
|
||||||
|
* @param {(...args: any[]) => any} func
|
||||||
|
* @param {(...errs: Error[]) => void} restorer
|
||||||
|
* @returns {(...args: any[]) => any}
|
||||||
|
*/
|
||||||
|
const recovery = (func, restorer = (...args) => {throw new Error(...args)}) => {
|
||||||
|
let resFunc
|
||||||
|
if(func instanceof AsyncFunction)
|
||||||
|
resFunc = async (...args) => {
|
||||||
|
try {return await func(...args)}
|
||||||
|
catch (e) {return start_restorer(restorer)(e)}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
resFunc = (...args) => {
|
||||||
|
try {return func(...args)}
|
||||||
|
catch (e) {return start_restorer(restorer)(e)}
|
||||||
|
}
|
||||||
|
return resFunc
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,49 @@
|
||||||
|
/**
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
const getCurrentTime = () => {
|
||||||
|
const date = new Date()
|
||||||
|
return '' +
|
||||||
|
String(date.getFullYear()) + '.' +
|
||||||
|
String(date.getMonth()+1).padStart(2, '0') + '.' +
|
||||||
|
String(date.getDate()).padStart(2, '0') + ' ' +
|
||||||
|
String(date.getHours() + 5).padStart(2, '0') + ':' +
|
||||||
|
String(date.getMinutes()).padStart(2, '0') + ':' +
|
||||||
|
String(date.getSeconds()).padStart(2, '0')
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {Object} Logger
|
||||||
|
* @property {(msg: string) => void} print
|
||||||
|
* @property {(wmsg: string) => void} wprint
|
||||||
|
* @property {(emsg: string) => void} eprint
|
||||||
|
* @property {(msg: string) => void} save
|
||||||
|
*/
|
||||||
|
|
||||||
|
/** @type {Logger} */
|
||||||
|
const logger = {
|
||||||
|
saved: [],
|
||||||
|
errors: [],
|
||||||
|
warnings: [],
|
||||||
|
logs: [],
|
||||||
|
/** @param {string} msg */
|
||||||
|
print: (msg) => {
|
||||||
|
const msg_format = `${getCurrentTime()} : Log : ${msg}`
|
||||||
|
logger.logs.push(msg_format);
|
||||||
|
console.log(msg_format)
|
||||||
|
},
|
||||||
|
/** @param {string} wmsg */
|
||||||
|
wprint: (wmsg) => {
|
||||||
|
const msg_format = `${getCurrentTime()} : Warning : ${wmsg}`
|
||||||
|
logger.warnings.push(msg_format);
|
||||||
|
console.log(msg_format)
|
||||||
|
},
|
||||||
|
/** @param {string} emsg */
|
||||||
|
eprint: (emsg) => {
|
||||||
|
const msg_format = `${getCurrentTime()} : Error : ${emsg}`
|
||||||
|
logger.errors.push(msg_format);
|
||||||
|
console.log(msg_format)
|
||||||
|
},
|
||||||
|
/** @param {string} msg */
|
||||||
|
save: (msg) => logger.saved.push(`${getCurrentTime()} : Data : ${msg}`),
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,67 @@
|
||||||
|
/**
|
||||||
|
* @typedef {Object} Page
|
||||||
|
* @property {HTMLElement} element
|
||||||
|
* @property {(...args: any[]) => void} init
|
||||||
|
* @property {boolean} always_mode
|
||||||
|
* @property {boolean} [first_init]
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string | HTMLElement} element
|
||||||
|
* @returns {HTMLElement}
|
||||||
|
*/
|
||||||
|
const get_element = recovery((element) => {
|
||||||
|
if(element instanceof HTMLElement)
|
||||||
|
return element
|
||||||
|
else if(typeof element === 'string')
|
||||||
|
return document.getElementById(element)
|
||||||
|
else
|
||||||
|
throw new Error(`element is not correct: ${element}`)
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @class PageManager
|
||||||
|
* @property {Object<string, Page>} pages
|
||||||
|
* @property {HTMLElement} page_space
|
||||||
|
*/
|
||||||
|
class PageManager {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string | HTMLElement} page_space_id
|
||||||
|
* @param {...Page} args
|
||||||
|
*/
|
||||||
|
constructor(page_space_id, ...args) {
|
||||||
|
recovery((page_space_id_f, ...args_f) => {
|
||||||
|
this.pages = {}
|
||||||
|
this.page_space = get_element(page_space_id_f)
|
||||||
|
let first_id
|
||||||
|
|
||||||
|
args_f.forEach((arg, index) => {
|
||||||
|
const {element, init = () => {}, always_mode = false} = arg
|
||||||
|
const el = get_element(element)
|
||||||
|
if(el instanceof HTMLElement){
|
||||||
|
const id = el.id?.trim() || `_page_${index}`
|
||||||
|
this.pages[id] = {element: el, init, always_mode}
|
||||||
|
if(first_id == null) first_id = id
|
||||||
|
} else
|
||||||
|
logger.wprint(`${element} is not html-element or id`)
|
||||||
|
})
|
||||||
|
}, logger.eprint) (page_space_id, ...args)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} page_id
|
||||||
|
* @param {...*} args
|
||||||
|
*/
|
||||||
|
set_page = recovery((page_id, ...args) => {
|
||||||
|
const page = this.pages[page_id]
|
||||||
|
|
||||||
|
this.page_space.innerHTML = ''
|
||||||
|
this.page_space.appendChild(page.element)
|
||||||
|
|
||||||
|
if(!page.first_init || page.always_mode) {
|
||||||
|
page.init(...args)
|
||||||
|
if(!page.first_init) page.first_init = true
|
||||||
|
}
|
||||||
|
}, logger.eprint)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,127 @@
|
||||||
|
/**
|
||||||
|
* @typedef {Object} PopupManagerArg
|
||||||
|
* @property {ol.Map} map - ol-карта
|
||||||
|
* @property {HTMLElement} popup_div - контейнер всплывающего окна
|
||||||
|
* @property {HTMLElement} [popup_close] - закрывашка
|
||||||
|
* @property {(feature: ol.Feature) => void} map_on - поведение при нажатии на map
|
||||||
|
* @property {[number, number]} offset - смещение popup
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Управление и создание ol-popup
|
||||||
|
*
|
||||||
|
* @class PopupManager
|
||||||
|
* @property {ol.Map} map - ol-карта
|
||||||
|
* @property {HTMLElement} popup_div - контейнер всплывающего окна
|
||||||
|
* @property {HTMLElement} [popup_close] - закрывашка
|
||||||
|
* @property {(feature: ol.Feature) => void} map_on - поведение при нажатии на map
|
||||||
|
*/
|
||||||
|
class PopupManager {
|
||||||
|
/**
|
||||||
|
* @param {PopupManagerArg} args
|
||||||
|
*/
|
||||||
|
constructor ({map, popup_div, popup_close, map_on = (feature) => {}, offset = [0, 0]}) {
|
||||||
|
/** Безопасный вызов */
|
||||||
|
recovery(
|
||||||
|
/**
|
||||||
|
* @function
|
||||||
|
* @param {PopupManager} self
|
||||||
|
*/
|
||||||
|
self => {
|
||||||
|
/** Инъекция карты */
|
||||||
|
if(map && map instanceof ol.Map) self.map = map
|
||||||
|
else throw new Error("Аргумент 'map' невалиден")
|
||||||
|
|
||||||
|
/** Инъекция поведения при нажатии на map */
|
||||||
|
this.set_map_on(map_on)
|
||||||
|
|
||||||
|
/** Инъекция popup-элемента */
|
||||||
|
if(popup_div && popup_div instanceof HTMLElement)
|
||||||
|
self.popup_div = popup_div
|
||||||
|
else throw new Error("Аргумент 'popup_div' невалиден")
|
||||||
|
|
||||||
|
/** Инициализация popup-элемента */
|
||||||
|
self.overlay = new ol.Overlay({
|
||||||
|
element: self.popup_div,
|
||||||
|
autoPan: true,
|
||||||
|
offset: offset
|
||||||
|
})
|
||||||
|
|
||||||
|
/** Регристрация overlay */
|
||||||
|
self.map.addOverlay(self.overlay)
|
||||||
|
|
||||||
|
/** Добавление поведения при нажатии на карту */
|
||||||
|
self.map.on('click', evt => {
|
||||||
|
/** Получение данных маркера */
|
||||||
|
const feature = self.map.forEachFeatureAtPixel(
|
||||||
|
evt.pixel,
|
||||||
|
(feature, _) => feature,
|
||||||
|
{ hitTolerance: 8 }
|
||||||
|
)
|
||||||
|
if(feature) {
|
||||||
|
/** Вызов переданной функции */
|
||||||
|
self.map_on(feature)
|
||||||
|
|
||||||
|
/** Прилинковка popup */
|
||||||
|
self.set_popup(feature.getGeometry().getCoordinates())
|
||||||
|
} else {
|
||||||
|
/** Олинковка popup */
|
||||||
|
self.set_popup()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
/** При нажатии не на маркеры убрать popup */
|
||||||
|
document.addEventListener('click', event => {
|
||||||
|
if (!event.target.closest('.ol-viewport') && !event.target.closest('.popup')) {
|
||||||
|
self.set_popup()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
/** Инъекция и инициалиазция закрывашки (если есть) */
|
||||||
|
if(popup_close) {
|
||||||
|
/** Инъекция закрывашки */
|
||||||
|
if (popup_close instanceof HTMLElement) self.popup_close = popup_close
|
||||||
|
else throw new Error("Аргумент 'popup_close' невалиден")
|
||||||
|
|
||||||
|
/** Инициаилазиця закрывашки */
|
||||||
|
self.popup_close.onclick = function () {
|
||||||
|
/** Отлинковка от координат */
|
||||||
|
self.set_popup()
|
||||||
|
/** Скрытие */
|
||||||
|
popup_close.blur()
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
(...e) => {
|
||||||
|
console.log("Ошибка при инициалиазции PopupManager");
|
||||||
|
e.forEach(er => {throw er});
|
||||||
|
}
|
||||||
|
) (this)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @function
|
||||||
|
* @param {[number, number] | undefined} [coord] - координаты привязки
|
||||||
|
*/
|
||||||
|
set_popup = recovery(
|
||||||
|
(coord = undefined) => this.overlay.setPosition(coord),
|
||||||
|
(...e) => {
|
||||||
|
console.log("Ошибка скрытии popup (PopupManager.set_popup)");
|
||||||
|
e.forEach(er => {throw er});
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Изменение поведения при нажатии на map
|
||||||
|
* @function
|
||||||
|
* @param {(feature: Object) => void} map_on - поведение при нажатии на map
|
||||||
|
*/
|
||||||
|
set_map_on = recovery(
|
||||||
|
map_on => this.map_on = map_on,
|
||||||
|
(...e) => {
|
||||||
|
console.log("Ошибка при инициалиазции PopupManager.set_map_on");
|
||||||
|
e.forEach(er => {throw er});
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,123 @@
|
||||||
|
/**
|
||||||
|
* Удобное хранение и освобождение временных ссылок
|
||||||
|
* ВНИМАНИЕ: При очистке освобождаются все ссылки
|
||||||
|
*
|
||||||
|
* @class RefManager
|
||||||
|
* @property {Map<string, string>} refs - хранилище
|
||||||
|
*/
|
||||||
|
class RefManager {
|
||||||
|
/**
|
||||||
|
* @param {Map<string, string> | Object} [refs]
|
||||||
|
*/
|
||||||
|
constructor (refs = null) {
|
||||||
|
recovery((self) => {
|
||||||
|
/** Инициализация хранилища */
|
||||||
|
self.refs = new Map()
|
||||||
|
|
||||||
|
if(refs) {
|
||||||
|
if(Array.isArray(refs))
|
||||||
|
refs.forEach(ref => { self.save({ref}) });
|
||||||
|
else {
|
||||||
|
/** Связки из аргумента refs */
|
||||||
|
const refs_entries = (refs instanceof Map)
|
||||||
|
? refs.entries()
|
||||||
|
: (typeof refs === 'object' && refs !== null)
|
||||||
|
? Object.entries(refs)
|
||||||
|
: (() => {throw new Error("Невалидный аргумент")})()
|
||||||
|
|
||||||
|
/** Сохранение ссылок в хранилище */
|
||||||
|
Array.from(refs_entries).forEach(
|
||||||
|
([key, ref]) => self.save({key, ref})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, (...e) => {
|
||||||
|
console.log("Ошибка инициализии RefManager");
|
||||||
|
e.forEach(er => {throw er});
|
||||||
|
}) (this)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Проверка ссылки
|
||||||
|
* @static @method
|
||||||
|
* @param {string} ref - ссылка
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
static check = recovery(ref => {
|
||||||
|
if(typeof ref !== 'string')
|
||||||
|
throw Error("Неизвестный объект в массиве строк")
|
||||||
|
return ref
|
||||||
|
}, (...e) => {
|
||||||
|
console.log("Ошибка при проверке значения (RefManager.check)");
|
||||||
|
e.forEach(er => {throw er});
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Сохранение сслыки
|
||||||
|
* @method
|
||||||
|
* @param {string} [key] - ключ
|
||||||
|
* @param {string} ref - ссылка
|
||||||
|
* @returns {{key: string, ref: string}}
|
||||||
|
*/
|
||||||
|
save = recovery(
|
||||||
|
({key = null, ref}) => {
|
||||||
|
var now_key = key
|
||||||
|
? key
|
||||||
|
: (Date.now() + Math.random()).toString(36)
|
||||||
|
|
||||||
|
this.refs.set(RefManager.check(now_key), RefManager.check(ref))
|
||||||
|
return {key: now_key, ref}
|
||||||
|
},
|
||||||
|
(...e) => {
|
||||||
|
console.log("Ошибка при сохранении (save)");
|
||||||
|
e.forEach(er => {throw er});
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Получение ссылки по ключу
|
||||||
|
* @method
|
||||||
|
* @param {string} key - ключ
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
get = recovery(
|
||||||
|
key => {
|
||||||
|
return this.refs.get(RefManager.check(key))
|
||||||
|
},
|
||||||
|
(...e) => {
|
||||||
|
console.log("Ошибка при извлечении ссылки (get)");
|
||||||
|
e.forEach(er => {throw er});
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Очищает все сохранённые ссылки и освобождает ресурсы
|
||||||
|
* @method
|
||||||
|
*/
|
||||||
|
clear = recovery(
|
||||||
|
() => {
|
||||||
|
Array.from(this.refs.values()).forEach(URL.revokeObjectURL);
|
||||||
|
this.refs.clear()
|
||||||
|
},
|
||||||
|
(...e) => {
|
||||||
|
console.log("Ошибка при очищении (clear)");
|
||||||
|
e.forEach(er => {throw er});
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Возвращает сконвертированную в объект список
|
||||||
|
* @method
|
||||||
|
* @returns {Object}
|
||||||
|
*/
|
||||||
|
get_all = recovery(
|
||||||
|
() => {
|
||||||
|
return Object.fromEntries(this.refs)
|
||||||
|
},
|
||||||
|
(...e) => {
|
||||||
|
console.log("Ошибка при конвертации и получени (get_all)");
|
||||||
|
e.forEach(er => {throw er});
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,61 @@
|
||||||
|
/**
|
||||||
|
* @typedef {import('./ref-manager.js').RefManager} RefManager
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Контроль за одним интерактивным целевым слоем.
|
||||||
|
*
|
||||||
|
* @class SingleLayerManager
|
||||||
|
* @property {ol.layer.Vector} layer - тот самый single слой
|
||||||
|
*/
|
||||||
|
class SingleLayerManager {
|
||||||
|
/**
|
||||||
|
* @param {ol.layer.Vector } layer - ol-слой
|
||||||
|
*/
|
||||||
|
constructor (layer) {
|
||||||
|
recovery(self => {
|
||||||
|
/** Инъекция слоя */
|
||||||
|
if(layer instanceof ol.layer.Vector || layer instanceof ol.layer.VectorImage)
|
||||||
|
self.layer = layer
|
||||||
|
else
|
||||||
|
throw new Error("Несоответствующий тип у слоя-аргумента")
|
||||||
|
}, (...e) => {
|
||||||
|
console.log("Ошибка инициализии SingleLayerManager");
|
||||||
|
e.forEach(er => {throw er});
|
||||||
|
})(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Меняет источник целевого слоя.
|
||||||
|
* @method
|
||||||
|
* @param {object} json - необработанный json
|
||||||
|
*/
|
||||||
|
set = recovery(
|
||||||
|
json => {
|
||||||
|
const features = new ol.format.GeoJSON().readFeatures(json, {
|
||||||
|
featureProjection: 'EPSG:3857'
|
||||||
|
})
|
||||||
|
this.layer.getSource().clear()
|
||||||
|
this.layer.getSource().addFeatures(features)
|
||||||
|
},
|
||||||
|
(...e) => {
|
||||||
|
console.log("Ошибка изменения слоя (singleLayerManager.set)");
|
||||||
|
e.forEach(er => {throw er});
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Очистка и удаление самого менеджера
|
||||||
|
* @method
|
||||||
|
*/
|
||||||
|
exit = recovery(
|
||||||
|
() => {
|
||||||
|
this.layer = null
|
||||||
|
},
|
||||||
|
(...e) => {
|
||||||
|
console.log("Ошибка изменения слоя (singleLayerManager.set)");
|
||||||
|
e.forEach(er => {throw er});
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,144 @@
|
||||||
|
/**
|
||||||
|
* @typedef {Object} SliderArg
|
||||||
|
* @property {string} next_ref - сслыка на иконку вперед
|
||||||
|
* @property {string} previous_ref - ссылка на иконку назадъ
|
||||||
|
* @property {{main: HTMLStyleElement, next: HTMLElement, previous: HTMLElement}} slider - разграниченный слайдер
|
||||||
|
* @property {Array<string>} images - массив ссылок
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Класс для создания слайдера
|
||||||
|
* @class
|
||||||
|
* @property {HTMLElement} main - DOM-элемент для слайдов
|
||||||
|
* @property {number} current_slide - текущий слайд
|
||||||
|
* @property {Array<HTMLElement>} slides - слайды
|
||||||
|
*/
|
||||||
|
class Slider {
|
||||||
|
/**
|
||||||
|
* @param {SliderArg} arg
|
||||||
|
*/
|
||||||
|
constructor ({slider: {main, next, previous}, next_ref, previous_ref, images = null}) {
|
||||||
|
/** Безопасный вызов */
|
||||||
|
recovery(
|
||||||
|
self => {
|
||||||
|
/** Инъекция DOM-элементов (слайдер) */
|
||||||
|
Object.entries({main, next, previous}).forEach(([name, value]) => {
|
||||||
|
if (value instanceof HTMLElement)
|
||||||
|
self[name] = value
|
||||||
|
else throw new Error(`Невалидный аргумент ${value}`)
|
||||||
|
})
|
||||||
|
|
||||||
|
/** Инициалиазация кнопок */
|
||||||
|
Object.entries({
|
||||||
|
[next_ref]: {
|
||||||
|
element: next,
|
||||||
|
func: this.next_slide
|
||||||
|
},
|
||||||
|
[previous_ref]: {
|
||||||
|
element: previous,
|
||||||
|
func: this.previous_slide
|
||||||
|
}
|
||||||
|
}).forEach(
|
||||||
|
([ref, {element, func}]) => {
|
||||||
|
element.src = ref
|
||||||
|
element.onclick = func.bind(self)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
self.current_slide = 0
|
||||||
|
self.slides = []
|
||||||
|
|
||||||
|
/** Инициализация фыфок (з****лся эти JSDoc'и писать)*/
|
||||||
|
if(Array.isArray(images))
|
||||||
|
images.forEach(self.add_slide)
|
||||||
|
},
|
||||||
|
(...e) => {
|
||||||
|
console.log("Ошибка при инициалиазции slider-page");
|
||||||
|
e.forEach(er => {throw er});
|
||||||
|
}
|
||||||
|
) (this)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Обновляет слайдер (смещает на 80vw * current_slide)
|
||||||
|
* @method
|
||||||
|
*/
|
||||||
|
update_slider = recovery(
|
||||||
|
() => this.main.style.transform = `translateX(-${this.current_slide * 80}vw)`,
|
||||||
|
(...e) => {
|
||||||
|
console.log("Ошибка при обновлении слайдера (Slider.update_slider)")
|
||||||
|
e.forEach(er => {throw er})
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Переключает на следующий слайд
|
||||||
|
* @method
|
||||||
|
*/
|
||||||
|
next_slide = recovery(
|
||||||
|
() => {
|
||||||
|
if (this.current_slide < this.slides.length - 1)
|
||||||
|
this.current_slide++
|
||||||
|
else
|
||||||
|
this.current_slide = 0
|
||||||
|
this.update_slider()
|
||||||
|
},
|
||||||
|
(...e) => {
|
||||||
|
console.log("Ошибка при переключении на следующий слайд")
|
||||||
|
e.forEach(er => {throw er})
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Переключает на предыдущий слайд
|
||||||
|
* @method
|
||||||
|
*/
|
||||||
|
previous_slide = recovery(
|
||||||
|
() => {
|
||||||
|
if (this.current_slide > 0)
|
||||||
|
this.current_slide--
|
||||||
|
else
|
||||||
|
this.current_slide = this.slides.length - 1
|
||||||
|
this.update_slider()
|
||||||
|
},
|
||||||
|
(...e) => {
|
||||||
|
console.log("Ошибка при переключении на следующий слайд")
|
||||||
|
e.forEach(er => {throw er})
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Добавление новых слайдов
|
||||||
|
* @method
|
||||||
|
*/
|
||||||
|
add_slide = recovery(
|
||||||
|
slide_ref => {
|
||||||
|
/** Изображение */
|
||||||
|
const img = document.createElement('img')
|
||||||
|
/** Применение стилей */
|
||||||
|
img.classList.add('slide')
|
||||||
|
/** Линковка ссылки */
|
||||||
|
img.src = slide_ref
|
||||||
|
|
||||||
|
/** Регистрация слайда в dom-элементе */
|
||||||
|
this.main.appendChild(img)
|
||||||
|
/** Регистрация в массиве */
|
||||||
|
this.slides.push(img)
|
||||||
|
},
|
||||||
|
(...e) => {
|
||||||
|
console.log("Ошибка при добавлении слайда")
|
||||||
|
e.forEach(er => {throw er})
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
clear = recovery(
|
||||||
|
() => {
|
||||||
|
this.main.innerHTML = ''
|
||||||
|
},
|
||||||
|
(...e) => {
|
||||||
|
console.log("Ошибка при очищении")
|
||||||
|
e.forEach(er => {throw er})
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,354 @@
|
||||||
|
:root,
|
||||||
|
:host {
|
||||||
|
--ol-background-color: white;
|
||||||
|
--ol-accent-background-color: #F5F5F5;
|
||||||
|
--ol-subtle-background-color: rgba(128, 128, 128, 0.25);
|
||||||
|
--ol-partial-background-color: rgba(255, 255, 255, 0.75);
|
||||||
|
--ol-foreground-color: #333333;
|
||||||
|
--ol-subtle-foreground-color: #666666;
|
||||||
|
--ol-brand-color: #00AAFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ol-box {
|
||||||
|
box-sizing: border-box;
|
||||||
|
border-radius: 2px;
|
||||||
|
border: 1.5px solid var(--ol-background-color);
|
||||||
|
background-color: var(--ol-partial-background-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ol-mouse-position {
|
||||||
|
top: 8px;
|
||||||
|
right: 8px;
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ol-scale-line {
|
||||||
|
background: var(--ol-partial-background-color);
|
||||||
|
border-radius: 4px;
|
||||||
|
bottom: 8px;
|
||||||
|
left: 8px;
|
||||||
|
padding: 2px;
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ol-scale-line-inner {
|
||||||
|
border: 1px solid var(--ol-subtle-foreground-color);
|
||||||
|
border-top: none;
|
||||||
|
color: var(--ol-foreground-color);
|
||||||
|
font-size: 10px;
|
||||||
|
text-align: center;
|
||||||
|
margin: 1px;
|
||||||
|
will-change: contents, width;
|
||||||
|
transition: all 0.25s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ol-scale-bar {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 8px;
|
||||||
|
left: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ol-scale-bar-inner {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ol-scale-step-marker {
|
||||||
|
width: 1px;
|
||||||
|
height: 15px;
|
||||||
|
background-color: var(--ol-foreground-color);
|
||||||
|
float: right;
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ol-scale-step-text {
|
||||||
|
position: absolute;
|
||||||
|
bottom: -5px;
|
||||||
|
font-size: 10px;
|
||||||
|
z-index: 11;
|
||||||
|
color: var(--ol-foreground-color);
|
||||||
|
text-shadow: -1.5px 0 var(--ol-partial-background-color), 0 1.5px var(--ol-partial-background-color), 1.5px 0 var(--ol-partial-background-color), 0 -1.5px var(--ol-partial-background-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ol-scale-text {
|
||||||
|
position: absolute;
|
||||||
|
font-size: 12px;
|
||||||
|
text-align: center;
|
||||||
|
bottom: 25px;
|
||||||
|
color: var(--ol-foreground-color);
|
||||||
|
text-shadow: -1.5px 0 var(--ol-partial-background-color), 0 1.5px var(--ol-partial-background-color), 1.5px 0 var(--ol-partial-background-color), 0 -1.5px var(--ol-partial-background-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ol-scale-singlebar {
|
||||||
|
position: relative;
|
||||||
|
height: 10px;
|
||||||
|
z-index: 9;
|
||||||
|
box-sizing: border-box;
|
||||||
|
border: 1px solid var(--ol-foreground-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ol-scale-singlebar-even {
|
||||||
|
background-color: var(--ol-subtle-foreground-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ol-scale-singlebar-odd {
|
||||||
|
background-color: var(--ol-background-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ol-unsupported {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ol-viewport,
|
||||||
|
.ol-unselectable {
|
||||||
|
-webkit-touch-callout: none;
|
||||||
|
-webkit-user-select: none;
|
||||||
|
-moz-user-select: none;
|
||||||
|
user-select: none;
|
||||||
|
-webkit-tap-highlight-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ol-viewport canvas {
|
||||||
|
all: unset;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ol-viewport {
|
||||||
|
touch-action: pan-x pan-y;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ol-selectable {
|
||||||
|
-webkit-touch-callout: default;
|
||||||
|
-webkit-user-select: text;
|
||||||
|
-moz-user-select: text;
|
||||||
|
user-select: text;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ol-grabbing {
|
||||||
|
cursor: -webkit-grabbing;
|
||||||
|
cursor: -moz-grabbing;
|
||||||
|
cursor: grabbing;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ol-grab {
|
||||||
|
cursor: move;
|
||||||
|
cursor: -webkit-grab;
|
||||||
|
cursor: -moz-grab;
|
||||||
|
cursor: grab;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ol-control {
|
||||||
|
position: absolute;
|
||||||
|
background-color: var(--ol-subtle-background-color);
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ol-zoom {
|
||||||
|
top: .5em;
|
||||||
|
left: .5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ol-rotate {
|
||||||
|
top: .5em;
|
||||||
|
right: .5em;
|
||||||
|
transition: opacity .25s linear, visibility 0s linear;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ol-rotate.ol-hidden {
|
||||||
|
opacity: 0;
|
||||||
|
visibility: hidden;
|
||||||
|
transition: opacity .25s linear, visibility 0s linear .25s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ol-zoom-extent {
|
||||||
|
top: 4.643em;
|
||||||
|
left: .5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ol-full-screen {
|
||||||
|
right: .5em;
|
||||||
|
top: .5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ol-control button {
|
||||||
|
display: block;
|
||||||
|
margin: 1px;
|
||||||
|
padding: 0;
|
||||||
|
color: var(--ol-subtle-foreground-color);
|
||||||
|
font-weight: bold;
|
||||||
|
text-decoration: none;
|
||||||
|
font-size: inherit;
|
||||||
|
text-align: center;
|
||||||
|
height: 1.375em;
|
||||||
|
width: 1.375em;
|
||||||
|
line-height: .4em;
|
||||||
|
background-color: var(--ol-background-color);
|
||||||
|
border: none;
|
||||||
|
border-radius: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ol-control button::-moz-focus-inner {
|
||||||
|
border: none;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ol-zoom-extent button {
|
||||||
|
line-height: 1.4em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ol-compass {
|
||||||
|
display: block;
|
||||||
|
font-weight: normal;
|
||||||
|
will-change: transform;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ol-touch .ol-control button {
|
||||||
|
font-size: 1.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ol-touch .ol-zoom-extent {
|
||||||
|
top: 5.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ol-control button:hover,
|
||||||
|
.ol-control button:focus {
|
||||||
|
text-decoration: none;
|
||||||
|
outline: 1px solid var(--ol-subtle-foreground-color);
|
||||||
|
color: var(--ol-foreground-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ol-zoom .ol-zoom-in {
|
||||||
|
border-radius: 2px 2px 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ol-zoom .ol-zoom-out {
|
||||||
|
border-radius: 0 0 2px 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ol-attribution {
|
||||||
|
text-align: right;
|
||||||
|
bottom: .5em;
|
||||||
|
right: .5em;
|
||||||
|
max-width: calc(100% - 1.3em);
|
||||||
|
display: flex;
|
||||||
|
flex-flow: row-reverse;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ol-attribution a {
|
||||||
|
color: var(--ol-subtle-foreground-color);
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ol-attribution ul {
|
||||||
|
margin: 0;
|
||||||
|
padding: 1px .5em;
|
||||||
|
color: var(--ol-foreground-color);
|
||||||
|
text-shadow: 0 0 2px var(--ol-background-color);
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ol-attribution li {
|
||||||
|
display: inline;
|
||||||
|
list-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ol-attribution li:not(:last-child):after {
|
||||||
|
content: " ";
|
||||||
|
}
|
||||||
|
|
||||||
|
.ol-attribution img {
|
||||||
|
max-height: 2em;
|
||||||
|
max-width: inherit;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ol-attribution button {
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ol-attribution.ol-collapsed ul {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ol-attribution:not(.ol-collapsed) {
|
||||||
|
background: var(--ol-partial-background-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ol-attribution.ol-uncollapsible {
|
||||||
|
bottom: 0;
|
||||||
|
right: 0;
|
||||||
|
border-radius: 4px 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ol-attribution.ol-uncollapsible img {
|
||||||
|
margin-top: -.2em;
|
||||||
|
max-height: 1.6em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ol-attribution.ol-uncollapsible button {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ol-zoomslider {
|
||||||
|
top: 4.5em;
|
||||||
|
left: .5em;
|
||||||
|
height: 200px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ol-zoomslider button {
|
||||||
|
position: relative;
|
||||||
|
height: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ol-touch .ol-zoomslider {
|
||||||
|
top: 5.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ol-overviewmap {
|
||||||
|
left: 0.5em;
|
||||||
|
bottom: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ol-overviewmap.ol-uncollapsible {
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
border-radius: 0 4px 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ol-overviewmap .ol-overviewmap-map,
|
||||||
|
.ol-overviewmap button {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ol-overviewmap .ol-overviewmap-map {
|
||||||
|
border: 1px solid var(--ol-subtle-foreground-color);
|
||||||
|
height: 150px;
|
||||||
|
width: 150px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ol-overviewmap:not(.ol-collapsed) button {
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ol-overviewmap.ol-collapsed .ol-overviewmap-map,
|
||||||
|
.ol-overviewmap.ol-uncollapsible button {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ol-overviewmap:not(.ol-collapsed) {
|
||||||
|
background: var(--ol-subtle-background-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ol-overviewmap-box {
|
||||||
|
border: 1.5px dotted var(--ol-subtle-foreground-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ol-overviewmap .ol-overviewmap-box:hover {
|
||||||
|
cursor: move;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ol-overviewmap .ol-viewport:hover {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,46 @@
|
||||||
|
<?php
|
||||||
|
namespace Catcher;
|
||||||
|
|
||||||
|
use Throwable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Вызов обработчика ошибок с защитой от повторных исключений.
|
||||||
|
*
|
||||||
|
* @param callable $restorer Функция восстановления, принимающая исключения.
|
||||||
|
* @param Throwable ...$errors Исключения, переданные для обработки.
|
||||||
|
* @return mixed
|
||||||
|
* @throws Throwable
|
||||||
|
*/
|
||||||
|
function start_restorer(callable $restorer, Throwable ...$errors) {
|
||||||
|
try {
|
||||||
|
return $restorer(...$errors);
|
||||||
|
} catch (Throwable $e) {
|
||||||
|
error_log("Restorer error: " . $e->getMessage());
|
||||||
|
throw $e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Оборачивает функцию и добавляет обработку ошибок.
|
||||||
|
*
|
||||||
|
* @param callable $func Основная функция.
|
||||||
|
* @param callable|null $restorer Обработчик ошибок (по умолчанию — пробрасывает исключение).
|
||||||
|
* @return callable
|
||||||
|
*/
|
||||||
|
function recovery(callable $func, callable $restorer = null): callable {
|
||||||
|
if ($restorer === null) {
|
||||||
|
$restorer = function (Throwable ...$errors) {
|
||||||
|
throw $errors[0]; // Пробрасываем первое исключение
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return function (...$args) use ($func, $restorer) {
|
||||||
|
try {
|
||||||
|
return $func(...$args);
|
||||||
|
} catch (Throwable $e) {
|
||||||
|
return start_restorer($restorer, $e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
?>
|
||||||
|
|
@ -0,0 +1,90 @@
|
||||||
|
<?php
|
||||||
|
namespace DataBaseManager\DBManager;
|
||||||
|
use DataBaseManager\Entitie\Entitie;
|
||||||
|
use PDO;
|
||||||
|
use InvalidArgumentException;
|
||||||
|
|
||||||
|
class DBManager
|
||||||
|
{
|
||||||
|
private $pdo;
|
||||||
|
private $entitie;
|
||||||
|
public function __construct(Entitie $entitie, object $config)
|
||||||
|
{
|
||||||
|
$this->entitie = $entitie;
|
||||||
|
$dsn = "{$config->driver}:host={$config->host};port={$config->port};dbname={$config->dbname}";
|
||||||
|
$username = $config->username;
|
||||||
|
$password = $config->password;
|
||||||
|
$options = isset($config->options) ? (array)$config->options : [];
|
||||||
|
$this->pdo = new PDO($dsn, $username, $password, $options);
|
||||||
|
}
|
||||||
|
public function create_table()
|
||||||
|
{
|
||||||
|
return $this->pdo->query($this->entitie->get_create_table());
|
||||||
|
}
|
||||||
|
public function delete_table()
|
||||||
|
{
|
||||||
|
return $this->pdo->query($this->entitie->get_delete_table());
|
||||||
|
}
|
||||||
|
public function create(array $params)
|
||||||
|
{
|
||||||
|
$stmt = $this->pdo->prepare($this->entitie->get_create());
|
||||||
|
return $stmt->execute($params);
|
||||||
|
}
|
||||||
|
public function select(array $params)
|
||||||
|
{
|
||||||
|
$sql = str_replace("*", $params[0], $this->entitie->get_select());
|
||||||
|
array_shift($params);
|
||||||
|
$stmt = $this->pdo->prepare($sql);
|
||||||
|
$stmt->execute($params);
|
||||||
|
return $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
}
|
||||||
|
public function select_all(array $params = ["*"])
|
||||||
|
{
|
||||||
|
$sql = str_replace("*", $params[0], $this->entitie->get_select_all());
|
||||||
|
$stmt = $this->pdo->prepare($sql);
|
||||||
|
$stmt->execute([]);
|
||||||
|
return $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
}
|
||||||
|
public function select_by_condition(array $params)
|
||||||
|
{
|
||||||
|
$sql = str_replace("*", $params[0], $this->entitie->get_select_all());
|
||||||
|
array_shift($params);
|
||||||
|
$stmt = $this->pdo->prepare($sql . ' WHERE ' . $params[0]);
|
||||||
|
$stmt->execute([]);
|
||||||
|
return $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
}
|
||||||
|
public function remove(array $params)
|
||||||
|
{
|
||||||
|
$stmt = $this->pdo->prepare($this->entitie->get_remove());
|
||||||
|
return $stmt->execute($params);
|
||||||
|
}
|
||||||
|
public function clear(array $params)
|
||||||
|
{
|
||||||
|
$stmt = $this->pdo->prepare($this->entitie->get_clear());
|
||||||
|
return $stmt->execute($params);
|
||||||
|
}
|
||||||
|
public function create_all(array $params)
|
||||||
|
{
|
||||||
|
$create_all = $this->entitie->get_create_all();
|
||||||
|
$stmt = $this->pdo->prepare($create_all(sizeof($params)));
|
||||||
|
$params_merge = array_merge(...$params);
|
||||||
|
return $stmt->execute($params_merge);
|
||||||
|
}
|
||||||
|
public function update(array $params)
|
||||||
|
{
|
||||||
|
$update = $this->entitie->get_update();
|
||||||
|
$stmt = $this->pdo->prepare($update($params[0]));
|
||||||
|
array_shift($params);
|
||||||
|
$stmt->execute($params);
|
||||||
|
return $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
}
|
||||||
|
public function update_by_condition(array $params)
|
||||||
|
{
|
||||||
|
$update = $this->entitie->get_update_by_condition();
|
||||||
|
$stmt = $this->pdo->prepare($update($params[0]));
|
||||||
|
array_shift($params);
|
||||||
|
$stmt->execute($params);
|
||||||
|
return $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,57 @@
|
||||||
|
<?php
|
||||||
|
namespace DataBaseManager\Entitie;
|
||||||
|
|
||||||
|
class Entitie
|
||||||
|
{
|
||||||
|
private array $column_array;
|
||||||
|
private string $table_name;
|
||||||
|
private string $create_table;
|
||||||
|
private string $delete_table;
|
||||||
|
private string $type_id;
|
||||||
|
private string $create;
|
||||||
|
private string $select;
|
||||||
|
private string $select_all;
|
||||||
|
private string $remove;
|
||||||
|
private string $clear;
|
||||||
|
private \Closure $create_all;
|
||||||
|
private \Closure $update;
|
||||||
|
private \Closure $update_by_condition;
|
||||||
|
|
||||||
|
public function __construct(array $column_array, string $table_name, string $type_id = "serial")
|
||||||
|
{
|
||||||
|
$this->column_array = $column_array;
|
||||||
|
$this->table_name = $table_name;
|
||||||
|
$this->type_id = $type_id;
|
||||||
|
|
||||||
|
$columns_sql = implode(", ", $this->column_array);
|
||||||
|
$column_names = implode(", ", array_map(fn($col) => explode(" ", $col)[0], $this->column_array));
|
||||||
|
$placeholders = implode(", ", array_fill(0, count($this->column_array), "?"));
|
||||||
|
$set_clause = implode(", ", array_map(fn($col) => explode(" ", $col)[0] . "=?", $this->column_array));
|
||||||
|
|
||||||
|
$this->create_table = "CREATE TABLE IF NOT EXISTS {$this->table_name} (id {$this->type_id}, {$columns_sql})";
|
||||||
|
$this->delete_table = "DROP TABLE IF EXISTS {$this->table_name}";
|
||||||
|
$this->create = "INSERT INTO {$this->table_name} ({$column_names}) VALUES ({$placeholders})";
|
||||||
|
$this->select = "SELECT * FROM {$this->table_name} WHERE id=?";
|
||||||
|
$this->select_all = "SELECT * FROM {$this->table_name}";
|
||||||
|
$this->remove = "DELETE FROM {$this->table_name} WHERE id=?";
|
||||||
|
$this->clear = "TRUNCATE TABLE {$this->table_name}";
|
||||||
|
|
||||||
|
$this->create_all = function($length) use ($placeholders, $column_names, $table_name) {
|
||||||
|
$placeholders_array = implode(", ", array_fill(0, $length, "({$placeholders})"));
|
||||||
|
return "INSERT INTO {$table_name} ({$column_names}) VALUES {$placeholders_array}";
|
||||||
|
};
|
||||||
|
$this->update = fn($id) => "UPDATE {$this->table_name} SET {$set_clause} WHERE id = ?";
|
||||||
|
$this->update_by_condition = fn($condition) => "UPDATE {$this->table_name} SET {$set_clause} WHERE {$condition}";
|
||||||
|
}
|
||||||
|
|
||||||
|
public function get_create_table(): string { return $this->create_table; }
|
||||||
|
public function get_delete_table(): string { return $this->delete_table; }
|
||||||
|
public function get_create(): string { return $this->create; }
|
||||||
|
public function get_select(): string { return $this->select; }
|
||||||
|
public function get_select_all(): string { return $this->select_all; }
|
||||||
|
public function get_remove(): string { return $this->remove; }
|
||||||
|
public function get_clear(): string { return $this->clear; }
|
||||||
|
public function get_create_all(): \Closure { return $this->create_all; }
|
||||||
|
public function get_update(): \Closure { return $this->update; }
|
||||||
|
public function get_update_by_condition(): \Closure { return $this->update_by_condition; }
|
||||||
|
}
|
||||||