This commit is contained in:
2025-03-31 00:22:21 +03:00
commit 38f2a05107
146 changed files with 66771 additions and 0 deletions

13
web/Dockerfile Normal file
View File

@@ -0,0 +1,13 @@
FROM node:20.18
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm install
COPY . .
EXPOSE 4002
CMD npm start

4
web/config.js Normal file
View File

@@ -0,0 +1,4 @@
const CONFIG = {
"api": "https://sheep-service.com/api/",
"wss": "wss://sheep-service.com/ws"
}

551
web/css/main.css Normal file
View File

@@ -0,0 +1,551 @@
@import url('https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500;700&display=swap');
@media (prefers-color-scheme: light) {
:root {
/* PrimaryColor */
--PrimaryColor: #28a55a;
--PrimaryColor: #f2bd53;
--PrimaryColorText: #2e2e2e;
/* BGColor */
--ColorThemes0: #fbfbfb;
--ColorThemes1: #f3f3f3;
--ColorThemes2: #e5e5df;
/* TextColor */
--ColorThemes3: #313131;
--ColorAnimation: linear-gradient(90deg, #f3f3f3, #efefef, #f3f3f3);
--shadow-l1: 0px 2px 4px rgba(0, 0, 0, 0.02), 0px 0px 2px rgba(0, 0, 0, 0.04), 0px 0px 1px rgba(0, 0, 0, 0.04);
--border-radius: 15px;
--CardAnimation: linear-gradient(to right, #fbfbfb 0%, #fbfbfb 30%, #d8d8d8 45%, #d8d8d8 50%, #fbfbfb 60%, #fbfbfb 100%);
}
}
@media (prefers-color-scheme: dark) {
:root {
/* PrimaryColor */
--PrimaryColor: #28a55a;
--PrimaryColor: #cb9e44;
--PrimaryColorText: #2e2e2e;
/* BGColor */
--ColorThemes0: #1c1c19;
--ColorThemes1: #21221d;
--ColorThemes2: #3a3a39;
/* TextColor */
--ColorThemes3: #f3f3f3;
--ColorAnimation: linear-gradient(90deg, #21221d, #242520, #21221d);
--shadow-l1: 0px 4px 8px rgba(0, 0, 0, 0.04), 0px 0px 2px rgba(0, 0, 0, 0.06), 0px 0px 1px rgba(0, 0, 0, 0.04);
--border-radius: 15px;
--CardAnimation: linear-gradient(to right, #1c1c19 0%, #1c1c19 30%, #252525 45%, #252525 50%, #1c1c19 60%, #1c1c19 100%);
}
}
* {
border: 0;
padding: 0;
font-family: 'Roboto', sans-serif;
margin: 0;
font-weight: 300;
outline: none;
}
*:disabled {
opacity: 0.6 !important;
cursor: no-drop !important;
}
@media (min-width: 800px) {
* {
scroll-snap-type: none !important;
}
}
@media (pointer:fine) {
::-webkit-scrollbar {
width: 7px;
height: 7px;
background-color: #f9f9fd;
border-radius: var(--border-radius);
background: 0;
}
::-webkit-scrollbar-thumb {
background: var(--PrimaryColor);
border-radius: var(--border-radius);
}
::-webkit-scrollbar-track {
background: 0;
border: 0;
margin: 0;
border-radius: var(--border-radius);
}
}
a {
text-decoration: none;
font-size: 15px;
font-weight: 700;
cursor: pointer;
}
body {
background-color: var(--ColorThemes2);
color: var(--ColorThemes3);
/* transition: .3s ease; */
}
/* Стили для анимации обновления страници свайпом */
@keyframes fade-in {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
@keyframes fade-out {
from {
opacity: 1;
}
to {
opacity: 0;
}
}
select {
-moz-appearance: none;
-webkit-appearance: none;
appearance: none;
appearance: none;
background-image: url(data:image/svg+xml;charset=US-ASCII,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%22292.4%22%20height%3D%22292.4%22%3E%3Cpath%20fill%3D%22%23F2BD53%22%20d%3D%22M287%2069.4a17.6%2017.6%200%200%200-13-5.4H18.4c-5%200-9.3%201.8-12.9%205.4A17.6%2017.6%200%200%200%200%2082.2c0%205%201.8%209.3%205.4%2012.9l128%20127.9c3.6%203.6%207.8%205.4%2012.8%205.4s9.2-1.8%2012.8-5.4L287%2095c3.5-3.5%205.4-7.8%205.4-12.8%200-5-1.9-9.2-5.5-12.8z%22%2F%3E%3C%2Fsvg%3E);
background-repeat: no-repeat;
background-position: right 0.7rem top 50%;
background-size: 0.65rem auto;
}
.hold-button{
user-select: none;
}
.custom-checkbox {
position: absolute;
z-index: -1;
opacity: 0;
}
.custom-checkbox+label {
display: inline-flex;
align-items: center;
user-select: none;
}
.custom-checkbox+label::before {
content: '';
display: inline-block;
width: 25px;
height: 25px;
flex-shrink: 0;
flex-grow: 0;
border: 1px solid #adb5bd;
border-radius: 10px;
margin-right: 0.5em;
background-repeat: no-repeat;
background-position: center center;
background-size: 50% 50%;
}
.custom-checkbox:checked+label::before {
border-color: var(--PrimaryColor);
background-color: var(--PrimaryColor);
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%23fff' d='M6.564.75l-3.59 3.612-1.538-1.55L0 4.26 2.974 7.25 8 2.193z'/%3e%3c/svg%3e");
}
.custom-checkbox:not(:disabled):not(:checked)+label:hover::before {
border-color: var(--PrimaryColor);
}
.custom-checkbox:not(:disabled):active+label::before {
background-color: var(--PrimaryColor);
border-color: var(--PrimaryColor);
}
.custom-checkbox:focus:not(:checked)+label::before {
border-color: var(--PrimaryColor);
}
.custom-checkbox:disabled+label::before {
background-color: #e9ecef;
}
#swipe_updater {
position: absolute;
top: 0px;
width: 100%;
}
#swipe_block {
width: calc(100% - 252px);
margin-left: 252px;
height: 50px;
display: flex;
justify-content: center;
align-items: flex-end;
position: relative;
}
#swipe_icon {
width: 20px;
fill: var(--ColorThemes3);
transform: rotate(0deg);
position: absolute;
margin-top: -45px;
top: -45px;
background: var(--ColorThemes2);
border: 2px solid var(--ColorThemes3);
border-radius: 50%;
padding: 10px;
display: flex;
overflow: hidden;
height: 0;
opacity: 0;
transition: height 0ms 400ms, opacity 400ms 0ms;
}
#swipe_icon[data-state="active"] {
height: 20px;
opacity: 1;
transition: height 0ms 0ms, opacity 400ms 0ms;
}
@media (max-width: 1100px) {
#swipe_block {
width: calc(100% - 122px);
margin-left: 122px;
}
}
@media (max-width: 700px) {
#swipe_block {
width: 100%;
margin-left: 0;
}
}
/* Уведомление и кнопка обновления приложения */
#update_banner {
height: 55px;
transition: .3s ease;
}
#update_banner .content {
margin: 0 auto;
max-width: 300px;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
#update_banner .headline {
font-weight: 800;
font-size: 15px;
color: var(--PrimaryColorText);
font-family: 'Roboto', sans-serif;
}
#update_banner .subhead {
font-size: 13px;
text-align: center;
color: var(--PrimaryColorText);
font-family: 'Roboto', sans-serif;
}
#update_banner[data-state="noupdate"] {
display: none;
}
#update_banner[data-state="updateavailable"] {
display: block;
cursor: pointer;
background-color: var(--PrimaryColor);
color: var(--PrimaryColorText);
transition: .3s ease;
opacity: 0.95;
z-index: 9999;
position: fixed;
width: 300px;
margin: 10px;
padding: 5px;
border-radius: 25px;
right: 0;
}
#update_banner_icon {
display: none;
}
#update_banner_icon svg {
padding: 0px 50px;
width: 25px;
margin: -4px;
fill: var(--PrimaryColorText);
}
@media (max-width: 700px) {
#update_banner[data-state="updateavailable"] {
width: calc(100% - 30px);
}
}
/* Стили для меню */
#navigation {
position: fixed;
width: 230px;
height: calc(100vh - 60px);
min-height: 510px;
background: var(--ColorThemes2);
padding: 36px 10px;
-webkit-transition: width .2s ease 0s;
-o-transition: width .2s ease 0s;
transition: width .2s ease 0s;
display: flex;
flex-direction: column;
justify-content: space-between;
}
#navigation>nav {
display: flex;
flex-direction: column;
height: 290px;
align-items: center;
justify-content: flex-start;
}
#navigation>nav>li {
width: 180px;
height: 50px;
list-style-type: none;
position: relative;
-webkit-transition: all .2s ease 0s;
-o-transition: all .2s ease 0s;
transition: all .2s ease 0s;
z-index: 1;
margin: 5px;
}
#navigation>nav>li>div {
display: flex;
flex-direction: row;
flex-wrap: nowrap;
align-items: center;
justify-content: flex-start;
height: 50px;
padding: 0 10px;
border-radius: var(--border-radius);
-webkit-transition: all .2s ease 0s;
-o-transition: all .2s ease 0s;
transition: all .2s ease 0s;
opacity: 0.8;
cursor: pointer;
border: 2px;
border: 2px solid var(--ColorThemes2);
overflow: hidden;
}
#navigation>nav>li>div[data-state="active"] {
background: var(--ColorThemes3);
border: 2px solid var(--ColorThemes3);
box-shadow: var(--shadow-l1);
}
#navigation>nav>li:has(div[data-state="active"]) {
z-index: 10;
}
#navigation>nav>li>div>svg {
width: 25px;
height: 25px;
min-width: 25px;
min-height: 25px;
fill: var(--ColorThemes3);
}
#navigation>nav>li>div[data-state="active"] svg {
fill: var(--ColorThemes2);
}
#navigation>nav>li>div>b {
margin-left: 15px;
font-size: 14px;
font-weight: 300;
color: var(--ColorThemes3);
white-space: nowrap;
}
#navigation>nav>li>div[data-state="active"] b {
color: var(--ColorThemes2);
}
#navigation>nav>li>a {
position: absolute;
width: 100%;
height: 100%;
top: 2px;
}
@media (hover: hover) {
#navigation>nav>li:hover {
transform: scale(1.01);
}
#navigation>nav>li:hover>div {
border: 2px solid var(--ColorThemes3);
}
}
@media (max-width: 1100px) {
#navigation {
width: 100px;
}
#navigation>nav>li {
width: 50px;
}
#navigation>nav>li>div {
width: 30px;
justify-content: center;
}
#navigation>nav>li>div>b {
display: none;
}
}
#app {
background: var(--ColorThemes0);
position: absolute;
margin-top: 40px;
margin-left: 250px;
padding: 5px;
width: calc(100% - 250px - 10px);
min-height: calc(100% - 50px);
border-radius: 20px 0 0 0;
-webkit-transition: all .2sease 0s;
-o-transition: all .2s ease 0s;
transition: all .2sease 0s;
box-shadow: var(--shadow-l1);
display: flex;
}
@media (max-width: 1100px) {
#app {
margin-left: 120px;
width: calc(100% - 130px);
}
}
@media (max-width: 700px),
(max-height: 540px) {
body {
background: var(--ColorThemes0);
}
#navigation {
width: 100%;
height: 60px;
min-height: 60px;
padding: 0;
z-index: 99999990;
bottom: -1px;
border: 0;
margin: 0;
box-shadow: 0px 24px 32px rgba(0, 0, 0, .09), 0px 16px 24px rgba(0, 0, 0, .09), 0px 4px 8px rgba(0, 0, 0, .09), 0px 0px 1px rgba(0, 0, 0, .09);
}
#navigation>nav {
display: flex;
flex-direction: row;
height: 100%;
justify-content: space-around;
align-items: center;
}
#navigation>nav>li {
width: 40px;
height: 40px;
}
#navigation>nav>li:hover {
transform: scale(1.0);
}
#navigation>nav>li:hover>div {
border: 0;
box-shadow: none;
}
#navigation>nav>li>div {
width: 100%;
height: 40px;
padding: 0;
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
border: 0;
box-shadow: none;
}
#navigation>nav>li>div>span {
display: none;
}
#navigation>nav>li>div[data-state="active"] {
background: 0;
border: 0;
box-shadow: none;
}
#navigation>nav>li>div[data-state="active"]>svg {
fill: var(--PrimaryColor);
}
#navigation>nav>li>button {
display: none;
}
#navigation[data-state="ios"] {
height: 70px;
}
#navigation[data-state="ios"]>nav {
align-items: flex-start;
}
#app {
margin-left: 0px;
width: 100%;
border-radius: 0;
padding: 0;
margin-top: 0;
padding-bottom: 80px;
height: auto;
}
}

94
web/fonts/PT_Mono/OFL.txt Normal file
View File

@@ -0,0 +1,94 @@
Copyright (c) 2011, ParaType Ltd. (http://www.paratype.com/public),
with Reserved Font Names "PT Sans", "PT Serif", "PT Mono" and "ParaType".
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
http://scripts.sil.org/OFL
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.

Binary file not shown.

View File

@@ -0,0 +1,96 @@
-------------------------------
UBUNTU FONT LICENCE Version 1.0
-------------------------------
PREAMBLE
This licence allows the licensed fonts to be used, studied, modified and
redistributed freely. The fonts, including any derivative works, can be
bundled, embedded, and redistributed provided the terms of this licence
are met. The fonts and derivatives, however, cannot be released under
any other licence. The requirement for fonts to remain under this
licence does not require any document created using the fonts or their
derivatives to be published under this licence, as long as the primary
purpose of the document is not to be a vehicle for the distribution of
the fonts.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this licence and clearly marked as such. This may
include source files, build scripts and documentation.
"Original Version" refers to the collection of Font Software components
as received under this licence.
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to
a new environment.
"Copyright Holder(s)" refers to all individuals and companies who have a
copyright ownership of the Font Software.
"Substantially Changed" refers to Modified Versions which can be easily
identified as dissimilar to the Font Software by users of the Font
Software comparing the Original Version with the Modified Version.
To "Propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification and with or without charging
a redistribution fee), making available to the public, and in some
countries other activities as well.
PERMISSION & CONDITIONS
This licence does not grant any rights under trademark law and all such
rights are reserved.
Permission is hereby granted, free of charge, to any person obtaining a
copy of the Font Software, to propagate the Font Software, subject to
the below conditions:
1) Each copy of the Font Software must contain the above copyright
notice and this licence. These can be included either as stand-alone
text files, human-readable headers or in the appropriate machine-
readable metadata fields within text or binary files as long as those
fields can be easily viewed by the user.
2) The font name complies with the following:
(a) The Original Version must retain its name, unmodified.
(b) Modified Versions which are Substantially Changed must be renamed to
avoid use of the name of the Original Version or similar names entirely.
(c) Modified Versions which are not Substantially Changed must be
renamed to both (i) retain the name of the Original Version and (ii) add
additional naming elements to distinguish the Modified Version from the
Original Version. The name of such Modified Versions must be the name of
the Original Version, with "derivative X" where X represents the name of
the new work, appended to that name.
3) The name(s) of the Copyright Holder(s) and any contributor to the
Font Software shall not be used to promote, endorse or advertise any
Modified Version, except (i) as required by this licence, (ii) to
acknowledge the contribution(s) of the Copyright Holder(s) or (iii) with
their explicit written permission.
4) The Font Software, modified or unmodified, in part or in whole, must
be distributed entirely under this licence, and must not be distributed
under any other licence. The requirement for fonts to remain under this
licence does not affect any document created using the Font Software,
except any version of the Font Software extracted from a document
created using the Font Software may only be distributed under this
licence.
TERMINATION
This licence becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF
COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER
DEALINGS IN THE FONT SOFTWARE.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

41
web/img/0.svg Normal file
View File

@@ -0,0 +1,41 @@
<svg xmlns:dc="http://purl.org/dc/elements/1.1/" inkscape:export-ydpi="96" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" xmlns:osb="http://www.openswatchbook.org/uri/2009/osb" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" inkscape:export-xdpi="96" width="48" xmlns="http://www.w3.org/2000/svg" xmlns:cc="http://creativecommons.org/ns#" height="48" xmlns:svg="http://www.w3.org/2000/svg" style="fill:none;stroke:none;" viewBox="0 0 48 48" version="1.1">
<defs/>
<sodipodi:namedview pagecolor="#ffffff" inkscape:pagecheckerboard="true" borderlayer="true" bordercolor="#666666" inkscape:document-units="px"/>
<title>synchronize-cloud</title>
<g inkscape:groupmode="layer" id="MainComposition_a4ec5bcbf1d44168962622fcea8276bf" inkscape:label="synchronize-cloud">
<g inkscape:groupmode="layer" transform="matrix(1, 0, 0, 1, 0, 0)" opacity="1" id="Layer_9907dd6ff6004617bd3a525195c313f2" inkscape:label="arrows">
<g transform="translate(24 29)">
<g transform="rotate(0)">
<g transform="scale(1 1)">
<g transform="translate(-24 -29)">
<g opacity="1" id="Group_2cb26585a6274aff82b52d6629f47ff3" inkscape:label="Group 1">
<g transform="matrix(1, 0, 0, 1, 18.772, 33.991)" opacity="1" id="Stroke_eac34415491147c99ffc4335fed73f62" stroke="#000000" stroke-width="3" stroke-opacity="1" inkscape:label="Group 4">
<path sodipodi:nodetypes="ccc" d="M -2.708,2.491 C -2.708,2.491 -2.273,-2.49 -2.273,-2.49 -2.273,-2.49 2.708,-2.055 2.708,-2.055" style="fill:none;stroke-dasharray:none;stroke-linecap:round;stroke-linejoin:round;">
<animate calcMode="spline" repeatCount="indefinite" attributeName="d" dur="1.166667" begin="0.000000" keyTimes="0; 0.107143; 0.178571; 0.821429; 0.892857; 1" keySplines="0 0 1 1; 0.333000 0.000000 0.667000 1.000000; 0.167000 0.167000 0.833000 0.833000; 0.333000 0.000000 0.667000 1.000000; 0.000000 0.000000 0.000000 0.000000" values="M -2.708,2.491 C -2.708,2.491 -2.273,-2.49 -2.273,-2.49 -2.273,-2.49 2.708,-2.055 2.708,-2.055; M -2.708,2.491 C -2.708,2.491 -2.273,-2.49 -2.273,-2.49 -2.273,-2.49 2.708,-2.055 2.708,-2.055; M -1.771,-2.28 C -1.771,-2.28 -1.761,-2.279 -1.761,-2.279 -1.761,-2.279 -1.78,-2.283 -1.78,-2.283; M -1.768,-2.281 C -1.768,-2.281 -1.758,-2.28 -1.758,-2.28 -1.758,-2.28 -1.778,-2.284 -1.778,-2.284; M -2.708,2.491 C -2.708,2.491 -2.273,-2.49 -2.273,-2.49 -2.273,-2.49 2.708,-2.055 2.708,-2.055; M -2.708,2.491 C -2.708,2.491 -2.273,-2.49 -2.273,-2.49 -2.273,-2.49 2.708,-2.055 2.708,-2.055"/>
</path>
</g>
<g transform="matrix(1, 0, 0, 1, 24.037, 34)" opacity="1" id="Stroke_808239d21b7e4a859d5a99d0baf98e17" stroke="#000000" stroke-width="3" stroke-opacity="1" inkscape:label="Group 3">
<path sodipodi:nodetypes="ccc" d="M 7.036,-2.5 C 6.006,0.413 3.228,2.5 -0.036,2.5 -3.227,2.5 -5.953,0.507 -7.036,-2.303" style="fill:none;stroke-dasharray:none;stroke-linecap:round;stroke-linejoin:round;"/>
</g>
<g transform="matrix(1, 0, 0, 1, 29.227, 24.009)" opacity="1" id="Stroke_c1f02aae6dea4564b6abe25c038848fe" stroke="#000000" stroke-width="3" stroke-opacity="1" inkscape:label="Group 2">
<path sodipodi:nodetypes="ccc" d="M 2.708,-2.49 C 2.708,-2.49 2.273,2.491 2.273,2.491 2.273,2.491 -2.708,2.055 -2.708,2.055" style="fill:none;stroke-dasharray:none;stroke-linecap:round;stroke-linejoin:round;">
<animate calcMode="spline" repeatCount="indefinite" attributeName="d" dur="1.166667" begin="0.000000" keyTimes="0; 0.107143; 0.178571; 0.821429; 0.892857; 1" keySplines="0 0 1 1; 0.333000 0.000000 0.667000 1.000000; 0.167000 0.167000 0.833000 0.833000; 0.333000 0.000000 0.667000 1.000000; 0.000000 0.000000 0.000000 0.000000" values="M 2.708,-2.49 C 2.708,-2.49 2.273,2.491 2.273,2.491 2.273,2.491 -2.708,2.055 -2.708,2.055; M 2.708,-2.49 C 2.708,-2.49 2.273,2.491 2.273,2.491 2.273,2.491 -2.708,2.055 -2.708,2.055; M 1.775,2.301 C 1.775,2.301 1.775,2.298 1.775,2.298 1.775,2.298 1.777,2.29 1.777,2.29; M 1.767,2.292 C 1.767,2.292 1.767,2.289 1.767,2.289 1.767,2.289 1.768,2.281 1.768,2.281; M 2.708,-2.49 C 2.708,-2.49 2.273,2.491 2.273,2.491 2.273,2.491 -2.708,2.055 -2.708,2.055; M 2.708,-2.49 C 2.708,-2.49 2.273,2.491 2.273,2.491 2.273,2.491 -2.708,2.055 -2.708,2.055"/>
</path>
</g>
<g transform="matrix(1, 0, 0, 1, 23.963, 24)" opacity="1" id="Stroke_b8202dc468474015b6784aeccf30c7fe" stroke="#000000" stroke-width="3" stroke-opacity="1" inkscape:label="Group 1">
<path sodipodi:nodetypes="ccc" d="M -7.036,2.5 C -6.006,-0.413 -3.229,-2.5 0.036,-2.5 3.228,-2.5 5.953,-0.507 7.036,2.303" style="fill:none;stroke-dasharray:none;stroke-linecap:round;stroke-linejoin:round;"/>
</g>
</g>
</g>
</g>
<animateTransform calcMode="spline" repeatCount="indefinite" attributeName="transform" dur="1.166667" begin="0.000000" type="rotate" keyTimes="0; 0.107143; 0.821429; 0.892857; 1" keySplines="0 0 1 1; 0.333000 0.000000 0.667000 1.000000; 0.333000 0.000000 0.667000 1.000000; 0.000000 0.000000 0.000000 0.000000" values="0; 0; 380; 360; 360"/>
</g>
</g>
</g>
<g inkscape:groupmode="layer" transform="matrix(1, 0, 0, 1, 0, 0)" opacity="1" id="Layer_27939f0fe5ec41fa8de333c2fda4c3f8" inkscape:label="cloud">
<g fill-opacity="1" transform="matrix(1, 0, 0, 1, 24, 22.5)" opacity="1" id="Fill_2eda0ab38e6f42adb64be815c093c02c" fill="#000000" inkscape:label="Group 1">
<path sodipodi:nodetypes="ccccccccccccccccccccccccc" d="M 15,-2.5 C 15,-2.5 14.42,-2.5 14.42,-2.5 13.67,-9.79 7.49,-15.5 0,-15.5 -7.49,-15.5 -13.67,-9.79 -14.42,-2.5 -14.42,-2.5 -15,-2.5 -15,-2.5 -19.96,-2.5 -24,1.54 -24,6.5 -24,11.46 -19.96,15.5 -15,15.5 -15,15.5 -12.17,15.5 -12.17,15.5 -12.38,14.91 -12.48,14.26 -12.42,13.59 -12.42,13.59 -12.32,12.5 -12.32,12.5 -12.32,12.5 -15,12.5 -15,12.5 -18.31,12.5 -21,9.81 -21,6.5 -21,3.19 -18.31,0.5 -15,0.5 -15,0.5 -13,0.5 -13,0.5 -12.17,0.5 -11.5,-0.17 -11.5,-1 -11.5,-7.34 -6.34,-12.5 0,-12.5 6.34,-12.5 11.5,-7.34 11.5,-1 11.5,-0.17 12.17,0.5 13,0.5 13,0.5 15,0.5 15,0.5 18.31,0.5 21,3.19 21,6.5 21,9.81 18.31,12.5 15,12.5 15,12.5 10.38,12.5 10.38,12.5 9.73,13.64 8.89,14.66 7.92,15.5 7.92,15.5 15,15.5 15,15.5 19.96,15.5 24,11.46 24,6.5 24,1.54 19.96,-2.5 15,-2.5 Z" style=""/>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 6.9 KiB

BIN
web/img/1.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

1
web/img/1.json Normal file

File diff suppressed because one or more lines are too long

6
web/img/1.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 30 KiB

BIN
web/img/2.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

BIN
web/img/IconKitchen.zip Normal file

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

BIN
web/img/badge.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

BIN
web/img/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

BIN
web/img/icon-192.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

BIN
web/img/icon-512.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

BIN
web/img/icons/info.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

1
web/img/icons/info.svg Normal file
View File

@@ -0,0 +1 @@
<svg fill="#1A1A1A" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" width="100px" height="100px"><path d="M 24 4 C 12.972066 4 4 12.972074 4 24 C 4 35.027926 12.972066 44 24 44 C 35.027934 44 44 35.027926 44 24 C 44 12.972074 35.027934 4 24 4 z M 24 7 C 33.406615 7 41 14.593391 41 24 C 41 33.406609 33.406615 41 24 41 C 14.593385 41 7 33.406609 7 24 C 7 14.593391 14.593385 7 24 7 z M 24 14 A 2 2 0 0 0 24 18 A 2 2 0 0 0 24 14 z M 23.976562 20.978516 A 1.50015 1.50015 0 0 0 22.5 22.5 L 22.5 33.5 A 1.50015 1.50015 0 1 0 25.5 33.5 L 25.5 22.5 A 1.50015 1.50015 0 0 0 23.976562 20.978516 z"/></svg>

After

Width:  |  Height:  |  Size: 606 B

BIN
web/img/map/1.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 110 KiB

3
web/img/sheep.svg Normal file
View File

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 30 30">
<path fill="#f2bd53" d="M 13 5 C 11.828237 5 10.793477 5.4713326 9.9394531 6.1503906 C 9.6361713 6.0843037 9.3462112 6 9 6 C 6.2504839 6 4 8.2504839 4 11 C 4 11.08059 4.0124881 11.1121 4.0175781 11.181641 C 2.9210459 12.039325 2.1098117 13.257229 2.0175781 14.726562 L 1.1054688 16.552734 A 1.0001165 1.0001165 0 0 0 2.7675781 17.640625 C 3.2526418 18.411838 3.9424236 19.03683 4.7597656 19.449219 L 5 19.675781 L 5 25 A 1.0001 1.0001 0 1 0 7 25 L 7 19.246094 A 1.0001 1.0001 0 0 0 6.6875 18.519531 L 6.046875 17.914062 A 1.0001 1.0001 0 0 0 5.7714844 17.728516 C 4.7470246 17.266071 4.0294077 16.259842 4.0039062 15.064453 A 1.0001 1.0001 0 0 0 4.0039062 14.939453 C 4.0261116 13.839378 4.6387768 12.906535 5.5429688 12.398438 A 1.0001 1.0001 0 0 0 6.0449219 11.394531 C 6.0183372 11.193412 6 11.068889 6 11 C 6 9.3315161 7.3315161 8 9 8 C 9.2766049 8 9.5584058 8.0531018 9.8652344 8.1464844 A 1.0001 1.0001 0 0 0 10.867188 7.8925781 C 11.414334 7.3394013 12.160136 7 13 7 C 13.943745 7 14.765257 7.4389147 15.322266 8.1289062 A 1.0001 1.0001 0 0 0 16.578125 8.3789062 C 17.019891 8.1381558 17.492971 8 18 8 C 19.125679 8 20.081212 8.6201548 20.599609 9.5390625 A 1.0001 1.0001 0 0 0 21.353516 10.039062 C 22.531272 10.179159 23.485346 10.980923 23.841797 12.068359 C 22.824708 11.859395 21.724766 12.146328 20.935547 12.935547 C 19.687547 14.183547 19.687547 16.207078 20.935547 17.455078 C 21.055851 17.575382 21.216262 17.686732 21.376953 17.796875 C 20.836332 18.524365 19.985264 19 19 19 C 18.492971 19 18.019891 18.861844 17.578125 18.621094 A 1.0001 1.0001 0 0 0 16.322266 18.871094 C 15.765471 19.56158 14.944275 20 14 20 C 13.291552 20 12.65587 19.746025 12.128906 19.320312 A 1.0001 1.0001 0 0 0 10.871094 19.320312 C 10.34413 19.746025 9.7084483 20 9 20 A 1.0001 1.0001 0 1 0 9 22 L 9 25 A 1.0001 1.0001 0 1 0 11 25 L 11 21.505859 C 11.170848 21.422563 11.337991 21.33349 11.5 21.236328 C 12.238737 21.67937 13.068046 22 14 22 C 14.730164 22 15.395058 21.803876 16 21.498047 L 16 25 A 1.0001 1.0001 0 1 0 18 25 L 18 20.806641 C 18.316031 20.913616 18.640829 21 19 21 C 19.343981 21 19.67546 20.953638 20 20.886719 L 20 25 A 1.0001 1.0001 0 1 0 22 25 L 22 20 A 1.0001 1.0001 0 0 0 22 19.974609 C 22.486448 19.603854 22.905789 19.152819 23.228516 18.630859 C 24.642494 19.048643 26.176371 19.140035 26.658203 18.658203 C 27.339744 17.976663 26.884402 15.198742 25.958984 13.625 A 1.0001 1.0001 0 0 0 25.966797 13.570312 C 25.987917 13.383949 26 13.19371 26 13 C 26 10.589178 24.248068 8.6335533 21.970703 8.171875 C 21.067822 6.9086944 19.672766 6 18 6 C 17.386577 6 16.866243 6.2341923 16.335938 6.4355469 C 15.448182 5.6006824 14.319642 5 13 5 z M 17.421875 12.279297 C 17.32925 12.296094 17.237609 12.342875 17.162109 12.421875 C 16.614109 12.997875 16 13.891 16 15 C 16 15.567 16.471922 16.023047 17.044922 15.998047 C 17.590922 15.974047 18 15.481547 18 14.935547 L 18 12.763672 C 18 12.435922 17.69975 12.228906 17.421875 12.279297 z M 23 14 C 23.552 14 24 14.448 24 15 C 24 15.552 23.552 16 23 16 C 22.448 16 22 15.552 22 15 C 22 14.448 22.448 14 23 14 z"/>
</svg>

After

Width:  |  Height:  |  Size: 3.1 KiB

223
web/index.html Normal file
View File

@@ -0,0 +1,223 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<!-- <meta name="viewport" content="width=device-width, initial-scale=1.0" /> -->
<meta
name="viewport"
content="user-scalable=no, initial-scale=1.0, maximum-scale=1.0"
/>
<!-- Start Single Page Apps for GitHub Pages -->
<script>
(function(){
var redirect = sessionStorage.redirect;
delete sessionStorage.redirect;
if (redirect && redirect != location.href) {
history.replaceState(null, null, redirect);
}
})();
</script>
<!-- End Single Page Apps for GitHub Pages -->
<title>Sheep Service</title>
<link rel="icon" href="/img/favicon.ico" sizes="any" />
<link rel="apple-touch-icon" href="/img/apple-touch-icon.png" />
<link rel="manifest" href="/manifest.json" />
<meta
name="theme-color"
content="#e5e5df"
media="(prefers-color-scheme: light)"
/>
<meta
name="theme-color"
content="#252523"
media="(prefers-color-scheme: dark)"
/>
<!-- Конфигурация SW -->
<script src="/sw.js"></script>
<link rel="stylesheet" href="/css/main.css" />
<script src="/config.js" defer></script>
<script src="/lib/router/router.js" defer></script>
<script src="/lib/router/routes.js" defer></script>
<!-- Подключение стилей Leaflet.js -->
<link rel="stylesheet" href="/lib/components/leaflet/leaflet.css" />
<script src="/lib/components/leaflet/leaflet.js"></script>
<link rel="stylesheet" href="/lib/components/geoman/leaflet-geoman.css" />
<script src="/lib/components/geoman/leaflet-geoman.min.js"></script>
<script src="/lib/components/turf.min.js" defer></script>
<script src="/lib/components/clipboard.js" defer></script>
<script src="/lib/components/colorGroup.js" defer></script>
<script src="/lib/components/makeid.js" defer></script>
<script src="/lib/components/swipeUpdater.js" defer></script>
<script src="/lib/components/detectBrowser.js" defer></script>
<script src="/lib/components/detectOS.js" defer></script>
<script src="/lib/components/formattedDate.js" defer></script>
<script src="/lib/pages/home/script.js" defer></script>
<link href="/lib/pages/home/style.css" rel="stylesheet" />
<script src="/lib/pages/territory/script.js" defer></script>
<link href="/lib/pages/territory/style.css" rel="stylesheet" />
<script src="/lib/pages/territory_manager/script.js" defer></script>
<link href="/lib/pages/territory_manager/style.css" rel="stylesheet" />
<script src="/lib/pages/card/script.js" defer></script>
<link href="/lib/pages/card/style.css" rel="stylesheet" />
<script src="/lib/pages/options/script.js" defer></script>
<link href="/lib/pages/options/style.css" rel="stylesheet" />
<script src="/lib/pages/sheeps/script.js" defer></script>
<link href="/lib/pages/sheeps/style.css" rel="stylesheet" />
<script src="/lib/pages/constructor/script.js" defer></script>
<link href="/lib/pages/constructor/style.css" rel="stylesheet" />
<script src="/lib/pages/editor/script.js" defer></script>
<link href="/lib/pages/editor/style.css" rel="stylesheet" />
<script src="/lib/pages/stand/script.js" defer></script>
<link href="/lib/pages/stand/style.css" rel="stylesheet" />
<script src="/lib/app.js" defer></script>
</head>
<body>
<!-- Кнопка сповіщення та оновлення додатків -->
<div id="update_banner" data-state="noupdate">
<div class="content">
<div class="headline"></div>
<div class="subhead"></div>
<div id="update_banner_icon">
<svg
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
version="1.0"
width="64px"
height="64px"
viewBox="0 0 128 128"
xml:space="preserve"
>
<g>
<path
d="M64 9.75A54.25 54.25 0 0 0 9.75 64H0a64 64 0 0 1 128 0h-9.75A54.25 54.25 0 0 0 64 9.75z"
></path>
<animateTransform
attributeName="transform"
type="rotate"
from="0 64 64"
to="360 64 64"
dur="1400ms"
repeatCount="indefinite"
></animateTransform>
</g>
</svg>
</div>
</div>
</div>
<!-- Анімація оновлення сторінки свайпом -->
<div id="swipe_updater">
<div id="swipe_block">
<svg
id="swipe_icon"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 448 512"
data-state="active"
>
<path
d="M413.1 222.5l22.2 22.2c9.4 9.4 9.4 24.6 0 33.9L241 473c-9.4 9.4-24.6 9.4-33.9 0L12.7 278.6c-9.4-9.4-9.4-24.6 0-33.9l22.2-22.2c9.5-9.5 25-9.3 34.3.4L184 343.4V56c0-13.3 10.7-24 24-24h32c13.3 0 24 10.7 24 24v287.4l114.8-120.5c9.3-9.8 24.8-10 34.3-.4z"
></path>
</svg>
</div>
</div>
<!-- Меню застосунку -->
<div id="navigation">
<nav>
<li>
<div id="nav-home">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<path
d="M9 2H4C2.897 2 2 2.897 2 4v7c0 1.103.897 2 2 2h5c1.103 0 2-.897 2-2V4C11 2.897 10.103 2 9 2zM20 2h-5c-1.103 0-2 .897-2 2v3c0 1.103.897 2 2 2h5c1.103 0 2-.897 2-2V4C22 2.897 21.103 2 20 2zM9 15H4c-1.103 0-2 .897-2 2v3c0 1.103.897 2 2 2h5c1.103 0 2-.897 2-2v-3C11 15.897 10.103 15 9 15zM20 11h-5c-1.103 0-2 .897-2 2v7c0 1.103.897 2 2 2h5c1.103 0 2-.897 2-2v-7C22 11.897 21.103 11 20 11z"
/>
</svg>
<b>Головна</b>
</div>
<a href="/" data-route></a>
</li>
<li id="li-territory" style="display: none;">
<div id="nav-territory">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 30 30">
<path
d="M24 2H14c-.55 0-1 .45-1 1v4l3.6 2.7c.25.19.4.49.4.8V14h8V3C25 2.45 24.55 2 24 2zM15.5 7C15.22 7 15 6.78 15 6.5v-2C15 4.22 15.22 4 15.5 4h2C17.78 4 18 4.22 18 4.5v2C18 6.78 17.78 7 17.5 7h-1.17H15.5zM23 4.5v2C23 6.78 22.78 7 22.5 7h-2C20.22 7 20 6.78 20 6.5v-2C20 4.22 20.22 4 20.5 4h2C22.78 4 23 4.22 23 4.5zM22.5 12h-2c-.28 0-.5-.22-.5-.5v-2C20 9.22 20.22 9 20.5 9h2C22.78 9 23 9.22 23 9.5v2C23 11.78 22.78 12 22.5 12zM1 11.51V27c0 .55.45 1 1 1h12c.55 0 1-.45 1-1V11.51c0-.32-.16-.62-.42-.81l-6-4.28C8.41 6.29 8.2 6.23 8 6.23S7.59 6.29 7.42 6.42l-6 4.28C1.16 10.89 1 11.19 1 11.51zM6.5 20h-2C4.22 20 4 19.78 4 19.5v-2C4 17.22 4.22 17 4.5 17h2C6.78 17 7 17.22 7 17.5v2C7 19.78 6.78 20 6.5 20zM7 22.5v2C7 24.78 6.78 25 6.5 25h-2C4.22 25 4 24.78 4 24.5v-2C4 22.22 4.22 22 4.5 22h2C6.78 22 7 22.22 7 22.5zM6.5 15h-2C4.22 15 4 14.78 4 14.5v-2C4 12.22 4.22 12 4.5 12h2C6.78 12 7 12.22 7 12.5v2C7 14.78 6.78 15 6.5 15zM9.5 17h2c.28 0 .5.22.5.5v2c0 .28-.22.5-.5.5h-2C9.22 20 9 19.78 9 19.5v-2C9 17.22 9.22 17 9.5 17zM9 14.5v-2C9 12.22 9.22 12 9.5 12h2c.28 0 .5.22.5.5v2c0 .28-.22.5-.5.5h-2C9.22 15 9 14.78 9 14.5zM9.5 22h2c.28 0 .5.22.5.5v2c0 .28-.22.5-.5.5h-2C9.22 25 9 24.78 9 24.5v-2C9 22.22 9.22 22 9.5 22zM17 17v10c0 .55.45 1 1 1h10c.55 0 1-.45 1-1V17c0-.55-.45-1-1-1H18C17.45 16 17 16.45 17 17zM19.5 18h2c.28 0 .5.22.5.5v2c0 .28-.22.5-.5.5h-2c-.28 0-.5-.22-.5-.5v-2C19 18.22 19.22 18 19.5 18zM27 18.5v2c0 .28-.22.5-.5.5h-2c-.28 0-.5-.22-.5-.5v-2c0-.28.22-.5.5-.5h2C26.78 18 27 18.22 27 18.5zM26.5 26h-2c-.28 0-.5-.22-.5-.5v-2c0-.28.22-.5.5-.5h2c.28 0 .5.22.5.5v2C27 25.78 26.78 26 26.5 26zM19.5 23h2c.28 0 .5.22.5.5v2c0 .28-.22.5-.5.5h-2c-.28 0-.5-.22-.5-.5v-2C19 23.22 19.22 23 19.5 23z"
/>
</svg>
<b>Території</b>
</div>
<a href="/territory" data-route></a>
</li>
<li id="li-sheeps" style="display: none;">
<div id="nav-sheeps">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
<path
d="M 42.5 14 C 37.813 14 34 18.038 34 23 C 34 27.962 37.813 32 42.5 32 C 47.187 32 51 27.962 51 23 C 51 18.038 47.187 14 42.5 14 z M 21.5 17 C 16.813 17 13 21.038 13 26 C 13 30.962 16.813 35 21.5 35 C 26.187 35 30 30.962 30 26 C 30 21.038 26.187 17 21.5 17 z M 42.5 18 C 44.981 18 47 20.243 47 23 C 47 25.757 44.981 28 42.5 28 C 40.019 28 38 25.757 38 23 C 38 20.243 40.019 18 42.5 18 z M 42.498047 34.136719 C 37.579021 34.136719 33.07724 35.947963 30.054688 38.962891 C 27.67058 37.796576 24.915421 37.136719 22 37.136719 C 14.956 37.136719 8.8129375 40.942422 6.7109375 46.607422 C 5.7409375 49.220422 7.7121406 52 10.494141 52 L 33.505859 52 C 35.43112 52 36.95694 50.674804 37.404297 49 L 53.431641 49 C 56.437641 49 59.121453 45.844281 57.564453 42.613281 C 55.084453 37.463281 49.169047 34.136719 42.498047 34.136719 z M 42.5 38.136719 C 47.565 38.136719 52.171937 40.633609 53.960938 44.349609 C 54.119938 44.687609 53.741687 45 53.429688 45 L 36.544922 45 C 35.777257 43.585465 34.746773 42.317451 33.503906 41.234375 C 35.78496 39.306575 39.034912 38.136719 42.5 38.136719 z"
/>
</svg>
<b>Вісники</b>
</div>
<a href="/sheeps" data-route></a>
</li>
<li id="li-schedule" style="display: none;">
<div id="nav-schedule">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
<path
d="M47 23c3.314 0 6 2.686 6 6v17c0 3.309-2.691 6-6 6H17c-3.309 0-6-2.691-6-6V29c0-3.314 2.686-6 6-6H47zM22 45v-2c0-.552-.448-1-1-1h-2c-.552 0-1 .448-1 1v2c0 .552.448 1 1 1h2C21.552 46 22 45.552 22 45zM22 38v-2c0-.552-.448-1-1-1h-2c-.552 0-1 .448-1 1v2c0 .552.448 1 1 1h2C21.552 39 22 38.552 22 38zM30 45v-2c0-.552-.448-1-1-1h-2c-.552 0-1 .448-1 1v2c0 .552.448 1 1 1h2C29.552 46 30 45.552 30 45zM30 38v-2c0-.552-.448-1-1-1h-2c-.552 0-1 .448-1 1v2c0 .552.448 1 1 1h2C29.552 39 30 38.552 30 38zM30 31v-2c0-.552-.448-1-1-1h-2c-.552 0-1 .448-1 1v2c0 .552.448 1 1 1h2C29.552 32 30 31.552 30 31zM38 45v-2c0-.552-.448-1-1-1h-2c-.552 0-1 .448-1 1v2c0 .552.448 1 1 1h2C37.552 46 38 45.552 38 45zM38 38v-2c0-.552-.448-1-1-1h-2c-.552 0-1 .448-1 1v2c0 .552.448 1 1 1h2C37.552 39 38 38.552 38 38zM38 31v-2c0-.552-.448-1-1-1h-2c-.552 0-1 .448-1 1v2c0 .552.448 1 1 1h2C37.552 32 38 31.552 38 31zM46 38v-2c0-.552-.448-1-1-1h-2c-.552 0-1 .448-1 1v2c0 .552.448 1 1 1h2C45.552 39 46 38.552 46 38zM46 31v-2c0-.552-.448-1-1-1h-2c-.552 0-1 .448-1 1v2c0 .552.448 1 1 1h2C45.552 32 46 31.552 46 31zM17 20c-2.308 0-4.407.876-6 2.305V18c0-3.309 2.691-6 6-6h30c3.309 0 6 2.691 6 6v4.305C51.407 20.876 49.308 20 47 20H17z"
/>
</svg>
<b>Графік зібрань</b>
</div>
<a href="/schedule" data-route></a>
</li>
<li id="li-stand" style="display: none;">
<div id="nav-stand">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 30 30">
<path
d="M 6.9707031 4 C 6.8307031 4 6.6807813 4.039375 6.5507812 4.109375 L 2.5507812 6.109375 C 2.0607813 6.349375 1.859375 6.9492188 2.109375 7.4492188 C 2.349375 7.9392188 2.9492187 8.140625 3.4492188 7.890625 L 6.4902344 6.3691406 L 12.5 20.650391 C 12.73 21.180391 13.040156 21.650547 13.410156 22.060547 C 12.040156 22.340547 11 23.56 11 25 C 11 26.65 12.35 28 14 28 C 15.65 28 17 26.65 17 25 C 17 24.52 16.869922 24.070156 16.669922 23.660156 C 17.479922 23.740156 18.319141 23.639062 19.119141 23.289062 L 26.400391 20.099609 C 26.910391 19.889609 27.159219 19.310781 26.949219 18.800781 C 26.749219 18.290781 26.160391 18.040234 25.650391 18.240234 C 25.630391 18.250234 25.619609 18.259531 25.599609 18.269531 L 18.320312 21.460938 C 16.770312 22.130938 14.999609 21.429141 14.349609 19.869141 L 7.9199219 4.609375 C 7.7599219 4.229375 7.3807031 3.99 6.9707031 4 z M 21.359375 8.0605469 C 21.229375 8.0605469 21.100703 8.090625 20.970703 8.140625 L 13.609375 11.269531 C 13.099375 11.479531 12.860078 12.070078 13.080078 12.580078 L 16.029297 19.179688 C 16.249297 19.689688 16.829844 19.930937 17.339844 19.710938 L 24.710938 16.589844 C 25.210938 16.369844 25.450234 15.789297 25.240234 15.279297 L 22.279297 8.6699219 C 22.119297 8.2899219 21.749375 8.0605469 21.359375 8.0605469 z M 14 24 C 14.56 24 15 24.44 15 25 C 15 25.56 14.56 26 14 26 C 13.44 26 13 25.56 13 25 C 13 24.44 13.44 24 14 24 z"
/>
</svg>
<b>Графік стенду</b>
</div>
<a href="/stand" data-route></a>
</li>
<li>
<div id="nav-options">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 172 172">
<path
d="M75.18001,14.33333c-3.43283,0 -6.36736,2.42659 -7.02669,5.79492l-2.39355,12.28971c-5.8821,2.22427 -11.32102,5.33176 -16.097,9.25228l-11.78581,-4.05924c-3.2465,-1.118 -6.81841,0.22441 -8.53841,3.19141l-10.80599,18.72852c-1.71283,2.97417 -1.08945,6.74999 1.49772,9.00033l9.44824,8.21647c-0.49137,3.0197 -0.81185,6.09382 -0.81185,9.25228c0,3.15846 0.32048,6.23258 0.81185,9.25228l-9.44824,8.21647c-2.58717,2.25033 -3.21055,6.02616 -1.49772,9.00032l10.80599,18.72852c1.71283,2.97417 5.29191,4.31623 8.53841,3.2054l11.78581,-4.05924c4.77441,3.91806 10.21756,7.01501 16.097,9.23828l2.39355,12.28972c0.65933,3.36833 3.59386,5.79492 7.02669,5.79492h21.63998c3.43283,0 6.36735,-2.42659 7.02669,-5.79492l2.39356,-12.28972c5.88211,-2.22427 11.32102,-5.33176 16.097,-9.25227l11.78581,4.05924c3.2465,1.118 6.81841,-0.21724 8.53841,-3.1914l10.80599,-18.74252c1.71284,-2.97417 1.08945,-6.73599 -1.49772,-8.98633l-9.44824,-8.21647c0.49137,-3.0197 0.81185,-6.09382 0.81185,-9.25228c0,-3.15846 -0.32048,-6.23258 -0.81185,-9.25228l9.44824,-8.21647c2.58717,-2.25033 3.21056,-6.02616 1.49772,-9.00033l-10.80599,-18.72852c-1.71283,-2.97417 -5.29191,-4.31624 -8.53841,-3.2054l-11.78581,4.05924c-4.7744,-3.91806 -10.21755,-7.01501 -16.097,-9.23828l-2.39356,-12.28971c-0.65933,-3.36833 -3.59385,-5.79492 -7.02669,-5.79492zM86,57.33333c15.83117,0 28.66667,12.8355 28.66667,28.66667c0,15.83117 -12.8355,28.66667 -28.66667,28.66667c-15.83117,0 -28.66667,-12.8355 -28.66667,-28.66667c0,-15.83117 12.8355,-28.66667 28.66667,-28.66667z"
></path>
</svg>
<b>Опції</b>
</div>
<a href="/options" data-route></a>
</li>
</nav>
</div>
<!-- Блок контенту застосунка -->
<div id="app"></div>
</body>
</html>

303
web/js/app.js Normal file
View File

@@ -0,0 +1,303 @@
let socket, username;
let listEntrances = []
let listApartment = []
let statusD = document.getElementById("status");
let hashD = document.getElementById("hash");
const urlParams = new URLSearchParams(window.location.search);
const share_hash = urlParams.get('share_hash');
let house = urlParams.get('id') ?? 81;
let entrance = 1;
let group_number = 1;
const api = "https://sheep-service.com";
const wss = "wss://sheep-service.com/ws";
// let color_status = [
// "#000000",
// "#C16917",
// "#b10202",
// "#3d3d3d",
// "#11734b",
// "#6cc5fc",
// "#5a3286"
// ];
// let color_status = [
// ["#ffffff", "#000000"],
// ["#e7af32", "#ffffff"],
// ["#fc2a2a", "#ffffff"],
// ["#3d3d3d", "#ffffff"],
// ["#11a568", "#ffffff"],
// ["#6cc5fc", "#ffffff"],
// ["#b381eb", "#ffffff"]
// ];
let color_status = [
["#ffffff", "#eaebef"],
["#fbf1e0", "#ff8300"],
["#fce3e2", "#ff0000"],
["#e2e0e0", "#3d3d3d"],
["#d5e9dd", "#11a568"],
["#d7ebfa", "#3fb4fc"],
["#e8dbf5", "#b381eb"]
];
function makeid(length) {
let result = '';
const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
const charactersLength = characters.length;
let counter = 0;
while (counter < length) {
result += characters.charAt(Math.floor(Math.random() * charactersLength));
counter += 1;
}
return result;
}
function start(name) {
if (!hash) return;
hashD.innerText = `HASH: ${name}`
username = name;
socket = new WebSocket(`${wss}?share_hash=${share_hash}`);
let user_hash = localStorage.getItem('hash') ?? share_hash;
socket.onopen = function (e) {
console.log("[WebSocket | open] Соединение установлено");
statusD.innerText = "WebSocket | open";
const message = {
event: 'connection',
id: getTimeInSeconds(),
date: getTimeInSeconds(),
hash: user_hash,
username: name,
data: {
id: 1,
entrance_id: 1,
apartment_number: "1",
group_number: 1,
status: 1,
description: "",
created_at: 1727541827,
updated_at: 1727541827
}
}
socket.send(JSON.stringify(message))
};
socket.onmessage = function (event) {
let data = JSON.parse(event.data)
if (data.event == 'connection') {
if (data.username == username) return
console.log(`Добавлен новый пользователь по имени ${data.username}`);
} else if (data.event == 'message') {
update(data);
if (data.username == username) return
console.log(`${data.username} пишет: `, data.data);
}
};
socket.onclose = function (event) {
if (event.wasClean) {
statusD.innerText = "WebSocket | close"
console.log(`[WebSocket | close] Соединение закрыто чисто, код=${event.code} причина=${event.reason}`);
} else {
statusD.innerText = "WebSocket | close"
console.log('[WebSocket | close] Соединение прервано');
// setTimeout(function() {
// start(username);
// }, 1000);
const result = confirm(`З'єднання розірвано! Перепідключитись?`);
if (result) {
getEntrances();
start(username);
}
}
};
socket.onerror = function (error) {
console.log(`[WebSocket | error]`);
statusD.innerText = "WebSocket | error"
};
}
function mess(entrance_number, id, date_type) {
console.log(id, listApartment[entrance_number]);
const pos = listApartment[entrance_number].map(e => e.id).indexOf(id);
let apartment = listApartment[entrance_number][pos];
console.log(pos, apartment);
let status = document.getElementById(`status_${id}`);
let description = document.getElementById(`description_${id}`);
let date = new Date(document.getElementById(`date_${id}`).value);
const timestamp = date.getTime();
apartment.description = description.value;
apartment.status = Number(status.value);
apartment.updated_at = date_type ? getTimeInSeconds(timestamp) : getTimeInSeconds(),
status.style.backgroundColor = color_status[status.value][0];
status.style.color = color_status[status.value][1];
status.style.border = `1px solid ${color_status[status.value][1]}`;
let user_hash = localStorage.getItem('hash') ?? share_hash;
let message = {
event: 'message',
id: getTimeInSeconds(),
date: getTimeInSeconds(),
hash: user_hash,
username: username,
data: {
id: apartment.id,
entrance_id: apartment.entrance_id,
apartment_number: apartment.apartment_number,
group_number: apartment.group_number,
status: apartment.status,
description: apartment.description,
updated_at: apartment.updated_at,
}
}
socket.send(JSON.stringify(message));
sort(apartment.id, apartment.entrance_id);
}
function update(message) {
if (!document.getElementById(`status_${message.data.id}`)) return;
let now = new Date(message.data.updated_at);
now.setMinutes(now.getMinutes() - now.getTimezoneOffset());
document.getElementById(`status_${message.data.id}`).style.backgroundColor = color_status[message.data.status][0];
document.getElementById(`status_${message.data.id}`).style.color = color_status[message.data.status][1];
document.getElementById(`status_${message.data.id}`).style.border = `1px solid ${color_status[message.data.status][1]}`;
document.getElementById(`status_${message.data.id}`).value = message.data.status;
document.getElementById(`description_${message.data.id}`).value = message.data.description;
document.getElementById(`date_${message.data.id}`).value = now.toISOString().slice(0, 16);
}
function getEntrances(house_id = house) {
let url = `${api}/api/house/${house_id}/entrances?share_hash=${share_hash}`;
fetch(url)
.then(function (response) {
return response.json();
})
.then(function (data) {
listEntrances = data;
document.getElementById('list').innerHTML = "";
for (let i = 0; i < listEntrances.length; i++) {
const element = listEntrances[i];
let status = () => {
if (element.history.name == "Групова") return '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M 12 1 C 9.1277778 1 6.7189086 3.0461453 6.1230469 5.7871094 L 8.078125 6.2128906 C 8.4822632 4.3538547 10.072222 3 12 3 C 14.27619 3 16 4.7238095 16 7 L 16 8 L 6 8 C 4.9069372 8 4 8.9069372 4 10 L 4 20 C 4 21.093063 4.9069372 22 6 22 L 18 22 C 19.093063 22 20 21.093063 20 20 L 20 10 C 20 8.9069372 19.093063 8 18 8 L 18 7 C 18 3.6761905 15.32381 1 12 1 z M 6 10 L 18 10 L 18 20 L 6 20 L 6 10 z M 12 13 C 10.9 13 10 13.9 10 15 C 10 16.1 10.9 17 12 17 C 13.1 17 14 16.1 14 15 C 14 13.9 13.1 13 12 13 z"/></svg>'
else return '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"> <path d="M 12 1 C 8.6761905 1 6 3.6761905 6 7 L 6 8 C 4.9069372 8 4 8.9069372 4 10 L 4 20 C 4 21.093063 4.9069372 22 6 22 L 18 22 C 19.093063 22 20 21.093063 20 20 L 20 10 C 20 8.9069372 19.093063 8 18 8 L 18 7 C 18 3.6761905 15.32381 1 12 1 z M 12 3 C 14.27619 3 16 4.7238095 16 7 L 16 8 L 8 8 L 8 7 C 8 4.7238095 9.7238095 3 12 3 z M 6 10 L 18 10 L 18 20 L 6 20 L 6 10 z M 12 13 C 10.9 13 10 13.9 10 15 C 10 16.1 10.9 17 12 17 C 13.1 17 14 16.1 14 15 C 14 13.9 13.1 13 12 13 z"/></svg>'
}
document.getElementById('list').innerHTML += `
<details ${element.history.name == "Групова" ? "open" : "disabled"}>
<summary>
<p>${element.title}</p>
${status()}
</summary>
<div id="apartments_${element.id}" class="apartments_list">
</div>
</details>
`;
getApartment(element.id, element.entrance_number);
console.log(element);
}
})
}
function getApartment(entrance_id, entrance_number) {
let url = `${api}/api/apartment/${entrance_id}?share_hash=${share_hash}`;
fetch(url)
.then(function (response) {
return response.json();
})
.then(function (data) {
listApartment[entrance_number] = data;
data.sort((a, b) => a.apartment_number - b.apartment_number);
data.sort((a, b) => a.updated_at - b.updated_at);
for (let i = 0; i < data.length; i++) {
const element = data[i];
let now = new Date(element.updated_at);
now.setMinutes(now.getMinutes() - now.getTimezoneOffset());
now = now.toISOString().slice(0, 16)
document.getElementById(`apartments_${entrance_id}`).innerHTML += `
<div id="card_${element.id}">
<span>кв.${element.title}</span>
<select id="status_${element.id}" onchange="mess(${entrance_number}, ${element.id})" style="background-color: ${color_status[element.status][0]}; color: ${color_status[element.status][1]}; border: 1px solid ${color_status[element.status][1]};" ${element.status == 2 ? "disabled" : ""}>
<option value="0" ${element.status == 0 ? "selected" : ""}></option>
<option value="1" ${element.status == 1 ? "selected" : ""}>Відмова</option>
<option value="2" ${element.status == 2 ? "selected" : ""}>Не заходити (Груба відмова)</option>
<option value="3" ${element.status == 3 ? "selected" : ""}>Нема домофона</option>
<option value="4" ${element.status == 4 ? "selected" : ""}>Повторна відвідина</option>
<option value="5" ${element.status == 5 ? "selected" : ""}>Немає вдома</option>
<option value="6" ${element.status == 6 ? "selected" : ""}>Свідки Єгови</option>
</select>
<input onchange="mess(${entrance_number}, ${element.id})" type="text" name="description" id="description_${element.id}" placeholder="Нотатки..." value="${element.description ?? ""}" ${element.status == 2 ? "disabled" : ""}>
<input onchange="mess(${entrance_number}, ${element.id}, true)" type="datetime-local" name="date" id="date_${element.id}" placeholder="Дата" value="${element.updated_at ? now : ""}" ${element.status == 2 ? "disabled" : ""} style="max-width: 170px;">
</div>
`;
}
})
}
function sort(id, entrance_id) {
let child = document.getElementById(`card_${id}`);
document.getElementById(`apartments_${entrance_id}`).removeChild(child);
document.getElementById(`apartments_${entrance_id}`).append(child);
child.style.border = "1px solid var(--PrimaryColor)";
}
function getTimeInSeconds(time = Date.now()) {
// Если время больше 10 знаков (это значит, что время в миллисекундах)
if (time.toString().length < 10) {
// Округляем до секунд, убирая последние 3 цифры (миллисекунды)
time = Math.floor(time * 1000);
}
return time;
}
getEntrances();
start(makeid(6));

116
web/lib/app.js Normal file
View File

@@ -0,0 +1,116 @@
let USER = {};
// Определение ID главного блока
let app = document.getElementById('app');
// Конфигурация роутера
Router.config({ mode: 'history' });
async function appReload() {
location.reload();
// Router.navigate(window.location.pathname, false).check();
// // Закрытие старого соединения WebSocket
// if (socket) socket.close(1000, "Перезапуск соединения");
// listEntrances = []
// listApartment = []
}
const Rotation = async () => {
const result = confirm(`Ви бажаєте провести ротацію територій між групами?`);
if (result) {
let rotationButton = document.getElementById('rotationButton-title');
rotationButton.innerText = "Зачекайте";
let uuid = localStorage.getItem("uuid");
const URL = `${CONFIG.api}rotation`;
await fetch(URL, {
method: 'GET',
headers: {
"Content-Type": "application/json, text/plain, */*",
"Authorization": uuid
}
})
.then(response => response.json())
.then(data => {
console.log(data);
rotationButton.innerText = 'Ротація завершилась успішно!';
Territory.house.setHTML();
Territory.homestead.setHTML();
setTimeout(() => {
rotationButton.innerText = "Провести ротацію";
}, 3000);
})
.catch(err => {
console.log(err);
rotationButton.innerText = "Помилка ротації!";
})
}
}
// Функция загрузки приложения
window.addEventListener('load', async function () {
console.log('[OS] ', detectOS());
if (window.matchMedia('(display-mode: standalone)').matches) {
if (detectOS() == 'Android') {
document.getElementById('navigation').dataset.state = '';
} else if (detectOS() == 'iOS') {
document.getElementById('navigation').dataset.state = 'ios';
localStorage.setItem('backToTop', 'false');
} else if (detectOS() == 'MacOS') {
document.getElementById('navigation').dataset.state = 'ios';
localStorage.setItem('backToTop', 'false');
} else {
document.getElementById('navigation').dataset.state = '';
}
}
let userInput = () => {
let h = prompt("Введіть ваше посилання з UUID:");
if (h) {
h = h.replace("https://sheep-service.com/?uuid=", "");
h = h.replace("https://sheep-service.com?uuid=", "");
h = h.replace("https://sheep-service.com?/hash=", "");
h = h.replace("https://sheep-service.com?hash=", "");
localStorage.setItem("uuid", h)
return h;
}
};
const urlParams = new URLSearchParams(window.location.search);
if (urlParams.get('uuid')) {
localStorage.setItem("uuid", urlParams.get('uuid'))
}
if (urlParams.get('hash')) {
localStorage.setItem("uuid", urlParams.get('hash'))
}
let uuid = localStorage.getItem("uuid") ? localStorage.getItem("uuid") : userInput();
if (uuid) {
const URL = `${CONFIG.api}auth`;
USER = await fetch(URL, {
method: 'GET',
headers: {
"Content-Type": "application/json, text/plain, */*",
"Authorization": uuid
}
}).then((response) => response.json());
console.log("USER Info: ", USER);
if (USER.administrator.uuid || USER.moderator.uuid) document.getElementById("li-sheeps").style.display = "";
if (USER.administrator.uuid || (USER.moderator.uuid && USER.moderator.can_add_schedule)) document.getElementById("li-schedule").style.display = "";
if (USER.administrator.uuid || (USER.moderator.uuid && USER.moderator.can_manager_territory)) document.getElementById("li-territory").style.display = "";
if (USER.administrator.uuid || USER.can_view_stand) document.getElementById("li-stand").style.display = "";
}
Router.check().listen();
});

View File

@@ -0,0 +1,5 @@
clipboard = (text) => {
navigator.clipboard.writeText(text)
.then(() => console.log("Текст скопійовано!"))
.catch(err => console.error(err))
}

View File

@@ -0,0 +1,20 @@
let colorGroup = (number) => {
switch (number) {
case 1:
return "#c1ae4d"
case 2:
return "#93c14d"
case 3:
return "#4dc1a7"
case 4:
return "#4d90c1"
case 5:
return "#654dc1"
case 6:
return "#c14db7"
case 7:
return "#c1734d"
default:
return "#C14D4D"
}
}

View File

@@ -0,0 +1,35 @@
detectBrowser = () => {
const userAgent = navigator.userAgent;
let browser = "unkown";
// Detect browser name
browser = (/ucbrowser/i).test(userAgent) ? 'UCBrowser' : browser;
browser = (/edg/i).test(userAgent) ? 'Edge' : browser;
browser = (/googlebot/i).test(userAgent) ? 'GoogleBot' : browser;
browser = (/chromium/i).test(userAgent) ? 'Chromium' : browser;
browser = (/firefox|fxios/i).test(userAgent) && !(/seamonkey/i).test(userAgent) ? 'Firefox' : browser;
browser = (/; msie|trident/i).test(userAgent) && !(/ucbrowser/i).test(userAgent) ? 'IE' : browser;
browser = (/chrome|crios/i).test(userAgent) && !(/opr|opera|chromium|edg|ucbrowser|googlebot/i).test(userAgent) ? 'Chrome' : browser;;
browser = (/safari/i).test(userAgent) && !(/chromium|edg|ucbrowser|chrome|crios|opr|opera|fxios|firefox/i).test(userAgent) ? 'Safari' : browser;
browser = (/opr|opera/i).test(userAgent) ? 'Opera' : browser;
// detect browser version
switch (browser) {
case 'UCBrowser': return `${browser}/${browserVersion(userAgent,/(ucbrowser)\/([\d\.]+)/i)}`;
case 'Edge': return `${browser}/${browserVersion(userAgent,/(edge|edga|edgios|edg)\/([\d\.]+)/i)}`;
case 'GoogleBot': return `${browser}/${browserVersion(userAgent,/(googlebot)\/([\d\.]+)/i)}`;
case 'Chromium': return `${browser}/${browserVersion(userAgent,/(chromium)\/([\d\.]+)/i)}`;
case 'Firefox': return `${browser}/${browserVersion(userAgent,/(firefox|fxios)\/([\d\.]+)/i)}`;
case 'Chrome': return `${browser}/${browserVersion(userAgent,/(chrome|crios)\/([\d\.]+)/i)}`;
case 'Safari': return `${browser}/${browserVersion(userAgent,/(safari)\/([\d\.]+)/i)}`;
case 'Opera': return `${browser}/${browserVersion(userAgent,/(opera|opr)\/([\d\.]+)/i)}`;
case 'IE': const version = browserVersion(userAgent,/(trident)\/([\d\.]+)/i);
// IE version is mapped using trident version
// IE/8.0 = Trident/4.0, IE/9.0 = Trident/5.0
return version ? `${browser}/${parseFloat(version) + 4.0}` : `${browser}/7.0`;
default: return `unknown/0.0.0.0`;
}
}
browserVersion = (userAgent,regex) => {
return userAgent.match(regex) ? userAgent.match(regex)[2] : null;
}

View File

@@ -0,0 +1,12 @@
function detectOS() {
const platform = navigator.platform.toLowerCase(),
iosPlatforms = ['iphone', 'ipad', 'ipod', 'ipod touch'];
if (platform.includes('mac')) return 'MacOS';
if (iosPlatforms.includes(platform)) return 'iOS';
if (platform.includes('win')) return 'Windows';
if (/android/.test(navigator.userAgent.toLowerCase())) return 'Android';
if (/linux/.test(platform)) return 'Linux';
return 'unknown';
}

View File

@@ -0,0 +1,9 @@
let formattedDate = (unix_timestamp) => {
if(!unix_timestamp) return
let date = new Date(unix_timestamp);
let year = date.getFullYear() >= 10 ? date.getFullYear() : "0" + date.getFullYear();
let month = (date.getMonth() + 1) >= 10 ? (date.getMonth() + 1) : "0" + (date.getMonth() + 1);
let weekday = date.getDate() >= 10 ? date.getDate() : "0" + date.getDate();
return weekday + '.' + month + '.' + year;
}

View File

@@ -0,0 +1,283 @@
.marker-icon,
.marker-icon:focus {
background-color: #ffffff;
border: 1px solid #3388ff;
border-radius: 50%;
margin: -4px 0 0 -4px !important;
width: 6px !important;
height: 6px !important;
outline: 0;
transition: opacity ease 0.3s;
}
.marker-icon-middle,
.marker-icon-middle:focus {
opacity: 0.7;
margin: -6px 0 0 -6px !important;
width: 10px !important;
height: 10px !important;
}
.leaflet-pm-draggable {
cursor: move !important;
}
.cursor-marker {
cursor: crosshair;
pointer-events: none;
opacity: 0;
}
.cursor-marker.visible {
opacity: 1 !important;
}
.leaflet-pm-invalid {
stroke: red;
transition: fill ease 0s, stroke ease 0s;
}
.rect-style-marker,
.rect-start-marker {
opacity: 0;
}
.rect-style-marker.visible,
.rect-start-marker.visible {
opacity: 1 !important;
}
.vertexmarker-disabled {
opacity: 0.7;
}
.pm-text-marker {
width: 0;
height: 0;
}
.pm-textarea {
background-color: #fff;
color: #000;
resize: none;
border: none;
outline: 0;
cursor: pointer;
border-radius: 3px;
padding-left: 7px;
padding-bottom: 0;
padding-top: 4px;
}
.leaflet-pm-draggable .pm-textarea {
cursor: move;
}
.pm-textarea:focus,
.pm-textarea:focus-within,
.pm-textarea:focus-visible,
.pm-textarea:active {
border: 2px solid #000;
outline: 0;
}
.pm-textarea.pm-disabled {
border: none;
user-select: none;
}
.pm-textarea.pm-hasfocus {
cursor: auto;
}
.leaflet-pm-toolbar {
}
.leaflet-pm-toolbar .leaflet-buttons-control-button {
padding: 5px;
box-sizing: border-box;
position: relative;
z-index: 3;
}
.leaflet-pm-toolbar
.leaflet-pm-actions-container
a.leaflet-pm-action:first-child:not(.pos-right),
.leaflet-pm-toolbar
.leaflet-pm-actions-container
a.leaflet-pm-action:last-child.pos-right {
border-radius: 0;
}
.leaflet-pm-toolbar .button-container a.leaflet-buttons-control-button {
border-radius: 0;
}
.leaflet-pm-toolbar
.button-container:last-child
a.leaflet-buttons-control-button {
border-radius: 0 0 2px 2px;
}
.leaflet-pm-toolbar
.button-container:first-child
a.leaflet-buttons-control-button {
border-radius: 2px 2px 0 0;
}
.leaflet-pm-toolbar
.button-container:last-child
a.leaflet-buttons-control-button {
border-bottom: none;
}
.leaflet-pm-toolbar .control-fa-icon {
font-size: 19px;
line-height: 24px;
}
.leaflet-pm-toolbar .control-icon {
width: 100%;
height: 100%;
box-sizing: border-box;
background-size: contain;
background-repeat: no-repeat;
background-position: center center;
}
.leaflet-pm-toolbar .leaflet-pm-icon-marker {
background-image: url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPHN2ZyB3aWR0aD0iMjRweCIgaGVpZ2h0PSIyNHB4IiB2aWV3Qm94PSIwIDAgMjQgMjQiIHZlcnNpb249IjEuMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayI+CiAgICA8IS0tIEdlbmVyYXRvcjogU2tldGNoIDUyLjUgKDY3NDY5KSAtIGh0dHA6Ly93d3cuYm9oZW1pYW5jb2RpbmcuY29tL3NrZXRjaCAtLT4KICAgIDx0aXRsZT5BdG9tcy9JY29ucy9Ub29scy9NYXJrZXI8L3RpdGxlPgogICAgPGRlc2M+Q3JlYXRlZCB3aXRoIFNrZXRjaC48L2Rlc2M+CiAgICA8ZGVmcz4KICAgICAgICA8cGF0aCBkPSJNMTUuNSwyNC44NzgyOTU5IEMxNS4yOTA5MjAxLDI0Ljg3NzIyMTkgMTUuMTc0NDg1NywyNC44NDY3ODE3IDE0LjY1OTA4NjYsMjQuMjM1NDE2MyBDMTAuMjE5Njk1NSwxOS40MTE4MDU0IDgsMTUuNTAxNDM5MiA4LDEyLjUwNDMxNzcgQzgsOC4zNTk3OTc0NiAxMS4zNTc4NjQ0LDUgMTUuNSw1IEMxOS42NDIxMzU2LDUgMjMsOC4zNTk3OTc0NiAyMywxMi41MDQzMTc3IEMyMywxNyAxOC4yODc4MjE3LDIxLjkyNjgzNzggMTYuMzMzNjYwMSwyNC4yNDQwMTg2IEMxNS44MjI0NjIyLDI0Ljg1MDE4MDIgMTUuNzA5MDc5OSwyNC44NzkzNjk5IDE1LjUsMjQuODc4Mjk1OSBaIE0xNS41LDE1LjUzMjY5NDggQzE3LjI3NTIwMSwxNS41MzI2OTQ4IDE4LjcxNDI4NTcsMTQuMTE4MDAwNCAxOC43MTQyODU3LDEyLjM3Mjg4NjQgQzE4LjcxNDI4NTcsMTAuNjI3NzcyMyAxNy4yNzUyMDEsOS4yMTMwNzc5MiAxNS41LDkuMjEzMDc3OTIgQzEzLjcyNDc5OSw5LjIxMzA3NzkyIDEyLjI4NTcxNDMsMTAuNjI3NzcyMyAxMi4yODU3MTQzLDEyLjM3Mjg4NjQgQzEyLjI4NTcxNDMsMTQuMTE4MDAwNCAxMy43MjQ3OTksMTUuNTMyNjk0OCAxNS41LDE1LjUzMjY5NDggWiIgaWQ9InBhdGgtMSI+PC9wYXRoPgogICAgPC9kZWZzPgogICAgPGcgaWQ9IlN5bWJvbHMiIHN0cm9rZT0ibm9uZSIgc3Ryb2tlLXdpZHRoPSIxIiBmaWxsPSJub25lIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiPgogICAgICAgIDxnIGlkPSJBdG9tcy9JY29ucy9Ub29scy9NYXJrZXIiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0zLjAwMDAwMCwgLTMuMDAwMDAwKSI+CiAgICAgICAgICAgIDxtYXNrIGlkPSJtYXNrLTIiIGZpbGw9IndoaXRlIj4KICAgICAgICAgICAgICAgIDx1c2UgeGxpbms6aHJlZj0iI3BhdGgtMSI+PC91c2U+CiAgICAgICAgICAgIDwvbWFzaz4KICAgICAgICAgICAgPHVzZSBpZD0iTWFzayIgZmlsbD0iIzVCNUI1QiIgZmlsbC1ydWxlPSJub256ZXJvIiB4bGluazpocmVmPSIjcGF0aC0xIj48L3VzZT4KICAgICAgICA8L2c+CiAgICA8L2c+Cjwvc3ZnPg==);
}
.leaflet-pm-toolbar .leaflet-pm-icon-polygon {
background-image: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCI+CiAgPGRlZnM+CiAgICA8cGF0aCBpZD0icG9seWdvbi1hIiBkPSJNMTkuNDIwNjg5Miw5LjE2NTA5NzI1IEMxOS4xNTIzNjgxLDguNjY5OTI5MTQgMTksOC4xMDI3NTgzMSAxOSw3LjUgQzE5LDUuNTY3MDAzMzggMjAuNTY3MDAzNCw0IDIyLjUsNCBDMjQuNDMyOTk2Niw0IDI2LDUuNTY3MDAzMzggMjYsNy41IEMyNiw5LjI2MzIzNTk1IDI0LjY5NjE0NzEsMTAuNzIxOTQwNyAyMywxMC45NjQ1NTU2IEwyMywxOS4wMzU0NDQ0IEMyNC42OTYxNDcxLDE5LjI3ODA1OTMgMjYsMjAuNzM2NzY0IDI2LDIyLjUgQzI2LDI0LjQzMjk5NjYgMjQuNDMyOTk2NiwyNiAyMi41LDI2IEMyMC43MzY3NjQsMjYgMTkuMjc4MDU5MywyNC42OTYxNDcxIDE5LjAzNTQ0NDQsMjMgTDEwLjk2NDU1NTYsMjMgQzEwLjcyMTk0MDcsMjQuNjk2MTQ3MSA5LjI2MzIzNTk1LDI2IDcuNSwyNiBDNS41NjcwMDMzOCwyNiA0LDI0LjQzMjk5NjYgNCwyMi41IEM0LDIwLjU2NzAwMzQgNS41NjcwMDMzOCwxOSA3LjUsMTkgQzguMTAyNzU4MzEsMTkgOC42Njk5MjkxNCwxOS4xNTIzNjgxIDkuMTY1MDk3MjUsMTkuNDIwNjg5MiBMMTkuNDIwNjg5Miw5LjE2NTA5NzI1IFogTTIwLjgzNDkwNzMsMTAuNTc5MzA2MyBMMTAuNTc5MzEwOCwyMC44MzQ5MDI3IEMxMC42MDg2NzMxLDIwLjg4OTA4ODggMTAuNjM2NjQ2OSwyMC45NDQxMzcyIDEwLjY2MzE4NDQsMjEgTDE5LjMzNjgxNTYsMjEgQzE5LjY4MjU3NzUsMjAuMjcyMTU0IDIwLjI3MjE1NCwxOS42ODI1Nzc1IDIxLDE5LjMzNjgxNTYgTDIxLDEwLjY2MzE4NDQgQzIwLjk0NDEzNzIsMTAuNjM2NjQ2OSAyMC44ODkwODg4LDEwLjYwODY3MzEgMjAuODM0OTAyNywxMC41NzkzMTA4IFogTTIyLjUsOSBDMjMuMzI4NDI3MSw5IDI0LDguMzI4NDI3MTIgMjQsNy41IEMyNCw2LjY3MTU3Mjg4IDIzLjMyODQyNzEsNiAyMi41LDYgQzIxLjY3MTU3MjksNiAyMSw2LjY3MTU3Mjg4IDIxLDcuNSBDMjEsOC4zMjg0MjcxMiAyMS42NzE1NzI5LDkgMjIuNSw5IFogTTIyLjUsMjQgQzIzLjMyODQyNzEsMjQgMjQsMjMuMzI4NDI3MSAyNCwyMi41IEMyNCwyMS42NzE1NzI5IDIzLjMyODQyNzEsMjEgMjIuNSwyMSBDMjEuNjcxNTcyOSwyMSAyMSwyMS42NzE1NzI5IDIxLDIyLjUgQzIxLDIzLjMyODQyNzEgMjEuNjcxNTcyOSwyNCAyMi41LDI0IFogTTcuNSwyNCBDOC4zMjg0MjcxMiwyNCA5LDIzLjMyODQyNzEgOSwyMi41IEM5LDIxLjY3MTU3MjkgOC4zMjg0MjcxMiwyMSA3LjUsMjEgQzYuNjcxNTcyODgsMjEgNiwyMS42NzE1NzI5IDYsMjIuNSBDNiwyMy4zMjg0MjcxIDYuNjcxNTcyODgsMjQgNy41LDI0IFoiLz4KICA8L2RlZnM+CiAgPGcgZmlsbD0ibm9uZSIgZmlsbC1ydWxlPSJldmVub2RkIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtMyAtMykiPgogICAgPG1hc2sgaWQ9InBvbHlnb24tYiIgZmlsbD0iI2ZmZiI+CiAgICAgIDx1c2UgeGxpbms6aHJlZj0iI3BvbHlnb24tYSIvPgogICAgPC9tYXNrPgogICAgPHVzZSBmaWxsPSIjNUI1QjVCIiBmaWxsLXJ1bGU9Im5vbnplcm8iIHhsaW5rOmhyZWY9IiNwb2x5Z29uLWEiLz4KICAgIDxnIGZpbGw9IiM1QjVCNUIiIG1hc2s9InVybCgjcG9seWdvbi1iKSI+CiAgICAgIDxyZWN0IHdpZHRoPSIzMCIgaGVpZ2h0PSIzMCIvPgogICAgPC9nPgogIDwvZz4KPC9zdmc+Cg==);
}
.leaflet-pm-toolbar .leaflet-pm-icon-polyline {
background-image: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCI+CiAgPGRlZnM+CiAgICA8cGF0aCBpZD0ibGluZS1hIiBkPSJNOS4xNjUwOTcyNSwxOS40MjA2ODkyIEwxOC40MjA2ODkyLDEwLjE2NTA5NzMgQzE4LjE1MjM2ODEsOS42Njk5MjkxNCAxOCw5LjEwMjc1ODMxIDE4LDguNSBDMTgsNi41NjcwMDMzOCAxOS41NjcwMDM0LDUgMjEuNSw1IEMyMy40MzI5OTY2LDUgMjUsNi41NjcwMDMzOCAyNSw4LjUgQzI1LDEwLjQzMjk5NjYgMjMuNDMyOTk2NiwxMiAyMS41LDEyIEMyMC44OTcyNDE3LDEyIDIwLjMzMDA3MDksMTEuODQ3NjMxOSAxOS44MzQ5MDI3LDExLjU3OTMxMDggTDEwLjU3OTMxMDgsMjAuODM0OTAyNyBDMTAuODQ3NjMxOSwyMS4zMzAwNzA5IDExLDIxLjg5NzI0MTcgMTEsMjIuNSBDMTEsMjQuNDMyOTk2NiA5LjQzMjk5NjYyLDI2IDcuNSwyNiBDNS41NjcwMDMzOCwyNiA0LDI0LjQzMjk5NjYgNCwyMi41IEM0LDIwLjU2NzAwMzQgNS41NjcwMDMzOCwxOSA3LjUsMTkgQzguMTAyNzU4MzEsMTkgOC42Njk5MjkxNCwxOS4xNTIzNjgxIDkuMTY1MDk3MjUsMTkuNDIwNjg5MiBaIE0yMS41LDEwIEMyMi4zMjg0MjcxLDEwIDIzLDkuMzI4NDI3MTIgMjMsOC41IEMyMyw3LjY3MTU3Mjg4IDIyLjMyODQyNzEsNyAyMS41LDcgQzIwLjY3MTU3MjksNyAyMCw3LjY3MTU3Mjg4IDIwLDguNSBDMjAsOS4zMjg0MjcxMiAyMC42NzE1NzI5LDEwIDIxLjUsMTAgWiBNNy41LDI0IEM4LjMyODQyNzEyLDI0IDksMjMuMzI4NDI3MSA5LDIyLjUgQzksMjEuNjcxNTcyOSA4LjMyODQyNzEyLDIxIDcuNSwyMSBDNi42NzE1NzI4OCwyMSA2LDIxLjY3MTU3MjkgNiwyMi41IEM2LDIzLjMyODQyNzEgNi42NzE1NzI4OCwyNCA3LjUsMjQgWiIvPgogIDwvZGVmcz4KICA8ZyBmaWxsPSJub25lIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0zIC0zKSI+CiAgICA8bWFzayBpZD0ibGluZS1iIiBmaWxsPSIjZmZmIj4KICAgICAgPHVzZSB4bGluazpocmVmPSIjbGluZS1hIi8+CiAgICA8L21hc2s+CiAgICA8dXNlIGZpbGw9IiM1QjVCNUIiIGZpbGwtcnVsZT0ibm9uemVybyIgeGxpbms6aHJlZj0iI2xpbmUtYSIvPgogICAgPGcgZmlsbD0iIzVCNUI1QiIgbWFzaz0idXJsKCNsaW5lLWIpIj4KICAgICAgPHJlY3Qgd2lkdGg9IjMwIiBoZWlnaHQ9IjMwIi8+CiAgICA8L2c+CiAgPC9nPgo8L3N2Zz4K);
}
.leaflet-pm-toolbar .leaflet-pm-icon-circle {
background-image: url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPHN2ZyB3aWR0aD0iMjRweCIgaGVpZ2h0PSIyNHB4IiB2aWV3Qm94PSIwIDAgMjQgMjQiIHZlcnNpb249IjEuMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayI+CiAgICA8IS0tIEdlbmVyYXRvcjogU2tldGNoIDUyLjUgKDY3NDY5KSAtIGh0dHA6Ly93d3cuYm9oZW1pYW5jb2RpbmcuY29tL3NrZXRjaCAtLT4KICAgIDx0aXRsZT5BdG9tcy9JY29ucy9Ub29scy9DaXJjbGU8L3RpdGxlPgogICAgPGRlc2M+Q3JlYXRlZCB3aXRoIFNrZXRjaC48L2Rlc2M+CiAgICA8ZGVmcz4KICAgICAgICA8cGF0aCBkPSJNMTguMjg5Nzc1MSw2Ljc4NjAyMjc1IEMxOC44OTI0MTMxLDYuMjk0NjQ5ODEgMTkuNjYxNzk3LDYgMjAuNSw2IEMyMi40MzI5OTY2LDYgMjQsNy41NjcwMDMzOCAyNCw5LjUgQzI0LDEwLjMzODIwMyAyMy43MDUzNTAyLDExLjEwNzU4NjkgMjMuMjEzOTc3MiwxMS43MTAyMjQ5IEMyMy43MTk1OTksMTIuODcxMjA1MyAyNCwxNC4xNTI4NTcxIDI0LDE1LjUgQzI0LDIwLjc0NjcwNTEgMTkuNzQ2NzA1MSwyNSAxNC41LDI1IEM5LjI1MzI5NDg4LDI1IDUsMjAuNzQ2NzA1MSA1LDE1LjUgQzUsMTAuMjUzMjk0OSA5LjI1MzI5NDg4LDYgMTQuNSw2IEMxNS44NDcxNDI5LDYgMTcuMTI4Nzk0Nyw2LjI4MDQwMDk4IDE4LjI4OTc3NTEsNi43ODYwMjI3NSBaIE0xNy4xNTA0MjI4LDguNDgxNzU4NiBDMTYuMzI2MzU4MSw4LjE3MDM5MjM2IDE1LjQzMzA3NzcsOCAxNC41LDggQzEwLjM1Nzg2NDQsOCA3LDExLjM1Nzg2NDQgNywxNS41IEM3LDE5LjY0MjEzNTYgMTAuMzU3ODY0NCwyMyAxNC41LDIzIEMxOC42NDIxMzU2LDIzIDIyLDE5LjY0MjEzNTYgMjIsMTUuNSBDMjIsMTQuNTY2OTIyMyAyMS44Mjk2MDc2LDEzLjY3MzY0MTkgMjEuNTE4MjQxNCwxMi44NDk1NzcyIEMyMS4xOTYwMzgzLDEyLjk0NzM5NjggMjAuODU0MTYyMiwxMyAyMC41LDEzIEMxOC41NjcwMDM0LDEzIDE3LDExLjQzMjk5NjYgMTcsOS41IEMxNyw5LjE0NTgzNzc4IDE3LjA1MjYwMzIsOC44MDM5NjE2OSAxNy4xNTA0MjI4LDguNDgxNzU4NiBaIE0xNC41LDE3IEMxMy42NzE1NzI5LDE3IDEzLDE2LjMyODQyNzEgMTMsMTUuNSBDMTMsMTQuNjcxNTcyOSAxMy42NzE1NzI5LDE0IDE0LjUsMTQgQzE1LjMyODQyNzEsMTQgMTYsMTQuNjcxNTcyOSAxNiwxNS41IEMxNiwxNi4zMjg0MjcxIDE1LjMyODQyNzEsMTcgMTQuNSwxNyBaIE0yMC41LDExIEMyMS4zMjg0MjcxLDExIDIyLDEwLjMyODQyNzEgMjIsOS41IEMyMiw4LjY3MTU3Mjg4IDIxLjMyODQyNzEsOCAyMC41LDggQzE5LjY3MTU3MjksOCAxOSw4LjY3MTU3Mjg4IDE5LDkuNSBDMTksMTAuMzI4NDI3MSAxOS42NzE1NzI5LDExIDIwLjUsMTEgWiIgaWQ9InBhdGgtMSI+PC9wYXRoPgogICAgPC9kZWZzPgogICAgPGcgaWQ9IlN5bWJvbHMiIHN0cm9rZT0ibm9uZSIgc3Ryb2tlLXdpZHRoPSIxIiBmaWxsPSJub25lIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiPgogICAgICAgIDxnIGlkPSJBdG9tcy9JY29ucy9Ub29scy9DaXJjbGUiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0zLjAwMDAwMCwgLTMuMDAwMDAwKSI+CiAgICAgICAgICAgIDxtYXNrIGlkPSJtYXNrLTIiIGZpbGw9IndoaXRlIj4KICAgICAgICAgICAgICAgIDx1c2UgeGxpbms6aHJlZj0iI3BhdGgtMSI+PC91c2U+CiAgICAgICAgICAgIDwvbWFzaz4KICAgICAgICAgICAgPHVzZSBpZD0iTWFzayIgZmlsbD0iIzVCNUI1QiIgZmlsbC1ydWxlPSJub256ZXJvIiB4bGluazpocmVmPSIjcGF0aC0xIj48L3VzZT4KICAgICAgICAgICAgPGcgaWQ9IkF0b21zL0NvbG9yL0dyZXkiIG1hc2s9InVybCgjbWFzay0yKSIgZmlsbD0iIzVCNUI1QiI+CiAgICAgICAgICAgICAgICA8cmVjdCBpZD0iUmVjdGFuZ2xlIiB4PSIwIiB5PSIwIiB3aWR0aD0iMzAiIGhlaWdodD0iMzAiPjwvcmVjdD4KICAgICAgICAgICAgPC9nPgogICAgICAgIDwvZz4KICAgIDwvZz4KPC9zdmc+);
}
.leaflet-pm-toolbar .leaflet-pm-icon-circle-marker {
background-image: url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KCjxzdmcgdmlld0JveD0iMCAwIDEwMCAxMDAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgc3Ryb2tlPSIjNUI1QjVCIiBzdHJva2Utd2lkdGg9IjgiCiAgICAgZmlsbD0ibm9uZSI+CjxjaXJjbGUgY3g9IjUwIiBjeT0iNTAiIHI9IjM1Ii8+CiAgPGNpcmNsZSBjeD0iNTAiIGN5PSI1MCIgcj0iMyIgZmlsbD0iIzVCNUI1QiIvPgo8L3N2Zz4=);
}
.leaflet-pm-toolbar .leaflet-pm-icon-rectangle {
background-image: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCI+CiAgPGRlZnM+CiAgICA8cGF0aCBpZD0icmVjdGFuZ2xlLWEiIGQ9Ik0yMywxMC45NjQ1NTU2IEwyMywxOS4wMzU0NDQ0IEMyNC42OTYxNDcxLDE5LjI3ODA1OTMgMjYsMjAuNzM2NzY0IDI2LDIyLjUgQzI2LDI0LjQzMjk5NjYgMjQuNDMyOTk2NiwyNiAyMi41LDI2IEMyMC43MzY3NjQsMjYgMTkuMjc4MDU5MywyNC42OTYxNDcxIDE5LjAzNTQ0NDQsMjMgTDEwLjk2NDU1NTYsMjMgQzEwLjcyMTk0MDcsMjQuNjk2MTQ3MSA5LjI2MzIzNTk1LDI2IDcuNSwyNiBDNS41NjcwMDMzOCwyNiA0LDI0LjQzMjk5NjYgNCwyMi41IEM0LDIwLjczNjc2NCA1LjMwMzg1MjkzLDE5LjI3ODA1OTMgNywxOS4wMzU0NDQ0IEw3LDEwLjk2NDU1NTYgQzUuMzAzODUyOTMsMTAuNzIxOTQwNyA0LDkuMjYzMjM1OTUgNCw3LjUgQzQsNS41NjcwMDMzOCA1LjU2NzAwMzM4LDQgNy41LDQgQzkuMjYzMjM1OTUsNCAxMC43MjE5NDA3LDUuMzAzODUyOTMgMTAuOTY0NTU1Niw3IEwxOS4wMzU0NDQ0LDcgQzE5LjI3ODA1OTMsNS4zMDM4NTI5MyAyMC43MzY3NjQsNCAyMi41LDQgQzI0LjQzMjk5NjYsNCAyNiw1LjU2NzAwMzM4IDI2LDcuNSBDMjYsOS4yNjMyMzU5NSAyNC42OTYxNDcxLDEwLjcyMTk0MDcgMjMsMTAuOTY0NTU1NiBaIE0yMSwxMC42NjMxODQ0IEMyMC4yNzIxNTQsMTAuMzE3NDIyNSAxOS42ODI1Nzc1LDkuNzI3ODQ1OTggMTkuMzM2ODE1Niw5IEwxMC42NjMxODQ0LDkgQzEwLjMxNzQyMjUsOS43Mjc4NDU5OCA5LjcyNzg0NTk4LDEwLjMxNzQyMjUgOSwxMC42NjMxODQ0IEw5LDE5LjMzNjgxNTYgQzkuNzI3ODQ1OTgsMTkuNjgyNTc3NSAxMC4zMTc0MjI1LDIwLjI3MjE1NCAxMC42NjMxODQ0LDIxIEwxOS4zMzY4MTU2LDIxIEMxOS42ODI1Nzc1LDIwLjI3MjE1NCAyMC4yNzIxNTQsMTkuNjgyNTc3NSAyMSwxOS4zMzY4MTU2IEwyMSwxMC42NjMxODQ0IFogTTcuNSw5IEM4LjMyODQyNzEyLDkgOSw4LjMyODQyNzEyIDksNy41IEM5LDYuNjcxNTcyODggOC4zMjg0MjcxMiw2IDcuNSw2IEM2LjY3MTU3Mjg4LDYgNiw2LjY3MTU3Mjg4IDYsNy41IEM2LDguMzI4NDI3MTIgNi42NzE1NzI4OCw5IDcuNSw5IFogTTIyLjUsOSBDMjMuMzI4NDI3MSw5IDI0LDguMzI4NDI3MTIgMjQsNy41IEMyNCw2LjY3MTU3Mjg4IDIzLjMyODQyNzEsNiAyMi41LDYgQzIxLjY3MTU3MjksNiAyMSw2LjY3MTU3Mjg4IDIxLDcuNSBDMjEsOC4zMjg0MjcxMiAyMS42NzE1NzI5LDkgMjIuNSw5IFogTTIyLjUsMjQgQzIzLjMyODQyNzEsMjQgMjQsMjMuMzI4NDI3MSAyNCwyMi41IEMyNCwyMS42NzE1NzI5IDIzLjMyODQyNzEsMjEgMjIuNSwyMSBDMjEuNjcxNTcyOSwyMSAyMSwyMS42NzE1NzI5IDIxLDIyLjUgQzIxLDIzLjMyODQyNzEgMjEuNjcxNTcyOSwyNCAyMi41LDI0IFogTTcuNSwyNCBDOC4zMjg0MjcxMiwyNCA5LDIzLjMyODQyNzEgOSwyMi41IEM5LDIxLjY3MTU3MjkgOC4zMjg0MjcxMiwyMSA3LjUsMjEgQzYuNjcxNTcyODgsMjEgNiwyMS42NzE1NzI5IDYsMjIuNSBDNiwyMy4zMjg0MjcxIDYuNjcxNTcyODgsMjQgNy41LDI0IFoiLz4KICA8L2RlZnM+CiAgPGcgZmlsbD0ibm9uZSIgZmlsbC1ydWxlPSJldmVub2RkIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtMyAtMykiPgogICAgPG1hc2sgaWQ9InJlY3RhbmdsZS1iIiBmaWxsPSIjZmZmIj4KICAgICAgPHVzZSB4bGluazpocmVmPSIjcmVjdGFuZ2xlLWEiLz4KICAgIDwvbWFzaz4KICAgIDx1c2UgZmlsbD0iIzVCNUI1QiIgZmlsbC1ydWxlPSJub256ZXJvIiB4bGluazpocmVmPSIjcmVjdGFuZ2xlLWEiLz4KICAgIDxnIGZpbGw9IiM1QjVCNUIiIG1hc2s9InVybCgjcmVjdGFuZ2xlLWIpIj4KICAgICAgPHJlY3Qgd2lkdGg9IjMwIiBoZWlnaHQ9IjMwIi8+CiAgICA8L2c+CiAgPC9nPgo8L3N2Zz4K);
}
.leaflet-pm-toolbar .leaflet-pm-icon-delete {
background-image: url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPHN2ZyB3aWR0aD0iMjRweCIgaGVpZ2h0PSIyNHB4IiB2aWV3Qm94PSIwIDAgMjQgMjQiIHZlcnNpb249IjEuMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayI+CiAgICA8IS0tIEdlbmVyYXRvcjogU2tldGNoIDUyLjUgKDY3NDY5KSAtIGh0dHA6Ly93d3cuYm9oZW1pYW5jb2RpbmcuY29tL3NrZXRjaCAtLT4KICAgIDx0aXRsZT5BdG9tcy9JY29ucy9Ub29scy9FcmFzZXI8L3RpdGxlPgogICAgPGRlc2M+Q3JlYXRlZCB3aXRoIFNrZXRjaC48L2Rlc2M+CiAgICA8ZGVmcz4KICAgICAgICA8cGF0aCBkPSJNMTcuNzg3NDIxOSwxOC40ODEyNTUyIEwxMS42NDgwMDc5LDEzLjM0OTgxODQgTDYuNDA0NjYwMDksMTkuMzgxNjAwMSBMMTAuNTUzOTE1NiwyMi45ODg0OTI5IEwxMy44NjkzNCwyMi45ODg0OTI5IEwxNy43ODc0MjE5LDE4LjQ4MTI1NTIgWiBNMTYuNTA3NDI1MiwyMi45ODg0OTI5IEwyNi4wMDAwMDAyLDIyLjk4ODQ5MjkgTDI2LjAwMDAwMDIsMjQuOTg4NDkyOSBMMTAuMDAwMDAwMiwyNC45ODg0OTI5IEw5LjgwNzA4MzEzLDI0Ljk4ODQ5MjkgTDUuMDkyNTQyMDQsMjAuODkxMDE5MiBDNC4yNTg5MTI4NSwyMC4xNjYzNTY0IDQuMTcwNTc4MTQsMTguOTAzMTExMiA0Ljg5NTI0MDkzLDE4LjA2OTQ4MiBMMTYuMDQ4MjQ0NCw1LjIzOTQxOTE2IEMxNi43NzI5MDcyLDQuNDA1Nzg5OTggMTguMDM2MTUyNSw0LjMxNzQ1NTI2IDE4Ljg2OTc4MTYsNS4wNDIxMTgwNiBMMjQuOTA3NDU4MywxMC4yOTA1OTAzIEMyNS43NDEwODc1LDExLjAxNTI1MzEgMjUuODI5NDIyMiwxMi4yNzg0OTgzIDI1LjEwNDc1OTQsMTMuMTEyMTI3NSBMMTYuNTA3NDI1MiwyMi45ODg0OTI5IFoiIGlkPSJwYXRoLTEiPjwvcGF0aD4KICAgIDwvZGVmcz4KICAgIDxnIGlkPSJTeW1ib2xzIiBzdHJva2U9Im5vbmUiIHN0cm9rZS13aWR0aD0iMSIgZmlsbD0ibm9uZSIgZmlsbC1ydWxlPSJldmVub2RkIj4KICAgICAgICA8ZyBpZD0iQXRvbXMvSWNvbnMvVG9vbHMvRXJhc2VyIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtMy4wMDAwMDAsIC0zLjAwMDAwMCkiPgogICAgICAgICAgICA8bWFzayBpZD0ibWFzay0yIiBmaWxsPSJ3aGl0ZSI+CiAgICAgICAgICAgICAgICA8dXNlIHhsaW5rOmhyZWY9IiNwYXRoLTEiPjwvdXNlPgogICAgICAgICAgICA8L21hc2s+CiAgICAgICAgICAgIDx1c2UgaWQ9IkNvbWJpbmVkLVNoYXBlIiBmaWxsPSIjNUI1QjVCIiBmaWxsLXJ1bGU9Im5vbnplcm8iIHhsaW5rOmhyZWY9IiNwYXRoLTEiPjwvdXNlPgogICAgICAgIDwvZz4KICAgIDwvZz4KPC9zdmc+);
}
.leaflet-pm-toolbar .leaflet-pm-icon-edit {
background-image: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCI+CiAgPGRlZnM+CiAgICA8cGF0aCBpZD0iZWRpdF9hbmNob3ItYSIgZD0iTTEzLjUsMTEgQzExLjU2NzAwMzQsMTEgMTAsOS40MzI5OTY2MiAxMCw3LjUgQzEwLDUuNTY3MDAzMzggMTEuNTY3MDAzNCw0IDEzLjUsNCBDMTUuNDMyOTk2Niw0IDE3LDUuNTY3MDAzMzggMTcsNy41IEMxNyw5LjQzMjk5NjYyIDE1LjQzMjk5NjYsMTEgMTMuNSwxMSBaIE0xMy41LDkgQzE0LjMyODQyNzEsOSAxNSw4LjMyODQyNzEyIDE1LDcuNSBDMTUsNi42NzE1NzI4OCAxNC4zMjg0MjcxLDYgMTMuNSw2IEMxMi42NzE1NzI5LDYgMTIsNi42NzE1NzI4OCAxMiw3LjUgQzEyLDguMzI4NDI3MTIgMTIuNjcxNTcyOSw5IDEzLjUsOSBaIE0xMi4wMDAyODg5LDcuNTI5NzM4OTMgQzEyLjAxMjU5ODMsOC4xNjI3MzY3MiAxMi40MTcwMTk3LDguNjk5NjY0MyAxMi45ODA3MTExLDguOTA3Njc5NjYgTDMsMTUgTDMsMTMgTDEyLjAwMDI4ODksNy41Mjk3Mzg5MyBaIE0xNC4yMTcyNzIyLDYuMTgyMjg0NzIgTDE5LjQ1MzEyNSwzIEwyMi42NTg5MzU1LDMgTDE0Ljk4OTEwMiw3LjY4MTczODg1IEMxNC45OTYyOTcxLDcuNjIyMTY0NTkgMTUsNy41NjE1MTQ3MiAxNSw3LjUgQzE1LDYuOTMxMzgzODEgMTQuNjgzNjA5OCw2LjQzNjY2NDUgMTQuMjE3MjcyMiw2LjE4MjI4NDcyIFogTTIzLjQ0MzQwNDIsMTkuMjg1MTczNiBMMjAuMTI4Mjc5OSwxOS4yODUxNzM2IEwyMS44NzI5OTgzLDIzLjUzNDk1MjUgQzIxLjk5NDUyOTYsMjMuODI5NTc3MyAyMS44NTU2NTQ2LDI0LjE1OTkyMDkgMjEuNTc3ODczNCwyNC4yODQ5MjA4IEwyMC4wNDE0Njc1LDI0Ljk1NDUxNDIgQzE5Ljc1NTA2MTMsMjUuMDc5NTE0MSAxOS40MzM4NzM4LDI0LjkzNjY3MDQgMTkuMzEyMzQyNiwyNC42NTA5NTE4IEwxNy42NTQ0MzY3LDIwLjYxNTQ1NDEgTDE0Ljk0NjE4NzMsMjMuNDAxMDE1MSBDMTQuNTg1MjgxMSwyMy43NzIxNzExIDE0LDIzLjQ4NjA0NjMgMTQsMjIuOTk5MjY1MyBMMTQsOS41NzE4MzUzMyBDMTQsOS4wNTkzMzU2MSAxNC42MjI1MzExLDguODA5NDkyIDE0Ljk0NjE1Niw5LjE3MDA4NTU1IEwyMy44MzQwMjkyLDE4LjMxMjAxNzkgQzI0LjE5MjUyOTEsMTguNjYxMzYxNSAyMy45Mjc5OTc5LDE5LjI4NTE3MzYgMjMuNDQzNDA0MiwxOS4yODUxNzM2IFoiLz4KICA8L2RlZnM+CiAgPGcgZmlsbD0ibm9uZSIgZmlsbC1ydWxlPSJldmVub2RkIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtMyAtMykiPgogICAgPG1hc2sgaWQ9ImVkaXRfYW5jaG9yLWIiIGZpbGw9IiNmZmYiPgogICAgICA8dXNlIHhsaW5rOmhyZWY9IiNlZGl0X2FuY2hvci1hIi8+CiAgICA8L21hc2s+CiAgICA8dXNlIGZpbGw9IiM1QjVCNUIiIGZpbGwtcnVsZT0ibm9uemVybyIgeGxpbms6aHJlZj0iI2VkaXRfYW5jaG9yLWEiLz4KICAgIDxnIGZpbGw9IiM1QjVCNUIiIG1hc2s9InVybCgjZWRpdF9hbmNob3ItYikiPgogICAgICA8cmVjdCB3aWR0aD0iMzAiIGhlaWdodD0iMzAiLz4KICAgIDwvZz4KICA8L2c+Cjwvc3ZnPgo=);
}
.leaflet-pm-toolbar .leaflet-pm-icon-drag {
background-image: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCI+CiAgPGRlZnM+CiAgICA8cGF0aCBpZD0ibW92ZS1hIiBkPSJNMjEsMTQgTDIxLDEwIEwyNywxNSBMMjEsMjAgTDIxLDE2IEwxNiwxNiBMMTYsMjEgTDIwLDIxIEwxNSwyNyBMMTAsMjEgTDE0LDIxIEwxNCwxNiBMOSwxNiBMOSwyMCBMMywxNSBMOSwxMCBMOSwxNCBMMTQsMTQgTDE0LDkgTDEwLDkgTDE1LDMgTDIwLDkgTDE2LDkgTDE2LDE0IEwyMSwxNCBaIi8+CiAgPC9kZWZzPgogIDxnIGZpbGw9Im5vbmUiIGZpbGwtcnVsZT0iZXZlbm9kZCIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTMgLTMpIj4KICAgIDxtYXNrIGlkPSJtb3ZlLWIiIGZpbGw9IiNmZmYiPgogICAgICA8dXNlIHhsaW5rOmhyZWY9IiNtb3ZlLWEiLz4KICAgIDwvbWFzaz4KICAgIDx1c2UgZmlsbD0iI0Q4RDhEOCIgeGxpbms6aHJlZj0iI21vdmUtYSIvPgogICAgPGcgZmlsbD0iIzVCNUI1QiIgbWFzaz0idXJsKCNtb3ZlLWIpIj4KICAgICAgPHJlY3Qgd2lkdGg9IjMwIiBoZWlnaHQ9IjMwIi8+CiAgICA8L2c+CiAgPC9nPgo8L3N2Zz4K);
}
.leaflet-pm-toolbar .leaflet-pm-icon-cut {
background-image: url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPHN2ZyB3aWR0aD0iMjRweCIgaGVpZ2h0PSIyNHB4IiB2aWV3Qm94PSIwIDAgMjQgMjQiIHZlcnNpb249IjEuMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayI+CiAgICA8IS0tIEdlbmVyYXRvcjogU2tldGNoIDUyLjUgKDY3NDY5KSAtIGh0dHA6Ly93d3cuYm9oZW1pYW5jb2RpbmcuY29tL3NrZXRjaCAtLT4KICAgIDx0aXRsZT5BdG9tcy9JY29ucy9Ub29scy9TY2lzc29yczwvdGl0bGU+CiAgICA8ZGVzYz5DcmVhdGVkIHdpdGggU2tldGNoLjwvZGVzYz4KICAgIDxkZWZzPgogICAgICAgIDxwYXRoIGQ9Ik0xMi45NjkxNTc0LDEzLjQ5Mzk0MzUgTDIxLjAzMTcwMzIsNS41NDE2NzAxMyBMMjMuNDY0OTQ5OSw1LjY3NzIyOTU3IEwxNy4wNDcwNzEzLDE0LjUxMDY4MTYgTDI3LjU2NjAzMzYsMTcuMTMzMzUzNSBMMjUuNzg5MTk0NCwxOC44MDEyNTg4IEwxNC41ODU0OTUxLDE3Ljg5ODc1MDYgTDEzLjY0ODc5NTUsMTkuMTg4MDA3IEMxMy43OTQ2MzksMTkuMjY1MDk1OCAxMy45MzY3OTg1LDE5LjM1MzQ0MTcgMTQuMDc0MTM3NywxOS40NTMyMjQ1IEMxNS42Mzc5NjQ4LDIwLjU4OTQxMTQgMTUuOTg0NjM1NywyMi43NzgyMDUyIDE0Ljg0ODQ0ODgsMjQuMzQyMDMyNCBDMTMuNzEyMjYxOSwyNS45MDU4NTk1IDExLjUyMzQ2ODEsMjYuMjUyNTMwNCA5Ljk1OTY0MDk2LDI1LjExNjM0MzUgQzguMzk1ODEzODQsMjMuOTgwMTU2NSA4LjA0OTE0Mjk2LDIxLjc5MTM2MjcgOS4xODUzMjk4NiwyMC4yMjc1MzU2IEM5Ljc0NTg3Mjc2LDE5LjQ1NjAxNDUgMTAuNTYyNjE4OCwxOC45ODA3NDc1IDExLjQzNDEyMTgsMTguODMzNjQwNyBMMTIuNjgwNTY1NiwxNy4xMTgwNTc5IEwxMi41MjM5NzI0LDE2LjM3NDcyMTYgTDExLjk1MDY5MzIsMTUuMzAxMjM5MSBMOS44OTMxMDY0NiwxNC43ODgyMjUxIEM5LjEzMDkzNzk2LDE1LjIzNTcyNjEgOC4xOTk3Nzg1NCwxNS4zOTY2NDQ3IDcuMjc0NDUzNTUsMTUuMTY1OTM1MiBDNS4zOTg4NzUxOSwxNC42OTgzMDEgNC4yNTc1MTA5NCwxMi43OTg3NTE5IDQuNzI1MTQ1MTUsMTAuOTIzMTczNiBDNS4xOTI3NzkzNSw5LjA0NzU5NTE5IDcuMDkyMzI4NDYsNy45MDYyMzA5NCA4Ljk2NzkwNjgyLDguMzczODY1MTUgQzEwLjg0MzQ4NTIsOC44NDE0OTkzNSAxMS45ODQ4NDk0LDEwLjc0MTA0ODUgMTEuNTE3MjE1MiwxMi42MTY2MjY4IEMxMS40NzYxNDY0LDEyLjc4MTM0NDkgMTEuNDI0MDMzNSwxMi45NDA0MDAxIDExLjM2MTg2MjcsMTMuMDkzMTk5OSBMMTIuOTY5MTU3NCwxMy40OTM5NDM1IFogTTcuNzU4Mjk3MzUsMTMuMjI1MzQzOCBDOC41NjIxMTY2NCwxMy40MjU3NTg0IDkuMzc2MjA5MTIsMTIuOTM2NjAyMyA5LjU3NjYyMzc4LDEyLjEzMjc4MyBDOS43NzcwMzg0NCwxMS4zMjg5NjM3IDkuMjg3ODgyMzMsMTAuNTE0ODcxMyA4LjQ4NDA2MzAzLDEwLjMxNDQ1NjYgQzcuNjgwMjQzNzMsMTAuMTE0MDQxOSA2Ljg2NjE1MTI2LDEwLjYwMzE5OCA2LjY2NTczNjYsMTEuNDA3MDE3MyBDNi40NjUzMjE5NCwxMi4yMTA4MzY2IDYuOTU0NDc4MDUsMTMuMDI0OTI5MSA3Ljc1ODI5NzM1LDEzLjIyNTM0MzggWiBNMTAuODAzMzYzOSwyMS40MDMxMDYxIEMxMC4zMTY0MjY2LDIyLjA3MzMxNzcgMTAuNDY0OTk5OCwyMy4wMTEzNzIyIDExLjEzNTIxMTUsMjMuNDk4MzA5NSBDMTEuODA1NDIzMSwyMy45ODUyNDY3IDEyLjc0MzQ3NzYsMjMuODM2NjczNSAxMy4yMzA0MTQ4LDIzLjE2NjQ2MTkgQzEzLjcxNzM1MjEsMjIuNDk2MjUwMiAxMy41Njg3Nzg4LDIxLjU1ODE5NTcgMTIuODk4NTY3MiwyMS4wNzEyNTg1IEMxMi4yMjgzNTU2LDIwLjU4NDMyMTIgMTEuMjkwMzAxMSwyMC43MzI4OTQ1IDEwLjgwMzM2MzksMjEuNDAzMTA2MSBaIiBpZD0icGF0aC0xIj48L3BhdGg+CiAgICA8L2RlZnM+CiAgICA8ZyBpZD0iU3ltYm9scyIgc3Ryb2tlPSJub25lIiBzdHJva2Utd2lkdGg9IjEiIGZpbGw9Im5vbmUiIGZpbGwtcnVsZT0iZXZlbm9kZCI+CiAgICAgICAgPGcgaWQ9IkF0b21zL0ljb25zL1Rvb2xzL1NjaXNzb3JzIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtMy4wMDAwMDAsIC0zLjAwMDAwMCkiPgogICAgICAgICAgICA8bWFzayBpZD0ibWFzay0yIiBmaWxsPSJ3aGl0ZSI+CiAgICAgICAgICAgICAgICA8dXNlIHhsaW5rOmhyZWY9IiNwYXRoLTEiPjwvdXNlPgogICAgICAgICAgICA8L21hc2s+CiAgICAgICAgICAgIDx1c2UgaWQ9Ik1hc2siIGZpbGw9IiM1QjVCNUIiIGZpbGwtcnVsZT0ibm9uemVybyIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMTYuMDkzMTk0LCAxNS42NjMzNTEpIHJvdGF0ZSgtMzIuMDAwMDAwKSB0cmFuc2xhdGUoLTE2LjA5MzE5NCwgLTE1LjY2MzM1MSkgIiB4bGluazpocmVmPSIjcGF0aC0xIj48L3VzZT4KICAgICAgICA8L2c+CiAgICA8L2c+Cjwvc3ZnPg==);
}
.leaflet-pm-toolbar .leaflet-pm-icon-snapping {
background-image: url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPHN2ZyB3aWR0aD0iMjRweCIgaGVpZ2h0PSIyNHB4IiB2aWV3Qm94PSIwIDAgMjQgMjQiIHZlcnNpb249IjEuMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayI+CiAgICA8IS0tIEdlbmVyYXRvcjogU2tldGNoIDU3LjEgKDgzMDg4KSAtIGh0dHBzOi8vc2tldGNoLmNvbSAtLT4KICAgIDx0aXRsZT5BdG9tcy9JY29ucy9Ub29scy9NYWduZXQ8L3RpdGxlPgogICAgPGRlc2M+Q3JlYXRlZCB3aXRoIFNrZXRjaC48L2Rlc2M+CiAgICA8ZGVmcz4KICAgICAgICA8cGF0aCBkPSJNMjEuOTk5NDc1OSwxMC45NDI4MTgzIEwyMS45OTk5OTg1LDE2LjM3MTA0MTcgQzIyLDE2LjY4NzIwMDcgMjIsMTcuMDA1ODI3OCAyMiwxNy4zMjY5NDExIEMyMiwyMS41NjQ2NTQ1IDE4LjY0MjEzNTYsMjUgMTQuNSwyNSBDMTAuMzU3ODY0NCwyNSA3LDIxLjU2NDY1NDUgNywxNy4zMjY5NDExIEw3LjAwMDg3NTA4LDEwLjk5MDc1MDcgTDExLjAwMjI4MDgsMTAuOTk4NDEyNSBDMTEuMDAxNzAzMywxMS42OTgwMTE0IDExLjAwMTI0NywxMi40MTY4MjQ4IDExLjAwMDg5OTIsMTMuMTU1NDg4NyBMMTEsMTcuMzI2OTQxMSBDMTEsMTkuMzc1NjgwOSAxMi41ODc2ODQxLDIxIDE0LjUsMjEgQzE2LjQxMjMxNTksMjEgMTgsMTkuMzc1NjgwOSAxOCwxNy4zMjY5NDExIEMxOCwxNS4wNzAyMDMyIDE3Ljk5OTU2OTYsMTIuOTYxOTY2OCAxNy45OTg1MzksMTAuOTkxMDAzMiBMMjEuOTk5NDc1OSwxMC45NDI4MTgzIFogTTEwLDcgQzEwLjU1MjI4NDcsNyAxMSw3LjQ0NzcxNTI1IDExLDggTDExLDEwIEw3LDEwIEw3LDggQzcsNy40NDc3MTUyNSA3LjQ0NzcxNTI1LDcgOCw3IEwxMCw3IFogTTIxLDcgQzIxLjU1MjI4NDcsNyAyMiw3LjQ0NzcxNTI1IDIyLDggTDIyLDEwIEwxOCwxMCBMMTgsOCBDMTgsNy40NDc3MTUyNSAxOC40NDc3MTUzLDcgMTksNyBMMjEsNyBaIiBpZD0icGF0aC0xIj48L3BhdGg+CiAgICA8L2RlZnM+CiAgICA8ZyBpZD0iU3ltYm9scyIgc3Ryb2tlPSJub25lIiBzdHJva2Utd2lkdGg9IjEiIGZpbGw9Im5vbmUiIGZpbGwtcnVsZT0iZXZlbm9kZCI+CiAgICAgICAgPGcgaWQ9IkF0b21zL0ljb25zL1Rvb2xzL01hZ25ldCIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTMuMDAwMDAwLCAtMy4wMDAwMDApIj4KICAgICAgICAgICAgPG1hc2sgaWQ9Im1hc2stMiIgZmlsbD0id2hpdGUiPgogICAgICAgICAgICAgICAgPHVzZSB4bGluazpocmVmPSIjcGF0aC0xIj48L3VzZT4KICAgICAgICAgICAgPC9tYXNrPgogICAgICAgICAgICA8dXNlIGlkPSJNYXNrIiBmaWxsPSIjNUI1QjVCIiBmaWxsLXJ1bGU9Im5vbnplcm8iIHRyYW5zZm9ybT0idHJhbnNsYXRlKDE0LjUwMDAwMCwgMTYuMDAwMDAwKSByb3RhdGUoNDUuMDAwMDAwKSB0cmFuc2xhdGUoLTE0LjUwMDAwMCwgLTE2LjAwMDAwMCkgIiB4bGluazpocmVmPSIjcGF0aC0xIj48L3VzZT4KICAgICAgICA8L2c+CiAgICA8L2c+Cjwvc3ZnPg==);
}
.leaflet-pm-toolbar .leaflet-pm-icon-rotate {
background-image: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCI+CiAgICA8ZGVmcz4KICAgICAgICA8cGF0aCBpZD0icm90YXRlIiBkPSJNMjEuMiw1LjhjLTAuMS0wLjItMC4yLTAuMy0wLjMtMC41bC0wLjEtMC4yYy0wLjEtMC4yLTAuMi0wLjMtMC4zLTAuNWwtMC4xLTAuMmMtMC4xLTAuMi0wLjItMC4zLTAuNC0wLjVsLTAuMi0wLjNsMi44LTMuMUwxOCwwLjZsLTQuNiwwLjFsMC41LDQuNWwwLjUsNC41bDMuMi0zLjZ2MC4xbDAuMSwwLjJjMC4xLDAuMSwwLjEsMC4yLDAuMiwwLjJsMC4xLDAuMkMxOCw3LDE4LDcuMSwxOC4xLDcuMmMwLjMsMC43LDAuNiwxLjQsMC43LDIuMWMwLjIsMS40LDAsMi45LTAuNiw0LjJMMTgsMTMuOUwxNy45LDE0bC0wLjMsMC41bC0wLjEsMC4yYy0wLjIsMC4yLTAuNCwwLjUtMC42LDAuN2MtMC41LDAuNS0xLjEsMS0xLjcsMS4zYy0wLjYsMC40LTEuMywwLjYtMi4xLDAuOGMtMC43LDAuMS0xLjUsMC4yLTIuMiwwLjFjLTAuOC0wLjEtMS41LTAuMy0yLjItMC41Yy0wLjctMC4zLTEuMy0wLjctMS45LTEuMmwtMC40LTAuNGwtMC4yLTAuM0w2LDE1Yy0wLjEtMC4xLTAuMi0wLjItMC4yLTAuM2wtMC4zLTAuNGwtMC4xLTAuMWwtMC4yLTAuNGMwLTAuMS0wLjEtMC4xLTAuMS0wLjJsLTAuMy0wLjVsLTAuMS0wLjJjLTAuMS0wLjMtMC4yLTAuNi0wLjMtMC45Yy0wLjItMC44LTAuMy0xLjYtMC4zLTIuNGMwLTAuMiwwLTAuMywwLTAuNVY4LjljMC0wLjIsMC0wLjMsMC4xLTAuNGwwLjEtMC42bDAuMi0wLjZjMC4zLTAuOCwwLjctMS41LDEuMi0yLjJjMC41LTAuNywxLjEtMS4zLDEuOC0xLjhjMC4yLTAuMSwwLjMtMC40LDAuMS0wLjZDNy41LDIuNiw3LjQsMi41LDcuMywyLjVINy4xTDcsMi42QzYuMSwzLDUuNCwzLjYsNC43LDQuMkM0LDQuOSwzLjUsNS43LDMsNi42Yy0wLjksMS44LTEuMiwzLjgtMC44LDUuOGMwLjEsMC41LDAuMiwwLjksMC4zLDEuNGwwLjMsMC44QzIuOSwxNC43LDMsMTQuOCwzLDE1bDAuMiwwLjRjMCwwLjEsMC4xLDAuMiwwLjEsMC4ybDAuMywwLjVjMC4xLDAuMiwwLjIsMC4zLDAuMywwLjVsMC4xLDAuMmMwLjEsMC4xLDAuMiwwLjMsMC4zLDAuNEw1LDE3LjhjMC43LDAuNywxLjYsMS4zLDIuNSwxLjhjMC45LDAuNSwxLjksMC44LDMsMC45YzAuNSwwLjEsMSwwLjEsMS41LDAuMWMwLjYsMCwxLjEsMCwxLjYtMC4xYzEtMC4yLDIuMS0wLjUsMy0xbDAuMi0wLjFjMC4yLTAuMSwwLjMtMC4yLDAuNS0wLjNsMC43LTAuNGMwLjItMC4xLDAuMy0wLjIsMC40LTAuM2wwLjItMC4yYzAuMi0wLjEsMC40LTAuMywwLjUtMC41bDAuMS0wLjFjMC4zLTAuMywwLjctMC43LDAuOS0xbDAuNi0wLjlsMC40LTAuNmMxLTEuOSwxLjQtNC4xLDEuMS02LjJDMjIsNy44LDIxLjcsNi43LDIxLjIsNS44eiIvPgogICAgPC9kZWZzPgogICAgPGcgZmlsbD0ibm9uZSIgZmlsbC1ydWxlPSJldmVub2RkIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgwIDIpIj4KICAgICAgICA8bWFzayBpZD0icm90YXRlLWIiIGZpbGw9IiNmZmYiPgogICAgICAgICAgICA8dXNlIHhsaW5rOmhyZWY9IiNyb3RhdGUiLz4KICAgICAgICA8L21hc2s+CiAgICAgICAgPHVzZSBmaWxsPSIjNUI1QjVCIiBmaWxsLXJ1bGU9Im5vbnplcm8iIHhsaW5rOmhyZWY9IiNyb3RhdGUiLz4KICAgICAgICA8ZyBmaWxsPSIjNUI1QjVCIiBtYXNrPSJ1cmwoI3JvdGF0ZS1iKSI+CiAgICAgICAgICAgIDxyZWN0IHdpZHRoPSIzMCIgaGVpZ2h0PSIzMCIvPgogICAgICAgIDwvZz4KICAgIDwvZz4KPC9zdmc+Cg==);
}
.leaflet-pm-toolbar .leaflet-pm-icon-text {
background-image: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCI+PGRlZnM+PHN0eWxlPi5jbHMtMXtmaWxsOm5vbmU7c3Ryb2tlOiM1YjViNWI7c3Ryb2tlLWxpbmVjYXA6cm91bmQ7c3Ryb2tlLWxpbmVqb2luOnJvdW5kO3N0cm9rZS13aWR0aDoyLjVweDt9PC9zdHlsZT48L2RlZnM+PHRpdGxlPlRleHQ8L3RpdGxlPjxnIGlkPSJFYmVuZV8yIiBkYXRhLW5hbWU9IkViZW5lIDIiPjxwb2x5bGluZSBjbGFzcz0iY2xzLTEiIHBvaW50cz0iMTkuNjQgNy4yNyAxOS42NCA0IDEyIDQgMTIgMjAgMTUuOTEgMjAgOC4wOSAyMCAxMiAyMCAxMiA0IDQuMzYgNCA0LjM2IDcuMjciLz48L2c+PC9zdmc+);
}
.leaflet-buttons-control-button:hover,
.leaflet-buttons-control-button:focus {
cursor: pointer;
background-color: #f4f4f4;
}
.active > .leaflet-buttons-control-button {
box-shadow: inset 0 -1px 5px 2px rgba(81, 77, 77, 0.31);
}
.leaflet-buttons-control-text-hide {
display: none;
}
.button-container {
position: relative;
}
.button-container .leaflet-pm-actions-container {
z-index: 2;
position: absolute;
top: 0;
left: 100%;
display: none;
white-space: nowrap;
direction: ltr;
}
.leaflet-right
.leaflet-pm-toolbar
.button-container
.leaflet-pm-actions-container {
right: 100%;
left: auto;
}
.button-container.active .leaflet-pm-actions-container {
display: block;
}
.button-container
.leaflet-pm-actions-container:not(.pos-right)
a.leaflet-pm-action:last-child {
border-radius: 0 3px 3px 0;
border-right: 0;
}
.button-container
.leaflet-pm-actions-container.pos-right
a.leaflet-pm-action:first-child {
border-radius: 3px 0 0 3px;
}
.button-container
.leaflet-pm-actions-container.pos-right
a.leaflet-pm-action:last-child {
border-right: 0;
}
.button-container .leaflet-pm-actions-container .leaflet-pm-action {
padding: 0 10px;
background-color: #666;
color: #fff;
display: inline-block;
width: auto;
border-right: 1px solid #eee;
user-select: none;
border-bottom: none;
height: 29px;
line-height: 29px;
}
.leaflet-pm-toolbar
.button-container:first-child.pos-right.active
a.leaflet-buttons-control-button {
border-top-left-radius: 0;
}
.leaflet-pm-toolbar
.button-container:first-child.active:not(.pos-right)
a.leaflet-buttons-control-button {
border-top-right-radius: 0;
}
.button-container .leaflet-pm-actions-container .leaflet-pm-action:hover,
.button-container .leaflet-pm-actions-container .leaflet-pm-action:focus {
cursor: pointer;
background-color: #777;
}
/* That the active control is always over the other controls */
.leaflet-pm-toolbar.activeChild {
z-index: 801;
}
.leaflet-buttons-control-button.pm-disabled {
background-color: #f4f4f4;
}
.leaflet-buttons-control-button.pm-disabled > .control-icon {
filter: opacity(0.6);
}

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 696 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 618 B

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,666 @@
/* required styles */
.leaflet-pane,
.leaflet-tile,
.leaflet-marker-icon,
.leaflet-marker-shadow,
.leaflet-tile-container,
.leaflet-pane > svg,
.leaflet-pane > canvas,
.leaflet-zoom-box,
.leaflet-image-layer,
.leaflet-layer {
position: absolute;
left: 0;
top: 0;
}
.leaflet-container {
overflow: hidden;
}
.leaflet-tile,
.leaflet-marker-icon,
.leaflet-marker-shadow {
-webkit-user-select: none;
-moz-user-select: none;
user-select: none;
-webkit-user-drag: none;
}
/* Prevents IE11 from highlighting tiles in blue */
.leaflet-tile::selection {
background: transparent;
}
/* Safari renders non-retina tile on retina better with this, but Chrome is worse */
.leaflet-safari .leaflet-tile {
image-rendering: -webkit-optimize-contrast;
}
/* hack that prevents hw layers "stretching" when loading new tiles */
.leaflet-safari .leaflet-tile-container {
width: 1600px;
height: 1600px;
-webkit-transform-origin: 0 0;
}
.leaflet-marker-icon,
.leaflet-marker-shadow {
display: block;
}
/* .leaflet-container svg: reset svg max-width decleration shipped in Joomla! (joomla.org) 3.x */
/* .leaflet-container img: map is broken in FF if you have max-width: 100% on tiles */
.leaflet-container .leaflet-overlay-pane svg {
max-width: none !important;
max-height: none !important;
}
.leaflet-container .leaflet-marker-pane img,
.leaflet-container .leaflet-shadow-pane img,
.leaflet-container .leaflet-tile-pane img,
.leaflet-container img.leaflet-image-layer,
.leaflet-container .leaflet-tile {
max-width: none !important;
max-height: none !important;
width: auto;
padding: 0;
}
.leaflet-container img.leaflet-tile {
/* See: https://bugs.chromium.org/p/chromium/issues/detail?id=600120 */
mix-blend-mode: plus-lighter;
}
.leaflet-container.leaflet-touch-zoom {
-ms-touch-action: pan-x pan-y;
touch-action: pan-x pan-y;
}
.leaflet-container.leaflet-touch-drag {
-ms-touch-action: pinch-zoom;
/* Fallback for FF which doesn't support pinch-zoom */
touch-action: none;
touch-action: pinch-zoom;
}
.leaflet-container.leaflet-touch-drag.leaflet-touch-zoom {
-ms-touch-action: none;
touch-action: none;
}
.leaflet-container {
-webkit-tap-highlight-color: transparent;
}
.leaflet-container a {
-webkit-tap-highlight-color: rgba(51, 181, 229, 0.4);
}
.leaflet-tile {
filter: inherit;
visibility: hidden;
}
.leaflet-tile-loaded {
visibility: inherit;
}
.leaflet-zoom-box {
width: 0;
height: 0;
-moz-box-sizing: border-box;
box-sizing: border-box;
z-index: 800;
}
/* workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=888319 */
.leaflet-overlay-pane svg {
-moz-user-select: none;
}
.leaflet-pane { z-index: 400; }
.leaflet-tile-pane { z-index: 200; }
.leaflet-overlay-pane { z-index: 400; }
.leaflet-shadow-pane { z-index: 500; }
.leaflet-marker-pane { z-index: 600; }
.leaflet-tooltip-pane { z-index: 650; }
.leaflet-popup-pane { z-index: 700; }
.leaflet-map-pane canvas { z-index: 100; }
.leaflet-map-pane svg { z-index: 200; }
.leaflet-vml-shape {
width: 1px;
height: 1px;
}
.lvml {
behavior: url(#default#VML);
display: inline-block;
position: absolute;
}
/* control positioning */
.leaflet-control {
position: relative;
z-index: 800;
pointer-events: visiblePainted; /* IE 9-10 doesn't have auto */
pointer-events: auto;
}
.leaflet-top,
.leaflet-bottom {
position: absolute;
z-index: 1000;
pointer-events: none;
}
.leaflet-top {
top: 0;
}
.leaflet-right {
right: 0;
}
.leaflet-bottom {
bottom: 0;
}
.leaflet-left {
left: 0;
}
.leaflet-control {
float: left;
clear: both;
}
.leaflet-right .leaflet-control {
float: right;
}
.leaflet-top .leaflet-control {
margin-top: 10px;
}
.leaflet-bottom .leaflet-control {
margin-bottom: 10px;
}
.leaflet-left .leaflet-control {
margin-left: 10px;
}
.leaflet-right .leaflet-control {
margin-right: 10px;
}
/* zoom and fade animations */
.leaflet-fade-anim .leaflet-popup {
opacity: 0;
-webkit-transition: opacity 0.2s linear;
-moz-transition: opacity 0.2s linear;
transition: opacity 0.2s linear;
}
.leaflet-fade-anim .leaflet-map-pane .leaflet-popup {
opacity: 1;
}
.leaflet-zoom-animated {
-webkit-transform-origin: 0 0;
-ms-transform-origin: 0 0;
transform-origin: 0 0;
}
svg.leaflet-zoom-animated {
will-change: transform;
}
.leaflet-zoom-anim .leaflet-zoom-animated {
-webkit-transition: -webkit-transform 0.25s cubic-bezier(0,0,0.25,1);
-moz-transition: -moz-transform 0.25s cubic-bezier(0,0,0.25,1);
transition: transform 0.25s cubic-bezier(0,0,0.25,1);
}
.leaflet-zoom-anim .leaflet-tile,
.leaflet-pan-anim .leaflet-tile {
-webkit-transition: none;
-moz-transition: none;
transition: none;
}
.leaflet-zoom-anim .leaflet-zoom-hide {
visibility: hidden;
}
/* cursors */
.leaflet-interactive {
cursor: pointer;
}
.leaflet-grab {
cursor: -webkit-grab;
cursor: -moz-grab;
cursor: grab;
}
.leaflet-crosshair,
.leaflet-crosshair .leaflet-interactive {
cursor: crosshair;
}
.leaflet-popup-pane,
.leaflet-control {
cursor: auto;
}
.leaflet-dragging .leaflet-grab,
.leaflet-dragging .leaflet-grab .leaflet-interactive,
.leaflet-dragging .leaflet-marker-draggable {
cursor: move;
cursor: -webkit-grabbing;
cursor: -moz-grabbing;
cursor: grabbing;
}
/* marker & overlays interactivity */
.leaflet-marker-icon,
.leaflet-marker-shadow,
.leaflet-image-layer,
.leaflet-pane > svg path,
.leaflet-tile-container {
pointer-events: none;
}
.leaflet-marker-icon.leaflet-interactive,
.leaflet-image-layer.leaflet-interactive,
.leaflet-pane > svg path.leaflet-interactive,
svg.leaflet-image-layer.leaflet-interactive path {
pointer-events: visiblePainted; /* IE 9-10 doesn't have auto */
pointer-events: auto;
}
/* visual tweaks */
.leaflet-container {
background: #ddd;
outline-offset: 1px;
}
.leaflet-container a {
color: #0078A8;
}
.leaflet-zoom-box {
border: 2px dotted #38f;
background: rgba(255,255,255,0.5);
}
/* general typography */
.leaflet-container {
font-family: "Helvetica Neue", Arial, Helvetica, sans-serif;
font-size: 12px;
font-size: 0.75rem;
line-height: 1.5;
}
/* general toolbar styles */
.leaflet-bar {
box-shadow: 0 1px 5px rgba(0,0,0,0.65);
border-radius: 4px;
}
.leaflet-bar a {
background-color: #fff;
border-bottom: 1px solid #ccc;
width: 26px;
height: 26px;
line-height: 26px;
display: block;
text-align: center;
text-decoration: none;
color: black;
}
.leaflet-bar a,
.leaflet-control-layers-toggle {
background-position: 50% 50%;
background-repeat: no-repeat;
display: block;
}
.leaflet-bar a:hover,
.leaflet-bar a:focus {
background-color: #f4f4f4;
}
.leaflet-bar a:first-child {
border-top-left-radius: 4px;
border-top-right-radius: 4px;
}
.leaflet-bar a:last-child {
border-bottom-left-radius: 4px;
border-bottom-right-radius: 4px;
border-bottom: none;
}
.leaflet-bar a.leaflet-disabled {
cursor: default;
background-color: #f4f4f4;
color: #bbb;
}
.leaflet-touch .leaflet-bar a {
width: 30px;
height: 30px;
line-height: 30px;
}
.leaflet-touch .leaflet-bar a:first-child {
border-top-left-radius: 2px;
border-top-right-radius: 2px;
}
.leaflet-touch .leaflet-bar a:last-child {
border-bottom-left-radius: 2px;
border-bottom-right-radius: 2px;
}
/* zoom control */
.leaflet-control-zoom-in,
.leaflet-control-zoom-out {
font: bold 18px 'Lucida Console', Monaco, monospace;
text-indent: 1px;
}
.leaflet-touch .leaflet-control-zoom-in, .leaflet-touch .leaflet-control-zoom-out {
font-size: 22px;
}
/* layers control */
.leaflet-control-layers {
box-shadow: 0 1px 5px rgba(0,0,0,0.4);
background: #fff;
border-radius: 5px;
}
.leaflet-control-layers-toggle {
background-image: url(images/layers.png);
width: 36px;
height: 36px;
}
.leaflet-retina .leaflet-control-layers-toggle {
background-image: url(images/layers-2x.png);
background-size: 26px 26px;
}
.leaflet-touch .leaflet-control-layers-toggle {
width: 44px;
height: 44px;
}
.leaflet-control-layers .leaflet-control-layers-list,
.leaflet-control-layers-expanded .leaflet-control-layers-toggle {
display: none;
}
.leaflet-control-layers-expanded .leaflet-control-layers-list {
display: block;
position: relative;
}
.leaflet-control-layers-expanded {
padding: 6px 10px 6px 6px;
color: #333;
background: #fff;
}
.leaflet-control-layers-scrollbar {
overflow-y: scroll;
overflow-x: hidden;
padding-right: 5px;
}
.leaflet-control-layers-selector {
margin-top: 2px;
position: relative;
top: 1px;
}
.leaflet-control-layers label {
display: block;
font-size: 13px;
font-size: 1.08333em;
}
.leaflet-control-layers-separator {
height: 0;
border-top: 1px solid #ddd;
margin: 5px -10px 5px -6px;
}
/* Default icon URLs */
.leaflet-default-icon-path { /* used only in path-guessing heuristic, see L.Icon.Default */
background-image: url(images/marker-icon.png);
}
/* attribution and scale controls */
.leaflet-container .leaflet-control-attribution {
background: #fff;
background: rgba(255, 255, 255, 0.8);
margin: 0;
}
.leaflet-control-attribution,
.leaflet-control-scale-line {
padding: 0 5px;
color: #333;
line-height: 1.4;
}
.leaflet-control-attribution a {
text-decoration: none;
}
.leaflet-control-attribution a:hover,
.leaflet-control-attribution a:focus {
text-decoration: underline;
}
.leaflet-attribution-flag {
display: inline !important;
vertical-align: baseline !important;
width: 1em;
height: 0.6669em;
}
.leaflet-left .leaflet-control-scale {
margin-left: 5px;
}
.leaflet-bottom .leaflet-control-scale {
margin-bottom: 5px;
}
.leaflet-control-scale-line {
border: 2px solid #777;
border-top: none;
line-height: 1.1;
padding: 2px 5px 1px;
white-space: nowrap;
-moz-box-sizing: border-box;
box-sizing: border-box;
background: rgba(255, 255, 255, 0.8);
text-shadow: 1px 1px #fff;
}
.leaflet-control-scale-line:not(:first-child) {
border-top: 2px solid #777;
border-bottom: none;
margin-top: -2px;
}
.leaflet-control-scale-line:not(:first-child):not(:last-child) {
border-bottom: 2px solid #777;
}
.leaflet-touch .leaflet-control-attribution,
.leaflet-touch .leaflet-control-layers,
.leaflet-touch .leaflet-bar {
box-shadow: none;
}
.leaflet-touch .leaflet-control-layers,
.leaflet-touch .leaflet-bar {
border: 2px solid rgba(0,0,0,0.2);
background-clip: padding-box;
}
/* popup */
.leaflet-popup {
position: absolute;
text-align: center;
margin-bottom: 20px;
}
.leaflet-popup-content-wrapper {
padding: 1px;
text-align: left;
border-radius: 12px;
}
.leaflet-popup-content {
margin: 13px 24px 13px 20px;
line-height: 1.3;
font-size: 13px;
font-size: 1.08333em;
min-height: 1px;
}
.leaflet-popup-content p {
margin: 17px 0;
margin: 1.3em 0;
}
.leaflet-popup-tip-container {
width: 40px;
height: 20px;
position: absolute;
left: 50%;
margin-top: -1px;
margin-left: -20px;
overflow: hidden;
pointer-events: none;
}
.leaflet-popup-tip {
width: 17px;
height: 17px;
padding: 1px;
margin: -10px auto 0;
pointer-events: auto;
-webkit-transform: rotate(45deg);
-moz-transform: rotate(45deg);
-ms-transform: rotate(45deg);
transform: rotate(45deg);
}
.leaflet-popup-content-wrapper,
.leaflet-popup-tip {
background: white;
color: #333;
box-shadow: 0 3px 14px rgba(0,0,0,0.4);
}
.leaflet-container a.leaflet-popup-close-button {
position: absolute;
top: 0;
right: 0;
border: none;
text-align: center;
width: 24px;
height: 24px;
font: 16px/24px Tahoma, Verdana, sans-serif;
color: #757575;
text-decoration: none;
background: transparent;
}
.leaflet-container a.leaflet-popup-close-button:hover,
.leaflet-container a.leaflet-popup-close-button:focus {
color: #585858;
}
.leaflet-popup-scrolled {
overflow: auto;
}
.leaflet-oldie .leaflet-popup-content-wrapper {
-ms-zoom: 1;
}
.leaflet-oldie .leaflet-popup-tip {
width: 24px;
margin: 0 auto;
-ms-filter: "progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678)";
filter: progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678);
}
.leaflet-oldie .leaflet-control-zoom,
.leaflet-oldie .leaflet-control-layers,
.leaflet-oldie .leaflet-popup-content-wrapper,
.leaflet-oldie .leaflet-popup-tip {
border: 1px solid #999;
}
/* div icon */
.leaflet-div-icon {
background: #4CAF50;
border: 1px solid #666;
width: 7px !important;
height: 7px !important;
margin-left: -5.5px !important;
margin-top: -5.5px !important;
border-radius: 10px;
}
/* Tooltip */
/* Base styles for the element that has a tooltip */
.leaflet-tooltip {
position: absolute;
padding: 6px;
background-color: #fff;
border: 1px solid #fff;
border-radius: 3px;
color: #222;
white-space: nowrap;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
pointer-events: none;
box-shadow: 0 1px 3px rgba(0,0,0,0.4);
}
.leaflet-tooltip.leaflet-interactive {
cursor: pointer;
pointer-events: auto;
}
.leaflet-tooltip-top:before,
.leaflet-tooltip-bottom:before,
.leaflet-tooltip-left:before,
.leaflet-tooltip-right:before {
position: absolute;
pointer-events: none;
border: 6px solid transparent;
background: transparent;
content: "";
}
/* Directions */
.leaflet-tooltip-bottom {
margin-top: 6px;
}
.leaflet-tooltip-top {
margin-top: -6px;
}
.leaflet-tooltip-bottom:before,
.leaflet-tooltip-top:before {
left: 50%;
margin-left: -6px;
}
.leaflet-tooltip-top:before {
bottom: 0;
margin-bottom: -12px;
border-top-color: #fff;
}
.leaflet-tooltip-bottom:before {
top: 0;
margin-top: -12px;
margin-left: -6px;
border-bottom-color: #fff;
}
.leaflet-tooltip-left {
margin-left: -6px;
}
.leaflet-tooltip-right {
margin-left: 6px;
}
.leaflet-tooltip-left:before,
.leaflet-tooltip-right:before {
top: 50%;
margin-top: -6px;
}
.leaflet-tooltip-left:before {
right: 0;
margin-right: -12px;
border-left-color: #fff;
}
.leaflet-tooltip-right:before {
left: 0;
margin-left: -12px;
border-right-color: #fff;
}
/* Printing */
@media print {
/* Prevent printers from removing background-images of controls. */
.leaflet-control {
-webkit-print-color-adjust: exact;
print-color-adjust: exact;
}
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,11 @@
function makeid(length) {
let result = '';
const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
const charactersLength = characters.length;
let counter = 0;
while (counter < length) {
result += characters.charAt(Math.floor(Math.random() * charactersLength));
counter += 1;
}
return result;
}

1
web/lib/components/qrcode.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,40 @@
// Скрипт перезагрузки страници свайпом.
let app_scroll = false;
let animID = document.getElementById('swipe_updater');
let animIconID = document.getElementById('swipe_icon');
window.addEventListener('scroll', function(e) {
if (window.matchMedia('(display-mode: standalone)').matches) {
let a = window.scrollY;
let b = 50;
let c = 125;
a = -a;
a = a;
animIconID.style.top = a/1.5;
console.log(window.scrollY);
if(window.scrollY <= -10){
animID.style.zIndex = 115;
} else {
animID.style.zIndex = 0;
}
if(window.scrollY <= -120){
if(app_scroll == false){
app_scroll = true;
animIconID.style.transform = 'rotate(180deg)';
animIconID.setAttribute('data-state', '')
}
} else if(window.scrollY >= 0){
if(app_scroll == true){
appReload();
app_scroll = false;
animIconID.style.transform = 'rotate(0deg)';
animIconID.setAttribute('data-state', 'active')
}
}
}
});

88
web/lib/components/turf.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,175 @@
<style>
*[disabled] {
opacity: 0.5;
}
select {
-moz-appearance: none;
-webkit-appearance: none;
appearance: none;
/* Arrow */
appearance: none;
background-image: url("data:image/svg+xml;charset=US-ASCII,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%22292.4%22%20height%3D%22292.4%22%3E%3Cpath%20fill%3D%22%237a899d%22%20d%3D%22M287%2069.4a17.6%2017.6%200%200%200-13-5.4H18.4c-5%200-9.3%201.8-12.9%205.4A17.6%2017.6%200%200%200%200%2082.2c0%205%201.8%209.3%205.4%2012.9l128%20127.9c3.6%203.6%207.8%205.4%2012.8%205.4s9.2-1.8%2012.8-5.4L287%2095c3.5-3.5%205.4-7.8%205.4-12.8%200-5-1.9-9.2-5.5-12.8z%22%2F%3E%3C%2Fsvg%3E");
background-repeat: no-repeat;
background-position: right 0.3rem top 50%;
background-size: 0.55rem auto;
}
#header {
display: flex;
justify-content: space-around;
}
#header p {
font-size: 20px;
}
#list {
display: flex;
flex-direction: column;
align-items: center;
}
details {
color: var(--ColorThemes3);
width: 100%;
min-width: 320px;
background: var(--ColorThemes1);
margin: 20px 0px;
border-radius: 10px;
overflow: hidden;
box-shadow: 0px 2px 4px rgba(0, 0, 0, 0.04), 0px 0px 2px rgba(0, 0, 0, 0.04),
0px 0px 1px rgba(0, 0, 0, 0.04);
}
@media (min-width: 900px) {
#list {
align-items: flex-start;
justify-content: space-between;
flex-direction: row;
flex-wrap: wrap;
}
details {
width: calc(50% - 10px);
}
}
details[disabled] summary,
details.disabled summary {
pointer-events: none;
user-select: none;
}
details summary::-webkit-details-marker,
details summary::marker {
display: none;
content: "";
}
summary {
cursor: pointer;
background: #7a8a9d;
background: var(--PrimaryColor);
color: var(--PrimaryColorText);
height: 45px;
font-weight: 500;
display: flex;
align-items: center;
justify-content: space-between;
}
summary p {
padding: 0 10px;
color: var(--PrimaryColorText);
font-size: 14px;
font-weight: 500;
}
summary svg {
width: 25px;
height: 25px;
padding: 0 10px;
fill: var(--PrimaryColorText);
}
.apartments_list {
padding: 10px;
border-bottom: 2px solid var(--PrimaryColor);
border-left: 2px solid var(--PrimaryColor);
border-right: 2px solid var(--PrimaryColor);
border-radius: 0 0 10px 10px;
}
@media (max-width: 500px) {
.apartments_list {
padding: 1px;
}
}
.apartments_list > div {
display: flex;
font-size: 14px;
border-radius: 8px;
margin: 10px 10px 15px 10px;
flex-direction: column;
align-items: stretch;
border: 1px solid var(--ColorThemes3);
background: var(--ColorThemes2);
}
.apartments_list > div > .info {
display: flex;
font-size: 14px;
align-items: center;
justify-content: space-between;
border-radius: 8px;
}
.apartments_list > div > .info > span {
min-width: 40px;
font-size: 12px;
position: relative;
margin: 5px;
}
.apartments_list > div > .info > select {
color: #3d3d3d;
border-radius: 6px;
border: 1px solid #eaebef;
margin: 5px;
background-color: var(--ColorThemes3);
min-width: 110px;
width: 100%;
padding: 4px 20px 4px 4px;
height: 30px;
}
.apartments_list > div > .info > input {
color: #3d3d3d;
border-radius: 6px;
border: 1px solid #eaebef;
margin: 5px;
background-color: var(--ColorThemes3);
background-color: var(--ColorThemes0);
border: 1px solid var(--ColorThemes1);
color: var(--ColorThemes3);
width: 100%;
min-width: 70px;
padding: 0 4px;
height: calc(30px - 2px);
appearance: none;
-webkit-appearance: none;
}
.apartments_list > div > textarea {
border-radius: 6px;
font-size: 14px;
margin: 5px;
background-color: var(--ColorThemes0);
border: 1px solid var(--ColorThemes1);
color: var(--ColorThemes3);
width: calc(100% - 22px);
min-width: 70px;
padding: 5px;
min-height: 40px;
appearance: none;
resize: vertical;
-webkit-appearance: none;
}
</style>
<div class="page-card">
<div id="header">
<p id="status"></p>
<p id="hash"></p>
</div>
<div id="list"></div>
</div>

View File

@@ -0,0 +1,358 @@
let socket, username;
let listEntrances = []
let listApartment = []
let holdTimer;
let startTime;
const Card = {
init: async (type, id) => {
let html = await fetch('/lib/pages/card/index.html').then((response) => response.text());
app.innerHTML = html;
house = id;
if (socket) socket.close(1000, "Перезапуск соединения");
if (type == "house") {
getEntrances();
start(makeid(6));
}
document.addEventListener("mousedown", handleStart);
document.addEventListener("touchstart", handleStart);
document.addEventListener("mouseup", handleCancel);
document.addEventListener("mouseleave", handleCancel);
document.addEventListener("touchend", handleCancel);
document.addEventListener("touchcancel", handleCancel);
function handleStart(event) {
const button = event.target.closest(".hold-button");
if (!button) return;
// event.preventDefault();
startTime = Date.now();
holdTimer = setTimeout(() => {
console.log("Долгое нажатие на", button.name);
let number_id = button.name.split("-");
mess(Number(number_id[0]), Number(number_id[1]));
}, 1000);
}
function handleCancel() {
const holdDuration = Date.now() - startTime; // Считаем, сколько длилось нажатие
if (holdDuration < 1000) {
clearTimeout(holdTimer); // Если нажали менее 1 секунды, сбрасываем таймер
}
}
}
}
// let color_status = [
// "#000000",
// "#C16917",
// "#b10202",
// "#3d3d3d",
// "#11734b",
// "#6cc5fc",
// "#5a3286"
// ];
// let color_status = [
// ["#ffffff", "#000000"],
// ["#e7af32", "#ffffff"],
// ["#fc2a2a", "#ffffff"],
// ["#3d3d3d", "#ffffff"],
// ["#11a568", "#ffffff"],
// ["#6cc5fc", "#ffffff"],
// ["#b381eb", "#ffffff"]
// ];
let color_status = [
["var(--ColorThemes2)", "var(--ColorThemes3)"],
["#fbf1e0", "#ff8300"],
["#fce3e2", "#ff0000"],
["#d7ddec", "#2919bd"],
["#d5e9dd", "#11a568"],
["#d7ebfa", "#3fb4fc"],
["#e8dbf5", "#b381eb"]
];
function start(name) {
if (!name) return;
document.getElementById("hash").innerText = `HASH: ${name}`
username = name;
let uuid = localStorage.getItem("uuid");
socket = new WebSocket(`${CONFIG.wss}?uuid=${uuid}`);
socket.onopen = function (e) {
console.log("[WebSocket | open] Соединение установлено");
document.getElementById("status").innerText = "WebSocket | open";
const message = {
event: 'connection',
id: getTimeInSeconds(),
date: getTimeInSeconds(),
uuid: uuid,
username: name,
data: {
id: 1,
entrance_id: 1,
apartment_number: 1,
title: "1",
group_number: 1,
status: 1,
description: "",
created_at: 1727541827,
updated_at: 1727541827
}
}
socket.send(JSON.stringify(message))
};
socket.onmessage = function (event) {
let data = JSON.parse(event.data)
if (data.event == 'connection') {
if (data.username == username) return
console.log(`Добавлен новый пользователь по имени ${data.username}`);
} else if (data.event == 'message') {
update(data);
if (data.username == username) return
console.log(`${data.username} пишет: `, data.data);
}
};
socket.onclose = function (event) {
if (event.wasClean) {
document.getElementById("status").innerText = "WebSocket | close"
console.log(`[WebSocket | close] Соединение закрыто чисто, код=${event.code} причина=${event.reason}`);
} else {
document.getElementById("status").innerText = "WebSocket | close"
console.log('[WebSocket | close] Соединение прервано');
// setTimeout(function() {
// start(username);
// }, 1000);
const result = confirm(`З'єднання розірвано! Перепідключитись?`);
if (result) {
getEntrances();
start(username);
}
}
};
socket.onerror = function (error) {
console.log(`[WebSocket | error]`);
document.getElementById("status").innerText = "WebSocket | error"
};
}
function mess(entrance_number, id, date_type) {
let sort_mode = localStorage.getItem('sort_mode') ?? true;
console.log(id, listApartment[entrance_number]);
const pos = listApartment[entrance_number].map(e => e.id).indexOf(id);
let apartment = listApartment[entrance_number][pos];
console.log(pos, apartment);
let status = document.getElementById(`status_${id}`);
let description = document.getElementById(`description_${id}`);
let date = new Date(document.getElementById(`date_${id}`).value);
const timestamp = date.getTime();
apartment.description = description.value;
apartment.status = Number(status.value);
apartment.updated_at = date_type ? getTimeInSeconds(timestamp) : getTimeInSeconds(),
status.style.backgroundColor = color_status[status.value][0];
status.style.color = color_status[status.value][1];
status.style.border = `1px solid ${color_status[status.value][1]}`;
let user_hash = localStorage.getItem('hash');
let message = {
event: 'message',
id: getTimeInSeconds(),
date: getTimeInSeconds(),
hash: user_hash,
username: username,
data: {
id: apartment.id,
entrance_id: apartment.entrance_id,
apartment_number: apartment.apartment_number,
title: apartment.title,
group_number: apartment.group_number,
status: apartment.status,
description: apartment.description,
updated_at: apartment.updated_at,
}
}
socket.send(JSON.stringify(message));
if (!date_type && sort_mode != 'false') sort(apartment.id, apartment.entrance_id);
}
function update(message) {
if (!document.getElementById(`status_${message.data.id}`)) return;
let now = new Date(message.data.updated_at);
now.setMinutes(now.getMinutes() - now.getTimezoneOffset());
document.getElementById(`card_${message.data.id}`).style.backgroundColor = color_status[message.data.status][0];
document.getElementById(`card_${message.data.id}`).style.color = color_status[message.data.status][1];
document.getElementById(`card_${message.data.id}`).style.border = `1px solid ${color_status[message.data.status][1]}`;
document.getElementById(`status_${message.data.id}`).style.backgroundColor = color_status[message.data.status][0];
document.getElementById(`status_${message.data.id}`).style.color = color_status[message.data.status][1];
document.getElementById(`status_${message.data.id}`).style.border = `1px solid ${color_status[message.data.status][1]}`;
document.getElementById(`status_${message.data.id}`).value = message.data.status;
document.getElementById(`description_${message.data.id}`).value = message.data.description;
document.getElementById(`date_${message.data.id}`).value = now.toISOString().slice(0, 16);
}
function getEntrances(house_id = house) {
const uuid = localStorage.getItem('uuid');
const URL = `${CONFIG.api}/house/${house_id}/entrances`;
fetch(URL, {
method: 'GET',
headers: {
"Content-Type": "application/json",
"Authorization": uuid
}
})
.then(function (response) {
return response.json();
})
.then(function (data) {
listEntrances = data;
document.getElementById('list').innerHTML = "";
for (let i = 0; i < listEntrances.length; i++) {
const element = listEntrances[i];
let status = () => {
if ((element.history.name == "Групова" || element.history.name == USER.name) && element.working) return "open";
else if (USER.administrator.uuid || (USER.moderator.uuid && USER.moderator.can_manager_territory)) return "close";
return "style='display: none;'"
}
let statusIcon = () => {
if ((element.history.name == "Групова" || element.history.name == USER.name) && element.working) return '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M 12 1 C 9.1277778 1 6.7189086 3.0461453 6.1230469 5.7871094 L 8.078125 6.2128906 C 8.4822632 4.3538547 10.072222 3 12 3 C 14.27619 3 16 4.7238095 16 7 L 16 8 L 6 8 C 4.9069372 8 4 8.9069372 4 10 L 4 20 C 4 21.093063 4.9069372 22 6 22 L 18 22 C 19.093063 22 20 21.093063 20 20 L 20 10 C 20 8.9069372 19.093063 8 18 8 L 18 7 C 18 3.6761905 15.32381 1 12 1 z M 6 10 L 18 10 L 18 20 L 6 20 L 6 10 z M 12 13 C 10.9 13 10 13.9 10 15 C 10 16.1 10.9 17 12 17 C 13.1 17 14 16.1 14 15 C 14 13.9 13.1 13 12 13 z"/></svg>'
else return '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"> <path d="M 12 1 C 8.6761905 1 6 3.6761905 6 7 L 6 8 C 4.9069372 8 4 8.9069372 4 10 L 4 20 C 4 21.093063 4.9069372 22 6 22 L 18 22 C 19.093063 22 20 21.093063 20 20 L 20 10 C 20 8.9069372 19.093063 8 18 8 L 18 7 C 18 3.6761905 15.32381 1 12 1 z M 12 3 C 14.27619 3 16 4.7238095 16 7 L 16 8 L 8 8 L 8 7 C 8 4.7238095 9.7238095 3 12 3 z M 6 10 L 18 10 L 18 20 L 6 20 L 6 10 z M 12 13 C 10.9 13 10 13.9 10 15 C 10 16.1 10.9 17 12 17 C 13.1 17 14 16.1 14 15 C 14 13.9 13.1 13 12 13 z"/></svg>'
}
document.getElementById('list').innerHTML += `
<details ${status()}>
<summary>
<p>${element.title}</p>
${statusIcon()}
</summary>
<div id="apartments_${element.id}" class="apartments_list">
</div>
</details>
`;
getApartment(element.id, element.entrance_number);
console.log(element);
}
})
}
function getApartment(entrance_id, entrance_number) {
const uuid = localStorage.getItem('uuid');
const URL = `${CONFIG.api}/apartment/${entrance_id}`;
fetch(URL, {
method: 'GET',
headers: {
"Content-Type": "application/json",
"Authorization": uuid
}
})
.then(function (response) {
return response.json();
})
.then(function (data) {
listApartment[entrance_number] = data;
data.sort((a, b) => a.apartment_number - b.apartment_number);
data.sort((a, b) => a.updated_at - b.updated_at);
for (let i = 0; i < data.length; i++) {
const element = data[i];
let now = new Date(element.updated_at);
now.setMinutes(now.getMinutes() - now.getTimezoneOffset());
now = now.toISOString().slice(0, 16)
let disabled = () => {
if (USER.administrator.uuid || (USER.moderator.uuid && USER.moderator.can_manager_territory)) return '';
else if (element.status == 2) return "disabled";
}
document.getElementById(`apartments_${entrance_id}`).innerHTML += `
<div id="card_${element.id}" style="border: 1px solid ${color_status[element.status][1]};background: ${color_status[element.status][0]};color: ${color_status[element.status][1]};">
<div class="info">
<span>кв.${element.title}</span>
<select id="status_${element.id}" onchange="mess(${entrance_number}, ${element.id})" style="background-color: ${color_status[element.status][0]}; color: ${color_status[element.status][1]}; border: 1px solid ${color_status[element.status][1]};" ${disabled()}>
<option value="0" ${element.status == 0 ? "selected" : ""}></option>
<option value="1" ${element.status == 1 ? "selected" : ""}>Відмова</option>
<option value="2" ${element.status == 2 ? "selected" : ""}>Не заходити (Груба відмова)</option>
<option value="3" ${element.status == 3 ? "selected" : ""}>Нема домофона</option>
<option value="4" ${element.status == 4 ? "selected" : ""}>Повторна відвідина</option>
<option value="5" ${element.status == 5 ? "selected" : ""}>Немає вдома</option>
<option value="6" ${element.status == 6 ? "selected" : ""}>Свідки Єгови</option>
</select>
<input onchange="mess(${entrance_number}, ${element.id}, true)" name="${entrance_number}-${element.id}" class="hold-button" type="datetime-local" id="date_${element.id}" placeholder="Дата" value="${element.updated_at ? now : ""}" ${disabled()} style="max-width: 170px;">
</div>
<textarea onchange="mess(${entrance_number}, ${element.id}, true)" id="description_${element.id}" placeholder="Нотатки..." ${disabled()}}>${element.description ?? ""}</textarea>
</div>
`;
}
})
}
function sort(id, entrance_id) {
let child = document.getElementById(`card_${id}`);
document.getElementById(`apartments_${entrance_id}`).removeChild(child);
document.getElementById(`apartments_${entrance_id}`).append(child);
child.style.border = "1px solid var(--PrimaryColor)";
}
function getTimeInSeconds(time = Date.now()) {
// Если время больше 10 знаков (это значит, что время в миллисекундах)
if (time.toString().length < 10) {
// Округляем до секунд, убирая последние 3 цифры (миллисекунды)
time = Math.floor(time * 1000);
}
return time;
}

View File

@@ -0,0 +1,6 @@
.page-card {
width: calc(100% - 40px);
display: flex;
flex-direction: column;
margin: 20px 20px 0 20px;
}

View File

@@ -0,0 +1,192 @@
<div class="page-constructor">
<details id="details-info" open>
<summary>
<span>Крок 1.</span> Інформація про будинок, територію, точки на карті
</summary>
<form id="info-form">
<div id="details-info-type">
<div class="tabs">
<input
type="radio"
id="info-type-house"
value="house"
name="info-type"
checked
/>
<label class="tab" for="info-type-house">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48">
<path
d="M 14.5 6 A 1.50015 1.50015 0 0 0 13 7.5 L 13 25 L 7.5 25 A 1.50015 1.50015 0 0 0 6 26.5 L 6 42.5 A 1.50015 1.50015 0 1 0 9 42.5 L 9 28 L 14.253906 28 A 1.50015 1.50015 0 0 0 14.740234 28 L 19 28 L 19 42.5 A 1.50015 1.50015 0 1 0 22 42.5 L 22 26.746094 A 1.50015 1.50015 0 0 0 22 26.259766 L 22 22 L 33.253906 22 A 1.50015 1.50015 0 0 0 33.740234 22 L 39 22 L 39 42.5 A 1.50015 1.50015 0 1 0 42 42.5 L 42 20.5 A 1.50015 1.50015 0 0 0 40.5 19 L 35 19 L 35 7.5 A 1.50015 1.50015 0 0 0 33.5 6 L 14.5 6 z M 16 9 L 32 9 L 32 19 L 20.5 19 A 1.50015 1.50015 0 0 0 19 20.5 L 19 25 L 16 25 L 16 9 z M 20 12 C 19.448 12 19 12.448 19 13 L 19 15 C 19 15.552 19.448 16 20 16 L 22 16 C 22.552 16 23 15.552 23 15 L 23 13 C 23 12.448 22.552 12 22 12 L 20 12 z M 26 12 C 25.448 12 25 12.448 25 13 L 25 15 C 25 15.552 25.448 16 26 16 L 28 16 C 28.552 16 29 15.552 29 15 L 29 13 C 29 12.448 28.552 12 28 12 L 26 12 z M 26 25 C 25.448 25 25 25.448 25 26 L 25 28 C 25 28.552 25.448 29 26 29 L 28 29 C 28.552 29 29 28.552 29 28 L 29 26 C 29 25.448 28.552 25 28 25 L 26 25 z M 33 25 C 32.448 25 32 25.448 32 26 L 32 28 C 32 28.552 32.448 29 33 29 L 35 29 C 35.552 29 36 28.552 36 28 L 36 26 C 36 25.448 35.552 25 35 25 L 33 25 z M 13 31 C 12.448 31 12 31.448 12 32 L 12 34 C 12 34.552 12.448 35 13 35 L 15 35 C 15.552 35 16 34.552 16 34 L 16 32 C 16 31.448 15.552 31 15 31 L 13 31 z M 26 31 C 25.448 31 25 31.448 25 32 L 25 34 C 25 34.552 25.448 35 26 35 L 28 35 C 28.552 35 29 34.552 29 34 L 29 32 C 29 31.448 28.552 31 28 31 L 26 31 z M 33 31 C 32.448 31 32 31.448 32 32 L 32 34 C 32 34.552 32.448 35 33 35 L 35 35 C 35.552 35 36 34.552 36 34 L 36 32 C 36 31.448 35.552 31 35 31 L 33 31 z M 13 37 C 12.448 37 12 37.448 12 38 L 12 40 C 12 40.552 12.448 41 13 41 L 15 41 C 15.552 41 16 40.552 16 40 L 16 38 C 16 37.448 15.552 37 15 37 L 13 37 z M 26 37 C 25.448 37 25 37.448 25 38 L 25 40 C 25 40.552 25.448 41 26 41 L 28 41 C 28.552 41 29 40.552 29 40 L 29 38 C 29 37.448 28.552 37 28 37 L 26 37 z M 33 37 C 32.448 37 32 37.448 32 38 L 32 40 C 32 40.552 32.448 41 33 41 L 35 41 C 35.552 41 36 40.552 36 40 L 36 38 C 36 37.448 35.552 37 35 37 L 33 37 z"
/>
</svg>
<span>Багатоквартирний будинок</span>
</label>
<input
type="radio"
id="info-type-homestead"
value="homestead"
name="info-type"
/>
<label class="tab" for="info-type-homestead">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48">
<path
d="M 24 5.015625 C 22.851301 5.015625 21.70304 5.3892757 20.753906 6.1367188 A 1.50015 1.50015 0 0 0 20.751953 6.1367188 L 8.859375 15.509766 C 7.0558128 16.931133 6 19.102989 6 21.400391 L 6 39.488281 C 6 41.403236 7.5850452 42.988281 9.5 42.988281 L 38.5 42.988281 C 40.414955 42.988281 42 41.403236 42 39.488281 L 42 21.400391 C 42 19.102989 40.944187 16.931133 39.140625 15.509766 L 39 15.396484 L 39 7.5 A 1.50015 1.50015 0 0 0 37.5 6 L 32.5 6 A 1.50015 1.50015 0 0 0 31 7.5 L 31 9.09375 L 27.246094 6.1367188 C 26.29696 5.3892758 25.148699 5.015625 24 5.015625 z M 24 8.0078125 C 24.489801 8.0078125 24.979759 8.1705836 25.390625 8.4941406 L 31.572266 13.363281 A 1.50015 1.50015 0 0 0 34 12.185547 L 34 9 L 36 9 L 36 16.125 A 1.50015 1.50015 0 0 0 36.572266 17.302734 L 37.285156 17.865234 C 38.369594 18.719867 39 20.019792 39 21.400391 L 39 39.488281 C 39 39.783326 38.795045 39.988281 38.5 39.988281 L 9.5 39.988281 C 9.2049548 39.988281 9 39.783326 9 39.488281 L 9 21.400391 C 9 20.019792 9.6304058 18.719867 10.714844 17.865234 L 22.609375 8.4941406 C 23.020241 8.1705836 23.510199 8.0078125 24 8.0078125 z M 14.5 23.988281 A 1.50015 1.50015 0 0 0 13 25.488281 L 13 33.488281 A 1.50015 1.50015 0 0 0 14.5 34.988281 L 20.5 34.988281 A 1.50015 1.50015 0 0 0 22 33.488281 L 22 25.488281 A 1.50015 1.50015 0 0 0 20.5 23.988281 L 14.5 23.988281 z M 27.5 23.988281 A 1.50015 1.50015 0 0 0 26 25.488281 L 26 33.488281 A 1.50015 1.50015 0 0 0 27.5 34.988281 L 33.5 34.988281 A 1.50015 1.50015 0 0 0 35 33.488281 L 35 25.488281 A 1.50015 1.50015 0 0 0 33.5 23.988281 L 27.5 23.988281 z M 16 26.988281 L 19 26.988281 L 19 31.988281 L 16 31.988281 L 16 26.988281 z M 29 26.988281 L 32 26.988281 L 32 31.988281 L 29 31.988281 L 29 26.988281 z"
/>
</svg>
<span>Житловий район</span>
</label>
<input
type="radio"
id="info-type-points"
value="points"
name="info-type"
/>
<label class="tab" for="info-type-points">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48">
<path
d="M 24 4 C 16.285455 4 10 10.285455 10 18 C 10 21.46372 11.272608 24.643548 13.359375 27.085938 L 13.359375 27.087891 L 13.361328 27.087891 C 13.361328 27.087891 19.149094 33.866566 21.298828 35.917969 C 22.798087 37.348278 25.199464 37.347492 26.699219 35.917969 C 29.129083 33.600994 34.636721 27.090553 34.640625 27.085938 L 34.642578 27.082031 C 36.728766 24.639939 38 21.462159 38 18 C 38 10.285455 31.714545 4 24 4 z M 24 7 C 30.093455 7 35 11.906545 35 18 C 35 20.73228 34.005417 23.211194 32.359375 25.136719 L 32.359375 25.138672 L 32.357422 25.138672 C 32.357422 25.138672 26.632181 31.83589 24.628906 33.746094 C 24.258577 34.099392 23.73947 34.099392 23.369141 33.746094 C 21.715477 32.16807 15.643092 25.141834 15.638672 25.136719 L 15.636719 25.132812 C 13.99327 23.20762 13 20.730712 13 18 C 13 11.906545 17.906545 7 24 7 z M 24 12 C 22.125 12 20.528815 12.757133 19.503906 13.910156 C 18.478997 15.063179 18 16.541667 18 18 C 18 19.458333 18.478997 20.936821 19.503906 22.089844 C 20.528815 23.242867 22.125 24 24 24 C 25.875 24 27.471185 23.242867 28.496094 22.089844 C 29.521003 20.936821 30 19.458333 30 18 C 30 16.541667 29.521003 15.063179 28.496094 13.910156 C 27.471185 12.757133 25.875 12 24 12 z M 24 15 C 25.124999 15 25.778816 15.367867 26.253906 15.902344 C 26.728997 16.436821 27 17.208333 27 18 C 27 18.791667 26.728997 19.563179 26.253906 20.097656 C 25.778816 20.632133 25.124999 21 24 21 C 22.875001 21 22.221184 20.632133 21.746094 20.097656 C 21.271003 19.563179 21 18.791667 21 18 C 21 17.208333 21.271003 16.436821 21.746094 15.902344 C 22.221184 15.367867 22.875001 15 24 15 z M 12.771484 29.441406 C 8.2264844 30.754406 5 32.953 5 36 C 5 41.252 14.558 44 24 44 C 33.442 44 43 41.252 43 36 C 43 32.954 39.775422 30.757359 35.232422 29.443359 C 34.654422 30.099359 33.863187 30.993844 32.992188 31.964844 C 37.418188 33.005844 40 34.691 40 36 C 40 38.039 33.767 41 24 41 C 14.233 41 8 38.039 8 36 C 8 34.69 10.586531 33.001938 15.019531 31.960938 C 14.152531 30.995938 13.355484 30.100406 12.771484 29.441406 z"
/>
</svg>
<span>Точки на карті</span>
</label>
<span class="glider"></span>
</div>
</div>
<div id="details-info-address" class="details-info-input">
<label for="info-address-title">Назва вулиці</label>
<input
type="text"
id="info-address-title"
name="address"
required
value=""
/>
</div>
<div id="details-info-number" class="details-info-input">
<label for="info-number-title">Номер будинку</label>
<input
type="text"
id="info-number-title"
name="number"
required
value=""
/>
</div>
<div id="details-info-settlement" class="details-info-input">
<label for="info-settlement-title">Місто</label>
<input
type="text"
id="info-settlement-title"
name="settlement"
required
value="Тернопіль"
/>
</div>
<div id="details-info-group" class="details-info-input">
<label for="info-group-title">Теократична група</label>
<input
type="number"
id="info-group-title"
name="group"
required
value=""
/>
</div>
<div id="details-info-osm" class="details-info-input">
<label for="info-settlement-title">OSM iD</label>
<div>
<input
type="text"
id="info-osm-title"
name="osm"
placeholder="123, 345, 678"
required
value=""
/>
<a href="https://www.openstreetmap.org/#map=19/49.561725/25.604458" target="_blank" title="Де знайти OSM iD ?"
><svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 48 48"
width="100px"
height="100px"
>
<path
d="M 24 4 C 12.972066 4 4 12.972074 4 24 C 4 35.027926 12.972066 44 24 44 C 35.027934 44 44 35.027926 44 24 C 44 12.972074 35.027934 4 24 4 z M 24 7 C 33.406615 7 41 14.593391 41 24 C 41 33.406609 33.406615 41 24 41 C 14.593385 41 7 33.406609 7 24 C 7 14.593391 14.593385 7 24 7 z M 24 14 A 2 2 0 0 0 24 18 A 2 2 0 0 0 24 14 z M 23.976562 20.978516 A 1.50015 1.50015 0 0 0 22.5 22.5 L 22.5 33.5 A 1.50015 1.50015 0 1 0 25.5 33.5 L 25.5 22.5 A 1.50015 1.50015 0 0 0 23.976562 20.978516 z"
/></svg
></a>
</div>
</div>
<button id="info-form-button" type="submit">Далі</button>
</form>
</details>
<details id="details-map" disabled>
<summary id="details-map-title">
<span>Крок 2.</span> Створення підїздів
</summary>
<form id="map-form">
<div class="editor-buttons" id="details-map-buttons-entranse">
<button type="button" onclick="Constructor.editor.drawEntranse()">
Створити новий під'їзд
</button>
<div>
<button type="button" id="ruler" onclick="Constructor.editor.drawRectangle()">
Лінійка
</button>
<button
type="button"
id="ruler_divide"
onclick="Constructor.editor.ruler()"
style="display: none"
>
Поділити
</button>
</div>
</div>
<div
class="editor-buttons"
id="details-map-buttons-homestead"
style="display: none"
>
<div>
<button type="button" id="ruler" onclick="Constructor.editor.drawRectangle()">
Лінійка
</button>
<button
type="button"
id="ruler_divide"
onclick="Constructor.editor.ruler()"
style="display: none"
>
Поділити
</button>
</div>
</div>
<div id="list-entranse" style="display: none"></div>
<div id="list-homestead" style="display: none"></div>
<div class="block-map">
<div id="map"></div>
</div>
<button id="map-form-button" type="submit">Далі</button>
</form>
</details>
<details id="details-area" disabled>
<summary><span>Крок 3.</span> Конструктор квартир <input type="number" value="1" id="next-apartment-title" onchange="Constructor.apartments.editNum(this)" title="Авто-номер наступної квартири"></summary>
<form id="area-form">
<div id="list-area"></div>
<button id="area-form-button" type="submit">Зберегти</button>
</form>
</details>
</div>

View File

@@ -0,0 +1,731 @@
let map, houseGroup, entransePolygonsGroup, entranseNumPolygonsGroup, splitPolygonsGroup, RectangleGroup;
let numApartments = 1;
let mode = '';
const Constructor = {
info: {
type: null,
group_id: null,
title: null,
number: null,
points: [],
points_number: [],
point_icons: [],
geo: [],
osm_id: [],
settlement: [],
description: null,
entrance: [],
apartments: {},
zoom: null
},
init: async () => {
let html = await fetch('/lib/pages/constructor/index.html').then((response) => response.text());
app.innerHTML = html;
map = "";
map = "";
houseGroup = "";
entransePolygonsGroup = "";
entranseNumPolygonsGroup = "";
splitPolygonsGroup = "";
RectangleGroup = "";
numApartments = 1;
Constructor.apartments.init();
document.querySelectorAll('input[name="info-type"]').forEach(radio => {
radio.addEventListener('change', event => {
console.log(`Выбран: ${event.target.value}`);
Constructor.info.type = event.target.value;
let detailsInfo_number = document.getElementById('details-info-number');
let detailsInfo_osm = document.getElementById('details-info-osm');
let detailsInfo_number_title = document.getElementById('info-number-title');
let detailsInfo_osm_title = document.getElementById('info-osm-title');
let detailsMap_title = document.getElementById('details-map-title');
let detailsMap_buttons_entranse = document.getElementById('details-map-buttons-entranse');
let detailsMap_buttons_homestead = document.getElementById('details-map-buttons-homestead');
let detailsMap_button = document.getElementById('map-form-button');
let detailsArea = document.getElementById('details-area');
switch (event.target.value) {
case 'points':
detailsInfo_number.style.display = "none";
detailsInfo_osm.style.display = "none";
detailsInfo_number_title.removeAttribute("required");
detailsInfo_osm_title.removeAttribute("required");
detailsMap_title.innerHTML = "<span>Крок 2.</span> Створення точок на карті"
detailsMap_buttons_entranse.style.display = "none";
detailsMap_buttons_homestead.style.display = "none";
detailsMap_button.innerText = "Зберегти";
detailsArea.style.display = "none";
break;
case 'homestead':
detailsInfo_number.style.display = "";
detailsInfo_osm.style.display = "";
detailsInfo_number_title.setAttribute("required", "");
detailsInfo_osm_title.setAttribute("required", "");
detailsMap_title.innerHTML = "<span>Крок 2.</span> Створення житлових територій"
detailsMap_buttons_entranse.style.display = "none";
detailsMap_buttons_homestead.style.display = "";
detailsMap_button.innerText = "Зберегти";
detailsArea.style.display = "none";
break;
default:
detailsInfo_number.style.display = "";
detailsInfo_osm.style.display = "";
detailsInfo_number_title.setAttribute("required", "");
detailsInfo_osm_title.setAttribute("required", "");
detailsMap_title.innerHTML = "<span>Крок 2.</span> Створення підʼїздів"
detailsMap_buttons_entranse.style.display = "";
detailsMap_buttons_homestead.style.display = "none";
detailsMap_button.innerText = "Далі";
detailsArea.style.display = "";
break;
}
});
});
document.getElementById("info-form").addEventListener("submit", function (event) {
event.preventDefault();
let details_map = document.getElementById("details-map");
details_map.removeAttribute("disabled");
details_map.open = true;
const infoForm = document.getElementById("info-form");
let osm = () => {
const a = document.getElementById("info-osm-title").value;
const b = a.replace(/\s+/g, "").split(',')
return b;
}
Constructor.info.type = infoForm.querySelector('input[name="info-type"]:checked').value;
Constructor.info.title = document.getElementById("info-address-title").value;
Constructor.info.number = document.getElementById("info-number-title").value;
Constructor.info.settlement = document.getElementById("info-settlement-title").value;
Constructor.info.group_id = Number(document.getElementById("info-group-title").value);
Constructor.info.osm_id = osm();
Constructor.osm.init();
Constructor.osm.setMap();
})
document.getElementById("map-form").addEventListener("submit", async function (event) {
event.preventDefault();
let details_area = document.getElementById("details-area");
details_area.removeAttribute("disabled");
details_area.open = true;
switch (Constructor.info.type) {
case 'points':
break;
case 'homestead':
await Constructor.api.setPack();
break;
default:
let details_area = document.getElementById("details-area");
details_area.removeAttribute("disabled");
details_area.open = true;
break;
}
})
document.getElementById("area-form").addEventListener("submit", async function (event) {
event.preventDefault();
await Constructor.api.setPack();
})
},
apartments: {
init: () => {
let listArea = document.getElementById('list-area');
listArea.innerHTML = ``;
for (const key in Constructor.info.apartments) {
const element = Constructor.info.apartments[key];
const pos = Constructor.info.entrance.map(e => e.editor_id).indexOf(key);
console.log(element);
let listArea = document.getElementById('list-area');
listArea.innerHTML += `
<div class="block-area" id="block-area-${key}">
<h3>${Constructor.info.entrance[pos].title}</h3>
<button class="addFloors" title="Додати поверх" type="button" onclick="Constructor.apartments.addFloors('${key}')"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 26 26"><path d="M 6.65625 4 C 6.367188 4 6.105469 4.113281 5.90625 4.3125 L 4.3125 5.90625 C 3.914063 6.304688 3.914063 7 4.3125 7.5 L 9.8125 13 L 4.3125 18.5 C 3.914063 19 3.914063 19.695313 4.3125 20.09375 L 5.90625 21.6875 C 6.40625 22.085938 7.101563 22.085938 7.5 21.6875 L 13 16.1875 L 18.5 21.6875 C 19 22.085938 19.695313 22.085938 20.09375 21.6875 L 21.6875 20.09375 C 22.085938 19.59375 22.085938 18.898438 21.6875 18.5 L 16.1875 13 L 21.6875 7.5 C 22.085938 7 22.085938 6.304688 21.6875 5.90625 L 20.09375 4.3125 C 19.59375 3.914063 18.898438 3.914063 18.5 4.3125 L 13 9.8125 L 7.5 4.3125 C 7.25 4.113281 6.945313 4 6.65625 4 Z"></path></svg></button>
<div id="area-${key}"></div>
</div>
`;
element.sort((a, b) => a.floors_number - b.floors_number);
let uniqueFloors = [...new Set(element.map(obj => obj.floors_number))];
for (let i = 0; i < uniqueFloors.length; i++) {
let num = uniqueFloors[i];
let areaBlock = document.getElementById(`area-${key}`);
let div = document.createElement('div');
div.className = "block-apartments-floors";
div.id = `floors-${key}-${num}`
div.innerHTML = `
<button class="addApartment" id="buttonApartment-${key}-${num}" title="Додати квартиру" type="button" onclick="Constructor.apartments.addApartment('${key}', '${num}')"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 26 26"><path d="M 6.65625 4 C 6.367188 4 6.105469 4.113281 5.90625 4.3125 L 4.3125 5.90625 C 3.914063 6.304688 3.914063 7 4.3125 7.5 L 9.8125 13 L 4.3125 18.5 C 3.914063 19 3.914063 19.695313 4.3125 20.09375 L 5.90625 21.6875 C 6.40625 22.085938 7.101563 22.085938 7.5 21.6875 L 13 16.1875 L 18.5 21.6875 C 19 22.085938 19.695313 22.085938 20.09375 21.6875 L 21.6875 20.09375 C 22.085938 19.59375 22.085938 18.898438 21.6875 18.5 L 16.1875 13 L 21.6875 7.5 C 22.085938 7 22.085938 6.304688 21.6875 5.90625 L 20.09375 4.3125 C 19.59375 3.914063 18.898438 3.914063 18.5 4.3125 L 13 9.8125 L 7.5 4.3125 C 7.25 4.113281 6.945313 4 6.65625 4 Z"></path></svg></button>
`;
areaBlock.prepend(div);
}
element.sort((a, b) => b.title - a.title);
for (let i = 0; i < element.length; i++) {
const apartment = element[i];
let num = apartment.floors_number;
let floorsBlock = document.getElementById(`floors-${key}-${apartment.floors_number}`);
let div = document.createElement('div');
div.className = "block-apartments-number";
div.id = `block-apartments-${key}-${apartment.editor_id}`
div.innerHTML = `
<input type="text" value="${apartment.title}" id="apartament-${key}-${apartment.editor_id}" onchange="Constructor.apartments.editApartment('${key}','${apartment.editor_id}')">
<button type="button" onclick="Constructor.apartments.deleteApartment('${key}','${apartment.editor_id}')">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 26 26"><path d="M 6.65625 4 C 6.367188 4 6.105469 4.113281 5.90625 4.3125 L 4.3125 5.90625 C 3.914063 6.304688 3.914063 7 4.3125 7.5 L 9.8125 13 L 4.3125 18.5 C 3.914063 19 3.914063 19.695313 4.3125 20.09375 L 5.90625 21.6875 C 6.40625 22.085938 7.101563 22.085938 7.5 21.6875 L 13 16.1875 L 18.5 21.6875 C 19 22.085938 19.695313 22.085938 20.09375 21.6875 L 21.6875 20.09375 C 22.085938 19.59375 22.085938 18.898438 21.6875 18.5 L 16.1875 13 L 21.6875 7.5 C 22.085938 7 22.085938 6.304688 21.6875 5.90625 L 20.09375 4.3125 C 19.59375 3.914063 18.898438 3.914063 18.5 4.3125 L 13 9.8125 L 7.5 4.3125 C 7.25 4.113281 6.945313 4 6.65625 4 Z"></path></svg>
</button>
`;
floorsBlock.prepend(div);
}
element.sort((a, b) => b.floors_number - a.floors_number);
}
},
editNum: (element) => {
numApartments = Number(element.value);
},
addFloors: (area) => {
let areaBlock = document.getElementById(`area-${area}`);
let uniqueFloors = [...new Set(Constructor.info.apartments[area].map(obj => obj.floors_number))];
let new_floors = uniqueFloors.length + 1;
let new_id = makeid(5);
let div = document.createElement('div');
div.className = "block-apartments-floors";
div.id = `floors-${area}-${new_floors}`
div.innerHTML = `
<h2>Поверх ${new_floors}</h2>
<div class="block-apartments-number" id="block-apartments-${area}-${new_id}">
<input type="text" value="${numApartments}" id="apartament-${area}-${new_id}" onchange="Constructor.apartments.editApartment('${area}', '${new_id}')">
<button type="button" onclick="Constructor.apartments.deleteApartment('${area}', '${new_id}')">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 26 26"><path d="M 6.65625 4 C 6.367188 4 6.105469 4.113281 5.90625 4.3125 L 4.3125 5.90625 C 3.914063 6.304688 3.914063 7 4.3125 7.5 L 9.8125 13 L 4.3125 18.5 C 3.914063 19 3.914063 19.695313 4.3125 20.09375 L 5.90625 21.6875 C 6.40625 22.085938 7.101563 22.085938 7.5 21.6875 L 13 16.1875 L 18.5 21.6875 C 19 22.085938 19.695313 22.085938 20.09375 21.6875 L 21.6875 20.09375 C 22.085938 19.59375 22.085938 18.898438 21.6875 18.5 L 16.1875 13 L 21.6875 7.5 C 22.085938 7 22.085938 6.304688 21.6875 5.90625 L 20.09375 4.3125 C 19.59375 3.914063 18.898438 3.914063 18.5 4.3125 L 13 9.8125 L 7.5 4.3125 C 7.25 4.113281 6.945313 4 6.65625 4 Z"></path></svg>
</button>
</div>
<button class="addApartment" id="buttonApartment-${area}-${new_floors}" title="Додати квартиру" type="button" onclick="Constructor.apartments.addApartment('${area}', '${new_floors}')"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 26 26"><path d="M 6.65625 4 C 6.367188 4 6.105469 4.113281 5.90625 4.3125 L 4.3125 5.90625 C 3.914063 6.304688 3.914063 7 4.3125 7.5 L 9.8125 13 L 4.3125 18.5 C 3.914063 19 3.914063 19.695313 4.3125 20.09375 L 5.90625 21.6875 C 6.40625 22.085938 7.101563 22.085938 7.5 21.6875 L 13 16.1875 L 18.5 21.6875 C 19 22.085938 19.695313 22.085938 20.09375 21.6875 L 21.6875 20.09375 C 22.085938 19.59375 22.085938 18.898438 21.6875 18.5 L 16.1875 13 L 21.6875 7.5 C 22.085938 7 22.085938 6.304688 21.6875 5.90625 L 20.09375 4.3125 C 19.59375 3.914063 18.898438 3.914063 18.5 4.3125 L 13 9.8125 L 7.5 4.3125 C 7.25 4.113281 6.945313 4 6.65625 4 Z"></path></svg></button>
`
areaBlock.prepend(div);
Constructor.info.apartments[area].push({
editor_id: new_id,
entrance_id: null,
apartment_number: Constructor.info.apartments[area].length,
title: numApartments,
floors_number: new_floors
});
numApartments++;
let next_apartment_title = document.getElementById('next-apartment-title');
next_apartment_title.value = numApartments;
},
addApartment: (area, floors) => {
let new_id = makeid(5);
Constructor.info.apartments[area].push({
editor_id: new_id,
entrance_id: null,
apartment_number: Constructor.info.apartments[area].length,
title: numApartments,
floors_number: Number(floors)
})
let floorsBlock = document.getElementById(`floors-${area}-${floors}`);
document.getElementById(`buttonApartment-${area}-${floors}`).remove();
floorsBlock.innerHTML += `
<div class="block-apartments-number" id="block-apartments-${area}-${new_id}">
<input type="text" value="${numApartments}" id="apartament-${area}-${new_id}" onchange="Constructor.apartments.editApartment('${area}', '${new_id}')">
<button type="button" onclick="Constructor.apartments.deleteApartment('${area}', '${new_id}')">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 26 26"><path d="M 6.65625 4 C 6.367188 4 6.105469 4.113281 5.90625 4.3125 L 4.3125 5.90625 C 3.914063 6.304688 3.914063 7 4.3125 7.5 L 9.8125 13 L 4.3125 18.5 C 3.914063 19 3.914063 19.695313 4.3125 20.09375 L 5.90625 21.6875 C 6.40625 22.085938 7.101563 22.085938 7.5 21.6875 L 13 16.1875 L 18.5 21.6875 C 19 22.085938 19.695313 22.085938 20.09375 21.6875 L 21.6875 20.09375 C 22.085938 19.59375 22.085938 18.898438 21.6875 18.5 L 16.1875 13 L 21.6875 7.5 C 22.085938 7 22.085938 6.304688 21.6875 5.90625 L 20.09375 4.3125 C 19.59375 3.914063 18.898438 3.914063 18.5 4.3125 L 13 9.8125 L 7.5 4.3125 C 7.25 4.113281 6.945313 4 6.65625 4 Z"></path></svg>
</button>
</div>
`;
floorsBlock.innerHTML += `<button class="addApartment" id="buttonApartment-${area}-${floors}" title="Додати квартиру" type="button" onclick="Constructor.apartments.addApartment('${area}', '${floors}')"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 26 26"><path d="M 6.65625 4 C 6.367188 4 6.105469 4.113281 5.90625 4.3125 L 4.3125 5.90625 C 3.914063 6.304688 3.914063 7 4.3125 7.5 L 9.8125 13 L 4.3125 18.5 C 3.914063 19 3.914063 19.695313 4.3125 20.09375 L 5.90625 21.6875 C 6.40625 22.085938 7.101563 22.085938 7.5 21.6875 L 13 16.1875 L 18.5 21.6875 C 19 22.085938 19.695313 22.085938 20.09375 21.6875 L 21.6875 20.09375 C 22.085938 19.59375 22.085938 18.898438 21.6875 18.5 L 16.1875 13 L 21.6875 7.5 C 22.085938 7 22.085938 6.304688 21.6875 5.90625 L 20.09375 4.3125 C 19.59375 3.914063 18.898438 3.914063 18.5 4.3125 L 13 9.8125 L 7.5 4.3125 C 7.25 4.113281 6.945313 4 6.65625 4 Z"></path></svg></button>`
numApartments++;
let next_apartment_title = document.getElementById('next-apartment-title');
next_apartment_title.value = numApartments;
},
editApartment: (area, apartament) => {
let input = document.getElementById(`apartament-${area}-${apartament}`);
input.setAttribute("value", input.value);
const pos = Constructor.info.apartments[area].map(e => e.editor_id).indexOf(apartament);
info.apartments[area][pos].title = input.value;
},
deleteApartment: (area, apartament) => {
document.getElementById(`block-apartments-${area}-${apartament}`).remove();
const pos = Constructor.info.apartments[area].map(e => e.editor_id).indexOf(apartament);
Constructor.info.apartments[area].splice(pos, 1);
numApartments--;
let next_apartment_title = document.getElementById('next-apartment-title');
next_apartment_title.value = numApartments;
}
},
osm: {
init: () => {
let center = { lat: 49.5629016, lng: 25.6145625 };
let zoom = 19;
let googleHybrid = L.tileLayer('http://{s}.google.com/vt/lyrs=s,h&x={x}&y={y}&z={z}', {
maxZoom: 20,
minZoom: 15,
subdomains: ['mt0', 'mt1', 'mt2', 'mt3']
});
let osm = L.tileLayer('http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
maxZoom: 19,
minZoom: 15
});
let mytile = L.tileLayer('https://tm.rozenrod.com/webp/{z}/{x}/{y}.webp', {
maxZoom: 20,
minZoom: 15,
tms: true
});
if (!map) {
houseGroup = new L.FeatureGroup();
splitPolygonsGroup = new L.FeatureGroup();
RectangleGroup = new L.FeatureGroup();
entransePolygonsGroup = new L.FeatureGroup();
entranseNumPolygonsGroup = new L.FeatureGroup();
map = L.map('map', {
renderer: L.canvas(),
center,
zoom,
layers: [
googleHybrid,
osm,
mytile,
houseGroup,
entransePolygonsGroup,
entranseNumPolygonsGroup,
splitPolygonsGroup,
RectangleGroup,
],
zoomControl: false
});
let baseMaps = {
"Google Hybrid": googleHybrid,
"OpenStreetMap": osm,
"Territory Map": mytile
};
let overlayMaps = {
"Будинки": houseGroup,
"Під'їзди": entransePolygonsGroup,
"Номера під'їздів": entranseNumPolygonsGroup,
"Слой редактирования": splitPolygonsGroup,
"Слой линейки": RectangleGroup,
};
L.control.layers(baseMaps, overlayMaps, { position: 'bottomright' }).addTo(map);
map.pm.setLang("ua");
map.pm.addControls({
position: 'bottomright',
drawCircleMarker: false,
drawPolyline: false,
drawPolygon: false,
drawRectangle: false,
drawCircle: false,
drawText: false,
drawMarker: false,
cutPolygon: false,
tooltips: false
});
map.pm.toggleControls()
map.pm.setGlobalOptions({
layerGroup: splitPolygonsGroup
})
}
Constructor.editor.init();
},
getOSM: async (wayId) => {
const overpassUrl = `https://overpass-api.de/api/interpreter?data=[out:json];way(${wayId});(._;>;);out;`;
return await fetch(overpassUrl)
.then(response => response.json())
.then(data => {
const nodes = new Map();
data.elements.forEach(el => {
if (el.type === "node") {
nodes.set(el.id, { lat: el.lat, lng: el.lon });
}
});
const way = data.elements.find(el => el.type === "way");
if (way) {
const coordinates = way.nodes.map(nodeId => nodes.get(nodeId));
console.log("Координаты точек:", coordinates);
return [coordinates];
} else {
console.log("Way не найден!");
}
})
.catch(error => console.error("Ошибка запроса:", error));
},
setMap: async () => {
houseGroup.clearLayers();
if (!Constructor.info.osm_id) {
let osm = () => {
const a = document.getElementById("info-osm-title").value;
const b = a.replace(/\s+/g, "").split(',')
return b;
}
Constructor.info.osm_id = osm();
}
for (let i = 0; i < Constructor.info.osm_id.length; i++) {
const element = await Constructor.osm.getOSM(Constructor.info.osm_id[i]);
let coords = [];
element[0].forEach((feature) => coords.push([feature.lat, feature.lng]));
let centerPoint = turf.centerOfMass(turf.polygon([coords]));
if (Constructor.info.type == "homestead") {
map.setView([centerPoint.geometry.coordinates[0], centerPoint.geometry.coordinates[1]], 17);
L.polygon(element, {
color: colorGroup(Constructor.info.group_id),
radius: 500,
fillOpacity: 0.3,
dashArray: '20, 15',
dashOffset: '20',
}).addTo(houseGroup);
} else if (Constructor.info.type == "house") {
map.setView([centerPoint.geometry.coordinates[0], centerPoint.geometry.coordinates[1]], 18);
Constructor.info.points.push(element);
Constructor.info.points_number.push(element[0][0]);
L.polygon(element, {
color: "#585858",
fillColor: colorGroup(Constructor.info.group_id),
fillOpacity: 0.8,
tm_id: `house_${i}`
}).addTo(houseGroup);
}
}
console.log(Constructor.info);
}
},
api: {
setPack: async () => {
let area_form_button = document.getElementById('area-form-button');
area_form_button.innerText = "Зачекайте...";
Constructor.info.geo = await map.getCenter();
Constructor.info.zoom = await map.getZoom();
console.log(Constructor.info);
const uuid = localStorage.getItem('uuid');
const URL = `${CONFIG.api}constructor`;
await fetch(URL, {
method: 'POST',
headers: {
"Content-Type": "application/json",
"Authorization": uuid
},
body: JSON.stringify(Constructor.info)
})
.then(response => {
if (response.status == 200) {
console.log({ 'setPack': 'ok' });
area_form_button.innerText = "Запис додано";
return response.json()
} else {
console.log('err');
area_form_button.innerText = "Помилка запису";
return
}
})
.then(data => {
console.log(data);
Router.navigate((`/territory/manager/${Constructor.info.type}/${data.id}`).replace(window.location.origin, ''));
setTimeout(() => {
area_form_button.innerText = "Зберегти";
}, 3000);
})
.catch(err => {
console.log(err);
area_form_button.innerText = "Помилка запису";
})
}
},
editor: {
init: () => {
map.on('pm:create', function (event) {
let layer = event.layer;
let newCoords = layer.getLatLngs()[0].map(function (coords) {
return [coords.lng, coords.lat];
});
newCoords.push(newCoords[0]);
let turfNew = turf.polygon([newCoords]);
if (mode == 'entranse') {
console.log(L.PM.Utils.findLayers(houseGroup));
for (let i = 0; i < L.PM.Utils.findLayers(houseGroup).length; i++) {
const polygon = L.PM.Utils.findLayers(houseGroup)[i]._latlngs;
let polygonCoords = polygon[0].map(function (coords) {
return [coords.lng, coords.lat];
});
polygonCoords.push(polygonCoords[0]); // Замикаємо полігон
let turfPolygon = turf.polygon([polygonCoords]);
// Пошук точки перехрестя
let intersections = turf.intersect(turfNew, turfPolygon);
if (intersections) {
let points = [];
let coords = [];
intersections.geometry.coordinates[0].forEach(function (feature) {
coords.push([feature[1], feature[0]])
points.push({ lat: feature[1], lng: feature[0] })
});
let centerPoint = turf.centerOfMass(turf.polygon([coords]));
let points_number = { lat: centerPoint.geometry.coordinates[0], lng: centerPoint.geometry.coordinates[1] }
let newID = makeid(6);
Constructor.info.entrance.push({
editor_id: newID,
house_id: null,
entrance_number: Constructor.info.entrance.length,
title: `Під'їзд ${Constructor.info.entrance.length + 1}`,
points: points,
points_number: points_number,
floors_quantity: null,
apartments_quantity: null,
created_at: null
})
Constructor.info.apartments[newID] = [];
Constructor.apartments.init();
let listEntranse = document.getElementById('list-entranse');
listEntranse.style.display = "";
listEntranse.innerHTML += `
<div class="house" style="align-items: center;" id="Entranse_${newID}">
<input type="number" value="${Constructor.info.entrance.length}" id="Entranse_input_${newID}" onchange="Constructor.editor.editEntranse('${newID}')">
<button type="button" onclick="Constructor.editor.dellEntranse('${newID}')">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 26 26"><path d="M 6.65625 4 C 6.367188 4 6.105469 4.113281 5.90625 4.3125 L 4.3125 5.90625 C 3.914063 6.304688 3.914063 7 4.3125 7.5 L 9.8125 13 L 4.3125 18.5 C 3.914063 19 3.914063 19.695313 4.3125 20.09375 L 5.90625 21.6875 C 6.40625 22.085938 7.101563 22.085938 7.5 21.6875 L 13 16.1875 L 18.5 21.6875 C 19 22.085938 19.695313 22.085938 20.09375 21.6875 L 21.6875 20.09375 C 22.085938 19.59375 22.085938 18.898438 21.6875 18.5 L 16.1875 13 L 21.6875 7.5 C 22.085938 7 22.085938 6.304688 21.6875 5.90625 L 20.09375 4.3125 C 19.59375 3.914063 18.898438 3.914063 18.5 4.3125 L 13 9.8125 L 7.5 4.3125 C 7.25 4.113281 6.945313 4 6.65625 4 Z"></path></svg>
</button>
</div>
`;
L.polygon(coords, { color: 'red' }).addTo(entransePolygonsGroup);
let myIcon = L.divIcon({ className: 'entranse_number', html: `<div class="markerEntranse">${Constructor.info.entrance.length}</div>` });
L.marker(centerPoint.geometry.coordinates, { icon: myIcon }).addTo(entranseNumPolygonsGroup);
}
}
}
});
},
drawEntranse: () => {
mode = 'entranse';
map.pm.setGlobalOptions({
layerGroup: splitPolygonsGroup
})
map.pm.setPathOptions({
color: '#f2bd53',
fillColor: '#f2bd53',
fillOpacity: 0.5,
radius: 500
});
if (map.pm.globalDragModeEnabled()) {
map.pm.disableDraw();
} else {
map.pm.enableDraw("Polygon", {
snappable: true,
snapDistance: 20,
tooltips: false,
templineStyle: { color: '#f2bd53' },
hintlineStyle: { color: '#f2bd53', dashArray: [5, 5] }
});
}
},
drawRectangle: () => {
mode = 'rectangle';
document.getElementById('ruler_divide').style.display = 'block'
document.getElementById('ruler').style.width = "calc(50% - 5px)"
document.getElementById('ruler_divide').style.width = "calc(50% - 5px)"
document.getElementById('ruler').innerHTML = 'Лінійка'
map.pm.toggleControls()
RectangleGroup.clearLayers();
RectangleGroup.addTo(map);
map.pm.setGlobalOptions({
layerGroup: RectangleGroup
})
map.pm.setPathOptions({
color: '#b645ef',
fillColor: '#b645ef',
fillOpacity: 0.5,
radius: 500
});
if (map.pm.globalDragModeEnabled()) {
map.pm.disableDraw();
} else {
map.pm.enableDraw("Rectangle", {
snappable: true,
snapDistance: 20,
tooltips: false,
templineStyle: { color: '#b645ef' },
hintlineStyle: { color: '#b645ef', dashArray: [5, 5] }
});
}
},
ruler: (n) => {
n = prompt('На сколько поделить линейку ?', 2);
const polygon = L.PM.Utils.findLayers(RectangleGroup)[0]._latlngs;
let newCoords = polygon[0].map(function (coords) {
return [coords.lng, coords.lat];
});
newCoords.push(newCoords[0]);
let turfNew = turf.polygon([newCoords]);
console.log(turfNew);
var line = turf.polygonToLine(turfNew);
console.log(line.geometry.coordinates);
coords = line.geometry.coordinates;
for (let i = 1; i < n; i++) {
let a1 = (((coords[2][1] - coords[1][1]) / n) * i + coords[1][1])
let b1 = (((coords[2][0] - coords[1][0]) / n) * i + coords[1][0])
let a2 = (((coords[3][1] - coords[0][1]) / n) * i + coords[0][1])
let b2 = (((coords[3][0] - coords[0][0]) / n) * i + coords[0][0])
let c1 = (((coords[1][1] - coords[0][1]) / n) * i + coords[0][1])
let d1 = (((coords[1][0] - coords[0][0]) / n) * i + coords[0][0])
let c2 = (((coords[2][1] - coords[3][1]) / n) * i + coords[3][1])
let d2 = (((coords[2][0] - coords[3][0]) / n) * i + coords[3][0])
L.circleMarker([a1, b1], { radius: 2, color: 'red' }).addTo(RectangleGroup);
L.circleMarker([a2, b2], { radius: 2, color: 'red' }).addTo(RectangleGroup);
L.circleMarker([c1, d1], { radius: 2, color: 'red' }).addTo(RectangleGroup);
L.circleMarker([c2, d2], { radius: 2, color: 'red' }).addTo(RectangleGroup);
}
coords.forEach(function (feature) {
L.circleMarker([feature[1], feature[0]], { radius: 2, color: 'red' }).addTo(RectangleGroup);
});
},
dellEntranse: (id) => {
delete Constructor.info.apartments[id];
const pos = Constructor.info.entrance.map(e => e.editor_id).indexOf(id);
console.log(pos);
Constructor.info.entrance.splice(pos, 1);
console.log(id);
document.getElementById(`Entranse_${id}`).remove();
let Entranse = Object.keys(entransePolygonsGroup._layers);
let numsEntranse = Object.keys(entranseNumPolygonsGroup._layers);
console.log(Entranse, Entranse[pos]);
console.log(numsEntranse, numsEntranse[pos]);
entransePolygonsGroup.removeLayer(entransePolygonsGroup._layers[Entranse[pos]]);
entranseNumPolygonsGroup.removeLayer(entranseNumPolygonsGroup._layers[numsEntranse[pos]]);
Constructor.apartments.init();
},
editEntranse: (id) => {
const input = document.getElementById(`Entranse_input_${id}`);
const pos = Constructor.info.entrance.map(e => e.editor_id).indexOf(id);
Constructor.info.entrance[pos].entrance_number = Number(input.value) - 1;
Constructor.info.entrance[pos].title = `Під'їзд ${input.value}`;
let numsEntranse = Object.keys(entranseNumPolygonsGroup._layers);
let newIcon = L.divIcon({ className: 'entranse_number', html: `<div class="markerEntranse">${input.value}</div>` });
entranseNumPolygonsGroup._layers[numsEntranse[pos]].setIcon(newIcon);
input.setAttribute("value", input.value);
Constructor.apartments.init();
}
}
}

View File

@@ -0,0 +1,494 @@
.page-constructor {
width: calc(100% - 40px);
display: flex;
flex-direction: column;
align-items: center;
margin: 20px 20px 0 20px;
}
.page-constructor form>button {
border-radius: 6px;
background: var(--PrimaryColor);
color: var(--PrimaryColorText);
width: 100%;
height: 40px;
font-size: 14px;
font-weight: 400;
margin: 20px 0;
text-transform: uppercase;
}
.page-constructor details {
border-radius: 10px;
width: 100%;
display: flex;
flex-direction: column;
align-items: stretch;
margin-bottom: 20px;
background: var(--ColorThemes1);
color: var(--ColorThemes3);
border: 1px solid var(--ColorThemes2);
box-shadow: var(--shadow-l1);
}
.page-constructor>details[disabled] summary,
.page-constructor>details.disabled summary {
pointer-events: none;
user-select: none;
}
.page-constructor>details summary::-webkit-details-marker,
.page-constructor>details summary::marker {
display: none;
content: "";
}
.page-constructor summary {
width: calc(100% - 40px);
cursor: pointer;
color: var(--ColorThemes3);
border-radius: var(--border-radius);
font-size: 16px;
font-weight: 300;
padding: 20px;
position: relative;
}
.page-constructor summary span {
font-weight: 500;
}
.page-constructor summary input {
font-weight: 500;
position: absolute;
right: 0;
top: 0;
padding: 10px;
margin: 13px;
font-size: 12px;
background: var(--ColorThemes3);
color: var(--ColorThemes0);
border-radius: 6px;
width: 40px;
}
.page-constructor #info-form,
.page-constructor #map-form,
.page-constructor #area-form {
padding: 0 20px;
}
#details-info-type {
display: flex;
align-items: center;
justify-content: center;
}
#details-info-type>.tabs {
display: flex;
position: relative;
background-color: var(--ColorThemes0);
padding: 4px;
border-radius: 6px;
width: calc(100% - 8px);
z-index: 2;
}
#details-info-type>.tabs>input[type="radio"] {
display: none;
}
#details-info-type>.tabs>.tab {
display: flex;
align-items: center;
justify-content: center;
height: 40px;
width: calc(100% / 3);
cursor: pointer;
padding: 0 15px;
transition: 0.15s ease-in;
color: var(--ColorThemes3);
fill: var(--ColorThemes3);
flex-direction: row;
z-index: 2;
}
#details-info-type>.tabs>.tab>svg {
width: 20px;
height: 20px;
}
#details-info-type>.tabs>.tab>span {
margin-left: 6px;
font-size: 12px;
font-weight: 400;
}
#details-info-type>.tabs>input[type="radio"]:checked+label {
color: var(--PrimaryColorText);
fill: var(--PrimaryColorText);
}
#details-info-type>.tabs>.glider {
position: absolute;
display: flex;
height: 40px;
width: calc((100% - 8px) / 3);
background-color: var(--PrimaryColor);
z-index: 1;
border-radius: 4px;
transition: 0.25s ease-out;
}
@media (min-width: 601px) {
#details-info-type>.tabs>input[id="info-type-house"]:checked~.glider {
transform: translateX(0);
}
#details-info-type>.tabs>input[id="info-type-homestead"]:checked~.glider {
transform: translateX(100%);
}
#details-info-type>.tabs>input[id="info-type-points"]:checked~.glider {
transform: translateX(200%);
}
}
@media (max-width: 600px) {
#details-info-type>.tabs {
flex-direction: column;
}
#details-info-type>.tabs>.tab {
width: calc(100% - 8px);
padding: 0 4px;
}
#details-info-type>.tabs>.glider {
width: calc(100% - 8px);
}
#details-info-type>.tabs>input[id="info-type-house"]:checked~.glider {
transform: translateY(0);
}
#details-info-type>.tabs>input[id="info-type-homestead"]:checked~.glider {
transform: translateY(100%);
}
#details-info-type>.tabs>input[id="info-type-points"]:checked~.glider {
transform: translateY(200%);
}
}
.page-constructor .details-info-input {
width: 100%;
display: flex;
margin: 20px 0;
align-items: flex-start;
flex-direction: column;
}
.page-constructor .details-info-input label {
display: flex;
justify-content: center;
flex-direction: column;
font-size: 12px;
font-weight: 500;
margin-bottom: 5px;
}
.page-constructor .details-info-input input {
width: calc(100% - 10px);
min-width: 140px;
padding: 0 5px;
border-radius: 6px;
height: 30px;
background: var(--ColorThemes0);
color: var(--ColorThemes3);
}
.page-constructor #details-info-osm div {
display: flex;
align-items: center;
width: 100%;
}
.page-constructor #details-info-osm input {
width: calc(100% - 10px);
min-width: 140px;
padding: 0 5px;
border-radius: 6px;
height: 30px;
}
.page-constructor .details-info-input a {
height: 26px;
width: 26px;
margin-left: 10px;
}
.page-constructor .details-info-input a>svg {
height: 26px;
width: 26px;
fill: var(--ColorThemes3)
}
.page-constructor #list-area {
margin-top: 15px;
display: flex;
overflow: auto;
}
.page-constructor #list-area h3 {
text-align: center;
font-size: 16px;
font-weight: 400;
margin: 10px;
padding: 7px;
color: var(--ColorThemes0);
background: var(--ColorThemes3);
border-radius: 4px;
}
.block-area {
min-height: 200px;
border: 1px solid var(--ColorThemes3);
border-style: dashed;
border-radius: 6px;
margin: 0 10px 10px 0;
}
.addFloors,
.addApartment {
display: flex;
position: relative;
width: 34px;
height: 34px;
background: var(--PrimaryColor);
color: var(--PrimaryColorText);
margin: 25px;
border-radius: 50%;
align-items: center;
justify-content: center;
font-size: 30px;
cursor: pointer;
}
.addFloors>svg,
.addApartment>svg {
width: 20px;
height: 20px;
fill: var(--PrimaryColorText);
transform: rotate(45deg);
}
.block-apartments-floors {
position: relative;
display: flex;
width: calc(100% - 22px);
border: 1px solid var(--ColorThemes3);
margin: 10px;
border-radius: 4px;
}
.block-apartments-floors h2 {
position: absolute;
width: 65px;
right: -1px;
top: -1px;
margin: 0;
background: var(--ColorThemes3);
color: var(--ColorThemes2);
border-radius: 0 4px 0 4px;
font-size: 12px;
padding: 2px 4px;
text-align: center;
}
.block-apartments-number {
display: flex;
position: relative;
width: 60px;
height: 60px;
background: var(--ColorThemes1);
border: 2px solid var(--PrimaryColor);
margin: 10px;
border-radius: 4px;
align-items: center;
justify-content: center;
}
.block-apartments-number input {
width: 50px;
height: 50px;
font-size: 16px;
font-weight: 400;
text-align: center;
color: var(--ColorThemes3);
background: 0;
}
.block-apartments-number button {
position: absolute;
top: 0;
right: 0;
width: 20px;
height: 20px;
border-radius: 0 4px 0 4px;
background: var(--PrimaryColor);
font-size: 16px;
margin: -2px;
border: 0;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
}
.block-apartments-number button>svg {
width: 16px;
height: 16px;
fill: var(--PrimaryColorText);
}
.block-map {
width: 100%;
height: 500px;
border-radius: 6px;
overflow: hidden;
}
#map {
width: 100%;
height: 100%;
}
.entranse_number {
left: -10px !important;
top: -10px !important;
}
.markerEntranse {
background: hsl(0deg 0% 52.12% / 90%);
font-size: 24px;
border: 2px solid #676767;
border-radius: 2px;
display: flex !important;
justify-content: center;
align-items: center;
color: #fff;
min-width: 30px;
min-height: 30px;
height: 30px;
width: 30px;
}
.editor-buttons {
margin-top: 15px;
}
.editor-buttons>button {
width: 100%;
min-height: 30px;
margin: 5px 0;
border: 0;
color: var(--PrimaryColorText);
display: flex;
align-items: center;
justify-content: center;
background: var(--PrimaryColor);
text-transform: uppercase;
cursor: pointer;
border-radius: 6px;
font-weight: 400;
font-size: 12px;
}
.editor-buttons>div {
display: flex;
width: 100%;
margin: 10px 0;
height: 30px;
justify-content: space-between;
}
.editor-buttons>div>button {
background: var(--ColorThemes3);
color: var(--ColorThemes1);
width: 100%;
margin: 0;
border-radius: 6px;
text-transform: uppercase;
font-weight: 400;
font-size: 12px;
}
.page-constructor #list-entranse,
.page-constructor #list-homestead {
width: 100%;
min-height: 86px;
display: flex;
flex-direction: row;
flex-wrap: nowrap;
overflow-x: initial;
overflow-y: auto;
border: 1px solid var(--ColorThemes3);
background: 0;
border-radius: 6px;
border-style: dashed;
margin: 10px 0;
}
.page-constructor #list-entranse>.house,
.page-constructor #list-homestead>.house {
display: flex;
position: relative;
width: 60px;
height: 60px;
background: var(--ColorThemes1);
border: 2px solid var(--PrimaryColor);
margin: 10px;
border-radius: 4px;
align-items: center;
justify-content: center;
}
.page-constructor #list-entranse>.house>input,
.page-constructor #list-homestead>.house>input {
width: 50px;
height: 50px;
font-size: 16px;
font-weight: 400;
text-align: center;
color: var(--ColorThemes3);
background: 0;
}
.page-constructor #list-entranse>.house>button,
.page-constructor #list-homestead>.house>button {
position: absolute;
top: 0;
right: 0;
width: 20px;
height: 20px;
border-radius: 0 4px 0 4px;
background: var(--PrimaryColor);
font-size: 16px;
margin: -2px;
border: 0;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
}
.page-constructor #list-entranse>.house>button>svg,
.page-constructor #list-homestead>.house>button>svg {
width: 16px;
height: 16px;
fill: var(--PrimaryColorText);
}

View File

@@ -0,0 +1,139 @@
<div class="page-editor">
<details id="details-info" open>
<summary>
<span>Крок 1.</span> Інформація про будинок, територію, точки на карті
</summary>
<form id="info-form">
<div id="details-info-address" class="details-info-input">
<label for="info-address-title">Назва вулиці</label>
<input
type="text"
id="info-address-title"
name="address"
required
value=""
/>
</div>
<div id="details-info-number" class="details-info-input">
<label for="info-number-title">Номер будинку</label>
<input
type="text"
id="info-number-title"
name="number"
required
value=""
/>
</div>
<div id="details-info-settlement" class="details-info-input">
<label for="info-settlement-title">Місто</label>
<input
type="text"
id="info-settlement-title"
name="settlement"
required
value=""
/>
</div>
<div id="details-info-group" class="details-info-input">
<label for="info-group-title">Теократична група</label>
<input
type="number"
id="info-group-title"
name="group"
required
value=""
/>
</div>
<div id="details-info-osm" class="details-info-input">
<label for="info-settlement-title">OSM iD</label>
<div>
<input
type="text"
id="info-osm-title"
name="osm"
placeholder="123, 345, 678"
required
value=""
/>
<a href="https://www.openstreetmap.org/#map=19/49.561725/25.604458" target="_blank" title="Де знайти OSM iD ?"
><svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 48 48"
width="100px"
height="100px"
>
<path
d="M 24 4 C 12.972066 4 4 12.972074 4 24 C 4 35.027926 12.972066 44 24 44 C 35.027934 44 44 35.027926 44 24 C 44 12.972074 35.027934 4 24 4 z M 24 7 C 33.406615 7 41 14.593391 41 24 C 41 33.406609 33.406615 41 24 41 C 14.593385 41 7 33.406609 7 24 C 7 14.593391 14.593385 7 24 7 z M 24 14 A 2 2 0 0 0 24 18 A 2 2 0 0 0 24 14 z M 23.976562 20.978516 A 1.50015 1.50015 0 0 0 22.5 22.5 L 22.5 33.5 A 1.50015 1.50015 0 1 0 25.5 33.5 L 25.5 22.5 A 1.50015 1.50015 0 0 0 23.976562 20.978516 z"
/></svg
></a>
</div>
</div>
</form>
</details>
<details id="details-map" open>
<summary id="details-map-title">
<span>Крок 2.</span> Перегляд
</summary>
<form id="map-form">
<!-- <div class="editor-buttons" id="details-map-buttons-entranse">
<button type="button" onclick="Editor.editor.drawEntranse()">
Створити новий під'їзд
</button>
<div>
<button type="button" id="ruler" onclick="Editor.editor.drawRectangle()">
Лінійка
</button>
<button
type="button"
id="ruler_divide"
onclick="Editor.editor.ruler()"
style="display: none"
>
Поділити
</button>
</div>
</div> -->
<div
class="editor-buttons"
id="details-map-buttons-homestead"
style="display: none"
>
<!-- <div>
<button type="button" id="ruler" onclick="Editor.editor.drawRectangle()">
Лінійка
</button>
<button
type="button"
id="ruler_divide"
onclick="Editor.editor.ruler()"
style="display: none"
>
Поділити
</button>
</div> -->
</div>
<div id="list-entranse" style="display: none"></div>
<div id="list-homestead" style="display: none"></div>
<div class="block-map">
<div id="map"></div>
</div>
<button id="map-form-button" type="submit" style="display: none;">Далі</button>
</form>
</details>
<details id="details-area" open style="display: none;">
<summary><span>Крок 3.</span> Конструктор квартир <input type="number" value="1" id="next-apartment-title" onchange="Editor.apartments.editNum(this)" title="Авто-номер наступної квартири"></summary>
<form id="area-form">
<div id="list-area"></div>
</form>
</details>
</div>

View File

@@ -0,0 +1,449 @@
const Editor = {
init: async (type, id) => {
let html = await fetch('/lib/pages/editor/index.html').then((response) => response.text());
app.innerHTML = html;
map = "";
houseGroup = "";
entransePolygonsGroup = "";
entranseNumPolygonsGroup = "";
splitPolygonsGroup = "";
RectangleGroup = "";
numApartments = 1;
Editor.info.setHTML(type, id);
// document.getElementById("area-form").addEventListener("submit", async function (event) {
// event.preventDefault();
// await Editor.api.setPack();
// })
},
loadAPI: async function (URL) {
let uuid = localStorage.getItem("uuid");
return await fetch(URL, {
method: 'GET',
headers: {
"Content-Type": "application/json",
"Authorization": uuid
}
}).then((response) => response.json());
},
info: {
list: {
type: null,
group_id: null,
title: null,
number: null,
points: [],
points_number: [],
point_icons: [],
geo: [],
osm_id: [],
settlement: [],
description: null,
entrance: [],
apartments: {}
},
setHTML: async (type, id) => {
let detailsInfo_address_title = document.getElementById('info-address-title');
let detailsInfo_number_title = document.getElementById('info-number-title');
let detailsInfo_settlement_title = document.getElementById('info-settlement-title');
let detailsInfo_group_title = document.getElementById('info-group-title');
let detailsInfo_osm_title = document.getElementById('info-osm-title');
Editor.info.list = await Editor.loadAPI(`${CONFIG.api}${type}/${id}`);
Editor.info.list.type = type;
Editor.info.list.entrance = [];
Editor.info.list.apartments = {};
console.log(Editor.info.list);
detailsInfo_address_title.value = Editor.info.list.title;
detailsInfo_number_title.value = Editor.info.list.number;
detailsInfo_settlement_title.value = Editor.info.list.settlement;
detailsInfo_group_title.value = Editor.info.list.group_id;
detailsInfo_osm_title.value = Editor.info.list.osm_id.join(", ");
Editor.osm.init();
Editor.info.setMap();
if (type == "house") {
Editor.entrances.setHTML(id);
document.getElementById('details-area').style.display = "";
}
},
setMap: async () => {
houseGroup.clearLayers();
for (let i = 0; i < Editor.info.list.points.length; i++) {
const element = Editor.info.list.points[i];
if (Editor.info.list.type == "homestead") {
map.setView([Editor.info.list.geo.lat, Editor.info.list.geo.lng], 17);
L.polygon(element, {
color: colorGroup(Editor.info.list.group_id),
radius: 500,
fillOpacity: 0.3,
dashArray: '20, 15',
dashOffset: '20',
}).addTo(houseGroup);
} else if (Editor.info.list.type == "house") {
map.setView([Editor.info.list.geo.lat, Editor.info.list.geo.lng], 18);
L.polygon(element, {
color: "#585858",
fillColor: colorGroup(Editor.info.list.group_id),
fillOpacity: 0.8,
tm_id: `house_${i}`
}).addTo(houseGroup);
}
}
}
},
entrances: {
list: [],
setHTML: async (id) => {
Editor.entrances.list = await Editor.loadAPI(`${CONFIG.api}house/${id}/entrances`);
console.log(Editor.entrances.list);
Editor.entrances.setMap()
},
setMap: async () => {
entransePolygonsGroup.clearLayers();
entranseNumPolygonsGroup.clearLayers();
for (let i = 0; i < Editor.entrances.list.length; i++) {
const element = Editor.entrances.list[i];
console.log(element);
let listEntranse = document.getElementById('list-entranse');
listEntranse.style.display = "";
listEntranse.innerHTML += `
<div class="house" style="align-items: center;" id="Entranse_${element.id}">
<p>${element.entrance_number + 1}</p>
</div>
`;
let listArea = document.getElementById('list-area');
listArea.innerHTML += `
<div class="block-area" id="block-area-${element.id}">
<h3>${element.title}</h3>
<button class="addFloors" title="Додати поверх" type="button" onclick="Editor.apartments.addFloors('${element.id}')"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 26 26"><path d="M 6.65625 4 C 6.367188 4 6.105469 4.113281 5.90625 4.3125 L 4.3125 5.90625 C 3.914063 6.304688 3.914063 7 4.3125 7.5 L 9.8125 13 L 4.3125 18.5 C 3.914063 19 3.914063 19.695313 4.3125 20.09375 L 5.90625 21.6875 C 6.40625 22.085938 7.101563 22.085938 7.5 21.6875 L 13 16.1875 L 18.5 21.6875 C 19 22.085938 19.695313 22.085938 20.09375 21.6875 L 21.6875 20.09375 C 22.085938 19.59375 22.085938 18.898438 21.6875 18.5 L 16.1875 13 L 21.6875 7.5 C 22.085938 7 22.085938 6.304688 21.6875 5.90625 L 20.09375 4.3125 C 19.59375 3.914063 18.898438 3.914063 18.5 4.3125 L 13 9.8125 L 7.5 4.3125 C 7.25 4.113281 6.945313 4 6.65625 4 Z"></path></svg></button>
<div id="area-${element.id}"></div>
</div>
`;
Editor.apartments.setHTML(element.id);
L.polygon(element.points, { color: 'red' }).addTo(entransePolygonsGroup);
let myIcon = L.divIcon({ className: 'entranse_number', html: `<div class="markerEntranse">${element.entrance_number + 1}</div>` });
L.marker(element.points_number, { icon: myIcon }).addTo(entranseNumPolygonsGroup);
}
}
},
apartments: {
list: [],
setHTML: async (id) => {
Editor.apartments.list[`${id}`] = await Editor.loadAPI(`${CONFIG.api}apartments/${id}`);
const uniqueFloors = [...new Set(Editor.apartments.list[`${id}`].map(item => item.floors_number))];
for (let i = 0; i < uniqueFloors.length; i++) {
let num = uniqueFloors[i];
let area = document.getElementById(`area-${id}`);
let div = document.createElement('div');
div.className = "block-apartments-floors";
div.id = `floors-${id}-${num}`
div.innerHTML = `
<h2>Поверх ${num}</h2>
<button class="addApartment" id="buttonApartment-${id}-${num}" title="Додати квартиру" type="button" onclick="Editor.apartments.addApartment('${id}', '${num}')"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 26 26"><path d="M 6.65625 4 C 6.367188 4 6.105469 4.113281 5.90625 4.3125 L 4.3125 5.90625 C 3.914063 6.304688 3.914063 7 4.3125 7.5 L 9.8125 13 L 4.3125 18.5 C 3.914063 19 3.914063 19.695313 4.3125 20.09375 L 5.90625 21.6875 C 6.40625 22.085938 7.101563 22.085938 7.5 21.6875 L 13 16.1875 L 18.5 21.6875 C 19 22.085938 19.695313 22.085938 20.09375 21.6875 L 21.6875 20.09375 C 22.085938 19.59375 22.085938 18.898438 21.6875 18.5 L 16.1875 13 L 21.6875 7.5 C 22.085938 7 22.085938 6.304688 21.6875 5.90625 L 20.09375 4.3125 C 19.59375 3.914063 18.898438 3.914063 18.5 4.3125 L 13 9.8125 L 7.5 4.3125 C 7.25 4.113281 6.945313 4 6.65625 4 Z"></path></svg></button>
`;
area.prepend(div);
}
Editor.apartments.list[`${id}`].sort((a, b) => b.title - a.title);
for (let i = 0; i < Editor.apartments.list[`${id}`].length; i++) {
const apartment = Editor.apartments.list[`${id}`][i];
let num = apartment.floors_number;
let floorsBlock = document.getElementById(`floors-${id}-${num}`);
let div = document.createElement('div');
div.className = "block-apartments-number";
div.id = `block-apartments-${id}-${apartment.id}`
div.innerHTML = `
<input type="text" value="${apartment.title}" id="apartament-${id}-${apartment.id}" onchange="Editor.apartments.editApartment('${id}','${apartment.id}')">
<button type="button" onclick="Editor.apartments.deleteApartment('${id}','${apartment.id}')">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 26 26"><path d="M 6.65625 4 C 6.367188 4 6.105469 4.113281 5.90625 4.3125 L 4.3125 5.90625 C 3.914063 6.304688 3.914063 7 4.3125 7.5 L 9.8125 13 L 4.3125 18.5 C 3.914063 19 3.914063 19.695313 4.3125 20.09375 L 5.90625 21.6875 C 6.40625 22.085938 7.101563 22.085938 7.5 21.6875 L 13 16.1875 L 18.5 21.6875 C 19 22.085938 19.695313 22.085938 20.09375 21.6875 L 21.6875 20.09375 C 22.085938 19.59375 22.085938 18.898438 21.6875 18.5 L 16.1875 13 L 21.6875 7.5 C 22.085938 7 22.085938 6.304688 21.6875 5.90625 L 20.09375 4.3125 C 19.59375 3.914063 18.898438 3.914063 18.5 4.3125 L 13 9.8125 L 7.5 4.3125 C 7.25 4.113281 6.945313 4 6.65625 4 Z"></path></svg>
</button>
`;
floorsBlock.prepend(div);
numApartments++;
let next_apartment_title = document.getElementById('next-apartment-title');
next_apartment_title.value = numApartments;
}
},
editNum: (element) => {
numApartments = Number(element.value);
},
addFloors: async (area) => {
let areaBlock = document.getElementById(`area-${area}`);
let uniqueFloors = [...new Set(Editor.apartments.list[area].map(obj => obj.floors_number))];
let new_floors = uniqueFloors.length + 1;
const uuid = localStorage.getItem('uuid');
const URL = `${CONFIG.api}/apartments/${area}`;
await fetch(URL, {
method: 'POST',
headers: {
"Content-Type": "application/json",
"Authorization": uuid
},
body: JSON.stringify({
apartment_number: Editor.apartments.list[area].length,
title: numApartments.toString(),
floors_number: Number(new_floors)
})
})
.then(response => response.json())
.then(data => {
console.log(data);
let div = document.createElement('div');
div.className = "block-apartments-floors";
div.id = `floors-${area}-${new_floors}`
div.innerHTML = `
<h2>Поверх ${new_floors}</h2>
<div class="block-apartments-number" id="block-apartments-${area}-${data.id}">
<input type="text" value="${numApartments}" id="apartament-${area}-${data.id}" onchange="Editor.apartments.editApartment('${area}', '${data.id}')">
<button type="button" onclick="Editor.apartments.deleteApartment('${area}', '${data.id}')">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 26 26"><path d="M 6.65625 4 C 6.367188 4 6.105469 4.113281 5.90625 4.3125 L 4.3125 5.90625 C 3.914063 6.304688 3.914063 7 4.3125 7.5 L 9.8125 13 L 4.3125 18.5 C 3.914063 19 3.914063 19.695313 4.3125 20.09375 L 5.90625 21.6875 C 6.40625 22.085938 7.101563 22.085938 7.5 21.6875 L 13 16.1875 L 18.5 21.6875 C 19 22.085938 19.695313 22.085938 20.09375 21.6875 L 21.6875 20.09375 C 22.085938 19.59375 22.085938 18.898438 21.6875 18.5 L 16.1875 13 L 21.6875 7.5 C 22.085938 7 22.085938 6.304688 21.6875 5.90625 L 20.09375 4.3125 C 19.59375 3.914063 18.898438 3.914063 18.5 4.3125 L 13 9.8125 L 7.5 4.3125 C 7.25 4.113281 6.945313 4 6.65625 4 Z"></path></svg>
</button>
</div>
<button class="addApartment" id="buttonApartment-${area}-${new_floors}" title="Додати квартиру" type="button" onclick="Editor.apartments.addApartment('${area}', '${new_floors}')"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 26 26"><path d="M 6.65625 4 C 6.367188 4 6.105469 4.113281 5.90625 4.3125 L 4.3125 5.90625 C 3.914063 6.304688 3.914063 7 4.3125 7.5 L 9.8125 13 L 4.3125 18.5 C 3.914063 19 3.914063 19.695313 4.3125 20.09375 L 5.90625 21.6875 C 6.40625 22.085938 7.101563 22.085938 7.5 21.6875 L 13 16.1875 L 18.5 21.6875 C 19 22.085938 19.695313 22.085938 20.09375 21.6875 L 21.6875 20.09375 C 22.085938 19.59375 22.085938 18.898438 21.6875 18.5 L 16.1875 13 L 21.6875 7.5 C 22.085938 7 22.085938 6.304688 21.6875 5.90625 L 20.09375 4.3125 C 19.59375 3.914063 18.898438 3.914063 18.5 4.3125 L 13 9.8125 L 7.5 4.3125 C 7.25 4.113281 6.945313 4 6.65625 4 Z"></path></svg></button>
`
areaBlock.prepend(div);
console.log(Editor.apartments.list[area]);
Editor.apartments.list[area].push({
id: data.id,
entrance_id: Number(area),
apartment_number: Editor.apartments.list[area].length,
title: numApartments.toString(),
floors_number: Number(new_floors)
});
numApartments++;
let next_apartment_title = document.getElementById('next-apartment-title');
next_apartment_title.value = numApartments;
})
.catch(err => {
console.log(err);
})
},
addApartment: async (area, floors) => {
const uuid = localStorage.getItem('uuid');
const URL = `${CONFIG.api}/apartments/${area}`;
await fetch(URL, {
method: 'POST',
headers: {
"Content-Type": "application/json",
"Authorization": uuid
},
body: JSON.stringify({
apartment_number: Editor.apartments.list[area].length,
title: numApartments.toString(),
floors_number: Number(floors)
})
})
.then(response => response.json())
.then(data => {
console.log(data);
Editor.apartments.list[area].push({
id: data.id,
entrance_id: Number(area),
apartment_number: Editor.apartments.list[area].length,
title: numApartments.toString(),
floors_number: Number(floors)
});
let floorsBlock = document.getElementById(`floors-${area}-${floors}`);
document.getElementById(`buttonApartment-${area}-${floors}`).remove();
floorsBlock.innerHTML += `
<div class="block-apartments-number" id="block-apartments-${area}-${data.id}">
<input type="text" value="${numApartments}" id="apartament-${area}-${data.id}" onchange="Editor.apartments.editApartment('${area}', '${data.id}')">
<button type="button" onclick="Editor.apartments.deleteApartment('${area}', '${data.id}')">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 26 26"><path d="M 6.65625 4 C 6.367188 4 6.105469 4.113281 5.90625 4.3125 L 4.3125 5.90625 C 3.914063 6.304688 3.914063 7 4.3125 7.5 L 9.8125 13 L 4.3125 18.5 C 3.914063 19 3.914063 19.695313 4.3125 20.09375 L 5.90625 21.6875 C 6.40625 22.085938 7.101563 22.085938 7.5 21.6875 L 13 16.1875 L 18.5 21.6875 C 19 22.085938 19.695313 22.085938 20.09375 21.6875 L 21.6875 20.09375 C 22.085938 19.59375 22.085938 18.898438 21.6875 18.5 L 16.1875 13 L 21.6875 7.5 C 22.085938 7 22.085938 6.304688 21.6875 5.90625 L 20.09375 4.3125 C 19.59375 3.914063 18.898438 3.914063 18.5 4.3125 L 13 9.8125 L 7.5 4.3125 C 7.25 4.113281 6.945313 4 6.65625 4 Z"></path></svg>
</button>
</div>
`;
floorsBlock.innerHTML += `<button class="addApartment" id="buttonApartment-${area}-${floors}" title="Додати квартиру" type="button" onclick="Editor.apartments.addApartment('${area}', '${floors}')"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 26 26"><path d="M 6.65625 4 C 6.367188 4 6.105469 4.113281 5.90625 4.3125 L 4.3125 5.90625 C 3.914063 6.304688 3.914063 7 4.3125 7.5 L 9.8125 13 L 4.3125 18.5 C 3.914063 19 3.914063 19.695313 4.3125 20.09375 L 5.90625 21.6875 C 6.40625 22.085938 7.101563 22.085938 7.5 21.6875 L 13 16.1875 L 18.5 21.6875 C 19 22.085938 19.695313 22.085938 20.09375 21.6875 L 21.6875 20.09375 C 22.085938 19.59375 22.085938 18.898438 21.6875 18.5 L 16.1875 13 L 21.6875 7.5 C 22.085938 7 22.085938 6.304688 21.6875 5.90625 L 20.09375 4.3125 C 19.59375 3.914063 18.898438 3.914063 18.5 4.3125 L 13 9.8125 L 7.5 4.3125 C 7.25 4.113281 6.945313 4 6.65625 4 Z"></path></svg></button>`
numApartments++;
let next_apartment_title = document.getElementById('next-apartment-title');
next_apartment_title.value = numApartments;
})
.catch(err => {
console.log(err);
})
},
editApartment: async (area, apartament) => {
let input = document.getElementById(`apartament-${area}-${apartament}`);
input.setAttribute("value", input.value);
const pos = Editor.apartments.list[area].map(e => e.id).indexOf(Number(apartament));
Editor.apartments.list[area][pos].title = input.value;
const uuid = localStorage.getItem('uuid');
const URL = `${CONFIG.api}/apartments/${area}`;
await fetch(URL, {
method: 'PUT',
headers: {
"Content-Type": "application/json",
"Authorization": uuid
},
body: JSON.stringify({
title: Editor.apartments.list[area][pos].title,
status: Editor.apartments.list[area][pos].status,
description: Editor.apartments.list[area][pos].description,
id: Editor.apartments.list[area][pos].id
})
})
.then(response => response.json())
.then(data => {
console.log(data);
})
.catch(err => {
console.log(err);
})
},
deleteApartment: async (area, apartament) => {
const pos = Editor.apartments.list[area].map(e => e.id).indexOf(Number(apartament));
const uuid = localStorage.getItem('uuid');
const URL = `${CONFIG.api}/apartments/${area}`;
await fetch(URL, {
method: 'DELETE',
headers: {
"Content-Type": "application/json",
"Authorization": uuid
},
body: JSON.stringify({
id: Editor.apartments.list[area][pos].id
})
})
.then(response => response.json())
.then(data => {
console.log(data);
document.getElementById(`block-apartments-${area}-${apartament}`).remove();
Editor.apartments.list[area].splice(pos, 1);
numApartments--;
let next_apartment_title = document.getElementById('next-apartment-title');
next_apartment_title.value = numApartments;
})
.catch(err => {
console.log(err);
})
}
},
osm: {
init: () => {
let center = { lat: 49.5629016, lng: 25.6145625 };
let zoom = 19;
let googleHybrid = L.tileLayer('http://{s}.google.com/vt/lyrs=s,h&x={x}&y={y}&z={z}', {
maxZoom: 20,
minZoom: 15,
subdomains: ['mt0', 'mt1', 'mt2', 'mt3']
});
let osm = L.tileLayer('http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
maxZoom: 19,
minZoom: 15
});
let mytile = L.tileLayer('https://tm.rozenrod.com/webp/{z}/{x}/{y}.webp', {
maxZoom: 20,
minZoom: 15,
tms: true
});
if (!map) {
houseGroup = new L.FeatureGroup();
splitPolygonsGroup = new L.FeatureGroup();
RectangleGroup = new L.FeatureGroup();
entransePolygonsGroup = new L.FeatureGroup();
entranseNumPolygonsGroup = new L.FeatureGroup();
map = L.map('map', {
renderer: L.canvas(),
center,
zoom,
layers: [
googleHybrid,
osm,
mytile,
houseGroup,
entransePolygonsGroup,
entranseNumPolygonsGroup,
splitPolygonsGroup,
RectangleGroup,
],
zoomControl: false
});
let baseMaps = {
"Google Hybrid": googleHybrid,
"OpenStreetMap": osm,
"Territory Map": mytile
};
let overlayMaps = {
"Будинки": houseGroup,
"Під'їзди": entransePolygonsGroup,
"Номера під'їздів": entranseNumPolygonsGroup,
"Слой редактирования": splitPolygonsGroup,
"Слой линейки": RectangleGroup,
};
L.control.layers(baseMaps, overlayMaps, { position: 'bottomright' }).addTo(map);
map.pm.setLang("ua");
map.pm.addControls({
position: 'bottomright',
drawCircleMarker: false,
drawPolyline: false,
drawPolygon: false,
drawRectangle: false,
drawCircle: false,
drawText: false,
drawMarker: false,
cutPolygon: false,
tooltips: false
});
map.pm.toggleControls()
map.pm.setGlobalOptions({
layerGroup: splitPolygonsGroup
})
}
}
}
}

View File

@@ -0,0 +1,500 @@
.page-editor {
width: calc(100% - 40px);
display: flex;
flex-direction: column;
align-items: center;
margin: 20px 20px 0 20px;
}
.page-editor form>button {
border-radius: 6px;
background: var(--PrimaryColor);
color: var(--PrimaryColorText);
width: 100%;
height: 40px;
font-size: 14px;
font-weight: 400;
margin: 20px 0;
text-transform: uppercase;
}
.page-editor details {
border-radius: 10px;
width: 100%;
display: flex;
flex-direction: column;
align-items: stretch;
margin-bottom: 20px;
background: var(--ColorThemes1);
color: var(--ColorThemes3);
border: 1px solid var(--ColorThemes2);
box-shadow: var(--shadow-l1);
}
.page-editor>details[disabled] summary,
.page-editor>details.disabled summary {
pointer-events: none;
user-select: none;
}
.page-editor>details summary::-webkit-details-marker,
.page-editor>details summary::marker {
display: none;
content: "";
}
.page-editor summary {
width: calc(100% - 40px);
cursor: pointer;
color: var(--ColorThemes3);
border-radius: var(--border-radius);
font-size: 16px;
font-weight: 300;
padding: 20px;
position: relative;
}
.page-editor summary span {
font-weight: 500;
}
.page-editor summary input {
font-weight: 500;
position: absolute;
right: 0;
top: 0;
padding: 10px;
margin: 13px;
font-size: 12px;
background: var(--ColorThemes3);
color: var(--ColorThemes0);
border-radius: 6px;
width: 40px;
}
.page-editor #info-form,
.page-editor #map-form,
.page-editor #area-form {
padding: 0 20px;
}
#details-info-type {
display: flex;
align-items: center;
justify-content: center;
}
#details-info-type>.tabs {
display: flex;
position: relative;
background-color: var(--ColorThemes0);
padding: 4px;
border-radius: 6px;
width: calc(100% - 8px);
z-index: 2;
}
#details-info-type>.tabs>input[type="radio"] {
display: none;
}
#details-info-type>.tabs>.tab {
display: flex;
align-items: center;
justify-content: center;
height: 40px;
width: calc(100% / 3);
cursor: pointer;
padding: 0 15px;
transition: 0.15s ease-in;
color: var(--ColorThemes3);
fill: var(--ColorThemes3);
flex-direction: row;
z-index: 2;
}
#details-info-type>.tabs>.tab>svg {
width: 20px;
height: 20px;
}
#details-info-type>.tabs>.tab>span {
margin-left: 6px;
font-size: 12px;
font-weight: 400;
}
#details-info-type>.tabs>input[type="radio"]:checked+label {
color: var(--PrimaryColorText);
fill: var(--PrimaryColorText);
}
#details-info-type>.tabs>.glider {
position: absolute;
display: flex;
height: 40px;
width: calc((100% - 8px) / 3);
background-color: var(--PrimaryColor);
z-index: 1;
border-radius: 4px;
transition: 0.25s ease-out;
}
@media (min-width: 601px) {
#details-info-type>.tabs>input[id="info-type-house"]:checked~.glider {
transform: translateX(0);
}
#details-info-type>.tabs>input[id="info-type-homestead"]:checked~.glider {
transform: translateX(100%);
}
#details-info-type>.tabs>input[id="info-type-points"]:checked~.glider {
transform: translateX(200%);
}
}
@media (max-width: 600px) {
#details-info-type>.tabs {
flex-direction: column;
}
#details-info-type>.tabs>.tab {
width: calc(100% - 8px);
padding: 0 4px;
}
#details-info-type>.tabs>.glider {
width: calc(100% - 8px);
}
#details-info-type>.tabs>input[id="info-type-house"]:checked~.glider {
transform: translateY(0);
}
#details-info-type>.tabs>input[id="info-type-homestead"]:checked~.glider {
transform: translateY(100%);
}
#details-info-type>.tabs>input[id="info-type-points"]:checked~.glider {
transform: translateY(200%);
}
}
.page-editor .details-info-input {
width: 100%;
display: flex;
margin: 20px 0;
align-items: flex-start;
flex-direction: column;
}
.page-editor .details-info-input label {
display: flex;
justify-content: center;
flex-direction: column;
font-size: 12px;
font-weight: 500;
margin-bottom: 5px;
}
.page-editor .details-info-input input {
width: calc(100% - 10px);
min-width: 140px;
padding: 0 5px;
border-radius: 6px;
height: 30px;
background: var(--ColorThemes0);
color: var(--ColorThemes3);
}
.page-editor #details-info-osm div {
display: flex;
align-items: center;
width: 100%;
}
.page-editor #details-info-osm input {
width: calc(100% - 10px);
min-width: 140px;
padding: 0 5px;
border-radius: 6px;
height: 30px;
}
.page-editor .details-info-input a {
height: 26px;
width: 26px;
margin-left: 10px;
}
.page-editor .details-info-input a>svg {
height: 26px;
width: 26px;
fill: var(--ColorThemes3)
}
.page-editor #list-area {
display: flex;
overflow: auto;
margin: 15px 0 20px 0;
}
.page-editor #list-area h3 {
text-align: center;
font-size: 16px;
font-weight: 400;
margin: 10px;
padding: 7px;
color: var(--ColorThemes0);
background: var(--ColorThemes3);
border-radius: 4px;
}
.block-area {
min-height: 200px;
border: 1px solid var(--ColorThemes3);
border-style: dashed;
border-radius: 6px;
margin: 0 10px 10px 0;
}
.addFloors,
.addApartment {
display: flex;
position: relative;
width: 34px;
height: 34px;
background: var(--PrimaryColor);
color: var(--PrimaryColorText);
margin: 25px;
border-radius: 50%;
align-items: center;
justify-content: center;
font-size: 30px;
cursor: pointer;
}
.addFloors>svg,
.addApartment>svg {
width: 20px;
height: 20px;
fill: var(--PrimaryColorText);
transform: rotate(45deg);
}
.block-apartments-floors {
position: relative;
display: flex;
width: calc(100% - 22px);
border: 1px solid var(--ColorThemes3);
margin: 10px;
border-radius: 4px;
}
.block-apartments-floors h2 {
position: absolute;
width: 65px;
right: -1px;
top: -1px;
margin: 0;
background: var(--ColorThemes3);
color: var(--ColorThemes2);
border-radius: 0 4px 0 4px;
font-size: 12px;
padding: 2px 4px;
text-align: center;
}
.block-apartments-number {
display: flex;
position: relative;
width: 60px;
height: 60px;
background: var(--ColorThemes1);
border: 2px solid var(--PrimaryColor);
margin: 10px;
border-radius: 4px;
align-items: center;
justify-content: center;
}
.block-apartments-number input {
width: 50px;
height: 50px;
font-size: 16px;
font-weight: 400;
text-align: center;
color: var(--ColorThemes3);
background: 0;
}
.block-apartments-number button {
position: absolute;
top: 0;
right: 0;
width: 20px;
height: 20px;
border-radius: 0 4px 0 4px;
background: var(--PrimaryColor);
font-size: 16px;
margin: -2px;
border: 0;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
}
.block-apartments-number button>svg {
width: 16px;
height: 16px;
fill: var(--PrimaryColorText);
}
.block-map {
width: 100%;
height: 500px;
border-radius: 6px;
overflow: hidden;
margin-bottom: 20px;
}
#map {
width: 100%;
height: 100%;
}
.entranse_number {
left: -10px !important;
top: -10px !important;
}
.markerEntranse {
background: hsl(0deg 0% 52.12% / 90%);
font-size: 24px;
border: 2px solid #676767;
border-radius: 2px;
display: flex !important;
justify-content: center;
align-items: center;
color: #fff;
min-width: 30px;
min-height: 30px;
height: 30px;
width: 30px;
}
.editor-buttons {
margin-top: 15px;
}
.editor-buttons>button {
width: 100%;
min-height: 30px;
margin: 5px 0;
border: 0;
color: var(--PrimaryColorText);
display: flex;
align-items: center;
justify-content: center;
background: var(--PrimaryColor);
text-transform: uppercase;
cursor: pointer;
border-radius: 6px;
font-weight: 400;
font-size: 12px;
}
.editor-buttons>div {
display: flex;
width: 100%;
margin: 10px 0;
height: 30px;
justify-content: space-between;
}
.editor-buttons>div>button {
background: var(--ColorThemes3);
color: var(--ColorThemes1);
width: 100%;
margin: 0;
border-radius: 6px;
text-transform: uppercase;
font-weight: 400;
font-size: 12px;
}
.page-editor #list-entranse,
.page-editor #list-homestead {
width: 100%;
min-height: 86px;
display: flex;
flex-direction: row;
flex-wrap: nowrap;
overflow-x: initial;
overflow-y: auto;
border: 1px solid var(--ColorThemes3);
background: 0;
border-radius: 6px;
border-style: dashed;
margin: 10px 0;
}
.page-editor #list-entranse>.house,
.page-editor #list-homestead>.house {
display: flex;
position: relative;
width: 60px;
height: 60px;
background: var(--ColorThemes1);
border: 2px solid var(--PrimaryColor);
margin: 10px;
border-radius: 4px;
align-items: center;
justify-content: center;
}
.page-editor #list-entranse>.house>input,
.page-editor #list-homestead>.house>input,
.page-editor #list-entranse>.house>p,
.page-editor #list-homestead>.house>p {
width: 50px;
height: 50px;
font-size: 16px;
font-weight: 400;
text-align: center;
color: var(--ColorThemes3);
background: 0;
display: flex;
align-items: center;
justify-content: center;
}
.page-editor #list-entranse>.house>button,
.page-editor #list-homestead>.house>button {
position: absolute;
top: 0;
right: 0;
width: 20px;
height: 20px;
border-radius: 0 4px 0 4px;
background: var(--PrimaryColor);
font-size: 16px;
margin: -2px;
border: 0;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
}
.page-editor #list-entranse>.house>button>svg,
.page-editor #list-homestead>.house>button>svg {
width: 16px;
height: 16px;
fill: var(--PrimaryColorText);
}

View File

@@ -0,0 +1,9 @@
<div class="page-home">
<details id="details-personal-territory" open>
<summary>
<span>Території для опрацювання</span>
</summary>
<div id="home-personal-territory-list"></div>
</details>
</div>

View File

@@ -0,0 +1,95 @@
const Home = {
init: async () => {
let html = await fetch('/lib/pages/home/index.html').then((response) => response.text());
app.innerHTML = html;
Home.house.setHTML();
Home.homestead.setHTML();
},
house: {
loadAPI: async function () {
let uuid = localStorage.getItem("uuid");
const URL = `${CONFIG.api}houses/list?mode=sheep`;
return await fetch(URL, {
method: 'GET',
headers: {
"Content-Type": "application/json",
"Authorization": uuid
}
}).then((response) => response.json());
},
setHTML: async function () {
let list = await Home.house.loadAPI();
list.sort((a, b) => b.id - a.id);
console.log(list);
let block_house = document.getElementById('home-personal-territory-list')
for (let i = 0; i < list.length; i++) {
const element = list[i];
block_house.innerHTML += `
<div class="card">
<i style="background-image: url(https://sheep-service.com/cards/house/T${element.id}.webp);"></i>
<div class="contents">
<div class="group" style="background: ${colorGroup(element.group_id)}">
<span>Група №${element.group_id}</span>
</div>
<div class="info">
<div>
<p>${element.title} ${element.number}</p>
</div>
</div>
</div>
<a href="/territory/card/house/${element.id}" data-route></a>
</div>
`;
}
}
},
homestead: {
loadAPI: async function () {
let uuid = localStorage.getItem("uuid");
const URL = `${CONFIG.api}homestead/list?mode=sheep`;
return await fetch(URL, {
method: 'GET',
headers: {
"Content-Type": "application/json",
"Authorization": uuid
}
}).then((response) => response.json());
},
setHTML: async function () {
let list = await Home.homestead.loadAPI();
list.sort((a, b) => b.id - a.id);
console.log(list);
let block_homestead = document.getElementById('home-personal-territory-list')
for (let i = 0; i < list.length; i++) {
const element = list[i];
block_homestead.innerHTML += `
<div class="card">
<i style="background-image: url(https://sheep-service.com/cards/homestead/H${element.id}.webp);"></i>
<div class="contents">
<div class="group" style="background: ${colorGroup(element.group_id)}">
<span>Група №${element.group_id}</span>
</div>
<div class="info">
<div>
<p>${element.title} ${element.number}</p>
</div>
</div>
</div>
<a href="/territory/card/homestead/${element.id}" data-route></a>
</div>
`;
}
}
}
}

View File

@@ -0,0 +1,191 @@
.page-home {
width: calc(100% - 40px);
display: flex;
flex-direction: column;
margin: 20px 20px 0 20px;
}
.page-home details {
border-radius: 15px;
width: 100%;
display: flex;
flex-direction: column;
align-items: stretch;
margin-bottom: 20px;
background: var(--ColorThemes1);
color: var(--ColorThemes3);
border: 1px solid var(--ColorThemes2);
box-shadow: var(--shadow-l1);
}
.page-home>details[disabled] summary,
.page-home>details.disabled summary {
pointer-events: none;
user-select: none;
}
.page-home>details summary::-webkit-details-marker,
.page-home>details summary::marker {
display: none;
content: "";
}
.page-home summary {
width: calc(100% - 40px);
cursor: pointer;
color: var(--ColorThemes3);
border-radius: var(--border-radius);
font-size: 16px;
font-weight: 300;
padding: 20px;
position: relative;
}
.page-home summary span {
font-weight: 500;
}
.page-home #home-personal-territory-list {
width: 100%;
margin: 0;
display: flex;
flex-wrap: wrap;
flex-direction: row;
align-content: flex-start;
justify-content: center;
overflow-y: auto;
align-items: flex-start;
transition: .3s ease;
}
.page-home .card {
position: relative;
width: 300px;
height: 200px;
background-color: var(--ColorThemes2);
margin: 0px 10px 20px 10px;
overflow: hidden;
cursor: pointer;
border-radius: 10px;
}
@media (max-width: 2300px) {
.page-home .card {
width: calc((100% / 5) - 30px);
}
}
@media (max-width: 1960px) {
.page-home .card {
width: calc((100% / 4) - 30px);
}
}
@media (max-width: 1640px) {
.page-home .card {
width: calc((100% / 3) - 30px);
}
}
@media (max-width: 1280px) {
.page-home .card {
width: calc((100% / 2) - 30px);
}
}
@media (max-width: 650px) {
.page-home .card {
width: 100%;
}
}
@media(hover: hover) {
.page-home .card:hover {
opacity: 0.8;
}
}
.page-home .card>i {
position: absolute;
width: 100%;
height: 100%;
object-fit: cover;
z-index: 1;
filter: blur(2px);
/* background-repeat: round; */
background-size: cover;
background-position: center;
background-image: url(https://tm.rozenrod.com/web/img/bg.webp);
}
.page-home .card>a {
position: absolute;
width: 100%;
height: 100%;
z-index: 10;
}
.page-home .contents {
position: absolute;
z-index: 2;
background: rgb(64 64 64 / 0.7);
width: 100%;
height: 100%;
font-size: 40px;
font-weight: 500;
color: #fff;
display: flex;
flex-direction: column;
align-items: center;
justify-content: space-between;
}
.page-home .group {
width: calc(100% - 20px);
max-height: 50px;
border-radius: 7px;
padding: 10px 0;
margin-top: 10px;
display: flex;
background: var(--PrimaryColor);
align-items: center;
flex-direction: column;
justify-content: space-around;
}
.page-home .group>span {
color: #fff;
font-size: 14px;
font-weight: 400;
}
.page-home .info {
width: calc(100% - 20px);
margin-bottom: 10px;
}
.page-home .info>div {
width: 100%;
height: 35px;
margin-top: 5px;
display: flex;
background: var(--ColorThemes0);
align-items: center;
justify-content: center;
font-size: 12px;
color: var(--ColorThemes3);
border-radius: 7px;
position: relative;
overflow: hidden;
}
.page-home .info>div>span {
color: var(--ColorThemes3);
font-size: 14px;
font-weight: 300;
z-index: 2;
}
.page-home .info>div>p {
color: var(--ColorThemes3);
font-size: 14px;
font-weight: 400;
padding: 10px;
z-index: 2;
}

View File

View File

@@ -0,0 +1,6 @@
const Options = {
init: async () => {
let html = await fetch('/lib/pages/options/index.html').then((response) => response.text());
app.innerHTML = html;
}
}

View File

View File

@@ -0,0 +1,250 @@
<div class="page-sheeps">
<div id="block-sheeps-list">
<div class="header">
<h1>Всі вісники</h1>
</div>
<div class="card-profile"></div>
<div class="card-profile"></div>
<div class="card-profile"></div>
<div class="card-profile"></div>
<div class="card-profile"></div>
<div class="card-profile"></div>
<div class="card-profile"></div>
<div class="card-profile"></div>
<div class="card-profile"></div>
<div class="card-profile"></div>
<div class="card-profile"></div>
<div class="card-profile"></div>
</div>
<div id="block-sheep-info">
<div id="sheep-mess">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
<path
d="M 25.042969 12 C 20.698969 12 18 15.029297 18 19.904297 C 18 24.104297 20.766812 28.599609 24.882812 28.599609 C 27.167812 28.599609 31 27.369813 31 19.132812 C 31 14.999813 28.494969 12 25.042969 12 z M 39.974609 12 C 35.672609 12 33 14.931437 33 19.648438 C 33 24.104438 34.862391 28.599609 39.025391 28.599609 C 43.603391 28.599609 46 23.922734 46 19.302734 C 46 14.285734 42.876609 12 39.974609 12 z M 25.042969 16 C 26.388969 16 27 17.623812 27 19.132812 C 27 20.776813 26.794812 24.599609 24.882812 24.599609 C 23.428813 24.599609 22 22.274297 22 19.904297 C 22 16.000297 24.132969 16 25.042969 16 z M 39.974609 16 C 41.224609 16 42 17.265734 42 19.302734 C 42 21.906734 40.886391 24.599609 39.025391 24.599609 C 37.594391 24.599609 37 21.375438 37 19.648438 C 37 16.000438 39.084609 16 39.974609 16 z M 13.689453 24 C 9.8134531 24 7 27.465234 7 32.240234 C 7 35.462234 9.0299688 40 13.542969 40 C 17.224969 40 20 36.250297 20 31.279297 C 20 27.061297 17.346453 24 13.689453 24 z M 50.75 24 C 46.94 24 43 27.440219 43 33.199219 C 43 36.541219 45.15 40 48.75 40 C 52.612 40 56 35.477312 56 30.320312 C 56 26.480313 53.939 24 50.75 24 z M 13.689453 28 C 15.514453 28 16 30.061297 16 31.279297 C 16 33.793297 14.852969 36 13.542969 36 C 11.603969 36 11 33.244234 11 32.240234 C 11 29.783234 12.131453 28 13.689453 28 z M 50.75 28 C 51.783 28 52 29.262313 52 30.320312 C 52 33.788312 49.866 36 48.75 36 C 47.514 36 47 34.175219 47 33.199219 C 47 29.652219 49.237 28 50.75 28 z M 31.806641 30.001953 C 29.371641 29.947953 27.296656 31.058937 25.597656 33.335938 C 22.515656 37.450938 21.695953 38.210313 19.376953 39.695312 C 17.528953 40.875312 14.998047 42.492141 14.998047 45.994141 C 14.998047 49.769141 18.164641 52.839844 22.056641 52.839844 C 23.872641 52.839844 25.588266 52.174453 27.697266 51.439453 C 29.071266 52.348453 30.722125 52.839844 32.453125 52.839844 C 34.215125 52.839844 35.869422 52.353125 37.232422 51.453125 C 41.805422 53.496125 44.150719 53.039344 45.636719 52.277344 C 47.314719 51.416344 48.403953 49.800703 48.876953 47.470703 C 49.107953 46.334703 48.997375 45.198781 48.609375 44.175781 C 48.012375 42.602781 46.757453 41.293531 45.064453 40.644531 C 43.137453 39.916531 42.576406 39.753172 41.316406 38.826172 C 39.672406 37.616172 39.015453 35.742422 37.814453 33.607422 C 36.601453 31.449422 34.298641 30.067953 31.806641 30.001953 z M 31.701172 34.001953 C 32.777172 34.029953 33.808125 34.644359 34.328125 35.568359 C 35.480125 37.621359 36.442312 40.205828 38.945312 42.048828 C 40.748312 43.375828 41.894813 43.725812 43.632812 44.382812 C 44.073813 44.550813 44.501812 44.883844 44.757812 45.339844 C 44.966813 45.711844 45.061031 46.164781 44.957031 46.675781 C 44.738031 47.753781 44.3535 48.44175 43.8125 48.71875 C 42.4475 49.41975 39.736125 48.192875 38.578125 47.671875 C 38.187125 47.495875 36.729781 46.582125 35.550781 47.703125 C 34.778781 48.436125 33.679125 48.839844 32.453125 48.839844 C 31.288125 48.839844 30.211922 48.444562 29.419922 47.726562 C 28.893922 47.247562 28.151609 47.085828 27.474609 47.298828 C 25.411609 47.948828 23.441594 48.839844 22.058594 48.839844 C 20.372594 48.839844 19.001953 47.562141 19.001953 45.994141 C 19.001953 44.795141 19.69525 44.240406 21.53125 43.066406 C 24.29525 41.302406 25.514688 40.146469 28.804688 35.730469 C 29.975687 34.161469 30.981172 33.980953 31.701172 34.001953 z"
/>
</svg>
<h1>Виберіть вісника для редагування</h1>
</div>
<form id="sheep-editor" style="display: none; opacity: 0">
<div class="header">
<h1>Інформація про вісника</h1>
<button onclick="Sheeps.editor.close()">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 26 26">
<path
d="M 6.65625 4 C 6.367188 4 6.105469 4.113281 5.90625 4.3125 L 4.3125 5.90625 C 3.914063 6.304688 3.914063 7 4.3125 7.5 L 9.8125 13 L 4.3125 18.5 C 3.914063 19 3.914063 19.695313 4.3125 20.09375 L 5.90625 21.6875 C 6.40625 22.085938 7.101563 22.085938 7.5 21.6875 L 13 16.1875 L 18.5 21.6875 C 19 22.085938 19.695313 22.085938 20.09375 21.6875 L 21.6875 20.09375 C 22.085938 19.59375 22.085938 18.898438 21.6875 18.5 L 16.1875 13 L 21.6875 7.5 C 22.085938 7 22.085938 6.304688 21.6875 5.90625 L 20.09375 4.3125 C 19.59375 3.914063 18.898438 3.914063 18.5 4.3125 L 13 9.8125 L 7.5 4.3125 C 7.25 4.113281 6.945313 4 6.65625 4 Z"
></path>
</svg>
</button>
</div>
<i id="sheep-editor-icon"></i>
<div class="editor-blocks-inputs" id="editor-blocks-inputs-uuid">
<label>UUID</label>
<input
id="sheep-editor-uuid"
type="text"
name="uuid"
value=""
disabled
style="display: none"
/>
<p id="sheep-editor-uuid-copy" style="cursor: copy !important"></p>
</div>
<div class="editor-blocks-inputs">
<label for="sheep-editor-name">Імʼя</label>
<input id="sheep-editor-name" type="text" name="name" required="" />
</div>
<div class="editor-blocks-inputs">
<label for="sheep-editor-group_id">Група</label>
<select id="sheep-editor-group_id" name="group_id">
<option value="1">Група 1</option>
<option value="2" selected>Група 2</option>
<option value="3">Група 3</option>
<option value="4">Група 4</option>
<option value="5">Група 5</option>
<option value="6">Група 6</option>
<option value="7">Група 7</option>
</select>
</div>
<div class="editor-blocks-inputs">
<label for="sheep-editor-appointment">Призначення</label>
<select id="sheep-editor-appointment" name="appointment">
<option value="lamb" selected>Вісник</option>
<option value="pioneer">Піонер</option>
<option value="attender">Служитель збору</option>
<option value="elder">Старійшина збору</option>
</select>
</div>
<div class="editor-blocks-inputs">
<label for="sheep-editor-mode">Права</label>
<select id="sheep-editor-mode" name="mode">
<option value="sheep" selected>Користувач</option>
<option value="moderator">Модератор</option>
<option value="administrator">Адміністратор</option>
</select>
</div>
<div
class="editor-blocks-inputs"
id="editor-blocks-inputs-uuid-moder"
style="display: none"
>
<label>UUID адміністратора/модератора</label>
<p id="sheep-editor-uuid-moder" style="cursor: copy !important"></p>
</div>
<div
class="editor-blocks-checkbox"
id="sheep-editor-access-moder"
style="display: none"
>
<p for="editor-access">Дозволи модератора</p>
<div>
<div class="checkbox">
<input
name="can_add_sheeps"
class="custom-checkbox"
id="sheep-editor-can_add_sheeps"
type="checkbox"
/>
<label for="sheep-editor-can_add_sheeps"> Create Sheeps </label>
</div>
<div class="checkbox">
<input
name="can_add_territory"
class="custom-checkbox"
id="sheep-editor-can_add_territory"
type="checkbox"
/>
<label for="sheep-editor-can_add_territory">
Create Territory
</label>
</div>
<div class="checkbox">
<input
name="can_manager_territory"
class="custom-checkbox"
id="sheep-editor-can_manager_territory"
type="checkbox"
/>
<label for="sheep-editor-can_manager_territory">
Manager Territory
</label>
</div>
<div class="checkbox">
<input
name="can_add_stand"
class="custom-checkbox"
id="sheep-editor-can_add_stand"
type="checkbox"
/>
<label for="sheep-editor-can_add_stand"> Create Stand </label>
</div>
<div class="checkbox">
<input
name="can_manager_stand"
class="custom-checkbox"
id="sheep-editor-can_manager_stand"
type="checkbox"
/>
<label for="sheep-editor-can_manager_stand"> Manager Stand </label>
</div>
<div class="checkbox">
<input
name="can_add_schedule"
class="custom-checkbox"
id="sheep-editor-can_add_schedule"
type="checkbox"
/>
<label for="sheep-editor-can_add_schedule"> Create Schedule </label>
</div>
</div>
</div>
<div class="editor-blocks-checkbox">
<p for="editor-access">Дозволи вісника</p>
<div>
<div class="checkbox">
<input
name="can_view_schedule"
class="custom-checkbox"
id="sheep-editor-can_view_schedule"
type="checkbox"
/>
<label for="sheep-editor-can_view_schedule"> View Schedule </label>
</div>
<div class="checkbox">
<input
name="can_view_stand"
class="custom-checkbox"
id="sheep-editor-can_view_stand"
type="checkbox"
/>
<label for="sheep-editor-can_view_stand"> View Stand </label>
</div>
<div class="checkbox">
<input
name="can_view_territory"
class="custom-checkbox"
id="sheep-editor-can_view_territory"
type="checkbox"
/>
<label for="sheep-editor-can_view_territory">
View Territory
</label>
</div>
</div>
</div>
<button id="sheep-editor-button" style="display: none">Зберегти</button>
</form>
</div>
<div id="block-sheep-addeds" style="display: none; opacity: 0">
<form id="sheep-addeds">
<div class="header">
<h1>Додавання нового вісника</h1>
<button onclick="Sheeps.addeds.close()" class="close" type="button">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 26 26">
<path
d="M 6.65625 4 C 6.367188 4 6.105469 4.113281 5.90625 4.3125 L 4.3125 5.90625 C 3.914063 6.304688 3.914063 7 4.3125 7.5 L 9.8125 13 L 4.3125 18.5 C 3.914063 19 3.914063 19.695313 4.3125 20.09375 L 5.90625 21.6875 C 6.40625 22.085938 7.101563 22.085938 7.5 21.6875 L 13 16.1875 L 18.5 21.6875 C 19 22.085938 19.695313 22.085938 20.09375 21.6875 L 21.6875 20.09375 C 22.085938 19.59375 22.085938 18.898438 21.6875 18.5 L 16.1875 13 L 21.6875 7.5 C 22.085938 7 22.085938 6.304688 21.6875 5.90625 L 20.09375 4.3125 C 19.59375 3.914063 18.898438 3.914063 18.5 4.3125 L 13 9.8125 L 7.5 4.3125 C 7.25 4.113281 6.945313 4 6.65625 4 Z"
></path>
</svg>
</button>
</div>
<i id="sheep-addeds-icon"></i>
<div class="addeds-blocks-inputs">
<label for="sheep-addeds-name">Імʼя</label>
<input id="sheep-addeds-name" type="text" name="name" required="" />
</div>
<div class="addeds-blocks-inputs">
<label for="sheep-addeds-group_id">Група</label>
<select id="sheep-addeds-group_id" name="group_id" required>
<option value="" selected disabled>Оберіть...</option>
<option value="1">Група 1</option>
<option value="2">Група 2</option>
<option value="3">Група 3</option>
<option value="4">Група 4</option>
<option value="5">Група 5</option>
<option value="6">Група 6</option>
<option value="7">Група 7</option>
</select>
</div>
<div class="addeds-blocks-inputs">
<label for="sheep-addeds-appointment">Призначення</label>
<select id="sheep-addeds-appointment" name="appointment" required>
<option value="lamb" selected>Вісник</option>
<option value="pioneer">Піонер</option>
<option value="attender">Служитель збору</option>
<option value="elder">Старійшина збору</option>
</select>
</div>
<button id="sheep-addeds-button">Додати</button>
</form>
</div>
</div>

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,482 @@
.page-sheeps {
width: calc(100% - 18px);
display: flex;
flex-direction: row;
margin: 20px 9px 0 9px;
justify-content: space-between;
position: relative;
}
#block-sheeps-list,
#block-sheep-info {
width: 100%;
margin: 0 10px 15px;
border-radius: 15px;
display: flex;
flex-direction: column;
overflow: hidden;
background: var(--ColorThemes1);
color: var(--ColorThemes3);
border: 1px solid var(--ColorThemes2);
box-shadow: var(--shadow-l1);
transition: all .2s ease 0s;
}
#block-sheep-info {
min-height: calc(100vh - 40px);
max-height: calc(100vh - 40px);
position: sticky;
overflow: auto;
top: 20px;
right: 0;
}
#block-sheeps-list>.header {
min-height: 40px;
width: calc(100% - 20px);
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
background: var(--PrimaryColor);
margin: 10px;
border-radius: 10px;
}
#block-sheeps-list>.header>h1 {
font-size: 16px;
font-weight: 400;
color: var(--PrimaryColorText);
margin-left: 10px;
}
#block-sheeps-list>.header>button {
display: flex;
position: relative;
width: 30px;
height: 30px;
background: var(--ColorThemes0);
margin-right: 5px;
border-radius: 8px;
align-items: center;
justify-content: center;
font-size: 30px;
cursor: pointer;
}
#block-sheeps-list>.header>button>svg {
width: 20px;
height: 20px;
fill: var(--ColorThemes3);
transform: rotate(45deg);
}
#block-sheeps-list>.card-profile {
width: calc(100% - 30px);
min-height: 100px;
background-color: var(--ColorThemes2);
border: 1px solid var(--ColorThemes0);
box-shadow: var(--shadow-l1);
border-radius: 10px;
margin: 10px;
display: flex;
flex-direction: row;
align-items: center;
padding: 5px;
cursor: pointer;
}
#block-sheeps-list>.card-profile>img,
#block-sheeps-list>.card-profile>svg {
width: 65px;
min-width: 65px;
height: 65px;
margin: 10px 15px 10px 10px;
fill: var(--PrimaryColor);
}
#block-sheeps-list>.card-profile>.info {
display: flex;
flex-direction: column;
height: 90px;
justify-content: space-between;
position: relative;
width: calc(100% - 95px);
}
#block-sheeps-list>.card-profile>.info>.text>h1 {
font-size: 16px;
color: var(--ColorThemes3);
font-weight: 400;
}
#block-sheeps-list>.card-profile>.info>.text>h2 {
font-size: 12px;
color: var(--ColorThemes3);
font-weight: 400;
opacity: 0.8;
}
#block-sheeps-list>.card-profile>.info>.access {
display: flex;
overflow-x: auto;
overflow-y: hidden;
max-width: 100%;
padding-bottom: 4px;
height: 30px;
}
#block-sheeps-list>.card-profile>.info>.access>b {
padding: 2px 5px;
border-radius: 5px;
background: var(--PrimaryColor);
color: var(--PrimaryColorText);
margin-right: 5px;
white-space: nowrap;
font-size: 13px;
height: 20px;
display: flex;
align-items: center;
justify-content: center;
}
#block-sheep-info>#sheep-mess {
width: 200px;
height: 200px;
display: flex;
flex-direction: column;
align-items: center;
position: absolute;
top: 50%;
left: 50%;
margin-top: -100px;
margin-left: -100px;
justify-content: center;
opacity: 0.6;
transition: all .2s ease 0s;
z-index: 1;
}
#block-sheep-info>#sheep-mess>svg {
width: 100px;
height: 100px;
fill: var(--ColorThemes3);
}
#block-sheep-info>#sheep-mess>h1 {
font-size: 20px;
color: var(--ColorThemes3);
font-weight: 400;
text-align: center;
}
#block-sheep-info>#sheep-editor {
display: flex;
padding: 10px;
flex-direction: column;
align-items: center;
transition: all .3s ease 0s;
z-index: 2;
}
#block-sheep-info>#sheep-editor>.header {
display: none;
min-height: 40px;
width: 100%;
flex-direction: row;
align-items: center;
justify-content: space-between;
background: var(--ColorThemes3);
margin: 0 0 10px 0;
border-radius: 10px;
}
#block-sheep-info>#sheep-editor>.header>h1 {
font-size: 16px;
font-weight: 400;
color: var(--ColorThemes0);
margin-left: 10px;
}
#block-sheep-info>#sheep-editor>.header>button {
display: flex;
position: relative;
width: 30px;
height: 30px;
background: var(--ColorThemes0);
margin-right: 5px;
border-radius: 8px;
align-items: center;
justify-content: center;
font-size: 30px;
cursor: pointer;
}
#block-sheep-info>#sheep-editor>.header>button>svg {
width: 20px;
height: 20px;
fill: var(--ColorThemes3);
}
#block-sheep-info>#sheep-editor>i>svg {
width: 100px;
height: 100px;
fill: var(--PrimaryColor);
}
#block-sheep-info>#sheep-editor>.editor-blocks-inputs {
width: 100%;
display: flex;
margin: 10px 0;
align-items: flex-start;
flex-direction: column;
}
#block-sheep-info>#sheep-editor>.editor-blocks-inputs label {
display: flex;
justify-content: center;
flex-direction: column;
font-size: 12px;
font-weight: 500;
margin-bottom: 5px;
}
#block-sheep-info>#sheep-editor>.editor-blocks-inputs input {
width: calc(100% - 10px);
min-width: 140px;
padding: 0 5px;
border-radius: 6px;
height: 30px;
background: var(--ColorThemes0);
color: var(--ColorThemes3);
}
#block-sheep-info>#sheep-editor>.editor-blocks-inputs select {
width: 100%;
min-width: 140px;
padding: 0 5px;
border-radius: 6px;
height: 30px;
background-color: var(--ColorThemes0);
color: var(--ColorThemes3);
}
#block-sheep-info>#sheep-editor>.editor-blocks-inputs p {
display: flex;
width: calc(100% - 10px);
min-width: 140px;
font-size: 14px;
padding: 0 5px;
border-radius: 6px;
height: 30px;
background: var(--ColorThemes0);
color: var(--ColorThemes3);
align-items: center;
cursor: copy;
}
#block-sheep-info>#sheep-editor>.editor-blocks-checkbox {
width: 100%;
display: flex;
margin: 10px 0;
align-items: flex-start;
flex-direction: column;
}
#block-sheep-info>#sheep-editor>.editor-blocks-checkbox>p {
display: flex;
justify-content: center;
flex-direction: column;
font-size: 12px;
font-weight: 500;
margin-bottom: 5px;
}
#block-sheep-info>#sheep-editor>.editor-blocks-checkbox>div {
background: var(--ColorThemes0);
border-radius: 6px;
width: calc(100% - 15px);
padding: 0 5px 0 10px;
}
#block-sheep-info>#sheep-editor>.editor-blocks-checkbox>div>.checkbox {
margin: 10px 0;
width: 100%;
font-size: 14px;
}
#block-sheep-info>#sheep-editor>.editor-blocks-checkbox>div>.checkbox>.custom-checkbox+label {
width: 100%;
display: flex;
align-items: center;
user-select: none;
flex-direction: row-reverse;
justify-content: space-between;
}
#block-sheep-info>#sheep-editor>button {
border-radius: 6px;
background: var(--PrimaryColor);
color: var(--PrimaryColorText);
width: 100%;
height: 40px;
font-size: 14px;
font-weight: 400;
margin: 20px 0 0 0;
text-transform: uppercase;
}
#block-sheep-addeds {
width: 100%;
height: fit-content;
margin: 0 10px 15px;
border-radius: 15px;
display: flex;
flex-direction: column;
overflow: hidden;
background: var(--ColorThemes1);
color: var(--ColorThemes3);
border: 1px solid var(--ColorThemes2);
box-shadow: var(--shadow-l1);
transition: all .2s ease 0s;
}
#block-sheep-addeds>#sheep-addeds {
display: flex;
padding: 10px;
flex-direction: column;
align-items: center;
transition: all .3s ease 0s;
}
#block-sheep-addeds>#sheep-addeds>div {
width: 100%;
display: flex;
justify-content: space-between;
}
#block-sheep-addeds>#sheep-addeds>.header {
min-height: 40px;
width: 100%;
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
background: var(--ColorThemes3);
margin: 0 0 10px 0;
border-radius: 10px;
}
#block-sheep-addeds>#sheep-addeds>.header>h1 {
font-size: 16px;
font-weight: 400;
color: var(--ColorThemes0);
margin-left: 10px;
}
#block-sheep-addeds>#sheep-addeds>.header>button {
display: flex;
position: relative;
width: 30px;
height: 30px;
background: var(--ColorThemes0);
margin-right: 5px;
border-radius: 8px;
align-items: center;
justify-content: center;
font-size: 30px;
cursor: pointer;
}
#block-sheep-addeds>#sheep-addeds>.header>button>svg {
width: 20px;
height: 20px;
fill: var(--ColorThemes3);
}
#block-sheep-addeds>#sheep-addeds>i>svg {
width: 100px;
height: 100px;
fill: var(--PrimaryColor);
}
#block-sheep-addeds>#sheep-addeds>.addeds-blocks-inputs {
width: 100%;
display: flex;
margin: 10px 0;
align-items: flex-start;
flex-direction: column;
}
#block-sheep-addeds>#sheep-addeds>.addeds-blocks-inputs label {
display: flex;
justify-content: center;
flex-direction: column;
font-size: 12px;
font-weight: 500;
margin-bottom: 5px;
}
#block-sheep-addeds>#sheep-addeds>.addeds-blocks-inputs input {
width: calc(100% - 10px);
min-width: 140px;
padding: 0 5px;
border-radius: 6px;
height: 30px;
background: var(--ColorThemes0);
color: var(--ColorThemes3);
}
#block-sheep-addeds>#sheep-addeds>.addeds-blocks-inputs select {
width: 100%;
min-width: 140px;
padding: 0 5px;
border-radius: 6px;
height: 30px;
background-color: var(--ColorThemes0);
color: var(--ColorThemes3);
}
#block-sheep-addeds>#sheep-addeds>button {
border-radius: 6px;
background: var(--PrimaryColor);
color: var(--PrimaryColorText);
width: 100%;
height: 40px;
font-size: 14px;
font-weight: 400;
margin: 20px 0 0 0;
text-transform: uppercase;
}
@media (min-width: 1001px),
(min-height: 541px) {
#block-sheeps-list,
#block-sheep-info {
opacity: 1 !important;
}
}
@media (max-width: 1000px),
(max-height: 540px) {
#block-sheeps-list {
display: none;
}
#block-sheep-info {
display: none;
min-height: fit-content;
}
#block-sheep-info>#sheep-editor>.header {
display: flex;
}
}
/* @media (min-height: 600px) and (max-height: 700px) {
#block-sheep-info>#sheep-editor>i>svg {
display: none;
}
} */

View File

@@ -0,0 +1,4 @@
<div class="page-stand">
<label for="dateSelect">Виберіть дату:</label>
<select id="dateSelect"></select>
</div>

View File

@@ -0,0 +1,38 @@
const Stand = {
init: async () => {
let html = await fetch('/lib/pages/stand/index.html').then((response) => response.text());
app.innerHTML = html;
let listDate = [1, 4];
function generateAvailableDates() {
let select = document.getElementById("dateSelect");
select.innerHTML = "";
let today = new Date();
today.setHours(0, 0, 0, 0);
let months = [today.getMonth(), today.getMonth() + 1];
let year = today.getFullYear();
months.forEach(month => {
let date = new Date(year, month, 1);
while (date.getMonth() === month) {
if (date >= today) {
let day = date.getDay();
if (listDate.includes(day)) {
let option = document.createElement("option");
option.value = date.toISOString().split("T")[0];
option.textContent = date.toLocaleDateString("uk-UA", {
weekday: "long", year: "numeric", month: "long", day: "numeric"
});
select.appendChild(option);
}
}
date.setDate(date.getDate() + 1);
}
});
}
generateAvailableDates();
}
}

View File

@@ -0,0 +1,20 @@
.page-stand {
width: calc(100% - 40px);
display: flex;
flex-direction: column;
margin: 20px 20px 0 20px;
}
.page-stand select {
width: 100%;
min-width: 140px;
padding: 0 5px;
border-radius: 6px;
height: 30px;
background-color: var(--ColorThemes1);
color: var(--ColorThemes3);
border: 1px solid var(--ColorThemes2);
margin: 10px 0;
box-shadow: var(--shadow-l1);
font-size: 14px;
}

View File

@@ -0,0 +1,39 @@
<div class="page-territory">
<div class="buttons-list">
<button onclick="Rotation()" id="rotationButton" style="display: none;">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48">
<path
d="M16.5 25.5c-.52.893-1.476 1.5-2.584 1.5-1.657 0-3-1.343-3-3s1.343-3 3-3c1.108 0 2.064.607 2.584 1.5H23v-11C23 8.468 20.533 6 17.5 6h-6C8.467 6 6 8.468 6 11.5v25c0 3.032 2.467 5.5 5.5 5.5h6c3.033 0 5.5-2.468 5.5-5.5v-11H16.5zM36.5 6h-6C27.467 6 25 8.468 25 11.5v11h7.879l-1.439-1.439c-.586-.586-.586-1.535 0-2.121s1.535-.586 2.121 0l4 4c.586.586.586 1.535 0 2.121l-4 4C33.268 29.354 32.884 29.5 32.5 29.5s-.768-.146-1.061-.439c-.586-.586-.586-1.535 0-2.121l1.439-1.439H25v11c0 3.032 2.467 5.5 5.5 5.5h6c3.033 0 5.5-2.468 5.5-5.5v-25C42 8.468 39.533 6 36.5 6z"
/>
</svg>
<span id="rotationButton-title">Провести ротацію</span>
</button>
<a href="/constructor" data-route id="constructorButton" style="display: none;">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48">
<path
d="M 14.5 4 C 12.015 4 10 6.015 10 8.5 L 10 39.5 C 10 41.985 12.015 44 14.5 44 L 22.042969 44 C 22.079969 43.749 22.138516 43.502672 22.228516 43.263672 L 22.283203 43.285156 C 22.353203 42.961156 23.236422 40.161109 23.982422 37.787109 C 24.272422 36.865109 24.779891 36.029703 25.462891 35.345703 L 33.904297 27 L 27.5 27 C 26.672 27 26 26.328 26 25.5 L 26 16 L 38 16 L 38 23.054688 C 38.72 22.587688 39.4975 22.254422 40.3125 22.107422 C 40.5365 22.067422 40.769 22.062922 41 22.044922 L 41 14.5 C 41 13.672 40.328 13 39.5 13 L 34 13 L 34 8.5 C 34 6.015 31.985 4 29.5 4 L 14.5 4 z M 30.5 18 A 1.50015 1.50015 0 1 0 30.5 21 L 33.5 21 A 1.50015 1.50015 0 1 0 33.5 18 L 30.5 18 z M 41.498047 24 C 41.224047 24.001 40.946969 24.025172 40.667969 24.076172 C 39.783969 24.235172 38.939563 24.696156 38.226562 25.410156 L 26.427734 37.208984 C 26.070734 37.565984 25.807969 38.011141 25.667969 38.494141 L 24.097656 43.974609 C 24.025656 44.164609 23.993 44.365406 24 44.566406 C 24.013 44.929406 24.155594 45.288406 24.433594 45.566406 C 24.710594 45.843406 25.067688 45.986 25.429688 46 C 25.630688 46.007 25.834391 45.975344 26.025391 45.902344 L 31.505859 44.332031 C 31.988859 44.192031 32.431062 43.930266 32.789062 43.572266 L 44.589844 31.773438 C 45.303844 31.060437 45.764828 30.216031 45.923828 29.332031 C 45.973828 29.053031 45.997047 28.775953 45.998047 28.501953 C 46.001047 27.307953 45.540687 26.179312 44.679688 25.320312 C 43.820687 24.460313 42.692047 23.998 41.498047 24 z M 22 35 C 22.828 35 23.5 35.672 23.5 36.5 C 23.5 37.328 22.828 38 22 38 C 21.172 38 20.5 37.328 20.5 36.5 C 20.5 35.672 21.172 35 22 35 z"
/>
</svg>
<span>Конструктор</span>
</a>
</div>
<div class="list-controls"></div>
<details open>
<summary>
<span>Багатоквартирні будинки</span>
</summary>
<div id="list-house"></div>
</details>
<details open>
<summary>
<span>Житлові райони</span>
</summary>
<div id="list-homestead"></div>
</details>
</div>

View File

@@ -0,0 +1,130 @@
const Territory = {
init: async () => {
let html = await fetch('/lib/pages/territory/index.html').then((response) => response.text());
app.innerHTML = html;
if (USER.administrator.uuid) document.getElementById('rotationButton').style.display = "";
if (USER.administrator.uuid || (USER.moderator.uuid && USER.moderator.can_add_territory)) document.getElementById("constructorButton").style.display = "";
Territory.house.setHTML();
Territory.homestead.setHTML();
},
house: {
loadAPI: async function () {
let uuid = localStorage.getItem("uuid");
const URL = `${CONFIG.api}houses/list`;
return await fetch(URL, {
method: 'GET',
headers: {
"Content-Type": "application/json",
"Authorization": uuid
}
}).then((response) => response.json());
},
setHTML: async function () {
let list = await Territory.house.loadAPI();
list.sort((a, b) => b.id - a.id);
console.log(list);
let block_house = document.getElementById('list-house')
block_house.innerHTML = "";
for (let i = 0; i < list.length; i++) {
const element = list[i];
let progress = ((element.entrance.working / element.entrance.quantity) * 100);
let pageURL = () => {
if (USER.administrator.uuid || (USER.moderator.uuid && USER.moderator.can_manager_territory)) return `/territory/manager/house/${element.id}`;
else return `/territory/card/house/${element.id}`
}
block_house.innerHTML += `
<div class="card">
<i style="background-image: url(https://sheep-service.com/cards/house/T${element.id}.webp);"></i>
<div class="contents">
<div class="group" style="background: ${colorGroup(element.group_id)}">
<span>Група №${element.group_id}</span>
</div>
<div class="info">
<div>
<div class="progress" style="width: ${progress}%"></div>
<span>Вільні під'їзди:</span>
<p>${element.entrance.quantity - element.entrance.working} / ${element.entrance.quantity}</p>
</div>
<div>
<p>${element.title} ${element.number}</p>
</div>
</div>
</div>
<a href="${pageURL()}" data-route></a>
</div>
`;
}
}
},
homestead: {
loadAPI: async function () {
let uuid = localStorage.getItem("uuid");
const URL = `${CONFIG.api}homestead/list`;
return await fetch(URL, {
method: 'GET',
headers: {
"Content-Type": "application/json",
"Authorization": uuid
}
}).then((response) => response.json());
},
setHTML: async function () {
let list = await Territory.homestead.loadAPI();
list.sort((a, b) => b.id - a.id);
console.log(list);
let block_homestead = document.getElementById('list-homestead')
block_homestead.innerHTML = "";
for (let i = 0; i < list.length; i++) {
const element = list[i];
let block_history = () => {
if (element.working) {
return `
<div>
<div class="progress" style="width: 100%"></div>
<p>${element.history.name}</p>
</div>
`
}
return ''
}
let pageURL = () => {
if (USER.administrator.uuid || (USER.moderator.uuid && USER.moderator.can_manager_territory)) return `/territory/manager/homestead/${element.id}`;
else return `/territory/card/homestead/${element.id}`
}
block_homestead.innerHTML += `
<div class="card">
<i style="background-image: url(https://sheep-service.com/cards/homestead/H${element.id}.webp);"></i>
<div class="contents">
<div class="group" style="background: ${colorGroup(element.group_id)}">
<span>Група №${element.group_id}</span>
</div>
<div class="info">
${block_history()}
<div>
<p>${element.title} ${element.number}</p>
</div>
</div>
</div>
<a href="${pageURL()}" data-route></a>
</div>
`;
}
}
}
}

View File

@@ -0,0 +1,247 @@
.page-territory {
width: calc(100% - 40px);
display: flex;
flex-direction: column;
margin: 20px 20px 0 20px;
}
.page-territory>.buttons-list {
padding: 10px;
margin-bottom: 20px;
background: var(--ColorThemes1);
color: var(--ColorThemes3);
border: 1px solid var(--ColorThemes2);
box-shadow: var(--shadow-l1);
border-radius: 15px;
overflow: auto;
display: flex;
}
.page-territory>.buttons-list>button,
.page-territory>.buttons-list>a {
cursor: pointer;
border-radius: 10px;
padding: 0 10px;
margin-right: 20px;
min-width: fit-content;
min-height: 40px;
background: var(--PrimaryColor);
display: flex;
align-items: center;
justify-content: center;
}
.page-territory>.buttons-list>button>span,
.page-territory>.buttons-list>a>span {
color: var(--PrimaryColorText);
font-size: 14px;
font-weight: normal;
}
.page-territory>.buttons-list>button>svg,
.page-territory>.buttons-list>a>svg {
width: 20px;
height: 20px;
fill: var(--PrimaryColorText);
margin-right: 10px;
}
.page-territory details {
border-radius: 15px;
width: 100%;
display: flex;
flex-direction: column;
align-items: stretch;
margin-bottom: 20px;
background: var(--ColorThemes1);
color: var(--ColorThemes3);
border: 1px solid var(--ColorThemes2);
box-shadow: var(--shadow-l1);
}
.page-territory>details[disabled] summary,
.page-territory>details.disabled summary {
pointer-events: none;
user-select: none;
}
.page-territory>details summary::-webkit-details-marker,
.page-territory>details summary::marker {
display: none;
content: "";
}
.page-territory summary {
width: calc(100% - 40px);
cursor: pointer;
color: var(--ColorThemes3);
border-radius: var(--border-radius);
font-size: 16px;
font-weight: 300;
padding: 20px;
position: relative;
}
.page-territory summary span {
font-weight: 500;
}
.page-territory #list-house,
.page-territory #list-homestead {
width: 100%;
margin: 0;
display: flex;
flex-wrap: wrap;
flex-direction: row;
align-content: flex-start;
justify-content: center;
overflow-y: auto;
align-items: flex-start;
transition: .3s ease;
}
.page-territory .card {
position: relative;
width: 300px;
height: 200px;
background-color: var(--ColorThemes2);
margin: 0px 10px 20px 10px;
overflow: hidden;
cursor: pointer;
border-radius: 10px;
}
@media (max-width: 2300px) {
.page-territory .card {
width: calc((100% / 5) - 40px);
}
}
@media (max-width: 1960px) {
.page-territory .card {
width: calc((100% / 4) - 40px);
}
}
@media (max-width: 1640px) {
.page-territory .card {
width: calc((100% / 3) - 40px);
}
}
@media (max-width: 1280px) {
.page-territory .card {
width: calc((100% / 2) - 40px);
}
}
@media (max-width: 650px) {
.page-territory .card {
width: 100%;
}
}
@media(hover: hover) {
.page-territory .card:hover {
opacity: 0.8;
}
}
.page-territory .card>i {
position: absolute;
width: 100%;
height: 100%;
object-fit: cover;
z-index: 1;
filter: blur(2px);
/* background-repeat: round; */
background-size: cover;
background-position: center;
background-image: url(https://tm.rozenrod.com/web/img/bg.webp);
}
.page-territory .card>a {
position: absolute;
width: 100%;
height: 100%;
z-index: 10;
}
.page-territory .contents {
position: absolute;
z-index: 2;
background: rgb(64 64 64 / 0.7);
width: 100%;
height: 100%;
font-size: 40px;
font-weight: 500;
color: #fff;
display: flex;
flex-direction: column;
align-items: center;
justify-content: space-between;
}
.page-territory .group {
width: calc(100% - 20px);
max-height: 50px;
border-radius: 7px;
padding: 10px 0;
margin-top: 10px;
display: flex;
background: var(--PrimaryColor);
align-items: center;
flex-direction: column;
justify-content: space-around;
}
.page-territory .group>span {
color: #fff;
font-size: 14px;
font-weight: 400;
}
.page-territory .info {
width: calc(100% - 20px);
margin-bottom: 10px;
}
.page-territory .info>div {
width: 100%;
height: 35px;
margin-top: 5px;
display: flex;
background: var(--ColorThemes0);
align-items: center;
justify-content: center;
font-size: 12px;
color: var(--ColorThemes3);
border-radius: 7px;
position: relative;
overflow: hidden;
}
.page-territory .progress {
background: var(--PrimaryColor);
height: 100%;
position: absolute;
z-index: 1;
left: 0;
}
.page-territory .info>div>span {
color: var(--ColorThemes3);
font-size: 14px;
font-weight: 300;
z-index: 2;
}
.page-territory .info>div>p {
color: var(--ColorThemes3);
font-size: 14px;
font-weight: 400;
padding: 10px;
z-index: 2;
}

View File

@@ -0,0 +1,75 @@
<div class="page-territory_manager">
<div id="territory-info">
<div class="territory-info-image" id="CardPicture">
<img id="info-picture" src="" alt="" style="display: none" />
<div id="map_territory_manager"></div>
<div class="menu-picture">
<a id="menu-picture-error" title="Зображення не знайдено!">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 30 30" style="fill: #c14d4d;">
<path
d="M 3.7070312 2.2929688 L 2.2929688 3.7070312 L 26.292969 27.707031 L 27.707031 26.292969 L 26.375 24.960938 C 27.299776 24.784872 28 23.976233 28 23 L 28 7 C 28 5.895 27.105 5 26 5 L 6.4140625 5 L 3.7070312 2.2929688 z M 2.1367188 6.2851562 C 2.0517188 6.5071563 2 6.747 2 7 L 2 23 C 2 24.105 2.895 25 4 25 L 20.851562 25 L 17.851562 22 L 5 22 L 5 15 L 7.2890625 12.710938 C 7.5140625 12.485938 7.7747812 12.317219 8.0507812 12.199219 L 2.1367188 6.2851562 z M 23 8 C 24.105 8 25 8.895 25 10 C 25 11.105 24.105 12 23 12 C 21.895 12 21 11.105 21 10 C 21 8.895 21.895 8 23 8 z M 19 14.001953 C 19.61925 14.001953 20.238437 14.238437 20.710938 14.710938 L 25 19 L 25 22 L 23.414062 22 L 16.707031 15.292969 L 17.289062 14.710938 C 17.761563 14.238437 18.38075 14.001953 19 14.001953 z"
/>
</svg>
</a>
<a id="menu-picture-ok" style="display: none;" title="Зображення знайдено" target="_blank">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 30 30" style="fill: #8BC34A;">
<path
d="M 4 5 C 2.895 5 2 5.895 2 7 L 2 23 C 2 24.105 2.895 25 4 25 L 26 25 C 27.105 25 28 24.105 28 23 L 28 7 C 28 5.895 27.105 5 26 5 L 4 5 z M 23 8 C 24.105 8 25 8.895 25 10 C 25 11.105 24.105 12 23 12 C 21.895 12 21 11.105 21 10 C 21 8.895 21.895 8 23 8 z M 9 13.001953 C 9.61925 13.001953 10.238437 13.238437 10.710938 13.710938 L 13.972656 16.972656 L 15 18 L 16.15625 19.15625 C 16.57825 19.57825 17.259641 19.574344 17.681641 19.152344 C 18.104641 18.730344 18.104641 18.044094 17.681641 17.621094 L 16.529297 16.470703 L 17.289062 15.710938 C 18.234063 14.765937 19.765937 14.765937 20.710938 15.710938 L 25 20 L 25 22 L 5 22 L 5 16 L 7.2890625 13.710938 C 7.7615625 13.238437 8.38075 13.001953 9 13.001953 z"
/>
</svg>
</a>
<button title="Створити нове зображення" onclick="Territory_Manager.getScreen()">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 30 30"> <path d="M 22.828125 3 C 22.316375 3 21.804562 3.1954375 21.414062 3.5859375 L 19 6 L 24 11 L 26.414062 8.5859375 C 27.195062 7.8049375 27.195062 6.5388125 26.414062 5.7578125 L 24.242188 3.5859375 C 23.851688 3.1954375 23.339875 3 22.828125 3 z M 17 8 L 5.2597656 19.740234 C 5.2597656 19.740234 6.1775313 19.658 6.5195312 20 C 6.8615312 20.342 6.58 22.58 7 23 C 7.42 23.42 9.6438906 23.124359 9.9628906 23.443359 C 10.281891 23.762359 10.259766 24.740234 10.259766 24.740234 L 22 13 L 17 8 z M 4 23 L 3.0566406 25.671875 A 1 1 0 0 0 3 26 A 1 1 0 0 0 4 27 A 1 1 0 0 0 4.328125 26.943359 A 1 1 0 0 0 4.3378906 26.939453 L 4.3632812 26.931641 A 1 1 0 0 0 4.3691406 26.927734 L 7 26 L 5.5 24.5 L 4 23 z"/></svg>
</button>
</div>
</div>
<div class="territory-info-text">
<h1>Вулиця:</h1>
<h2 id="info-title">--</h2>
</div>
<div class="territory-info-text">
<h1>Номер:</h1>
<h2 id="info-number">--</h2>
</div>
<div class="territory-info-text">
<h1>Населений пункт:</h1>
<h2 id="info-settlement">--</h2>
</div>
<div class="territory-info-text">
<textarea
name="info-description"
id="info-description"
placeholder="Примітка"
></textarea>
</div>
<a id="editor_button" style="display: none" data-route>Змінити територію</a>
</div>
<div id="territory-entrance"></div>
</div>
<div id="territory-new" style="display: none; opacity: 0">
<div class="mess">
<span>Налаштування</span>
<select id="new-worker-name" name="new-worker-name" required="">
<option value="" selected="" disabled="">Оберіть...</option>
</select>
<div>
<button onclick="Territory_Manager.mess.close()">Закрити</button>
<button
id="new-worker-button"
onclick="Territory_Manager.newWorker()"
style="background: var(--PrimaryColor); color: var(--PrimaryColorText)"
>
Призначити
</button>
</div>
</div>
</div>

View File

@@ -0,0 +1,506 @@
let map_territory, type_territory;
const Territory_Manager = {
init: async (type, id) => {
let html = await fetch('/lib/pages/territory_manager/index.html').then((response) => response.text());
app.innerHTML = html;
type_territory = type;
let sheeps_list = [];
if (Sheeps.sheeps_list.list.length == 0) {
sheeps_list = await Sheeps.sheeps_list.loadAPI();
} else {
sheeps_list = Sheeps.sheeps_list.list;
}
let editor_button = document.getElementById('editor_button');
if (USER.administrator.uuid || (USER.moderator.uuid && USER.moderator.can_add_territory)){
editor_button.style.display = "";
editor_button.setAttribute("href", `/territory/editor/${type}/${id}`)
}
await Territory_Manager.info.setHTML(type, id);
Territory_Manager.entrances.setHTML(type, id);
for (let i = 0; i < sheeps_list.length; i++) {
const element = sheeps_list[i];
if (Territory_Manager.info.list.group_id == element.group_id) {
document.getElementById('new-worker-name').innerHTML += `
<option value="${element.name}">${element.name}</option>
`;
}
}
},
info: {
list: {},
loadAPI: async (url) => {
let uuid = localStorage.getItem("uuid");
Territory_Manager.info.list = await fetch(url, {
method: 'GET',
headers: {
"Content-Type": "application/json",
"Authorization": uuid
}
}).then((response) => response.json());
return Territory_Manager.info.list;
},
setHTML: async (type, id) => {
if (type == "house") {
let url = `${CONFIG.api}house/${id}`;
let data = await Territory_Manager.info.loadAPI(url);
Territory_Manager.map(type, data);
let info_picture = document.getElementById('info-picture');
let info_title = document.getElementById('info-title');
let info_number = document.getElementById('info-number');
let info_settlement = document.getElementById('info-settlement');
let info_description = document.getElementById('info-description');
info_picture.addEventListener("click", (event) => {
let state = info_picture.getAttribute('data-state')
if (state == 'active') info_picture.setAttribute('data-state', '')
else info_picture.setAttribute('data-state', 'active')
});
info_picture.setAttribute("src", ``);
info_title.innerText = data.title;
info_number.innerText = data.number;
info_settlement.innerText = data.settlement;
info_description.value = data.description;
} else if (type == "homestead") {
let url = `${CONFIG.api}homestead/${id}`;
let data = await Territory_Manager.info.loadAPI(url);
Territory_Manager.map(type, data);
let info_picture = document.getElementById('info-picture');
let info_title = document.getElementById('info-title');
let info_number = document.getElementById('info-number');
let info_settlement = document.getElementById('info-settlement');
let info_description = document.getElementById('info-description');
info_picture.addEventListener("click", (event) => {
let state = info_picture.getAttribute('data-state')
if (state == 'active') info_picture.setAttribute('data-state', '')
else info_picture.setAttribute('data-state', 'active')
});
info_picture.setAttribute("src", ``);
info_title.innerText = data.title;
info_number.innerText = data.number;
info_settlement.innerText = data.settlement;
info_description.value = data.description;
}
urlImage = `https://sheep-service.com/cards/${type}/${type == "house" ? "T" : "H"}${id}.webp`;
const checkImage = await fetch(urlImage);
if(checkImage.ok) {
document.getElementById('menu-picture-error').style.display = 'none';
document.getElementById('menu-picture-ok').style.display = '';
document.getElementById('menu-picture-ok').setAttribute("href", urlImage);
} else {
document.getElementById('menu-picture-error').style.display = '';
document.getElementById('menu-picture-ok').style.display = 'none';
}
}
},
entrances: {
list: [],
loadAPI: async (url) => {
let uuid = localStorage.getItem("uuid");
Territory_Manager.entrances.list = await fetch(url, {
method: 'GET',
headers: {
"Content-Type": "application/json",
"Authorization": uuid
}
}).then((response) => response.json());
return Territory_Manager.entrances.list;
},
setHTML: async (type, id) => {
if (type == "house") {
let url = `${CONFIG.api}house/${id}/entrances`;
let listEntrances = await Territory_Manager.entrances.loadAPI(url);
document.getElementById('territory-entrance').innerHTML = "";
for (let i = 0; i < listEntrances.length; i++) {
const element = listEntrances[i];
let name = () => {
if (element.history.name == "Групова")
return `<p>${element.history.name + " " + element.history.group_id}</p>`;
else {
let sheeps_list = Sheeps.sheeps_list.list;
let filtered = sheeps_list.filter(item => item.group_id === Territory_Manager.info.list.group_id);
let sheep = filtered.find(item => item.name === element.history.name);
return `<a href="/sheeps/${sheep.uuid}" data-route="">${element.history.name}</a>`;
}
}
if (element.working) {
document.getElementById('territory-entrance').innerHTML += `
<div class="entrance" data-state="working">
<div id="title">
<h1>${element.title}</h1>
<a href="/territory/card/${type}/${id}" title="Редактор квартир" data-route>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48"><path d="M 12.5 6 C 8.9280619 6 6 8.9280619 6 12.5 L 6 35.5 C 6 39.071938 8.9280619 42 12.5 42 L 35.5 42 C 39.071938 42 42 39.071938 42 35.5 L 42 12.5 C 42 8.9280619 39.071938 6 35.5 6 L 12.5 6 z M 12.5 9 L 14 9 L 14 15 L 9 15 L 9 12.5 C 9 10.549938 10.549938 9 12.5 9 z M 17 9 L 35.5 9 C 37.450062 9 39 10.549938 39 12.5 L 39 15 L 17 15 L 17 9 z M 9 18 L 14 18 L 14 23 L 9 23 L 9 18 z M 17 18 L 39 18 L 39 23 L 17 23 L 17 18 z M 9 26 L 14 26 L 14 31 L 9 31 L 9 26 z M 17 26 L 39 26 L 39 31 L 17 31 L 17 26 z M 9 34 L 14 34 L 14 39 L 12.5 39 C 10.549938 39 9 37.450062 9 35.5 L 9 34 z M 17 34 L 39 34 L 39 35.5 C 39 37.450062 37.450062 39 35.5 39 L 17 39 L 17 34 z"/></svg>
</a>
</div>
<div>
<h1>Територію опрацьовує: </h1>
${name()}
</div>
<div>
<h1>Територія видана: </h1>
<h2>${formattedDate(element.history.date.start)}</h2>
</div>
<div>
<h1>Варто забрати: </h1>
<h2>${formattedDate(element.history.date.end) ?? formattedDate(element.history.date.start + (1000 * 2629743 * 4))}</h2>
</div>
<div class="edit_working">
<button onclick="Territory_Manager.endWorker('${type}', ${id}, ${element.history.id})" id="end-working-button" style="color: #121214;background: #c14d4d;">Забрати</button>
<button onclick="Territory_Manager.share('${type}', ${i})">Відправити посилання</button>
</div>
</div>
`;
} else {
document.getElementById('territory-entrance').innerHTML += `
<div class="entrance">
<div id="title">
<h1>${element.title}</h1>
</div>
<div>
<h1>Територія не опрацьовується</h1>
</div>
<div>
<h1>Останнє опрацювання: </h1>
<h2>${formattedDate(element.history.date.end) ?? "..."}</h2>
</div>
<div class="edit">
<button onclick="Territory_Manager.newWorker('${type}',${id}, ${i}, 'Групова')" id="group-working-button" style="color: var(--ColorThemes0);background: var(--ColorThemes3);">Призначити груповою</button>
<button onclick="Territory_Manager.mess.open('${type}', ${id}, ${i})">Призначити вісника</button>
</div>
</div>
`;
}
console.log(element);
}
} else if (type == "homestead") {
let url = `${CONFIG.api}homestead/${id}`;
let element = await Territory_Manager.entrances.loadAPI(url);
document.getElementById('territory-entrance').innerHTML = "";
let name = () => {
if (element.history.name == "Групова")
return `<p>${element.history.name + " " + element.history.group_id}</p>`;
else
return `<a href="/sheeps/${element.history.name}" data-route="">${element.history.name}</a>`;
}
if (element.working) {
document.getElementById('territory-entrance').innerHTML += `
<div class="entrance" data-state="working">
<div>
<h1>Територію опрацьовує: </h1>
${name()}
</div>
<div>
<h1>Територія видана: </h1>
<h2>${formattedDate(element.history.date.start)}</h2>
</div>
<div>
<h1>Варто забрати: </h1>
<h2>${formattedDate(element.history.date.end) ?? formattedDate(element.history.date.start + (1000 * 2629743 * 4))}</h2>
</div>
<div class="edit_working">
<button onclick="Territory_Manager.endWorker('${type}', ${id}, ${element.history.id})" id="end-working-button" style="color: #121214;background: #c14d4d;">Забрати</button>
<button onclick="Territory_Manager.share('${type}', ${i})">Відправити посилання</button>
</div>
</div>
`;
} else {
document.getElementById('territory-entrance').innerHTML += `
<div class="entrance">
<div>
<h1>Територія не опрацьовується</h1>
</div>
<div>
<h1>Останнє опрацювання: </h1>
<h2>${formattedDate(element.history.date.end) ?? "..."}</h2>
</div>
<div class="edit">
<button onclick="Territory_Manager.newWorker('${type}',${id}, ${i}, 'Групова')" id="group-working-button" style="color: var(--ColorThemes0);background: var(--ColorThemes3);">Призначити груповою</button>
<button onclick="Territory_Manager.mess.open('${type}',${id}, ${i})">Призначити вісника</button>
</div>
</div>
`;
}
console.log(element);
}
}
},
map: (type, data) => {
map_territory = {};
let center = { lat: 49.5629016, lng: 25.6145625 };
let zoom = 19;
let mytile = L.tileLayer('https://tm.rozenrod.com/webp/{z}/{x}/{y}.webp', {
maxZoom: 20,
minZoom: 15,
tms: true
});
map_territory = L.map('map_territory_manager', {
renderer: L.canvas(),
center,
zoom,
zoomControl: false,
layers: [
mytile
],
});
map_territory.pm.setLang("ua");
if (type == "homestead") {
if (data.geo.lat) map_territory.setView([data.geo.lat, data.geo.lng], 15);
else map_territory.setView([data.points[0][0][0].lat, data.points[0][0][0].lng], 15);
L.polygon(data.points, {
color: colorGroup(data.group_id),
radius: 500,
fillOpacity: 0.3,
dashArray: '20, 15',
dashOffset: '20',
}).on('click', function (e) {
}).addTo(map_territory);
} else if (type == "house") {
map_territory.setView([data.geo.lat, data.geo.lng], 17);
L.polygon(data.points, {
color: "#585858",
fillColor: colorGroup(data.group_id),
fillOpacity: 0.8,
tm_id: `house_${i}`
}).on('click', function (e) {
}).addTo(map_territory);
}
},
mess: {
open: (type, id, number) => {
const block = document.getElementById('territory-new');
const new_worker_button = document.getElementById('new-worker-button');
block.style.display = "";
setTimeout(() => {
block.style.opacity = "1";
}, 100)
new_worker_button.setAttribute("onclick", `Territory_Manager.newWorker('${type}', ${id}, ${number})`);
},
close: () => {
const block = document.getElementById('territory-new');
block.style.opacity = "0";
setTimeout(() => {
block.style.display = "none";
}, 200)
}
},
newWorker: async (type, id, number, name) => {
const uuid = localStorage.getItem('uuid');
const new_worker_button = document.getElementById('new-worker-button');
const group_worker_button = document.getElementById('group-working-button');
let URL, territory_id, data;
const pos = Sheeps.sheeps_list.list.map(e => e.name).indexOf(name ?? document.getElementById("new-worker-name").value);
let sheep = Sheeps.sheeps_list.list[pos];
if (type == "house") {
territory_id = Territory_Manager.entrances.list[number].id;
data = {
name: name ?? document.getElementById("new-worker-name").value,
group_id: Territory_Manager.info.list.group_id,
sheep_id: sheep ? sheep.id : null
}
URL = `${CONFIG.api}history/entrance/${territory_id}`;
}
else if (type == "homestead") {
territory_id = Territory_Manager.info.list.id;
data = {
name: name ?? document.getElementById("new-worker-name").value,
group_id: Territory_Manager.info.list.group_id,
sheep_id: sheep ? sheep.id : null
}
URL = `${CONFIG.api}history/homestead/${territory_id}`;
}
console.log(territory_id, data);
await fetch(URL, {
method: 'POST',
headers: {
"Content-Type": "application/json",
"Authorization": uuid
},
body: JSON.stringify(data)
})
.then(response => {
if (response.status == 200) {
Territory_Manager.mess.close();
Territory_Manager.entrances.list = [];
Territory_Manager.entrances.setHTML(type, id);
return response.json()
} else {
console.log('err');
new_worker_button.innerText = "Помилка";
group_worker_button.innerText = "Помилка";
return
}
})
},
endWorker: async (type, id, territory_id) => {
const end_working_button = document.getElementById('end-working-button');
let uuid = localStorage.getItem('uuid');
let URL = "";
if (type == "house") {
URL = `${CONFIG.api}history/entrance/${territory_id}`;
}
else if (type == "homestead") {
URL = `${CONFIG.api}history/homestead/${territory_id}`;
}
await fetch(URL, {
method: 'PUT',
headers: {
"Content-Type": "application/json",
"Authorization": uuid
},
data: JSON.stringify({})
})
.then(response => {
if (response.status == 200) {
Territory_Manager.entrances.list = [];
Territory_Manager.entrances.setHTML(type, id);
return response.json()
} else {
console.log('err');
end_working_button.innerText = "Помилка";
return
}
})
},
share: async (type, number) => {
let territory, id;
if (type == "house") {
territory = Territory_Manager.entrances.list[number];
id = Territory_Manager.entrances.list[number].id;
}
else if (type == "homestead") {
territory = Territory_Manager.info.list;
id = Territory_Manager.info.list.id;
}
let description = () => {
if (Territory_Manager.info.list.description && Territory_Manager.info.list.description.length > 0) {
return `\n\nДодатково:\n${Territory_Manager.info.list.description}`;
} else {
return '';
}
}
let sheepUuid = () => {
const pos = Sheeps.sheeps_list.list.map(e => e.name).indexOf(territory.history.name);
let sheep = Sheeps.sheeps_list.list[pos];
if (sheep) {
return `\n\nПосилання на Sheep-Service:\n• https://sheep-service.com/?uuid=${sheep.uuid}`;
} else {
return '';
}
}
if (navigator.share) {
console.log("Congrats! Your browser supports Web Share API")
try {
let url;
if(type == 'house') url = `https://sheep-service.com/cards/house/T${Territory_Manager.info.list.id}.webp`;
if(type == 'homestead') url = `https://sheep-service.com/cards/homestead/H${Territory_Manager.info.list.id}.webp`;
const response = await fetch(url);
const blob = await response.blob();
const file = new File([blob], `${type == "house" ? "E" + id : "H" + id}.webp`, { type: blob.type });
await navigator.share({
text: `Територія:\n${type == "house" ? "E" + id : "H" + id}\n\nНаселений пункт:\n${Territory_Manager.info.list.settlement}\n\nВулиця:\n${Territory_Manager.info.list.title + " " + Territory_Manager.info.list.number}${description()}\n\nПризначення:\nЗ ${formattedDate(territory.history.date.start)}\n • До ${formattedDate(territory.history.date.end) ?? formattedDate(territory.history.date.start + (1000 * 2629743 * 4))}${sheepUuid()}`,
files: [file]
});
console.log('Успешно отправлено!');
} catch (error) {
console.error('Ошибка при отправке:', error);
}
} else {
console.log("Sorry! Your browser does not support Web Share API")
}
},
getScreen: async () => {
const center = map_territory.getCenter();
console.log(center.lat, center.lng);
let lat = center.lat;
let lng = center.lng;
let wayId = Territory_Manager.info.list.osm_id;
let zoom = map_territory.getZoom() + 2 ?? 17;
let address = Territory_Manager.info.list.title;
let number = Territory_Manager.info.list.number;
let id = Territory_Manager.info.list.id;
let url = `https://sheep-service.com/api/generator/cards?lat=${lat}&lng=${lng}&type=${type_territory}&wayId=${wayId}&zoom=${zoom}&address=${address}&number=${number}&id=${id}`;
let uuid = localStorage.getItem("uuid");
let result = await fetch(url, {
method: 'GET',
headers: {
"Content-Type": "application/json",
"Authorization": uuid
}
}).then((response) => response.json());
urlImage = `https://sheep-service.com/cards/${type}/${type == "house" ? "T" : "H"}${id}.webp`;
if(result) {
document.getElementById('menu-picture-error').style.display = 'none';
document.getElementById('menu-picture-ok').style.display = '';
document.getElementById('menu-picture-ok').setAttribute("href", urlImage);
} else {
document.getElementById('menu-picture-error').style.display = '';
document.getElementById('menu-picture-ok').style.display = 'none';
}
}
}

View File

@@ -0,0 +1,418 @@
.page-territory_manager {
width: calc(100% - 18px);
display: flex;
flex-direction: row;
margin: 20px 9px 0 9px;
justify-content: space-between;
position: relative;
}
#territory-info,
#territory-entrance {
width: 100%;
min-height: calc(100vh - 40px - 50px);
height: fit-content;
margin: 0 10px 15px;
border-radius: 15px;
display: flex;
flex-direction: column;
overflow: hidden;
background: var(--ColorThemes1);
color: var(--ColorThemes3);
border: 1px solid var(--ColorThemes2);
box-shadow: var(--shadow-l1);
transition: all .2sease 0s;
}
#territory-info {
position: sticky;
overflow: auto;
top: 20px;
}
#territory-info>.territory-info-image {
width: calc(100% - 20px);
min-height: 200px;
margin: 10px;
border-radius: 10px;
background: var(--ColorThemes0);
position: relative;
display: flex;
overflow: hidden;
background-position: 50%;
background-size: cover;
justify-content: center;
aspect-ratio: 1147 / 751;
max-height: 300px;
}
#territory-info>.territory-info-image>img {
width: 100%;
object-fit: cover;
z-index: 3;
cursor: pointer;
}
#territory-info>.territory-info-image>#map_territory_manager {
width: 100%;
height: 100%;
}
#territory-info>.territory-info-image>img[data-state="active"] {
object-fit: contain;
}
#territory-info>.territory-info-image>.menu-picture {
position: absolute;
right: 10px;
top: 10px;
z-index: 999;
background: var(--ColorThemes0);
border: 1px solid var(--ColorThemes2);
padding: 5px;
border-radius: 6px;
min-width: 70px;
display: flex;
justify-content: space-between;
}
#territory-info>.territory-info-image>.menu-picture>a {
width: 30px;
height: 30px;
display: flex;
align-items: center;
justify-content: center;
}
#territory-info>.territory-info-image>.menu-picture>a>svg {
width: 22px;
height: 22px;
}
#territory-info>.territory-info-image>.menu-picture>button {
width: 30px;
height: 30px;
border-radius: 2px;
background: var(--PrimaryColor);
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
}
#territory-info>.territory-info-image>.menu-picture>button>svg {
width: 20px;
height: 20px;
fill: var(--PrimaryColorText);
}
#territory-info>.territory-info-text {
width: calc(100% - 40px);
display: flex;
flex-direction: row;
margin: 10px;
border-radius: 10px;
position: relative;
align-items: center;
background: var(--ColorThemes0);
padding: 10px;
}
#territory-info>.territory-info-text>h1 {
font-size: 14px;
font-weight: 400;
color: var(--ColorThemes3);
}
#territory-info>.territory-info-text>h2 {
font-size: 14px;
font-weight: 300;
color: var(--ColorThemes3);
opacity: 0.8;
margin: 0 7px;
}
#territory-info>.territory-info-text>textarea {
width: 100%;
min-height: 100px;
background: var(--ColorThemes0);
color: var(--ColorThemes3);
font-size: 14px;
resize: vertical;
border-radius: 0;
}
#territory-info>#editor_button {
width: calc(100% - 20px);
font-size: 13px;
font-weight: 400;
min-height: 40px;
display: flex;
background: var(--PrimaryColor);
color: var(--PrimaryColorText);
cursor: pointer;
border: 0;
position: relative;
border-radius: 8px;
margin: 20px 10px 10px 10px;
align-items: center;
justify-content: center;
}
#territory-entrance>.entrance {
width: calc(100% - 40px);
color: var(--ColorThemes3);
background-color: var(--ColorThemes2);
border: 1px solid var(--ColorThemes0);
box-shadow: var(--shadow-l1);
display: flex;
flex-direction: column;
align-items: flex-start;
margin: 10px;
padding: 10px;
min-height: 100px;
justify-content: center;
position: relative;
border-radius: 10px;
}
#territory-entrance>.entrance[data-state="working"] {
border: 1px solid var(--PrimaryColor);
}
#territory-entrance>.entrance>#title {
display: flex;
align-items: center;
height: 30px;
margin-bottom: 10px;
justify-content: space-between;
}
#territory-entrance>.entrance>#title>h1 {
width: 100%;
height: 30px;
background: var(--ColorThemes0);
color: var(--ColorThemes3);
border-radius: 6px;
font-size: 16px;
font-weight: 400;
display: flex;
align-items: center;
justify-content: center;
}
#territory-entrance>.entrance>#title>a {
min-height: 30px;
min-width: 30px;
padding: 0;
margin: 0 0 0 10px;
background: var(--PrimaryColor);
display: flex;
align-items: center;
justify-content: center;
}
#territory-entrance>.entrance>#title>a>svg {
width: 25px;
height: 25px;
fill: var(--PrimaryColorText);
}
#territory-entrance>.entrance>div {
display: flex;
margin-bottom: 10px;
flex-direction: row;
align-items: center;
width: 100%;
}
#territory-entrance>.entrance>div>h1 {
font-size: 14px;
font-weight: 400;
color: var(--ColorThemes3);
}
#territory-entrance>.entrance>div>h2,
#territory-entrance>.entrance>div>a,
#territory-entrance>.entrance>div>p {
font-size: 14px;
font-weight: 300;
color: var(--ColorThemes3);
opacity: 0.8;
margin: 0 7px;
display: flex;
align-items: flex-start;
}
#territory-entrance>.entrance>div>a,
#territory-entrance>.entrance>div>p {
color: var(--ColorThemes0);
background: var(--ColorThemes3);
border-radius: 6px;
padding: 2px 5px;
font-weight: 400;
opacity: 1;
}
.date_old {
background: var(--ColorThemes1) !important;
color: var(--ColorThemes3) !important;
font-size: 18px !important;
padding: 5px 10px !important;
}
#territory-entrance>.entrance>.edit_working,
#territory-entrance>.entrance>.edit {
margin-bottom: 0;
}
#territory-entrance>.entrance>.edit_working>button,
#territory-entrance>.entrance>.edit>button {
width: 100%;
font-size: 13px;
font-weight: 400;
min-height: 40px;
display: flex;
padding: 2px 10px;
background: var(--PrimaryColor);
color: var(--PrimaryColorText);
cursor: pointer;
border: 0;
position: relative;
border-radius: 8px;
align-items: center;
justify-content: center;
}
#territory-entrance>.entrance>.edit_working>button:nth-child(2n),
#territory-entrance>.entrance>.edit>button:nth-child(2n) {
margin-left: 5px;
}
#territory-entrance>.entrance>.edit_working>button:nth-child(2n-1),
#territory-entrance>.entrance>.edit>button:nth-child(2n-1) {
margin-right: 5px;
}
#territory-entrance>.entrance>button>span {
display: none;
}
#territory-entrance>.entrance>button>svg {
width: 20px;
height: 20px;
fill: var(--PrimaryColorText);
}
#territory-entrance>.entrance>.apartment_button {
width: 100%;
font-size: 13px;
font-weight: 400;
min-height: 40px;
display: flex;
background: var(--PrimaryColor);
color: var(--PrimaryColorText);
cursor: pointer;
border: 0;
position: relative;
border-radius: 8px;
margin: 20px 0 2px 0;
align-items: center;
justify-content: center;
}
#territory-new {
display: flex;
width: 100%;
height: 100%;
left: 0;
top: 0;
position: fixed;
z-index: 999;
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
background: rgb(31 31 33 / 41%);
transition: all .2s ease 0s;
}
#territory-new>.mess {
display: flex;
width: 300px;
height: 200px;
border: 1px solid var(--ColorThemes2);
background: var(--ColorThemes0);
box-shadow: 0px 2px 3px rgb(0 0 0 / 5%), 0px 5px 15px rgb(0 0 0 / 5%), 0px 4px 8px rgb(0 0 0 / 5%), 0px 0px 1px rgb(0 0 0 / 5%);
position: absolute;
top: 50%;
left: 50%;
margin-top: -110px;
margin-left: -160px;
flex-direction: column;
align-items: center;
z-index: 9999;
padding: 10px;
border-radius: 15px;
justify-content: space-between;
}
#territory-new>.mess>span {
color: var(--ColorThemes3);
font-size: 18px;
text-align: center;
}
#territory-new>.mess>select {
width: 100%;
min-width: 140px;
padding: 0 5px;
border-radius: 6px;
height: 30px;
background-color: var(--ColorThemes2);
color: var(--ColorThemes3);
}
#territory-new>.mess>div {
width: 100%;
display: flex;
justify-content: space-between;
flex-direction: row;
}
#territory-new>.mess>div>button {
text-decoration: none;
font-size: 15px;
font-weight: 400;
height: 35px;
color: var(--ColorThemes0);
padding: 2px 10px;
background: var(--ColorThemes3);
cursor: pointer;
border: 0;
margin-bottom: 5px;
margin-left: 0;
width: calc(50% - 5px);
border-radius: 10px;
}
@media (max-width: 1000px),
(max-height: 540px) {
.page-territory_manager {
flex-direction: column;
justify-content: flex-start;
}
#territory-info,
#territory-entrance {
width: calc(100% - 20px);
height: fit-content;
min-height: 100px;
}
#territory-info {
position: relative;
top: auto;
}
}

142
web/lib/router/router.js Normal file
View File

@@ -0,0 +1,142 @@
const Router = {
routes: [],
mode: null,
root: '/',
config: function(options) {
this.mode = options && options.mode && options.mode == 'history' && !!(history.pushState) ? 'history' : 'hash';
this.root = options && options.root ? '/' + this.clearSlashes(options.root) + '/' : '/';
return this;
},
getFragment: function() {
let fragment = '';
if(this.mode === 'history') {
// fragment = this.clearSlashes(decodeURI(location.pathname + location.search));
fragment = this.clearSlashes(decodeURI(window.location.href.replace('/#', '').replace(window.location.origin, '/')));
fragment = fragment.replace(/\?(.*)$/, '');
fragment = this.root != '/' ? fragment.replace(this.root, '') : fragment;
} else {
let match = window.location.href.match(/#(.*)$/);
fragment = match ? match[1] : '';
}
return this.clearSlashes(fragment);
},
getParams: function () {
let query = '';
if(this.mode === 'history') {
query = decodeURI(window.location.href.replace('/#', '')).split("?")[1];
} else {
let index = window.location.hash.indexOf("?");
query = (index !== -1) ? window.location.hash.substring(index) : "";
}
let _query = {};
if (typeof query !== "string") {
return _query;
}
if (query[0] === "?") {
query = query.substring(1);
}
query.split("&").forEach(function (row) {
let parts = row.split("=");
if (parts[0] !== "") {
if (parts[1] === undefined) {
parts[1] = true;
}
_query[decodeURIComponent(parts[0])] = decodeURIComponent(parts[1]);
}
});
return _query;
},
clearSlashes: function(path) {
return path.toString().replace(/\/$/, '').replace(/^\//, '');
},
add: function(re, handler, options) {
if(typeof re == 'function') {
handler = re;
re = '';
}
this.routes.push({ re: re, handler: handler, options: options});
return this;
},
remove: function(param) {
for(let i=0, r; i < this.routes.length, r = this.routes[i]; i++) {
if(r.handler === param || r.re.toString() === param.toString()) {
this.routes.splice(i, 1);
return this;
}
}
return this;
},
flush: function() {
this.routes = [];
this.mode = null;
this.root = '/';
return this;
},
check: function(f) {
let fragment = f || this.getFragment();
for(let i=0; i < this.routes.length; i++) {
let match = fragment.match(this.routes[i].re);
if(fragment == '' || fragment == '/') match = 'home'.match(this.routes[i].re);
if(match) {
match.shift();
let query = this.getParams();
this.routes[i].handler.apply({query}, match);
return this;
}
}
return this;
},
listen: function() {
let self = this;
let current = self.getFragment();
let current_query = self.getParams();
let fn = function() {
if(current !== self.getFragment()) {
current = self.getFragment();
self.check(current);
}
// if(current !== self.getFragment() || JSON.stringify(current_query) !== JSON.stringify(self.getParams())) {
// current = self.getFragment();
// current_query = self.getParams();
// self.check(current);
// }
}
clearInterval(this.interval);
this.interval = setInterval(fn, 50);
return this;
},
navigate: function(path, mode = true) {
path = path || '';
if(mode){
if(this.mode === 'history') {
history.replaceState({position: window.pageYOffset}, null);
history.pushState({position: 0}, null, this.root + this.clearSlashes(path));
} else {
window.location.href = window.location.href.replace(/#(.*)$/, '') + '#' + path;
}
}
return this;
},
update: function(key, value) {
let url = window.location;
let URLParams = new URLSearchParams(url.search);
let RouterParams = Router.getParams();
URLParams.set(key, value);
history.replaceState({position: window.pageYOffset}, null, window.location.origin + window.location.pathname + '?' + URLParams.toString());
}
}
window.addEventListener('click', function (event) {
if (!event.target.matches('[data-route]')) return;
event.preventDefault();
Router.navigate((event.target.href).replace(window.location.origin, ''));
}, false);
if(Router.mode === 'history'){
window.addEventListener('popstate', function (event) {
if (!history.state.url) return;
Router.navigate(history.state.url);
}, false);
}

78
web/lib/router/routes.js Normal file
View File

@@ -0,0 +1,78 @@
Router
.add('territory/manager/(.*)/(.*)', function (type, id) {
Territory_Manager.init(type, id);
pageActive()
routerScroll()
})
.add('territory/editor/(.*)/(.*)', function (type, id) {
Editor.init(type, id);
pageActive()
routerScroll()
})
.add('territory/card/(.*)/(.*)', function (type, id) {
Card.init(type, id);
pageActive()
routerScroll()
})
.add('territory', function () {
Territory.init();
pageActive('territory')
routerScroll()
})
.add('sheeps/(.*)', function (name) {
Sheeps.init(name);
routerScroll();
pageActive('sheeps')
})
.add('sheeps', function () {
Sheeps.init();
routerScroll();
pageActive('sheeps')
})
.add('home', function () {
Home.init();
pageActive('home')
routerScroll()
})
.add('schedule', function () {
Options.init();
routerScroll();
pageActive('schedule')
})
.add('stand', function () {
Stand.init();
routerScroll();
pageActive('stand')
})
.add('options', function () {
Options.init();
routerScroll();
pageActive('options')
})
.add('constructor', function () {
Constructor.init();
routerScroll();
pageActive()
})
.add(function () {
page_404();
routerScroll();
pageActive();
})
function routerScroll() {
if (!history.state) return;
window.scroll(0, history.state.position);
}
function pageActive(element) {
let nav = document.getElementsByTagName("nav")[0];
let dots = nav.getElementsByTagName("li");
for (i = 0; i < dots.length; i++) {
dots[i].children[0].setAttribute('data-state', '');
}
if (element) document.getElementById(`nav-${element}`).setAttribute('data-state', 'active')
}

18
web/manifest.json Normal file
View File

@@ -0,0 +1,18 @@
{
"author": "rozenrod.com",
"name": "Sheep Service",
"short_name": "Sheep Service",
"display": "standalone",
"start_url": "/",
"scope": "/",
"background_color": "#232325",
"theme_color": "#121214",
"version": "0.4.4",
"icons": [
{ "src": "img/favicon.ico", "type": "image/x-icon", "sizes": "32x32" },
{ "src": "img/icon-192.png", "type": "image/png", "sizes": "192x192" },
{ "src": "img/icon-512.png", "type": "image/png", "sizes": "512x512" },
{ "src": "img/icon-192-maskable.png", "type": "image/png", "sizes": "192x192", "purpose": "maskable" },
{ "src": "img/icon-512-maskable.png", "type": "image/png", "sizes": "512x512", "purpose": "maskable" }
]
}

764
web/package-lock.json generated Normal file
View File

@@ -0,0 +1,764 @@
{
"name": "WEB Sheep Service",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "WEB Sheep Service",
"version": "1.0.0",
"license": "ISC",
"dependencies": {
"express": "^4.21.0"
}
},
"node_modules/accepts": {
"version": "1.3.8",
"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
"integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==",
"dependencies": {
"mime-types": "~2.1.34",
"negotiator": "0.6.3"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/array-flatten": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
"integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg=="
},
"node_modules/body-parser": {
"version": "1.20.3",
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz",
"integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==",
"dependencies": {
"bytes": "3.1.2",
"content-type": "~1.0.5",
"debug": "2.6.9",
"depd": "2.0.0",
"destroy": "1.2.0",
"http-errors": "2.0.0",
"iconv-lite": "0.4.24",
"on-finished": "2.4.1",
"qs": "6.13.0",
"raw-body": "2.5.2",
"type-is": "~1.6.18",
"unpipe": "1.0.0"
},
"engines": {
"node": ">= 0.8",
"npm": "1.2.8000 || >= 1.4.16"
}
},
"node_modules/bytes": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
"integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/call-bind-apply-helpers": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.1.tgz",
"integrity": "sha512-BhYE+WDaywFg2TBWYNXAE+8B1ATnThNBqXHP5nQu0jWJdVvY2hvkpyB3qOmtmDePiS5/BDQ8wASEWGMWRG148g==",
"dependencies": {
"es-errors": "^1.3.0",
"function-bind": "^1.1.2"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/call-bound": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.3.tgz",
"integrity": "sha512-YTd+6wGlNlPxSuri7Y6X8tY2dmm12UMH66RpKMhiX6rsk5wXXnYgbUcOt8kiS31/AjfoTOvCsE+w8nZQLQnzHA==",
"dependencies": {
"call-bind-apply-helpers": "^1.0.1",
"get-intrinsic": "^1.2.6"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/content-disposition": {
"version": "0.5.4",
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
"integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==",
"dependencies": {
"safe-buffer": "5.2.1"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/content-type": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz",
"integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/cookie": {
"version": "0.7.1",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz",
"integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/cookie-signature": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
"integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ=="
},
"node_modules/debug": {
"version": "2.6.9",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
"dependencies": {
"ms": "2.0.0"
}
},
"node_modules/depd": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
"integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/destroy": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz",
"integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==",
"engines": {
"node": ">= 0.8",
"npm": "1.2.8000 || >= 1.4.16"
}
},
"node_modules/dunder-proto": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
"integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
"dependencies": {
"call-bind-apply-helpers": "^1.0.1",
"es-errors": "^1.3.0",
"gopd": "^1.2.0"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/ee-first": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
"integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="
},
"node_modules/encodeurl": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
"integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/es-define-property": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
"integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
"engines": {
"node": ">= 0.4"
}
},
"node_modules/es-errors": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
"engines": {
"node": ">= 0.4"
}
},
"node_modules/es-object-atoms": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
"integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
"dependencies": {
"es-errors": "^1.3.0"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/escape-html": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
"integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow=="
},
"node_modules/etag": {
"version": "1.8.1",
"resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
"integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/express": {
"version": "4.21.2",
"resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz",
"integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==",
"dependencies": {
"accepts": "~1.3.8",
"array-flatten": "1.1.1",
"body-parser": "1.20.3",
"content-disposition": "0.5.4",
"content-type": "~1.0.4",
"cookie": "0.7.1",
"cookie-signature": "1.0.6",
"debug": "2.6.9",
"depd": "2.0.0",
"encodeurl": "~2.0.0",
"escape-html": "~1.0.3",
"etag": "~1.8.1",
"finalhandler": "1.3.1",
"fresh": "0.5.2",
"http-errors": "2.0.0",
"merge-descriptors": "1.0.3",
"methods": "~1.1.2",
"on-finished": "2.4.1",
"parseurl": "~1.3.3",
"path-to-regexp": "0.1.12",
"proxy-addr": "~2.0.7",
"qs": "6.13.0",
"range-parser": "~1.2.1",
"safe-buffer": "5.2.1",
"send": "0.19.0",
"serve-static": "1.16.2",
"setprototypeof": "1.2.0",
"statuses": "2.0.1",
"type-is": "~1.6.18",
"utils-merge": "1.0.1",
"vary": "~1.1.2"
},
"engines": {
"node": ">= 0.10.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
}
},
"node_modules/finalhandler": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz",
"integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==",
"dependencies": {
"debug": "2.6.9",
"encodeurl": "~2.0.0",
"escape-html": "~1.0.3",
"on-finished": "2.4.1",
"parseurl": "~1.3.3",
"statuses": "2.0.1",
"unpipe": "~1.0.0"
},
"engines": {
"node": ">= 0.8"
}
},
"node_modules/forwarded": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
"integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/fresh": {
"version": "0.5.2",
"resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
"integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/function-bind": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/get-intrinsic": {
"version": "1.2.7",
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.7.tgz",
"integrity": "sha512-VW6Pxhsrk0KAOqs3WEd0klDiF/+V7gQOpAvY1jVU/LHmaD/kQO4523aiJuikX/QAKYiW6x8Jh+RJej1almdtCA==",
"dependencies": {
"call-bind-apply-helpers": "^1.0.1",
"es-define-property": "^1.0.1",
"es-errors": "^1.3.0",
"es-object-atoms": "^1.0.0",
"function-bind": "^1.1.2",
"get-proto": "^1.0.0",
"gopd": "^1.2.0",
"has-symbols": "^1.1.0",
"hasown": "^2.0.2",
"math-intrinsics": "^1.1.0"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/get-proto": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
"integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
"dependencies": {
"dunder-proto": "^1.0.1",
"es-object-atoms": "^1.0.0"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/gopd": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
"integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/has-symbols": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
"integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/hasown": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
"dependencies": {
"function-bind": "^1.1.2"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/http-errors": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz",
"integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==",
"dependencies": {
"depd": "2.0.0",
"inherits": "2.0.4",
"setprototypeof": "1.2.0",
"statuses": "2.0.1",
"toidentifier": "1.0.1"
},
"engines": {
"node": ">= 0.8"
}
},
"node_modules/iconv-lite": {
"version": "0.4.24",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
"integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
"dependencies": {
"safer-buffer": ">= 2.1.2 < 3"
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/inherits": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
},
"node_modules/ipaddr.js": {
"version": "1.9.1",
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
"integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==",
"engines": {
"node": ">= 0.10"
}
},
"node_modules/math-intrinsics": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
"integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
"engines": {
"node": ">= 0.4"
}
},
"node_modules/media-typer": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
"integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/merge-descriptors": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz",
"integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==",
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/methods": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
"integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/mime": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
"integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==",
"bin": {
"mime": "cli.js"
},
"engines": {
"node": ">=4"
}
},
"node_modules/mime-db": {
"version": "1.52.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/mime-types": {
"version": "2.1.35",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
"dependencies": {
"mime-db": "1.52.0"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/ms": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
},
"node_modules/negotiator": {
"version": "0.6.3",
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
"integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/object-inspect": {
"version": "1.13.3",
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.3.tgz",
"integrity": "sha512-kDCGIbxkDSXE3euJZZXzc6to7fCrKHNI/hSRQnRuQ+BWjFNzZwiFF8fj/6o2t2G9/jTj8PSIYTfCLelLZEeRpA==",
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/on-finished": {
"version": "2.4.1",
"resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
"integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
"dependencies": {
"ee-first": "1.1.1"
},
"engines": {
"node": ">= 0.8"
}
},
"node_modules/parseurl": {
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
"integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/path-to-regexp": {
"version": "0.1.12",
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz",
"integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ=="
},
"node_modules/proxy-addr": {
"version": "2.0.7",
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
"integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==",
"dependencies": {
"forwarded": "0.2.0",
"ipaddr.js": "1.9.1"
},
"engines": {
"node": ">= 0.10"
}
},
"node_modules/qs": {
"version": "6.13.0",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz",
"integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==",
"dependencies": {
"side-channel": "^1.0.6"
},
"engines": {
"node": ">=0.6"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/range-parser": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
"integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/raw-body": {
"version": "2.5.2",
"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz",
"integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==",
"dependencies": {
"bytes": "3.1.2",
"http-errors": "2.0.0",
"iconv-lite": "0.4.24",
"unpipe": "1.0.0"
},
"engines": {
"node": ">= 0.8"
}
},
"node_modules/safe-buffer": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
]
},
"node_modules/safer-buffer": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
},
"node_modules/send": {
"version": "0.19.0",
"resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz",
"integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==",
"dependencies": {
"debug": "2.6.9",
"depd": "2.0.0",
"destroy": "1.2.0",
"encodeurl": "~1.0.2",
"escape-html": "~1.0.3",
"etag": "~1.8.1",
"fresh": "0.5.2",
"http-errors": "2.0.0",
"mime": "1.6.0",
"ms": "2.1.3",
"on-finished": "2.4.1",
"range-parser": "~1.2.1",
"statuses": "2.0.1"
},
"engines": {
"node": ">= 0.8.0"
}
},
"node_modules/send/node_modules/encodeurl": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
"integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/send/node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
},
"node_modules/serve-static": {
"version": "1.16.2",
"resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz",
"integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==",
"dependencies": {
"encodeurl": "~2.0.0",
"escape-html": "~1.0.3",
"parseurl": "~1.3.3",
"send": "0.19.0"
},
"engines": {
"node": ">= 0.8.0"
}
},
"node_modules/setprototypeof": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
"integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="
},
"node_modules/side-channel": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz",
"integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==",
"dependencies": {
"es-errors": "^1.3.0",
"object-inspect": "^1.13.3",
"side-channel-list": "^1.0.0",
"side-channel-map": "^1.0.1",
"side-channel-weakmap": "^1.0.2"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/side-channel-list": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz",
"integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==",
"dependencies": {
"es-errors": "^1.3.0",
"object-inspect": "^1.13.3"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/side-channel-map": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz",
"integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==",
"dependencies": {
"call-bound": "^1.0.2",
"es-errors": "^1.3.0",
"get-intrinsic": "^1.2.5",
"object-inspect": "^1.13.3"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/side-channel-weakmap": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz",
"integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==",
"dependencies": {
"call-bound": "^1.0.2",
"es-errors": "^1.3.0",
"get-intrinsic": "^1.2.5",
"object-inspect": "^1.13.3",
"side-channel-map": "^1.0.1"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/statuses": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
"integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/toidentifier": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
"integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==",
"engines": {
"node": ">=0.6"
}
},
"node_modules/type-is": {
"version": "1.6.18",
"resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
"integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
"dependencies": {
"media-typer": "0.3.0",
"mime-types": "~2.1.24"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/unpipe": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
"integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/utils-merge": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
"integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==",
"engines": {
"node": ">= 0.4.0"
}
},
"node_modules/vary": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
"integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",
"engines": {
"node": ">= 0.8"
}
}
}
}

14
web/package.json Normal file
View File

@@ -0,0 +1,14 @@
{
"name": "WEB Sheep Service",
"version": "1.0.0",
"main": "server.js",
"scripts": {
"start": "node server.js"
},
"author": "",
"license": "ISC",
"description": "",
"dependencies": {
"express": "^4.21.0"
}
}

254
web/screenshot.html Normal file
View File

@@ -0,0 +1,254 @@
<html>
<head>
<meta charset="UTF-8" />
<link rel="stylesheet" href="/lib/components/leaflet/leaflet.css" />
<script src="/lib/components/leaflet/leaflet.js"></script>
<link rel="stylesheet" href="/lib/components/geoman/leaflet-geoman.css" />
<script src="/lib/components/geoman/leaflet-geoman.min.js"></script>
<script src="/lib/components/turf.min.js" defer></script>
<script src="/lib/components/qrcode.min.js"></script>
<style>
@import url("https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500;700&display=swap");
* {
border: 0;
padding: 0;
font-family: "Roboto", sans-serif;
margin: 0;
font-weight: 500;
outline: none;
}
#content {
width: 100%;
height: 100%;
position: absolute;
z-index: 9999;
display: flex;
flex-direction: column;
}
#content > #title {
width: calc(100% - 30px);
min-height: 120px;
background: rgb(255 255 255 / 51%);
backdrop-filter: blur(10px);
display: flex;
flex-direction: row;
justify-content: space-between;
padding: 15px;
align-items: center;
color: #333;
}
#content > #title > div {
display: flex;
flex-direction: column;
width: 100%;
align-items: center;
}
#content > #title > h1 {
font-size: 65px;
font-weight: 700;
margin: 0;
min-width: 200px;
text-align: center;
}
#content > #title > div > h2 {
font-size: 45px;
font-weight: 700;
margin: 0;
}
#content > #title > div > h3 {
font-size: 38px;
font-weight: 500;
margin: 0px 0 10px 0;
}
#qrcode {
position: absolute;
bottom: 160px;
left: 20px;
width: 130px;
height: 130px;
background: #fff;
padding: 10px;
border-radius: 2px;
}
.line-left {
position: absolute;
left: 0;
top: 150px;
width: 10px;
height: calc(100% - 150px);
background: rgb(255 255 255 / 51%);
backdrop-filter: blur(10px);
}
.line-right {
position: absolute;
right: 0;
top: 150px;
width: 10px;
height: calc(100% - 150px);
background: rgb(255 255 255 / 51%);
backdrop-filter: blur(10px);
}
.line-bottom {
position: absolute;
left: 10px;
bottom: 0;
width: calc(100% - 20px);
height: 150px;
background: rgb(255 255 255 / 51%);
backdrop-filter: blur(10px);
}
#map {
width: 100%;
height: 100%;
}
</style>
</head>
<body>
<div id="content">
<div id="title">
<div>
<h3>Картка плану території</h3>
<h2 id="address"></h2>
</div>
<h1 id="number"></h1>
</div>
<div id="qrcode"></div>
<div class="line-left"></div>
<div class="line-right"></div>
<div class="line-bottom"></div>
</div>
<div id="map"></div>
<script>
const urlParams = new URLSearchParams(window.location.search);
let center = {
lat: urlParams.get("lat") ?? 49.589443,
lng: urlParams.get("lng") ?? 25.5812376,
};
let zoom = urlParams.get("zoom") ?? 19;
let type = urlParams.get("type") ?? "homestead";
let wayIds = urlParams.get("wayId") ?? "396982388";
let address = urlParams.get("address") ?? "Житловий район";
let number = urlParams.get("number") ?? 36;
let map = L.map("map", {
renderer: L.canvas(),
center,
zoom,
zoomControl: false,
});
let layer = L.tileLayer("https://tm.rozenrod.com/webp/{z}/{x}/{y}.webp", {
maxZoom: 20,
minZoom: 15,
tms: true,
}).addTo(map);
window.onload = async function () {
if (type && wayIds) {
new QRCode(document.getElementById("qrcode"), {
text: `https://www.google.com/maps/search/?api=1&query=${center.lat},${center.lng}`, // Ваша ссылка
width: 130,
height: 130,
correctLevel: QRCode.CorrectLevel.L, // Минимальный уровень коррекции (меньше деталей)
version: 5,
});
let wayId = wayIds.split(",");
console.log(wayId);
for (let i = 0; i < wayId.length; i++) {
const element = wayId[i];
let geo = await getOSM(element);
let coords = [];
geo[0].forEach((feature) =>
coords.push([feature.lat, feature.lng])
);
let centerPoint = turf.centerOfMass(turf.polygon([coords]));
console.log(centerPoint);
if (!urlParams.get("lat")) {
map.setView(
[
centerPoint.geometry.coordinates[0],
centerPoint.geometry.coordinates[1],
],
urlParams.get("zoom") ?? 17
);
document.getElementById("qrcode").innerHTML = "";
new QRCode(document.getElementById("qrcode"), {
text: `https://www.google.com/maps/search/?api=1&query=${centerPoint.geometry.coordinates[0]},${centerPoint.geometry.coordinates[1]}`, // Ваша ссылка
width: 130,
height: 130,
});
}
if (type == "house") {
document.getElementById("address").innerText = address;
document.getElementById("number").innerText = "T" + number;
L.polygon(geo, {
color: "#a12121",
fillColor: "#c12525",
fillOpacity: 0.4,
}).addTo(map);
} else if (type == "homestead") {
document.getElementById("address").innerText = address;
document.getElementById("number").innerText = "H" + number;
L.polygon(geo, {
color: "#a12121",
fillColor: "#c12525",
radius: 500,
fillOpacity: 0.3,
dashArray: "20, 15",
dashOffset: "20",
}).addTo(map);
}
}
}
async function getOSM(wayId) {
const overpassUrl = `https://overpass-api.de/api/interpreter?data=[out:json];way(${wayId});(._;>;);out;`;
return await fetch(overpassUrl)
.then((response) => response.json())
.then((data) => {
const nodes = new Map();
data.elements.forEach((el) => {
if (el.type === "node") {
nodes.set(el.id, { lat: el.lat, lng: el.lon });
}
});
const way = data.elements.find((el) => el.type === "way");
if (way) {
const coordinates = way.nodes.map((nodeId) =>
nodes.get(nodeId)
);
console.log("Координаты точек:", coordinates);
return [coordinates];
} else {
console.log("Way не найден!");
}
})
.catch((error) => console.error("Ошибка запроса:", error));
}
layer.on("load", () => {
window.status = "ready";
});
};
</script>
</body>
</html>

21
web/server.js Normal file
View File

@@ -0,0 +1,21 @@
const express = require("express");
const path = require("path");
const app = express();
const PORT = 4002;
const DIR = process.env.CARDS_PATH || path.join(__dirname, '../cards');
// Раздача статических файлов из папки, указанной в DIR
app.use('/cards', express.static(DIR));
// Раздача других статических файлов из текущей папки
app.use(express.static(__dirname));
// Обработка 404 ошибки
app.use((req, res) => {
res.status(404).sendFile(path.join(__dirname, "index.html"));
});
app.listen(PORT, () => {
console.log(`Serving app on port ${PORT} ...`);
});

31
web/server.py Normal file
View File

@@ -0,0 +1,31 @@
import os
from http.server import HTTPServer, SimpleHTTPRequestHandler
# Default error message template
DEFAULT_ERROR_MESSAGE = """\
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Single Page Apps for GitHub Pages</title>
<script>
sessionStorage.redirect = location.href;
</script>
<meta http-equiv="refresh" content="0;URL='/'">
</head>
<body>
</body>
</html>
"""
class MyHandler(SimpleHTTPRequestHandler):
def send_error(self, code, message=None):
if code == 404:
self.error_message_format = DEFAULT_ERROR_MESSAGE
SimpleHTTPRequestHandler.send_error(self, code, message)
if __name__ == '__main__':
httpd = HTTPServer(('', 4002), MyHandler)
print("Serving app on port 4002 ...")
httpd.serve_forever()

238
web/share.html Normal file
View File

@@ -0,0 +1,238 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta
name="viewport"
content="user-scalable=no, initial-scale=1.0, maximum-scale=1.0"
/>
<title>Sheep Service</title>
<link rel="icon" href="/img/favicon.ico" sizes="any" />
<link rel="apple-touch-icon" href="/img/apple-touch-icon.png" />
<script src="js/app.js" defer></script>
<style>
@import url("https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500;700&display=swap");
@media (prefers-color-scheme: light) {
:root {
/* PrimaryColor */
--PrimaryColor: #28a55a;
--PrimaryColor: #f2bd53;
--PrimaryColorText: #2e2e2e;
/* BGColor */
--ColorThemes0: #fbfbfb;
--ColorThemes1: #f3f3f3;
--ColorThemes2: #e5e5df;
/* TextColor */
--ColorThemes3: #313131;
--ColorAnimation: linear-gradient(90deg, #f3f3f3, #efefef, #f3f3f3);
--shadow-l1: 0px 2px 4px rgba(0, 0, 0, 0.02),
0px 0px 2px rgba(0, 0, 0, 0.04), 0px 0px 1px rgba(0, 0, 0, 0.04);
--border-radius: 15px;
--CardAnimation: linear-gradient(
to right,
#fbfbfb 0%,
#fbfbfb 30%,
#d8d8d8 45%,
#d8d8d8 50%,
#fbfbfb 60%,
#fbfbfb 100%
);
}
}
@media (prefers-color-scheme: dark) {
:root {
/* PrimaryColor */
--PrimaryColor: #28a55a;
--PrimaryColor: #cb9e44;
--PrimaryColorText: #2e2e2e;
/* BGColor */
--ColorThemes0: #1c1c19;
--ColorThemes1: #21221d;
--ColorThemes2: #252523;
/* TextColor */
--ColorThemes3: #f3f3f3;
--ColorAnimation: linear-gradient(90deg, #21221d, #242520, #21221d);
--shadow-l1: 0px 4px 8px rgba(0, 0, 0, 0.04),
0px 0px 2px rgba(0, 0, 0, 0.06), 0px 0px 1px rgba(0, 0, 0, 0.04);
--border-radius: 15px;
--CardAnimation: linear-gradient(
to right,
#1c1c19 0%,
#1c1c19 30%,
#252525 45%,
#252525 50%,
#1c1c19 60%,
#1c1c19 100%
);
}
}
* {
border: 0;
padding: 0;
font-family: "Roboto", sans-serif;
margin: 0;
font-weight: 300;
outline: none;
}
*[disabled] {
opacity: 0.6 !important;
cursor: no-drop !important;
}
body {
background: var(--ColorThemes0);
color: var(--ColorThemes3);
}
select {
-moz-appearance: none;
-webkit-appearance: none;
appearance: none;
/* Arrow */
appearance: none;
background-image: url("data:image/svg+xml;charset=US-ASCII,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%22292.4%22%20height%3D%22292.4%22%3E%3Cpath%20fill%3D%22%237a899d%22%20d%3D%22M287%2069.4a17.6%2017.6%200%200%200-13-5.4H18.4c-5%200-9.3%201.8-12.9%205.4A17.6%2017.6%200%200%200%200%2082.2c0%205%201.8%209.3%205.4%2012.9l128%20127.9c3.6%203.6%207.8%205.4%2012.8%205.4s9.2-1.8%2012.8-5.4L287%2095c3.5-3.5%205.4-7.8%205.4-12.8%200-5-1.9-9.2-5.5-12.8z%22%2F%3E%3C%2Fsvg%3E");
background-repeat: no-repeat;
background-position: right 0.3rem top 50%;
background-size: 0.55rem auto;
}
#header {
display: flex;
justify-content: space-around;
margin: 10px;
}
#header p {
font-size: 20px;
}
#list {
display: flex;
flex-direction: column;
align-items: center;
padding: 10px;
}
details {
color: var(--ColorThemes3);
width: 100%;
min-width: 320px;
background: var(--ColorThemes1);
margin: 20px 0px;
border-radius: 10px;
overflow: hidden;
box-shadow: 0px 2px 4px rgba(0, 0, 0, 0.04),
0px 0px 2px rgba(0, 0, 0, 0.04), 0px 0px 1px rgba(0, 0, 0, 0.04);
}
@media (min-width: 500px) {
details {
/* width: calc(100% - 30px); */
}
}
details[disabled] summary,
details.disabled summary {
pointer-events: none;
user-select: none;
}
details summary::-webkit-details-marker,
details summary::marker {
display: none;
content: "";
}
summary {
cursor: pointer;
background: #7a8a9d;
background: var(--PrimaryColor);
height: 45px;
display: flex;
align-items: center;
justify-content: space-between;
}
summary p {
padding: 0 10px;
color: var(--PrimaryColorText);
font-size: 14px;
font-weight: 500;
}
summary svg {
width: 25px;
height: 25px;
padding: 0 10px;
fill: var(--PrimaryColorText);
}
.apartments_list {
padding: 10px;
border-top: 2px solid var(--ColorThemes1);
border-bottom: 2px solid var(--PrimaryColor);
border-left: 2px solid var(--PrimaryColor);
border-right: 2px solid var(--PrimaryColor);
border-style: dotted;
border-radius: 0 0 10px 10px;
}
@media (max-width: 500px) {
.apartments_list {
padding: 0px;
}
}
.apartments_list div {
display: flex;
font-size: 14px;
align-items: center;
justify-content: space-between;
border-radius: 8px;
margin: 5px 0;
}
.apartments_list div span {
min-width: 30px;
font-size: 12px;
position: relative;
margin: 5px;
}
.apartments_list div select {
color: #3d3d3d;
border-radius: 6px;
border: 1px solid #eaebef;
margin: 5px;
background-color: #fff;
min-width: 110px;
width: 100%;
padding: 4px 20px 4px 4px;
height: 30px;
}
.apartments_list div input {
color: #3d3d3d;
border-radius: 6px;
border: 1px solid #eaebef;
margin: 5px;
background-color: #fff;
width: 100%;
min-width: 70px;
padding: 0 4px;
height: calc(30px - 2px);
appearance: none;
-webkit-appearance: none;
}
</style>
</head>
<body>
<div id="header">
<p id="status"></p>
<p id="hash"></p>
</div>
<div id="list"></div>
</body>
</html>

15
web/sw.js Normal file
View File

@@ -0,0 +1,15 @@
self.addEventListener('install', async event => {
})
self.addEventListener('activate', async event => {
})
self.addEventListener('fetch', event => {
})
self.addEventListener('message', event => {
if (event.data === 'skipWaiting') return skipWaiting();
});