Compare commits
7 Commits
afd896161e
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 7b4420ddbd | |||
| c797c6e7fe | |||
| 546f2e27af | |||
| e18976ace3 | |||
| eed96ca8c0 | |||
| 1790852242 | |||
| c8f7f35c79 |
10
.idea/libraries/batoulapps_adhan.xml
generated
Normal file
10
.idea/libraries/batoulapps_adhan.xml
generated
Normal file
@@ -0,0 +1,10 @@
|
||||
<component name="libraryTable">
|
||||
<library name="batoulapps.adhan" type="repository">
|
||||
<properties maven-id="com.batoulapps.adhan:adhan:1.2.1" />
|
||||
<CLASSES>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/com/batoulapps/adhan/adhan/1.2.1/adhan-1.2.1.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES />
|
||||
</library>
|
||||
</component>
|
||||
16
.idea/libraries/batoulapps_adhan_adhan2.xml
generated
Normal file
16
.idea/libraries/batoulapps_adhan_adhan2.xml
generated
Normal file
@@ -0,0 +1,16 @@
|
||||
<component name="libraryTable">
|
||||
<library name="batoulapps.adhan.adhan2" type="repository">
|
||||
<properties maven-id="com.batoulapps.adhan:adhan2:0.0.6" />
|
||||
<CLASSES>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/com/batoulapps/adhan/adhan2/0.0.6/adhan2-0.0.6.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-datetime/0.7.1/kotlinx-datetime-0.7.1.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-serialization-core/1.6.2/kotlinx-serialization-core-1.6.2.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-serialization-core-jvm/1.6.2/kotlinx-serialization-core-jvm-1.6.2.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-common/1.9.21/kotlin-stdlib-common-1.9.21.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/2.2.20/kotlin-stdlib-2.2.20.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES />
|
||||
</library>
|
||||
</component>
|
||||
15
.idea/libraries/jetbrains_kotlinx_datetime.xml
generated
Normal file
15
.idea/libraries/jetbrains_kotlinx_datetime.xml
generated
Normal file
@@ -0,0 +1,15 @@
|
||||
<component name="libraryTable">
|
||||
<library name="jetbrains.kotlinx.datetime" type="repository">
|
||||
<properties maven-id="org.jetbrains.kotlinx:kotlinx-datetime:0.7.0-0.6.x-compat" />
|
||||
<CLASSES>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-datetime/0.7.0-0.6.x-compat/kotlinx-datetime-0.7.0-0.6.x-compat.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/2.1.20/kotlin-stdlib-2.1.20.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-serialization-core/1.6.2/kotlinx-serialization-core-1.6.2.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-serialization-core-jvm/1.6.2/kotlinx-serialization-core-jvm-1.6.2.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-common/1.9.21/kotlin-stdlib-common-1.9.21.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES />
|
||||
</library>
|
||||
</component>
|
||||
@@ -10,6 +10,7 @@
|
||||
<sourceFolder url="file://$MODULE_DIR$/testResources" type="java-test-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/html" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/audiofiles" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/OurAirports" isTestSource="false" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
@@ -38,5 +39,6 @@
|
||||
</orderEntry>
|
||||
<orderEntry type="library" name="google.cloud.texttospeech" level="project" />
|
||||
<orderEntry type="library" name="projectlombok.lombok" level="project" />
|
||||
<orderEntry type="library" name="batoulapps.adhan" level="project" />
|
||||
</component>
|
||||
</module>
|
||||
84563
OurAirports/world-airports.csv
Normal file
84563
OurAirports/world-airports.csv
Normal file
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 one or more lines are too long
253
html/webpage/assets/css/bootstrap4-clockpicker.css
vendored
Normal file
253
html/webpage/assets/css/bootstrap4-clockpicker.css
vendored
Normal file
@@ -0,0 +1,253 @@
|
||||
/* !
|
||||
* ClockPicker v0.2.2 for Bootstrap (https://weareoutman.github.io/clockpicker/)
|
||||
* Copyright 2014 Wang Shenwei
|
||||
* Licensed under MIT (https://github.com/weareoutman/clockpicker/blob/gh-pages/LICENSE)
|
||||
* Bootstrap 4 compatibility by djibe (https://github.com/djibe/clockpicker) */
|
||||
|
||||
:root {
|
||||
--primary-color: 0, 123, 255;
|
||||
}
|
||||
|
||||
.clockpicker .input-group-addon {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.clockpicker-moving {
|
||||
cursor: move;
|
||||
}
|
||||
|
||||
.clockpicker-align-left.popover > .arrow {
|
||||
left: 25px;
|
||||
}
|
||||
|
||||
.clockpicker-align-top.popover > .arrow {
|
||||
top: 17px;
|
||||
}
|
||||
|
||||
.clockpicker-align-right.popover > .arrow {
|
||||
left: auto;
|
||||
right: 25px;
|
||||
}
|
||||
|
||||
.clockpicker-align-bottom.popover > .arrow {
|
||||
top: auto;
|
||||
bottom: 6px;
|
||||
}
|
||||
|
||||
.clockpicker-popover {
|
||||
-webkit-animation: pickerFadeIn 0.2s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
animation: pickerFadeIn 0.2s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
border-radius: 4px;
|
||||
border: 0;
|
||||
-webkit-transform: scale(1);
|
||||
transform: scale(1);
|
||||
-webkit-transform-origin: center top 0px;
|
||||
transform-origin: center top 0px;
|
||||
-webkit-box-shadow: 0 24px 38px 3px rgba(0, 0, 0, 0.14), 0 9px 46px 8px rgba(0, 0, 0, 0.12), 0 11px 15px 0 rgba(0, 0, 0, 0.2);
|
||||
box-shadow: 0 24px 38px 3px rgba(0, 0, 0, 0.14), 0 9px 46px 8px rgba(0, 0, 0, 0.12), 0 11px 15px 0 rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.clockpicker-popover.top {
|
||||
-webkit-transform-origin: center bottom 0px;
|
||||
transform-origin: center bottom 0px;
|
||||
}
|
||||
|
||||
.clockpicker-popover * {
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.clockpicker-popover .popover-header {
|
||||
-webkit-box-align: center;
|
||||
-ms-flex-align: center;
|
||||
align-items: center;
|
||||
-webkit-box-pack: center;
|
||||
-ms-flex-pack: center;
|
||||
justify-content: center;
|
||||
background-color: var(--primary, #007bff);
|
||||
color: #fff;
|
||||
display: -webkit-box;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
font-size: 3rem;
|
||||
font-weight: normal;
|
||||
letter-spacing: normal;
|
||||
text-align: center;
|
||||
padding: 0.5rem;
|
||||
border-top-left-radius: 4px;
|
||||
border-top-right-radius: 4px;
|
||||
}
|
||||
|
||||
.clockpicker-popover .popover-header span {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.clockpicker-popover .popover-body {
|
||||
background-color: #fff;
|
||||
padding: 1rem 0.75rem 0.75rem;
|
||||
border-bottom-right-radius: 4px;
|
||||
border-bottom-left-radius: 4px;
|
||||
}
|
||||
|
||||
.clockpicker-popover .btn {
|
||||
border: 0 !important;
|
||||
border-radius: 4px;
|
||||
-webkit-box-shadow: none;
|
||||
box-shadow: none;
|
||||
font-size: 0.8125rem;
|
||||
font-weight: 500;
|
||||
padding: 0.59375rem 1rem;
|
||||
min-width: 0;
|
||||
margin: 0;
|
||||
margin-left: 0.25rem;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.clockpicker-popover .btn:focus, .clockpicker-popover .btn:hover, .clockpicker-popover .btn:active {
|
||||
outline: none !important;
|
||||
background-image: -webkit-gradient(linear, left top, left bottom, from(rgba(0, 0, 0, 0.12)), to(rgba(0, 0, 0, 0.12)));
|
||||
background-image: linear-gradient(180deg, rgba(0, 0, 0, 0.12), rgba(0, 0, 0, 0.12));
|
||||
-webkit-box-shadow: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.clockpicker-span-hours {
|
||||
margin-right: 0.25rem;
|
||||
}
|
||||
|
||||
.clockpicker-span-minutes {
|
||||
margin-left: 0.25rem;
|
||||
}
|
||||
|
||||
.clockpicker-close-block {
|
||||
margin-top: 0.75rem;
|
||||
}
|
||||
|
||||
.clockpicker-plate {
|
||||
background-color: #ededee;
|
||||
border-radius: 50%;
|
||||
width: 200px;
|
||||
height: 200px;
|
||||
overflow: visible;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.clockpicker-canvas, .clockpicker-dial {
|
||||
width: 200px;
|
||||
height: 200px;
|
||||
position: absolute;
|
||||
left: -1px;
|
||||
top: -1px;
|
||||
}
|
||||
|
||||
.clockpicker-minutes {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.clockpicker-tick {
|
||||
border-radius: 50%;
|
||||
line-height: 26px;
|
||||
text-align: center;
|
||||
width: 26px;
|
||||
height: 26px;
|
||||
position: absolute;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.clockpicker-tick.active, .clockpicker-tick:not(.disabled):hover {
|
||||
background-color: rgba(var(--primary-color, 0, 123, 255), 0.25);
|
||||
}
|
||||
|
||||
.clockpicker-tick.disabled {
|
||||
color: #eee;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.clockpicker-dial {
|
||||
-webkit-transition: opacity 350ms, -webkit-transform 350ms;
|
||||
transition: opacity 350ms, -webkit-transform 350ms;
|
||||
transition: transform 350ms, opacity 350ms;
|
||||
transition: transform 350ms, opacity 350ms, -webkit-transform 350ms;
|
||||
}
|
||||
|
||||
.clockpicker-dial-out {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.clockpicker-hours.clockpicker-dial-out {
|
||||
-webkit-transform: scale(1.2, 1.2);
|
||||
transform: scale(1.2, 1.2);
|
||||
}
|
||||
|
||||
.clockpicker-minutes.clockpicker-dial-out {
|
||||
-webkit-transform: scale(0.8, 0.8);
|
||||
transform: scale(0.8, 0.8);
|
||||
}
|
||||
|
||||
.clockpicker-canvas {
|
||||
-webkit-transition: opacity 175ms;
|
||||
transition: opacity 175ms;
|
||||
}
|
||||
|
||||
.clockpicker-canvas-out {
|
||||
opacity: 0.25;
|
||||
}
|
||||
|
||||
.clockpicker-canvas line {
|
||||
stroke: var(--primary, #007bff);
|
||||
stroke-width: 2;
|
||||
stroke-linecap: round;
|
||||
}
|
||||
|
||||
.clockpicker-canvas-bearing {
|
||||
stroke: none;
|
||||
fill: var(--primary, #007bff);
|
||||
}
|
||||
|
||||
.clockpicker-canvas-fg {
|
||||
stroke: none;
|
||||
fill: rgba(var(--primary-color, 0, 123, 255), 0.5);
|
||||
}
|
||||
|
||||
.clockpicker-canvas-bg {
|
||||
stroke: none;
|
||||
fill: rgba(var(--primary-color, 0, 123, 255), 0.25);
|
||||
}
|
||||
|
||||
.clockpicker-canvas-bg-trans {
|
||||
fill: rgba(var(--primary-color, 0, 123, 255), 0.25);
|
||||
}
|
||||
|
||||
.clockpicker-buttons-am-pm {
|
||||
color: white;
|
||||
display: none;
|
||||
-ms-flex-wrap: nowrap;
|
||||
flex-wrap: nowrap;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-box-direction: normal;
|
||||
-ms-flex-direction: column;
|
||||
flex-direction: column;
|
||||
-ms-flex-pack: distribute;
|
||||
justify-content: space-around;
|
||||
-webkit-box-align: center;
|
||||
-ms-flex-align: center;
|
||||
align-items: center;
|
||||
font-size: 1rem;
|
||||
margin-left: 0.75rem;
|
||||
}
|
||||
|
||||
@keyframes pickerFadeIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
-webkit-transform: scale(0.8);
|
||||
transform: scale(0.8);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
-webkit-transform: scale(1);
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
252
html/webpage/assets/css/bootstrap4-clockpicker.min.css
vendored
Normal file
252
html/webpage/assets/css/bootstrap4-clockpicker.min.css
vendored
Normal file
@@ -0,0 +1,252 @@
|
||||
/* !
|
||||
* ClockPicker v0.2.2 for Bootstrap (https://weareoutman.github.io/clockpicker/)
|
||||
* Copyright 2014 Wang Shenwei
|
||||
* Licensed under MIT (https://github.com/weareoutman/clockpicker/blob/gh-pages/LICENSE)
|
||||
* Bootstrap 4 compatibility by djibe (https://github.com/djibe/clockpicker) */
|
||||
|
||||
:root {
|
||||
--primary-color: 0,123,255;
|
||||
}
|
||||
|
||||
.clockpicker .input-group-addon {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.clockpicker-moving {
|
||||
cursor: move;
|
||||
}
|
||||
|
||||
.clockpicker-align-left.popover > .arrow {
|
||||
left: 25px;
|
||||
}
|
||||
|
||||
.clockpicker-align-top.popover > .arrow {
|
||||
top: 17px;
|
||||
}
|
||||
|
||||
.clockpicker-align-right.popover > .arrow {
|
||||
left: auto;
|
||||
right: 25px;
|
||||
}
|
||||
|
||||
.clockpicker-align-bottom.popover > .arrow {
|
||||
top: auto;
|
||||
bottom: 6px;
|
||||
}
|
||||
|
||||
.clockpicker-popover {
|
||||
-webkit-animation: pickerFadeIn .2s cubic-bezier(.4,0,.2,1);
|
||||
animation: pickerFadeIn .2s cubic-bezier(.4,0,.2,1);
|
||||
border-radius: 4px;
|
||||
border: 0;
|
||||
-webkit-transform: scale(1);
|
||||
transform: scale(1);
|
||||
-webkit-transform-origin: center top 0;
|
||||
transform-origin: center top 0;
|
||||
-webkit-box-shadow: 0 24px 38px 3px rgba(0,0,0,.14),0 9px 46px 8px rgba(0,0,0,.12),0 11px 15px 0 rgba(0,0,0,.2);
|
||||
box-shadow: 0 24px 38px 3px rgba(0,0,0,.14),0 9px 46px 8px rgba(0,0,0,.12),0 11px 15px 0 rgba(0,0,0,.2);
|
||||
}
|
||||
|
||||
.clockpicker-popover.top {
|
||||
-webkit-transform-origin: center bottom 0;
|
||||
transform-origin: center bottom 0;
|
||||
}
|
||||
|
||||
.clockpicker-popover * {
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.clockpicker-popover .popover-header {
|
||||
-webkit-box-align: center;
|
||||
-ms-flex-align: center;
|
||||
align-items: center;
|
||||
-webkit-box-pack: center;
|
||||
-ms-flex-pack: center;
|
||||
justify-content: center;
|
||||
background-color: var(--primary,#007bff);
|
||||
color: #fff;
|
||||
display: -webkit-box;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
font-size: 3rem;
|
||||
font-weight: 400;
|
||||
letter-spacing: normal;
|
||||
text-align: center;
|
||||
padding: .5rem;
|
||||
border-top-left-radius: 4px;
|
||||
border-top-right-radius: 4px;
|
||||
}
|
||||
|
||||
.clockpicker-popover .popover-header span {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.clockpicker-popover .popover-body {
|
||||
background-color: #fff;
|
||||
padding: 1rem .75rem .75rem;
|
||||
border-bottom-right-radius: 4px;
|
||||
border-bottom-left-radius: 4px;
|
||||
}
|
||||
|
||||
.clockpicker-popover .btn {
|
||||
border: 0!important;
|
||||
border-radius: 4px;
|
||||
-webkit-box-shadow: none;
|
||||
box-shadow: none;
|
||||
font-size: .8125rem;
|
||||
font-weight: 500;
|
||||
padding: .59375rem 1rem;
|
||||
min-width: 0;
|
||||
margin: 0 0 0 .25rem;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.clockpicker-popover .btn:active, .clockpicker-popover .btn:focus, .clockpicker-popover .btn:hover {
|
||||
outline: 0!important;
|
||||
background-image: -webkit-gradient(linear,left top,left bottom,from(rgba(0,0,0,.12)),to(rgba(0,0,0,.12)));
|
||||
background-image: linear-gradient(180deg,rgba(0,0,0,.12),rgba(0,0,0,.12));
|
||||
-webkit-box-shadow: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.clockpicker-span-hours {
|
||||
margin-right: .25rem;
|
||||
}
|
||||
|
||||
.clockpicker-span-minutes {
|
||||
margin-left: .25rem;
|
||||
}
|
||||
|
||||
.clockpicker-close-block {
|
||||
margin-top: .75rem;
|
||||
}
|
||||
|
||||
.clockpicker-plate {
|
||||
background-color: #ededee;
|
||||
border-radius: 50%;
|
||||
width: 200px;
|
||||
height: 200px;
|
||||
overflow: visible;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.clockpicker-canvas, .clockpicker-dial {
|
||||
width: 200px;
|
||||
height: 200px;
|
||||
position: absolute;
|
||||
left: -1px;
|
||||
top: -1px;
|
||||
}
|
||||
|
||||
.clockpicker-minutes {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.clockpicker-tick {
|
||||
border-radius: 50%;
|
||||
line-height: 26px;
|
||||
text-align: center;
|
||||
width: 26px;
|
||||
height: 26px;
|
||||
position: absolute;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.clockpicker-tick.active, .clockpicker-tick:not(.disabled):hover {
|
||||
background-color: rgba(var(--primary-color,0,123,255),.25);
|
||||
}
|
||||
|
||||
.clockpicker-tick.disabled {
|
||||
color: #eee;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.clockpicker-dial {
|
||||
-webkit-transition: opacity 350ms,-webkit-transform 350ms;
|
||||
transition: opacity 350ms,-webkit-transform 350ms;
|
||||
transition: transform 350ms,opacity 350ms;
|
||||
transition: transform 350ms,opacity 350ms,-webkit-transform 350ms;
|
||||
}
|
||||
|
||||
.clockpicker-dial-out {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.clockpicker-hours.clockpicker-dial-out {
|
||||
-webkit-transform: scale(1.2,1.2);
|
||||
transform: scale(1.2,1.2);
|
||||
}
|
||||
|
||||
.clockpicker-minutes.clockpicker-dial-out {
|
||||
-webkit-transform: scale(.8,.8);
|
||||
transform: scale(.8,.8);
|
||||
}
|
||||
|
||||
.clockpicker-canvas {
|
||||
-webkit-transition: opacity 175ms;
|
||||
transition: opacity 175ms;
|
||||
}
|
||||
|
||||
.clockpicker-canvas-out {
|
||||
opacity: .25;
|
||||
}
|
||||
|
||||
.clockpicker-canvas line {
|
||||
stroke: var(--primary,#007bff);
|
||||
stroke-width: 2;
|
||||
stroke-linecap: round;
|
||||
}
|
||||
|
||||
.clockpicker-canvas-bearing {
|
||||
stroke: none;
|
||||
fill: var(--primary,#007bff);
|
||||
}
|
||||
|
||||
.clockpicker-canvas-fg {
|
||||
stroke: none;
|
||||
fill: rgba(var(--primary-color,0,123,255),.5);
|
||||
}
|
||||
|
||||
.clockpicker-canvas-bg {
|
||||
stroke: none;
|
||||
fill: rgba(var(--primary-color,0,123,255),.25);
|
||||
}
|
||||
|
||||
.clockpicker-canvas-bg-trans {
|
||||
fill: rgba(var(--primary-color,0,123,255),.25);
|
||||
}
|
||||
|
||||
.clockpicker-buttons-am-pm {
|
||||
color: #fff;
|
||||
display: none;
|
||||
-ms-flex-wrap: nowrap;
|
||||
flex-wrap: nowrap;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-box-direction: normal;
|
||||
-ms-flex-direction: column;
|
||||
flex-direction: column;
|
||||
-ms-flex-pack: distribute;
|
||||
justify-content: space-around;
|
||||
-webkit-box-align: center;
|
||||
-ms-flex-align: center;
|
||||
align-items: center;
|
||||
font-size: 1rem;
|
||||
margin-left: .75rem;
|
||||
}
|
||||
|
||||
@keyframes pickerFadeIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
-webkit-transform: scale(.8);
|
||||
transform: scale(.8);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
-webkit-transform: scale(1);
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,10 +58,6 @@
|
||||
margin-right: .5rem!important;
|
||||
}
|
||||
|
||||
.mb-2 {
|
||||
margin-bottom: .5rem!important;
|
||||
}
|
||||
|
||||
.mb-3 {
|
||||
margin-bottom: 1rem!important;
|
||||
}
|
||||
|
||||
806
html/webpage/assets/css/flatpickr.min.css
vendored
Normal file
806
html/webpage/assets/css/flatpickr.min.css
vendored
Normal file
@@ -0,0 +1,806 @@
|
||||
.flatpickr-calendar {
|
||||
background: transparent;
|
||||
opacity: 0;
|
||||
display: none;
|
||||
text-align: center;
|
||||
visibility: hidden;
|
||||
padding: 0;
|
||||
-webkit-animation: none;
|
||||
animation: none;
|
||||
direction: ltr;
|
||||
border: 0;
|
||||
font-size: 14px;
|
||||
line-height: 24px;
|
||||
border-radius: 5px;
|
||||
position: absolute;
|
||||
width: 307.875px;
|
||||
-webkit-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
-ms-touch-action: manipulation;
|
||||
touch-action: manipulation;
|
||||
background: #fff;
|
||||
-webkit-box-shadow: 1px 0 0 #e6e6e6,-1px 0 0 #e6e6e6,0 1px 0 #e6e6e6,0 -1px 0 #e6e6e6,0 3px 13px rgba(0,0,0,0.08);
|
||||
box-shadow: 1px 0 0 #e6e6e6,-1px 0 0 #e6e6e6,0 1px 0 #e6e6e6,0 -1px 0 #e6e6e6,0 3px 13px rgba(0,0,0,0.08);
|
||||
}
|
||||
|
||||
.flatpickr-calendar.open, .flatpickr-calendar.inline {
|
||||
opacity: 1;
|
||||
max-height: 640px;
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.flatpickr-calendar.open {
|
||||
display: inline-block;
|
||||
z-index: 99999;
|
||||
}
|
||||
|
||||
.flatpickr-calendar.animate.open {
|
||||
-webkit-animation: fpFadeInDown 300ms cubic-bezier(.23,1,.32,1);
|
||||
animation: fpFadeInDown 300ms cubic-bezier(.23,1,.32,1);
|
||||
}
|
||||
|
||||
.flatpickr-calendar.inline {
|
||||
display: block;
|
||||
position: relative;
|
||||
top: 2px;
|
||||
}
|
||||
|
||||
.flatpickr-calendar.static {
|
||||
position: absolute;
|
||||
top: calc(100% + 2px);
|
||||
}
|
||||
|
||||
.flatpickr-calendar.static.open {
|
||||
z-index: 999;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.flatpickr-calendar.multiMonth .flatpickr-days .dayContainer:nth-child(n + 1) .flatpickr-day.inRange:nth-child(7n + 7) {
|
||||
-webkit-box-shadow: none !important;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
|
||||
.flatpickr-calendar.multiMonth .flatpickr-days .dayContainer:nth-child(n + 2) .flatpickr-day.inRange:nth-child(7n + 1) {
|
||||
-webkit-box-shadow: -2px 0 0 #e6e6e6,5px 0 0 #e6e6e6;
|
||||
box-shadow: -2px 0 0 #e6e6e6,5px 0 0 #e6e6e6;
|
||||
}
|
||||
|
||||
.flatpickr-calendar .hasWeeks .dayContainer, .flatpickr-calendar .hasTime .dayContainer {
|
||||
border-bottom: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
border-bottom-left-radius: 0;
|
||||
}
|
||||
|
||||
.flatpickr-calendar .hasWeeks .dayContainer {
|
||||
border-left: 0;
|
||||
}
|
||||
|
||||
.flatpickr-calendar.hasTime .flatpickr-time {
|
||||
height: 40px;
|
||||
border-top: 1px solid #e6e6e6;
|
||||
}
|
||||
|
||||
.flatpickr-calendar.noCalendar.hasTime .flatpickr-time {
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.flatpickr-calendar:before, .flatpickr-calendar:after {
|
||||
position: absolute;
|
||||
display: block;
|
||||
pointer-events: none;
|
||||
border: solid transparent;
|
||||
content: '';
|
||||
height: 0;
|
||||
width: 0;
|
||||
left: 22px;
|
||||
}
|
||||
|
||||
.flatpickr-calendar.rightMost:before, .flatpickr-calendar.arrowRight:before, .flatpickr-calendar.rightMost:after, .flatpickr-calendar.arrowRight:after {
|
||||
left: auto;
|
||||
right: 22px;
|
||||
}
|
||||
|
||||
.flatpickr-calendar.arrowCenter:before, .flatpickr-calendar.arrowCenter:after {
|
||||
left: 50%;
|
||||
right: 50%;
|
||||
}
|
||||
|
||||
.flatpickr-calendar:before {
|
||||
border-width: 5px;
|
||||
margin: 0 -5px;
|
||||
}
|
||||
|
||||
.flatpickr-calendar:after {
|
||||
border-width: 4px;
|
||||
margin: 0 -4px;
|
||||
}
|
||||
|
||||
.flatpickr-calendar.arrowTop:before, .flatpickr-calendar.arrowTop:after {
|
||||
bottom: 100%;
|
||||
}
|
||||
|
||||
.flatpickr-calendar.arrowTop:before {
|
||||
border-bottom-color: #e6e6e6;
|
||||
}
|
||||
|
||||
.flatpickr-calendar.arrowTop:after {
|
||||
border-bottom-color: #fff;
|
||||
}
|
||||
|
||||
.flatpickr-calendar.arrowBottom:before, .flatpickr-calendar.arrowBottom:after {
|
||||
top: 100%;
|
||||
}
|
||||
|
||||
.flatpickr-calendar.arrowBottom:before {
|
||||
border-top-color: #e6e6e6;
|
||||
}
|
||||
|
||||
.flatpickr-calendar.arrowBottom:after {
|
||||
border-top-color: #fff;
|
||||
}
|
||||
|
||||
.flatpickr-calendar:focus {
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
.flatpickr-wrapper {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.flatpickr-months {
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.flatpickr-months .flatpickr-month {
|
||||
background: transparent;
|
||||
color: rgba(0,0,0,0.9);
|
||||
fill: rgba(0,0,0,0.9);
|
||||
height: 34px;
|
||||
line-height: 1;
|
||||
text-align: center;
|
||||
position: relative;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
overflow: hidden;
|
||||
-webkit-box-flex: 1;
|
||||
-webkit-flex: 1;
|
||||
-ms-flex: 1;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.flatpickr-months .flatpickr-prev-month, .flatpickr-months .flatpickr-next-month {
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
height: 34px;
|
||||
padding: 10px;
|
||||
z-index: 3;
|
||||
color: rgba(0,0,0,0.9);
|
||||
fill: rgba(0,0,0,0.9);
|
||||
}
|
||||
|
||||
.flatpickr-months .flatpickr-prev-month.flatpickr-disabled, .flatpickr-months .flatpickr-next-month.flatpickr-disabled {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.flatpickr-months .flatpickr-prev-month i, .flatpickr-months .flatpickr-next-month i {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.flatpickr-months .flatpickr-prev-month.flatpickr-prev-month, .flatpickr-months .flatpickr-next-month.flatpickr-prev-month {
|
||||
left: 0;
|
||||
}
|
||||
|
||||
/* /*rtl:begin:ignore */
|
||||
|
||||
/* /*rtl:end:ignore */
|
||||
|
||||
.flatpickr-months .flatpickr-prev-month.flatpickr-next-month, .flatpickr-months .flatpickr-next-month.flatpickr-next-month {
|
||||
right: 0;
|
||||
}
|
||||
|
||||
/* /*rtl:begin:ignore */
|
||||
|
||||
/* /*rtl:end:ignore */
|
||||
|
||||
.flatpickr-months .flatpickr-prev-month:hover, .flatpickr-months .flatpickr-next-month:hover {
|
||||
color: #959ea9;
|
||||
}
|
||||
|
||||
.flatpickr-months .flatpickr-prev-month:hover svg, .flatpickr-months .flatpickr-next-month:hover svg {
|
||||
fill: #f64747;
|
||||
}
|
||||
|
||||
.flatpickr-months .flatpickr-prev-month svg, .flatpickr-months .flatpickr-next-month svg {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
}
|
||||
|
||||
.flatpickr-months .flatpickr-prev-month svg path, .flatpickr-months .flatpickr-next-month svg path {
|
||||
-webkit-transition: fill .1s;
|
||||
transition: fill .1s;
|
||||
fill: inherit;
|
||||
}
|
||||
|
||||
.numInputWrapper {
|
||||
position: relative;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.numInputWrapper input, .numInputWrapper span {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.numInputWrapper input {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.numInputWrapper input::-ms-clear {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.numInputWrapper input::-webkit-outer-spin-button, .numInputWrapper input::-webkit-inner-spin-button {
|
||||
margin: 0;
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
|
||||
.numInputWrapper span {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
width: 14px;
|
||||
padding: 0 4px 0 2px;
|
||||
height: 50%;
|
||||
line-height: 50%;
|
||||
opacity: 0;
|
||||
cursor: pointer;
|
||||
border: 1px solid rgba(57,57,57,0.15);
|
||||
-webkit-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.numInputWrapper span:hover {
|
||||
background: rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.numInputWrapper span:active {
|
||||
background: rgba(0,0,0,0.2);
|
||||
}
|
||||
|
||||
.numInputWrapper span:after {
|
||||
display: block;
|
||||
content: "";
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.numInputWrapper span.arrowUp {
|
||||
top: 0;
|
||||
border-bottom: 0;
|
||||
}
|
||||
|
||||
.numInputWrapper span.arrowUp:after {
|
||||
border-left: 4px solid transparent;
|
||||
border-right: 4px solid transparent;
|
||||
border-bottom: 4px solid rgba(57,57,57,0.6);
|
||||
top: 26%;
|
||||
}
|
||||
|
||||
.numInputWrapper span.arrowDown {
|
||||
top: 50%;
|
||||
}
|
||||
|
||||
.numInputWrapper span.arrowDown:after {
|
||||
border-left: 4px solid transparent;
|
||||
border-right: 4px solid transparent;
|
||||
border-top: 4px solid rgba(57,57,57,0.6);
|
||||
top: 40%;
|
||||
}
|
||||
|
||||
.numInputWrapper span svg {
|
||||
width: inherit;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.numInputWrapper span svg path {
|
||||
fill: rgba(0,0,0,0.5);
|
||||
}
|
||||
|
||||
.numInputWrapper:hover {
|
||||
background: rgba(0,0,0,0.05);
|
||||
}
|
||||
|
||||
.numInputWrapper:hover span {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.flatpickr-current-month {
|
||||
font-size: 135%;
|
||||
line-height: inherit;
|
||||
font-weight: 300;
|
||||
color: inherit;
|
||||
position: absolute;
|
||||
width: 75%;
|
||||
left: 12.5%;
|
||||
padding: 7.48px 0 0 0;
|
||||
line-height: 1;
|
||||
height: 34px;
|
||||
display: inline-block;
|
||||
text-align: center;
|
||||
-webkit-transform: translate3d(0,0,0);
|
||||
transform: translate3d(0,0,0);
|
||||
}
|
||||
|
||||
.flatpickr-current-month span.cur-month {
|
||||
font-family: inherit;
|
||||
font-weight: 700;
|
||||
color: inherit;
|
||||
display: inline-block;
|
||||
margin-left: .5ch;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.flatpickr-current-month span.cur-month:hover {
|
||||
background: rgba(0,0,0,0.05);
|
||||
}
|
||||
|
||||
.flatpickr-current-month .numInputWrapper {
|
||||
width: 6ch;
|
||||
width: 7ch\0;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.flatpickr-current-month .numInputWrapper span.arrowUp:after {
|
||||
border-bottom-color: rgba(0,0,0,0.9);
|
||||
}
|
||||
|
||||
.flatpickr-current-month .numInputWrapper span.arrowDown:after {
|
||||
border-top-color: rgba(0,0,0,0.9);
|
||||
}
|
||||
|
||||
.flatpickr-current-month input.cur-year {
|
||||
background: transparent;
|
||||
-webkit-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
color: inherit;
|
||||
cursor: text;
|
||||
padding: 0 0 0 .5ch;
|
||||
margin: 0;
|
||||
display: inline-block;
|
||||
font-size: inherit;
|
||||
font-family: inherit;
|
||||
font-weight: 300;
|
||||
line-height: inherit;
|
||||
height: auto;
|
||||
border: 0;
|
||||
border-radius: 0;
|
||||
vertical-align: initial;
|
||||
-webkit-appearance: textfield;
|
||||
-moz-appearance: textfield;
|
||||
appearance: textfield;
|
||||
}
|
||||
|
||||
.flatpickr-current-month input.cur-year:focus {
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
.flatpickr-current-month input.cur-year[disabled], .flatpickr-current-month input.cur-year[disabled]:hover {
|
||||
font-size: 100%;
|
||||
color: rgba(0,0,0,0.5);
|
||||
background: transparent;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.flatpickr-current-month .flatpickr-monthDropdown-months {
|
||||
appearance: menulist;
|
||||
background: transparent;
|
||||
border: none;
|
||||
border-radius: 0;
|
||||
box-sizing: border-box;
|
||||
color: inherit;
|
||||
cursor: pointer;
|
||||
font-size: inherit;
|
||||
font-family: inherit;
|
||||
font-weight: 300;
|
||||
height: auto;
|
||||
line-height: inherit;
|
||||
margin: -1px 0 0 0;
|
||||
outline: none;
|
||||
padding: 0 0 0 .5ch;
|
||||
position: relative;
|
||||
vertical-align: initial;
|
||||
-webkit-box-sizing: border-box;
|
||||
-webkit-appearance: menulist;
|
||||
-moz-appearance: menulist;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.flatpickr-current-month .flatpickr-monthDropdown-months:focus, .flatpickr-current-month .flatpickr-monthDropdown-months:active {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.flatpickr-current-month .flatpickr-monthDropdown-months:hover {
|
||||
background: rgba(0,0,0,0.05);
|
||||
}
|
||||
|
||||
.flatpickr-current-month .flatpickr-monthDropdown-months .flatpickr-monthDropdown-month {
|
||||
background-color: transparent;
|
||||
outline: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.flatpickr-weekdays {
|
||||
background: transparent;
|
||||
text-align: center;
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
-webkit-box-align: center;
|
||||
-webkit-align-items: center;
|
||||
-ms-flex-align: center;
|
||||
align-items: center;
|
||||
height: 28px;
|
||||
}
|
||||
|
||||
.flatpickr-weekdays .flatpickr-weekdaycontainer {
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
-webkit-box-flex: 1;
|
||||
-webkit-flex: 1;
|
||||
-ms-flex: 1;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
span.flatpickr-weekday {
|
||||
cursor: default;
|
||||
font-size: 90%;
|
||||
background: transparent;
|
||||
color: rgba(0,0,0,0.54);
|
||||
line-height: 1;
|
||||
margin: 0;
|
||||
text-align: center;
|
||||
display: block;
|
||||
-webkit-box-flex: 1;
|
||||
-webkit-flex: 1;
|
||||
-ms-flex: 1;
|
||||
flex: 1;
|
||||
font-weight: bolder;
|
||||
}
|
||||
|
||||
.dayContainer, .flatpickr-weeks {
|
||||
padding: 1px 0 0 0;
|
||||
}
|
||||
|
||||
.flatpickr-days {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
-webkit-box-align: start;
|
||||
-webkit-align-items: flex-start;
|
||||
-ms-flex-align: start;
|
||||
align-items: flex-start;
|
||||
width: 307.875px;
|
||||
}
|
||||
|
||||
.flatpickr-days:focus {
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
.dayContainer {
|
||||
padding: 0;
|
||||
outline: 0;
|
||||
text-align: left;
|
||||
width: 307.875px;
|
||||
min-width: 307.875px;
|
||||
max-width: 307.875px;
|
||||
-webkit-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
display: inline-block;
|
||||
display: -ms-flexbox;
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
display: flex;
|
||||
-webkit-flex-wrap: wrap;
|
||||
flex-wrap: wrap;
|
||||
-ms-flex-wrap: wrap;
|
||||
-ms-flex-pack: justify;
|
||||
-webkit-justify-content: space-around;
|
||||
justify-content: space-around;
|
||||
-webkit-transform: translate3d(0,0,0);
|
||||
transform: translate3d(0,0,0);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.dayContainer + .dayContainer {
|
||||
-webkit-box-shadow: -1px 0 0 #e6e6e6;
|
||||
box-shadow: -1px 0 0 #e6e6e6;
|
||||
}
|
||||
|
||||
.flatpickr-day {
|
||||
background: none;
|
||||
border: 1px solid transparent;
|
||||
border-radius: 150px;
|
||||
-webkit-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
color: #393939;
|
||||
cursor: pointer;
|
||||
font-weight: 400;
|
||||
width: 14.2857143%;
|
||||
-webkit-flex-basis: 14.2857143%;
|
||||
-ms-flex-preferred-size: 14.2857143%;
|
||||
flex-basis: 14.2857143%;
|
||||
max-width: 39px;
|
||||
height: 39px;
|
||||
line-height: 39px;
|
||||
margin: 0;
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
-webkit-box-pack: center;
|
||||
-webkit-justify-content: center;
|
||||
-ms-flex-pack: center;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.flatpickr-day.inRange, .flatpickr-day.prevMonthDay.inRange, .flatpickr-day.nextMonthDay.inRange, .flatpickr-day.today.inRange, .flatpickr-day.prevMonthDay.today.inRange, .flatpickr-day.nextMonthDay.today.inRange, .flatpickr-day:hover, .flatpickr-day.prevMonthDay:hover, .flatpickr-day.nextMonthDay:hover, .flatpickr-day:focus, .flatpickr-day.prevMonthDay:focus, .flatpickr-day.nextMonthDay:focus {
|
||||
cursor: pointer;
|
||||
outline: 0;
|
||||
background: #e6e6e6;
|
||||
border-color: #e6e6e6;
|
||||
}
|
||||
|
||||
.flatpickr-day.today {
|
||||
border-color: #959ea9;
|
||||
}
|
||||
|
||||
.flatpickr-day.today:hover, .flatpickr-day.today:focus {
|
||||
border-color: #959ea9;
|
||||
background: #959ea9;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.flatpickr-day.selected, .flatpickr-day.startRange, .flatpickr-day.endRange, .flatpickr-day.selected.inRange, .flatpickr-day.startRange.inRange, .flatpickr-day.endRange.inRange, .flatpickr-day.selected:focus, .flatpickr-day.startRange:focus, .flatpickr-day.endRange:focus, .flatpickr-day.selected:hover, .flatpickr-day.startRange:hover, .flatpickr-day.endRange:hover, .flatpickr-day.selected.prevMonthDay, .flatpickr-day.startRange.prevMonthDay, .flatpickr-day.endRange.prevMonthDay, .flatpickr-day.selected.nextMonthDay, .flatpickr-day.startRange.nextMonthDay, .flatpickr-day.endRange.nextMonthDay {
|
||||
background: #569ff7;
|
||||
-webkit-box-shadow: none;
|
||||
box-shadow: none;
|
||||
color: #fff;
|
||||
border-color: #569ff7;
|
||||
}
|
||||
|
||||
.flatpickr-day.selected.startRange, .flatpickr-day.startRange.startRange, .flatpickr-day.endRange.startRange {
|
||||
border-radius: 50px 0 0 50px;
|
||||
}
|
||||
|
||||
.flatpickr-day.selected.endRange, .flatpickr-day.startRange.endRange, .flatpickr-day.endRange.endRange {
|
||||
border-radius: 0 50px 50px 0;
|
||||
}
|
||||
|
||||
.flatpickr-day.selected.startRange + .endRange:not(:nth-child(7n + 1)), .flatpickr-day.startRange.startRange + .endRange:not(:nth-child(7n + 1)), .flatpickr-day.endRange.startRange + .endRange:not(:nth-child(7n + 1)) {
|
||||
-webkit-box-shadow: -10px 0 0 #569ff7;
|
||||
box-shadow: -10px 0 0 #569ff7;
|
||||
}
|
||||
|
||||
.flatpickr-day.selected.startRange.endRange, .flatpickr-day.startRange.startRange.endRange, .flatpickr-day.endRange.startRange.endRange {
|
||||
border-radius: 50px;
|
||||
}
|
||||
|
||||
.flatpickr-day.inRange {
|
||||
border-radius: 0;
|
||||
-webkit-box-shadow: -5px 0 0 #e6e6e6,5px 0 0 #e6e6e6;
|
||||
box-shadow: -5px 0 0 #e6e6e6,5px 0 0 #e6e6e6;
|
||||
}
|
||||
|
||||
.flatpickr-day.flatpickr-disabled, .flatpickr-day.flatpickr-disabled:hover, .flatpickr-day.prevMonthDay, .flatpickr-day.nextMonthDay, .flatpickr-day.notAllowed, .flatpickr-day.notAllowed.prevMonthDay, .flatpickr-day.notAllowed.nextMonthDay {
|
||||
color: rgba(57,57,57,0.3);
|
||||
background: transparent;
|
||||
border-color: transparent;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.flatpickr-day.flatpickr-disabled, .flatpickr-day.flatpickr-disabled:hover {
|
||||
cursor: not-allowed;
|
||||
color: rgba(57,57,57,0.1);
|
||||
}
|
||||
|
||||
.flatpickr-day.week.selected {
|
||||
border-radius: 0;
|
||||
-webkit-box-shadow: -5px 0 0 #569ff7,5px 0 0 #569ff7;
|
||||
box-shadow: -5px 0 0 #569ff7,5px 0 0 #569ff7;
|
||||
}
|
||||
|
||||
.flatpickr-day.hidden {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.rangeMode .flatpickr-day {
|
||||
margin-top: 1px;
|
||||
}
|
||||
|
||||
.flatpickr-weekwrapper {
|
||||
float: left;
|
||||
}
|
||||
|
||||
.flatpickr-weekwrapper .flatpickr-weeks {
|
||||
padding: 0 12px;
|
||||
-webkit-box-shadow: 1px 0 0 #e6e6e6;
|
||||
box-shadow: 1px 0 0 #e6e6e6;
|
||||
}
|
||||
|
||||
.flatpickr-weekwrapper .flatpickr-weekday {
|
||||
float: none;
|
||||
width: 100%;
|
||||
line-height: 28px;
|
||||
}
|
||||
|
||||
.flatpickr-weekwrapper span.flatpickr-day, .flatpickr-weekwrapper span.flatpickr-day:hover {
|
||||
display: block;
|
||||
width: 100%;
|
||||
max-width: none;
|
||||
color: rgba(57,57,57,0.3);
|
||||
background: transparent;
|
||||
cursor: default;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.flatpickr-innerContainer {
|
||||
display: block;
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
-webkit-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.flatpickr-rContainer {
|
||||
display: inline-block;
|
||||
padding: 0;
|
||||
-webkit-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.flatpickr-time {
|
||||
text-align: center;
|
||||
outline: 0;
|
||||
display: block;
|
||||
height: 0;
|
||||
line-height: 40px;
|
||||
max-height: 40px;
|
||||
-webkit-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
overflow: hidden;
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.flatpickr-time:after {
|
||||
content: "";
|
||||
display: table;
|
||||
clear: both;
|
||||
}
|
||||
|
||||
.flatpickr-time .numInputWrapper {
|
||||
-webkit-box-flex: 1;
|
||||
-webkit-flex: 1;
|
||||
-ms-flex: 1;
|
||||
flex: 1;
|
||||
width: 40%;
|
||||
height: 40px;
|
||||
float: left;
|
||||
}
|
||||
|
||||
.flatpickr-time .numInputWrapper span.arrowUp:after {
|
||||
border-bottom-color: #393939;
|
||||
}
|
||||
|
||||
.flatpickr-time .numInputWrapper span.arrowDown:after {
|
||||
border-top-color: #393939;
|
||||
}
|
||||
|
||||
.flatpickr-time.hasSeconds .numInputWrapper {
|
||||
width: 26%;
|
||||
}
|
||||
|
||||
.flatpickr-time.time24hr .numInputWrapper {
|
||||
width: 49%;
|
||||
}
|
||||
|
||||
.flatpickr-time input {
|
||||
background: transparent;
|
||||
-webkit-box-shadow: none;
|
||||
box-shadow: none;
|
||||
border: 0;
|
||||
border-radius: 0;
|
||||
text-align: center;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
height: inherit;
|
||||
line-height: inherit;
|
||||
color: #393939;
|
||||
font-size: 14px;
|
||||
position: relative;
|
||||
-webkit-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
-webkit-appearance: textfield;
|
||||
-moz-appearance: textfield;
|
||||
appearance: textfield;
|
||||
}
|
||||
|
||||
.flatpickr-time input.flatpickr-hour {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.flatpickr-time input.flatpickr-minute, .flatpickr-time input.flatpickr-second {
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.flatpickr-time input:focus {
|
||||
outline: 0;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
.flatpickr-time .flatpickr-time-separator, .flatpickr-time .flatpickr-am-pm {
|
||||
height: inherit;
|
||||
float: left;
|
||||
line-height: inherit;
|
||||
color: #393939;
|
||||
font-weight: bold;
|
||||
width: 2%;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
-webkit-align-self: center;
|
||||
-ms-flex-item-align: center;
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
.flatpickr-time .flatpickr-am-pm {
|
||||
outline: 0;
|
||||
width: 18%;
|
||||
cursor: pointer;
|
||||
text-align: center;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.flatpickr-time input:hover, .flatpickr-time .flatpickr-am-pm:hover, .flatpickr-time input:focus, .flatpickr-time .flatpickr-am-pm:focus {
|
||||
background: #eee;
|
||||
}
|
||||
|
||||
.flatpickr-input[readonly] {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
@keyframes fpFadeInDown {
|
||||
from {
|
||||
opacity: 0;
|
||||
-webkit-transform: translate3d(0,-20px,0);
|
||||
transform: translate3d(0,-20px,0);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
-webkit-transform: translate3d(0,0,0);
|
||||
transform: translate3d(0,0,0);
|
||||
}
|
||||
}
|
||||
|
||||
406
html/webpage/assets/css/jquery-clockpicker.css
Normal file
406
html/webpage/assets/css/jquery-clockpicker.css
Normal file
@@ -0,0 +1,406 @@
|
||||
/* !
|
||||
* ClockPicker v0.0.7 for jQuery (http://weareoutman.github.io/clockpicker/)
|
||||
* Copyright 2014 Wang Shenwei.
|
||||
* Licensed under MIT (https://github.com/weareoutman/clockpicker/blob/gh-pages/LICENSE)
|
||||
*
|
||||
* Bootstrap v3.1.1 (http://getbootstrap.com)
|
||||
* Copyright 2011-2014 Twitter, Inc.
|
||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) */
|
||||
|
||||
/* Picked from bootstrap: .popover, .btn, .text-primary */
|
||||
|
||||
.popover {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 1010;
|
||||
display: none;
|
||||
max-width: 276px;
|
||||
padding: 1px;
|
||||
text-align: left;
|
||||
white-space: normal;
|
||||
background-color: #fff;
|
||||
background-clip: padding-box;
|
||||
border: 1px solid #ccc;
|
||||
border: 1px solid rgba(0, 0, 0, .2);
|
||||
border-radius: 6px;
|
||||
-webkit-box-shadow: 0 5px 10px rgba(0, 0, 0, .2);
|
||||
box-shadow: 0 5px 10px rgba(0, 0, 0, .2);
|
||||
}
|
||||
|
||||
.popover.top {
|
||||
margin-top: -10px;
|
||||
}
|
||||
|
||||
.popover.right {
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.popover.bottom {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.popover.left {
|
||||
margin-left: -10px;
|
||||
}
|
||||
|
||||
.popover-title {
|
||||
padding: 8px 14px;
|
||||
margin: 0;
|
||||
font-size: 14px;
|
||||
font-weight: normal;
|
||||
line-height: 18px;
|
||||
background-color: #f7f7f7;
|
||||
border-bottom: 1px solid #ebebeb;
|
||||
border-radius: 5px 5px 0 0;
|
||||
}
|
||||
|
||||
.popover-content {
|
||||
padding: 9px 14px;
|
||||
}
|
||||
|
||||
.popover > .arrow, .popover > .arrow:after {
|
||||
position: absolute;
|
||||
display: block;
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-color: transparent;
|
||||
border-style: solid;
|
||||
overflow: visible;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
z-index: auto;
|
||||
background-color: transparent;
|
||||
-webkit-box-shadow: none;
|
||||
box-shadow: none;
|
||||
bottom: auto;
|
||||
left: auto;
|
||||
right: auto;
|
||||
top: auto;
|
||||
-webkit-transform: none;
|
||||
-ms-transform: none;
|
||||
transform: none;
|
||||
}
|
||||
|
||||
.popover > .arrow {
|
||||
border-width: 11px;
|
||||
}
|
||||
|
||||
.popover > .arrow:after {
|
||||
content: "";
|
||||
border-width: 10px;
|
||||
}
|
||||
|
||||
.popover.top > .arrow {
|
||||
bottom: -11px;
|
||||
left: 50%;
|
||||
margin-left: -11px;
|
||||
border-top-color: #999;
|
||||
border-top-color: rgba(0, 0, 0, .25);
|
||||
border-bottom-width: 0;
|
||||
}
|
||||
|
||||
.popover.top > .arrow:after {
|
||||
bottom: 1px;
|
||||
margin-left: -10px;
|
||||
content: " ";
|
||||
border-top-color: #fff;
|
||||
border-bottom-width: 0;
|
||||
}
|
||||
|
||||
.popover.right > .arrow {
|
||||
top: 50%;
|
||||
left: -11px;
|
||||
margin-top: -11px;
|
||||
border-right-color: #999;
|
||||
border-right-color: rgba(0, 0, 0, .25);
|
||||
border-left-width: 0;
|
||||
}
|
||||
|
||||
.popover.right > .arrow:after {
|
||||
bottom: -10px;
|
||||
left: 1px;
|
||||
content: " ";
|
||||
border-right-color: #fff;
|
||||
border-left-width: 0;
|
||||
}
|
||||
|
||||
.popover.bottom > .arrow {
|
||||
top: -11px;
|
||||
left: 50%;
|
||||
margin-left: -11px;
|
||||
border-top-width: 0;
|
||||
border-bottom-color: #999;
|
||||
border-bottom-color: rgba(0, 0, 0, .25);
|
||||
}
|
||||
|
||||
.popover.bottom > .arrow:after {
|
||||
top: 1px;
|
||||
margin-left: -10px;
|
||||
content: " ";
|
||||
border-top-width: 0;
|
||||
border-bottom-color: #fff;
|
||||
}
|
||||
|
||||
.popover.left > .arrow {
|
||||
top: 50%;
|
||||
right: -11px;
|
||||
margin-top: -11px;
|
||||
border-right-width: 0;
|
||||
border-left-color: #999;
|
||||
border-left-color: rgba(0, 0, 0, .25);
|
||||
}
|
||||
|
||||
.popover.left > .arrow:after {
|
||||
right: 1px;
|
||||
bottom: -10px;
|
||||
content: " ";
|
||||
border-right-width: 0;
|
||||
border-left-color: #fff;
|
||||
}
|
||||
|
||||
.btn {
|
||||
cursor: pointer;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
background-image: none;
|
||||
border: 1px solid transparent;
|
||||
}
|
||||
|
||||
.btn:focus, .btn:active:focus, .btn.active:focus {
|
||||
outline: thin dotted;
|
||||
outline: 5px auto -webkit-focus-ring-color;
|
||||
outline-offset: -2px;
|
||||
}
|
||||
|
||||
.btn:hover, .btn:focus {
|
||||
color: #333;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.btn:active, .btn.active {
|
||||
background-image: none;
|
||||
outline: 0;
|
||||
-webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125);
|
||||
box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125);
|
||||
}
|
||||
|
||||
.btn-default {
|
||||
color: #333;
|
||||
background-color: #fff;
|
||||
border-color: #ccc;
|
||||
}
|
||||
|
||||
.btn-default:hover, .btn-default:focus, .btn-default:active, .btn-default.active, .open .dropdown-toggle.btn-default {
|
||||
color: #333;
|
||||
background-color: #ebebeb;
|
||||
border-color: #adadad;
|
||||
}
|
||||
|
||||
.btn-default:active, .btn-default.active, .open .dropdown-toggle.btn-default {
|
||||
background-image: none;
|
||||
}
|
||||
|
||||
.btn-block {
|
||||
display: block;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.text-primary {
|
||||
color: #428bca;
|
||||
}
|
||||
|
||||
/* !
|
||||
* ClockPicker v{package.version} for Bootstrap (http://weareoutman.github.io/clockpicker/)
|
||||
* Copyright 2014 Wang Shenwei.
|
||||
* Licensed under MIT (https://github.com/weareoutman/clockpicker/blob/gh-pages/LICENSE) */
|
||||
|
||||
.clockpicker .input-group-addon {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.clockpicker-moving {
|
||||
cursor: move;
|
||||
}
|
||||
|
||||
.clockpicker-align-left.popover > .arrow {
|
||||
left: 25px;
|
||||
}
|
||||
|
||||
.clockpicker-align-top.popover > .arrow {
|
||||
top: 17px;
|
||||
}
|
||||
|
||||
.clockpicker-align-right.popover > .arrow {
|
||||
left: auto;
|
||||
right: 25px;
|
||||
}
|
||||
|
||||
.clockpicker-align-bottom.popover > .arrow {
|
||||
top: auto;
|
||||
bottom: 6px;
|
||||
}
|
||||
|
||||
.clockpicker-popover .popover-title {
|
||||
background-color: #fff;
|
||||
color: #999;
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
line-height: 30px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.clockpicker-popover .popover-title span {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.clockpicker-popover .popover-content {
|
||||
background-color: #f8f8f8;
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
.popover-content:last-child {
|
||||
border-bottom-left-radius: 5px;
|
||||
border-bottom-right-radius: 5px;
|
||||
}
|
||||
|
||||
.clockpicker-plate {
|
||||
background-color: #fff;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 50%;
|
||||
width: 200px;
|
||||
height: 200px;
|
||||
overflow: visible;
|
||||
position: relative;
|
||||
-webkit-touch-callout: none;
|
||||
-webkit-user-select: none;
|
||||
-khtml-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.clockpicker-canvas, .clockpicker-dial {
|
||||
width: 200px;
|
||||
height: 200px;
|
||||
position: absolute;
|
||||
left: -1px;
|
||||
top: -1px;
|
||||
}
|
||||
|
||||
.clockpicker-minutes {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.clockpicker-tick {
|
||||
border-radius: 50%;
|
||||
color: #666;
|
||||
line-height: 26px;
|
||||
text-align: center;
|
||||
width: 26px;
|
||||
height: 26px;
|
||||
position: absolute;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.clockpicker-tick.active, .clockpicker-tick:hover {
|
||||
background-color: rgb(192, 229, 247);
|
||||
background-color: rgba(0, 149, 221, .25);
|
||||
}
|
||||
|
||||
.clockpicker-button {
|
||||
background-image: none;
|
||||
background-color: #fff;
|
||||
border-width: 1px 0 0;
|
||||
border-top-left-radius: 0;
|
||||
border-top-right-radius: 0;
|
||||
margin: 0;
|
||||
padding: 10px 0;
|
||||
}
|
||||
|
||||
.clockpicker-button:hover {
|
||||
background-image: none;
|
||||
background-color: #ebebeb;
|
||||
}
|
||||
|
||||
.clockpicker-button:focus {
|
||||
outline: none!important;
|
||||
}
|
||||
|
||||
.clockpicker-dial {
|
||||
-webkit-transition: -webkit-transform 350ms, opacity 350ms;
|
||||
-moz-transition: -moz-transform 350ms, opacity 350ms;
|
||||
-ms-transition: -ms-transform 350ms, opacity 350ms;
|
||||
-o-transition: -o-transform 350ms, opacity 350ms;
|
||||
transition: transform 350ms, opacity 350ms;
|
||||
}
|
||||
|
||||
.clockpicker-dial-out {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.clockpicker-hours.clockpicker-dial-out {
|
||||
-webkit-transform: scale(1.2, 1.2);
|
||||
-moz-transform: scale(1.2, 1.2);
|
||||
-ms-transform: scale(1.2, 1.2);
|
||||
-o-transform: scale(1.2, 1.2);
|
||||
transform: scale(1.2, 1.2);
|
||||
}
|
||||
|
||||
.clockpicker-minutes.clockpicker-dial-out {
|
||||
-webkit-transform: scale(.8, .8);
|
||||
-moz-transform: scale(.8, .8);
|
||||
-ms-transform: scale(.8, .8);
|
||||
-o-transform: scale(.8, .8);
|
||||
transform: scale(.8, .8);
|
||||
}
|
||||
|
||||
.clockpicker-canvas {
|
||||
-webkit-transition: opacity 175ms;
|
||||
-moz-transition: opacity 175ms;
|
||||
-ms-transition: opacity 175ms;
|
||||
-o-transition: opacity 175ms;
|
||||
transition: opacity 175ms;
|
||||
}
|
||||
|
||||
.clockpicker-canvas-out {
|
||||
opacity: 0.25;
|
||||
}
|
||||
|
||||
.clockpicker-canvas-bearing, .clockpicker-canvas-fg {
|
||||
stroke: none;
|
||||
fill: rgb(0, 149, 221);
|
||||
}
|
||||
|
||||
.clockpicker-canvas-bg {
|
||||
stroke: none;
|
||||
fill: rgb(192, 229, 247);
|
||||
}
|
||||
|
||||
.clockpicker-canvas-bg-trans {
|
||||
fill: rgba(0, 149, 221, .25);
|
||||
}
|
||||
|
||||
.clockpicker-canvas line {
|
||||
stroke: rgb(0, 149, 221);
|
||||
stroke-width: 1;
|
||||
stroke-linecap: round;
|
||||
/*shape-rendering: crispEdges;*/
|
||||
}
|
||||
|
||||
.clockpicker-button.am-button {
|
||||
margin: 1px;
|
||||
padding: 5px;
|
||||
border: 1px solid rgba(0, 0, 0, .2);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.clockpicker-button.pm-button {
|
||||
margin: 1px 1px 1px 136px;
|
||||
padding: 5px;
|
||||
border: 1px solid rgba(0, 0, 0, .2);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
431
html/webpage/assets/css/litepicker.css
Normal file
431
html/webpage/assets/css/litepicker.css
Normal file
@@ -0,0 +1,431 @@
|
||||
/* !
|
||||
*
|
||||
* ../css/litepicker.css
|
||||
* Litepicker v2.0.12 (https://github.com/wakirin/Litepicker)
|
||||
* Package: litepicker (https://www.npmjs.com/package/litepicker)
|
||||
* License: MIT (https://github.com/wakirin/Litepicker/blob/master/LICENCE.md)
|
||||
* Copyright 2019-2021 Rinat G.
|
||||
*
|
||||
* Hash: 2f11f1f0300ea13b17b5
|
||||
* */
|
||||
|
||||
:root {
|
||||
--litepicker-container-months-color-bg: #fff;
|
||||
--litepicker-container-months-box-shadow-color: #ddd;
|
||||
--litepicker-footer-color-bg: #fafafa;
|
||||
--litepicker-footer-box-shadow-color: #ddd;
|
||||
--litepicker-tooltip-color-bg: #fff;
|
||||
--litepicker-month-header-color: #333;
|
||||
--litepicker-button-prev-month-color: #9e9e9e;
|
||||
--litepicker-button-next-month-color: #9e9e9e;
|
||||
--litepicker-button-prev-month-color-hover: #2196f3;
|
||||
--litepicker-button-next-month-color-hover: #2196f3;
|
||||
--litepicker-month-width: calc(var(--litepicker-day-width) * 7);
|
||||
--litepicker-month-weekday-color: #9e9e9e;
|
||||
--litepicker-month-week-number-color: #9e9e9e;
|
||||
--litepicker-day-width: 38px;
|
||||
--litepicker-day-color: #333;
|
||||
--litepicker-day-color-hover: #2196f3;
|
||||
--litepicker-is-today-color: #f44336;
|
||||
--litepicker-is-in-range-color: #bbdefb;
|
||||
--litepicker-is-locked-color: #9e9e9e;
|
||||
--litepicker-is-start-color: #fff;
|
||||
--litepicker-is-start-color-bg: #2196f3;
|
||||
--litepicker-is-end-color: #fff;
|
||||
--litepicker-is-end-color-bg: #2196f3;
|
||||
--litepicker-button-cancel-color: #fff;
|
||||
--litepicker-button-cancel-color-bg: #9e9e9e;
|
||||
--litepicker-button-apply-color: #fff;
|
||||
--litepicker-button-apply-color-bg: #2196f3;
|
||||
--litepicker-button-reset-color: #909090;
|
||||
--litepicker-button-reset-color-hover: #2196f3;
|
||||
--litepicker-highlighted-day-color: #333;
|
||||
--litepicker-highlighted-day-color-bg: #ffeb3b;
|
||||
}
|
||||
|
||||
.show-week-numbers {
|
||||
--litepicker-month-width: calc(var(--litepicker-day-width) * 8);
|
||||
}
|
||||
|
||||
.litepicker {
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
||||
font-size: 0.8em;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.litepicker button {
|
||||
border: none;
|
||||
background: none;
|
||||
}
|
||||
|
||||
.litepicker .container__main {
|
||||
display: -webkit-box;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.litepicker .container__months {
|
||||
display: -webkit-box;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
-ms-flex-wrap: wrap;
|
||||
flex-wrap: wrap;
|
||||
background-color: var(--litepicker-container-months-color-bg);
|
||||
border-radius: 5px;
|
||||
-webkit-box-shadow: 0 0 5px var(--litepicker-container-months-box-shadow-color);
|
||||
box-shadow: 0 0 5px var(--litepicker-container-months-box-shadow-color);
|
||||
width: calc(var(--litepicker-month-width) + 10px);
|
||||
-webkit-box-sizing: content-box;
|
||||
box-sizing: content-box;
|
||||
}
|
||||
|
||||
.litepicker .container__months.columns-2 {
|
||||
width: calc((var(--litepicker-month-width) * 2) + 20px);
|
||||
}
|
||||
|
||||
.litepicker .container__months.columns-3 {
|
||||
width: calc((var(--litepicker-month-width) * 3) + 30px);
|
||||
}
|
||||
|
||||
.litepicker .container__months.columns-4 {
|
||||
width: calc((var(--litepicker-month-width) * 4) + 40px);
|
||||
}
|
||||
|
||||
.litepicker .container__months.split-view .month-item-header .button-previous-month, .litepicker .container__months.split-view .month-item-header .button-next-month {
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.litepicker .container__months .month-item {
|
||||
padding: 5px;
|
||||
width: var(--litepicker-month-width);
|
||||
-webkit-box-sizing: content-box;
|
||||
box-sizing: content-box;
|
||||
}
|
||||
|
||||
.litepicker .container__months .month-item-header {
|
||||
display: -webkit-box;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
-webkit-box-pack: justify;
|
||||
-ms-flex-pack: justify;
|
||||
justify-content: space-between;
|
||||
font-weight: 500;
|
||||
padding: 10px 5px;
|
||||
text-align: center;
|
||||
-webkit-box-align: center;
|
||||
-ms-flex-align: center;
|
||||
align-items: center;
|
||||
color: var(--litepicker-month-header-color);
|
||||
}
|
||||
|
||||
.litepicker .container__months .month-item-header div {
|
||||
-webkit-box-flex: 1;
|
||||
-ms-flex: 1;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.litepicker .container__months .month-item-header div > .month-item-name {
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.litepicker .container__months .month-item-header div > .month-item-year {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.litepicker .container__months .month-item-header .reset-button {
|
||||
color: var(--litepicker-button-reset-color);
|
||||
}
|
||||
|
||||
.litepicker .container__months .month-item-header .reset-button > svg {
|
||||
fill: var(--litepicker-button-reset-color);
|
||||
}
|
||||
|
||||
.litepicker .container__months .month-item-header .reset-button * {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.litepicker .container__months .month-item-header .reset-button:hover {
|
||||
color: var(--litepicker-button-reset-color-hover);
|
||||
}
|
||||
|
||||
.litepicker .container__months .month-item-header .reset-button:hover > svg {
|
||||
fill: var(--litepicker-button-reset-color-hover);
|
||||
}
|
||||
|
||||
.litepicker .container__months .month-item-header .button-previous-month, .litepicker .container__months .month-item-header .button-next-month {
|
||||
visibility: hidden;
|
||||
text-decoration: none;
|
||||
padding: 3px 5px;
|
||||
border-radius: 3px;
|
||||
-webkit-transition: color 0.3s, border 0.3s;
|
||||
transition: color 0.3s, border 0.3s;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.litepicker .container__months .month-item-header .button-previous-month *, .litepicker .container__months .month-item-header .button-next-month * {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.litepicker .container__months .month-item-header .button-previous-month {
|
||||
color: var(--litepicker-button-prev-month-color);
|
||||
}
|
||||
|
||||
.litepicker .container__months .month-item-header .button-previous-month > svg, .litepicker .container__months .month-item-header .button-previous-month > img {
|
||||
fill: var(--litepicker-button-prev-month-color);
|
||||
}
|
||||
|
||||
.litepicker .container__months .month-item-header .button-previous-month:hover {
|
||||
color: var(--litepicker-button-prev-month-color-hover);
|
||||
}
|
||||
|
||||
.litepicker .container__months .month-item-header .button-previous-month:hover > svg {
|
||||
fill: var(--litepicker-button-prev-month-color-hover);
|
||||
}
|
||||
|
||||
.litepicker .container__months .month-item-header .button-next-month {
|
||||
color: var(--litepicker-button-next-month-color);
|
||||
}
|
||||
|
||||
.litepicker .container__months .month-item-header .button-next-month > svg, .litepicker .container__months .month-item-header .button-next-month > img {
|
||||
fill: var(--litepicker-button-next-month-color);
|
||||
}
|
||||
|
||||
.litepicker .container__months .month-item-header .button-next-month:hover {
|
||||
color: var(--litepicker-button-next-month-color-hover);
|
||||
}
|
||||
|
||||
.litepicker .container__months .month-item-header .button-next-month:hover > svg {
|
||||
fill: var(--litepicker-button-next-month-color-hover);
|
||||
}
|
||||
|
||||
.litepicker .container__months .month-item-weekdays-row {
|
||||
display: -webkit-box;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
justify-self: center;
|
||||
-webkit-box-pack: start;
|
||||
-ms-flex-pack: start;
|
||||
justify-content: flex-start;
|
||||
color: var(--litepicker-month-weekday-color);
|
||||
}
|
||||
|
||||
.litepicker .container__months .month-item-weekdays-row > div {
|
||||
padding: 5px 0;
|
||||
font-size: 85%;
|
||||
-webkit-box-flex: 1;
|
||||
-ms-flex: 1;
|
||||
flex: 1;
|
||||
width: var(--litepicker-day-width);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.litepicker .container__months .month-item:first-child .button-previous-month {
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.litepicker .container__months .month-item:last-child .button-next-month {
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.litepicker .container__months .month-item.no-previous-month .button-previous-month {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.litepicker .container__months .month-item.no-next-month .button-next-month {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.litepicker .container__days {
|
||||
display: -webkit-box;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
-ms-flex-wrap: wrap;
|
||||
flex-wrap: wrap;
|
||||
justify-self: center;
|
||||
-webkit-box-pack: start;
|
||||
-ms-flex-pack: start;
|
||||
justify-content: flex-start;
|
||||
text-align: center;
|
||||
-webkit-box-sizing: content-box;
|
||||
box-sizing: content-box;
|
||||
}
|
||||
|
||||
.litepicker .container__days > div, .litepicker .container__days > a {
|
||||
padding: 5px 0;
|
||||
width: var(--litepicker-day-width);
|
||||
}
|
||||
|
||||
.litepicker .container__days .day-item {
|
||||
color: var(--litepicker-day-color);
|
||||
text-align: center;
|
||||
text-decoration: none;
|
||||
border-radius: 3px;
|
||||
-webkit-transition: color 0.3s, border 0.3s;
|
||||
transition: color 0.3s, border 0.3s;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.litepicker .container__days .day-item:hover {
|
||||
color: var(--litepicker-day-color-hover);
|
||||
-webkit-box-shadow: inset 0 0 0 1px var(--litepicker-day-color-hover);
|
||||
box-shadow: inset 0 0 0 1px var(--litepicker-day-color-hover);
|
||||
}
|
||||
|
||||
.litepicker .container__days .day-item.is-today {
|
||||
color: var(--litepicker-is-today-color);
|
||||
}
|
||||
|
||||
.litepicker .container__days .day-item.is-locked {
|
||||
color: var(--litepicker-is-locked-color);
|
||||
}
|
||||
|
||||
.litepicker .container__days .day-item.is-locked:hover {
|
||||
color: var(--litepicker-is-locked-color);
|
||||
-webkit-box-shadow: none;
|
||||
box-shadow: none;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.litepicker .container__days .day-item.is-in-range {
|
||||
background-color: var(--litepicker-is-in-range-color);
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.litepicker .container__days .day-item.is-start-date {
|
||||
color: var(--litepicker-is-start-color);
|
||||
background-color: var(--litepicker-is-start-color-bg);
|
||||
border-top-left-radius: 5px;
|
||||
border-bottom-left-radius: 5px;
|
||||
border-top-right-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
}
|
||||
|
||||
.litepicker .container__days .day-item.is-start-date.is-flipped {
|
||||
border-top-left-radius: 0;
|
||||
border-bottom-left-radius: 0;
|
||||
border-top-right-radius: 5px;
|
||||
border-bottom-right-radius: 5px;
|
||||
}
|
||||
|
||||
.litepicker .container__days .day-item.is-end-date {
|
||||
color: var(--litepicker-is-end-color);
|
||||
background-color: var(--litepicker-is-end-color-bg);
|
||||
border-top-left-radius: 0;
|
||||
border-bottom-left-radius: 0;
|
||||
border-top-right-radius: 5px;
|
||||
border-bottom-right-radius: 5px;
|
||||
}
|
||||
|
||||
.litepicker .container__days .day-item.is-end-date.is-flipped {
|
||||
border-top-left-radius: 5px;
|
||||
border-bottom-left-radius: 5px;
|
||||
border-top-right-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
}
|
||||
|
||||
.litepicker .container__days .day-item.is-start-date.is-end-date {
|
||||
border-top-left-radius: 5px;
|
||||
border-bottom-left-radius: 5px;
|
||||
border-top-right-radius: 5px;
|
||||
border-bottom-right-radius: 5px;
|
||||
}
|
||||
|
||||
.litepicker .container__days .day-item.is-highlighted {
|
||||
color: var(--litepicker-highlighted-day-color);
|
||||
background-color: var(--litepicker-highlighted-day-color-bg);
|
||||
}
|
||||
|
||||
.litepicker .container__days .week-number {
|
||||
display: -webkit-box;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
-webkit-box-align: center;
|
||||
-ms-flex-align: center;
|
||||
align-items: center;
|
||||
-webkit-box-pack: center;
|
||||
-ms-flex-pack: center;
|
||||
justify-content: center;
|
||||
color: var(--litepicker-month-week-number-color);
|
||||
font-size: 85%;
|
||||
}
|
||||
|
||||
.litepicker .container__footer {
|
||||
text-align: right;
|
||||
padding: 10px 5px;
|
||||
margin: 0 5px;
|
||||
background-color: var(--litepicker-footer-color-bg);
|
||||
-webkit-box-shadow: inset 0px 3px 3px 0px var(--litepicker-footer-box-shadow-color);
|
||||
box-shadow: inset 0px 3px 3px 0px var(--litepicker-footer-box-shadow-color);
|
||||
border-bottom-left-radius: 5px;
|
||||
border-bottom-right-radius: 5px;
|
||||
}
|
||||
|
||||
.litepicker .container__footer .preview-date-range {
|
||||
margin-right: 10px;
|
||||
font-size: 90%;
|
||||
}
|
||||
|
||||
.litepicker .container__footer .button-cancel {
|
||||
background-color: var(--litepicker-button-cancel-color-bg);
|
||||
color: var(--litepicker-button-cancel-color);
|
||||
border: 0;
|
||||
padding: 3px 7px 4px;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.litepicker .container__footer .button-cancel * {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.litepicker .container__footer .button-apply {
|
||||
background-color: var(--litepicker-button-apply-color-bg);
|
||||
color: var(--litepicker-button-apply-color);
|
||||
border: 0;
|
||||
padding: 3px 7px 4px;
|
||||
border-radius: 3px;
|
||||
margin-left: 10px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.litepicker .container__footer .button-apply:disabled {
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.litepicker .container__footer .button-apply * {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.litepicker .container__tooltip {
|
||||
position: absolute;
|
||||
margin-top: -4px;
|
||||
padding: 4px 8px;
|
||||
border-radius: 4px;
|
||||
background-color: var(--litepicker-tooltip-color-bg);
|
||||
-webkit-box-shadow: 0 1px 3px rgba(0,0,0,0.25);
|
||||
box-shadow: 0 1px 3px rgba(0,0,0,0.25);
|
||||
white-space: nowrap;
|
||||
font-size: 11px;
|
||||
pointer-events: none;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.litepicker .container__tooltip:before {
|
||||
position: absolute;
|
||||
bottom: -5px;
|
||||
left: calc(50% - 5px);
|
||||
border-top: 5px solid rgba(0,0,0,0.12);
|
||||
border-right: 5px solid transparent;
|
||||
border-left: 5px solid transparent;
|
||||
content: "";
|
||||
}
|
||||
|
||||
.litepicker .container__tooltip:after {
|
||||
position: absolute;
|
||||
bottom: -4px;
|
||||
left: calc(50% - 4px);
|
||||
border-top: 4px solid var(--litepicker-tooltip-color-bg);
|
||||
border-right: 4px solid transparent;
|
||||
border-left: 4px solid transparent;
|
||||
content: "";
|
||||
}
|
||||
|
||||
978
html/webpage/assets/js/bootstrap4-clockpicker.js
vendored
Normal file
978
html/webpage/assets/js/bootstrap4-clockpicker.js
vendored
Normal file
@@ -0,0 +1,978 @@
|
||||
/*!
|
||||
* ClockPicker v0.2.3 original by (http://weareoutman.github.io/clockpicker/)
|
||||
* Copyright 2014 Wang Shenwei.
|
||||
* Licensed under MIT (https://github.com/weareoutman/clockpicker/blob/gh-pages/LICENSE)
|
||||
* Bootstrap 4 support by djibe
|
||||
*/
|
||||
|
||||
(function($) {
|
||||
var $win = $(window),
|
||||
$doc = $(document),
|
||||
$body;
|
||||
|
||||
// Can I use inline svg ?
|
||||
var svgNS = "http://www.w3.org/2000/svg",
|
||||
svgSupported =
|
||||
"SVGAngle" in window &&
|
||||
(function() {
|
||||
var supported,
|
||||
el = document.createElement("div");
|
||||
el.innerHTML = "<svg/>";
|
||||
supported = (el.firstChild && el.firstChild.namespaceURI) == svgNS;
|
||||
el.innerHTML = "";
|
||||
return supported;
|
||||
})();
|
||||
|
||||
// Can I use transition ?
|
||||
var transitionSupported = (function() {
|
||||
var style = document.createElement("div").style;
|
||||
return (
|
||||
"transition" in style ||
|
||||
"WebkitTransition" in style ||
|
||||
"MozTransition" in style ||
|
||||
"msTransition" in style ||
|
||||
"OTransition" in style
|
||||
);
|
||||
})();
|
||||
|
||||
// Listen touch events in touch screen device, instead of mouse events in desktop.
|
||||
var touchSupported = "ontouchstart" in window,
|
||||
mousedownEvent = "mousedown" + (touchSupported ? " touchstart" : ""),
|
||||
mousemoveEvent =
|
||||
"mousemove.clockpicker" +
|
||||
(touchSupported ? " touchmove.clockpicker" : ""),
|
||||
mouseupEvent =
|
||||
"mouseup.clockpicker" + (touchSupported ? " touchend.clockpicker" : "");
|
||||
|
||||
// Vibrate the device if supported
|
||||
var vibrate = navigator.vibrate
|
||||
? "vibrate"
|
||||
: navigator.webkitVibrate
|
||||
? "webkitVibrate"
|
||||
: null;
|
||||
|
||||
function createSvgElement(name) {
|
||||
return document.createElementNS(svgNS, name);
|
||||
}
|
||||
|
||||
function leadingZero(num) {
|
||||
return (num < 10 ? "0" : "") + num;
|
||||
}
|
||||
|
||||
// Get a unique id
|
||||
var idCounter = 0;
|
||||
function uniqueId(prefix) {
|
||||
var id = ++idCounter + "";
|
||||
return prefix ? prefix + id : id;
|
||||
}
|
||||
|
||||
// Clock size
|
||||
var dialRadius = 100,
|
||||
outerRadius = 80,
|
||||
// innerRadius = 80 on 12 hour clock
|
||||
innerRadius = 54,
|
||||
tickRadius = 13;
|
||||
(diameter = dialRadius * 2), (duration = transitionSupported ? 350 : 1);
|
||||
|
||||
// Popover template
|
||||
var tpl = [
|
||||
'<div class="popover clockpicker-popover">',
|
||||
'<div class="arrow"></div>',
|
||||
'<div class="popover-header">',
|
||||
'<span class="clockpicker-span-hours"></span>',
|
||||
":",
|
||||
'<span class="clockpicker-span-minutes text-white-50"></span>',
|
||||
'<span class="clockpicker-buttons-am-pm"></span>',
|
||||
"</div>",
|
||||
'<div class="popover-body">',
|
||||
'<div class="clockpicker-plate">',
|
||||
'<div class="clockpicker-canvas"></div>',
|
||||
'<div class="clockpicker-dial clockpicker-hours"></div>',
|
||||
'<div class="clockpicker-dial clockpicker-minutes clockpicker-dial-out"></div>',
|
||||
"</div>",
|
||||
'<div class="clockpicker-close-block justify-content-end"></div>',
|
||||
"</div>",
|
||||
"</div>"
|
||||
].join("");
|
||||
|
||||
// ClockPicker
|
||||
function ClockPicker(element, options) {
|
||||
var popover = $(tpl),
|
||||
plate = popover.find(".clockpicker-plate"),
|
||||
hoursView = popover.find(".clockpicker-hours"),
|
||||
minutesView = popover.find(".clockpicker-minutes"),
|
||||
isInput = element.prop("tagName") === "INPUT",
|
||||
input = isInput ? element : element.find("input"),
|
||||
isHTML5 = input.prop("type") === "time",
|
||||
addon = element.find(".input-group-addon"),
|
||||
popoverBody = popover.find(".popover-body"),
|
||||
closeBlock = popoverBody.find(".clockpicker-close-block"),
|
||||
self = this,
|
||||
timer;
|
||||
|
||||
this.id = uniqueId("cp");
|
||||
this.element = element;
|
||||
this.options = options;
|
||||
this.options.hourstep = this.parseStep(this.options.hourstep, 12);
|
||||
this.options.minutestep = this.parseStep(this.options.minutestep, 60);
|
||||
this.isAppended = false;
|
||||
this.isShown = false;
|
||||
this.currentView = "hours";
|
||||
this.isInput = isInput;
|
||||
this.isHTML5 = isHTML5;
|
||||
this.input = input;
|
||||
this.addon = addon;
|
||||
this.popover = popover;
|
||||
this.plate = plate;
|
||||
this.hoursView = hoursView;
|
||||
this.minutesView = minutesView;
|
||||
this.spanHours = popover.find(".clockpicker-span-hours");
|
||||
this.spanMinutes = popover.find(".clockpicker-span-minutes");
|
||||
this.buttonsAmPm = popover.find(".clockpicker-buttons-am-pm");
|
||||
this.currentPlacementClass = options.placement;
|
||||
this.raiseCallback = function() {
|
||||
raiseCallback.apply(self, arguments);
|
||||
};
|
||||
|
||||
// Setup for for 12 hour clock if option is selected
|
||||
if (options.twelvehour) {
|
||||
$(this.buttonsAmPm).css("display", "flex");
|
||||
|
||||
$('<a class="btn-am">AM</a>')
|
||||
.on("click", function() {
|
||||
self.amOrPm = "AM";
|
||||
$(this).removeClass("text-white-50");
|
||||
$(".btn-pm").addClass("text-white-50");
|
||||
if (options.ampmSubmit) {
|
||||
setTimeout(function() {
|
||||
self.done();
|
||||
}, duration / 2);
|
||||
}
|
||||
})
|
||||
.appendTo(this.buttonsAmPm);
|
||||
|
||||
$('<a class="btn-pm text-white-50">PM</a>')
|
||||
.on("click", function() {
|
||||
self.amOrPm = "PM";
|
||||
$(this).removeClass("text-white-50");
|
||||
$(".btn-am").addClass("text-white-50");
|
||||
if (options.ampmSubmit) {
|
||||
setTimeout(function() {
|
||||
self.done();
|
||||
}, duration / 2);
|
||||
}
|
||||
})
|
||||
.appendTo(this.buttonsAmPm);
|
||||
}
|
||||
|
||||
if (!options.autoclose) {
|
||||
// If autoclose is not setted, append a button
|
||||
closeBlock
|
||||
.append(
|
||||
'<button type="button" class="btn btn-sm btn-outline-primary cancel">' +
|
||||
options.canceltext +
|
||||
"</button>"
|
||||
)
|
||||
.on("click", ".cancel", function () {
|
||||
self.hide();
|
||||
});
|
||||
|
||||
closeBlock
|
||||
.css("display", "flex")
|
||||
.append(
|
||||
'<button type="button" class="btn btn-sm btn-outline-primary done">' +
|
||||
options.donetext +
|
||||
"</button>"
|
||||
)
|
||||
.on("click", ".done", $.proxy(this.done, this));
|
||||
}
|
||||
|
||||
// Placement and arrow align - make sure they make sense.
|
||||
if (
|
||||
/^(top|bottom)/.test(options.placement) &&
|
||||
(options.align === "top" || options.align === "bottom")
|
||||
)
|
||||
options.align = "left";
|
||||
if (
|
||||
(options.placement === "left" || options.placement === "right") &&
|
||||
(options.align === "left" || options.align === "right")
|
||||
)
|
||||
options.align = "top";
|
||||
|
||||
popover.addClass(options.placement);
|
||||
popover.addClass("clockpicker-align-" + options.align);
|
||||
|
||||
this.spanHours.click($.proxy(this.toggleView, this, "hours"));
|
||||
this.spanMinutes.click($.proxy(this.toggleView, this, "minutes"));
|
||||
|
||||
// Show or toggle
|
||||
if (!options.addonOnly) {
|
||||
input.on("focus.clockpicker click.clockpicker", $.proxy(this.show, this));
|
||||
}
|
||||
addon.on("click.clockpicker", $.proxy(this.toggle, this));
|
||||
|
||||
// Build ticks
|
||||
var tickTpl = $('<div class="clockpicker-tick"></div>'),
|
||||
i,
|
||||
tick,
|
||||
radian,
|
||||
radius;
|
||||
|
||||
// Hours view
|
||||
if (options.twelvehour) {
|
||||
for (i = 0; i < 12; i += options.hourstep) {
|
||||
tick = tickTpl.clone();
|
||||
radian = (i / 6) * Math.PI;
|
||||
radius = outerRadius;
|
||||
tick.css("font-size", "120%");
|
||||
tick.css({
|
||||
left: dialRadius + Math.sin(radian) * radius - tickRadius,
|
||||
top: dialRadius - Math.cos(radian) * radius - tickRadius
|
||||
});
|
||||
tick.html(i === 0 ? 12 : i);
|
||||
hoursView.append(tick);
|
||||
tick.on(mousedownEvent, mousedown);
|
||||
}
|
||||
} else {
|
||||
for (i = 0; i < 24; i += options.hourstep) {
|
||||
var isDisabled = false;
|
||||
if (
|
||||
options.disabledhours &&
|
||||
$.inArray(i, options.disabledhours) != -1
|
||||
) {
|
||||
var isDisabled = true;
|
||||
}
|
||||
tick = tickTpl.clone();
|
||||
radian = (i / 6) * Math.PI;
|
||||
var inner = i > 0 && i < 13;
|
||||
radius = inner ? innerRadius : outerRadius;
|
||||
tick.css({
|
||||
left: dialRadius + Math.sin(radian) * radius - tickRadius,
|
||||
top: dialRadius - Math.cos(radian) * radius - tickRadius
|
||||
});
|
||||
if (inner) {
|
||||
tick.css("font-size", "120%");
|
||||
}
|
||||
if (isDisabled) {
|
||||
tick.addClass("disabled");
|
||||
}
|
||||
tick.html(i === 0 ? "00" : i);
|
||||
hoursView.append(tick);
|
||||
if (!isDisabled) {
|
||||
tick.on(mousedownEvent, mousedown);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Minutes view
|
||||
var incrementValue = Math.max(options.minutestep, 5);
|
||||
for (i = 0; i < 60; i += incrementValue) {
|
||||
tick = tickTpl.clone();
|
||||
radian = (i / 30) * Math.PI;
|
||||
tick.css({
|
||||
left: dialRadius + Math.sin(radian) * outerRadius - tickRadius,
|
||||
top: dialRadius - Math.cos(radian) * outerRadius - tickRadius
|
||||
});
|
||||
tick.css("font-size", "120%");
|
||||
tick.html(leadingZero(i));
|
||||
minutesView.append(tick);
|
||||
tick.on(mousedownEvent, mousedown);
|
||||
}
|
||||
|
||||
// Clicking on minutes view space
|
||||
plate.on(mousedownEvent, function(e) {
|
||||
if ($(e.target).closest(".clockpicker-tick").length === 0) {
|
||||
mousedown(e, true);
|
||||
}
|
||||
});
|
||||
|
||||
// Mousedown or touchstart
|
||||
function mousedown(e, space) {
|
||||
var offset = plate.offset(),
|
||||
isTouch = /^touch/.test(e.type),
|
||||
x0 = offset.left + dialRadius,
|
||||
y0 = offset.top + dialRadius,
|
||||
dx = (isTouch ? e.originalEvent.touches[0] : e).pageX - x0,
|
||||
dy = (isTouch ? e.originalEvent.touches[0] : e).pageY - y0,
|
||||
z = Math.sqrt(dx * dx + dy * dy),
|
||||
moved = false;
|
||||
|
||||
// When clicking on minutes view space, check the mouse position
|
||||
if (
|
||||
space &&
|
||||
(z < outerRadius - tickRadius || z > outerRadius + tickRadius)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
e.preventDefault();
|
||||
|
||||
// Set cursor style of body after 200ms
|
||||
var movingTimer = setTimeout(function() {
|
||||
$body.addClass("clockpicker-moving");
|
||||
}, 200);
|
||||
|
||||
// Place the canvas to top
|
||||
if (svgSupported) {
|
||||
plate.append(self.canvas);
|
||||
}
|
||||
|
||||
// Clock
|
||||
self.setHand(dx, dy, true);
|
||||
|
||||
// Mousemove on document
|
||||
$doc.off(mousemoveEvent).on(mousemoveEvent, function(e) {
|
||||
e.preventDefault();
|
||||
var isTouch = /^touch/.test(e.type),
|
||||
x = (isTouch ? e.originalEvent.touches[0] : e).pageX - x0,
|
||||
y = (isTouch ? e.originalEvent.touches[0] : e).pageY - y0;
|
||||
if (!moved && x === dx && y === dy) {
|
||||
// Clicking in chrome on windows will trigger a mousemove event
|
||||
return;
|
||||
}
|
||||
moved = true;
|
||||
self.setHand(x, y, true);
|
||||
});
|
||||
|
||||
// Mouseup on document
|
||||
$doc.off(mouseupEvent).on(mouseupEvent, function(e) {
|
||||
$doc.off(mouseupEvent);
|
||||
e.preventDefault();
|
||||
var isTouch = /^touch/.test(e.type),
|
||||
x = (isTouch ? e.originalEvent.changedTouches[0] : e).pageX - x0,
|
||||
y = (isTouch ? e.originalEvent.changedTouches[0] : e).pageY - y0;
|
||||
if ((space || moved) && x === dx && y === dy) {
|
||||
self.setHand(x, y);
|
||||
}
|
||||
if (self.currentView === "hours") {
|
||||
self.toggleView("minutes", duration / 2);
|
||||
} else {
|
||||
if (options.autoclose) {
|
||||
if (!options.ampmSubmit) {
|
||||
self.minutesView.addClass("clockpicker-dial-out");
|
||||
setTimeout(function() {
|
||||
self.done();
|
||||
}, duration / 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
plate.prepend(canvas);
|
||||
|
||||
// Reset cursor style of body
|
||||
clearTimeout(movingTimer);
|
||||
$body.removeClass("clockpicker-moving");
|
||||
|
||||
// Unbind mousemove event
|
||||
$doc.off(mousemoveEvent);
|
||||
});
|
||||
}
|
||||
|
||||
if (svgSupported) {
|
||||
// Draw clock hands and others
|
||||
var canvas = popover.find(".clockpicker-canvas"),
|
||||
svg = createSvgElement("svg");
|
||||
svg.setAttribute("class", "clockpicker-svg");
|
||||
svg.setAttribute("width", diameter);
|
||||
svg.setAttribute("height", diameter);
|
||||
var g = createSvgElement("g");
|
||||
g.setAttribute(
|
||||
"transform",
|
||||
"translate(" + dialRadius + "," + dialRadius + ")"
|
||||
);
|
||||
var bearing = createSvgElement("circle");
|
||||
bearing.setAttribute("class", "clockpicker-canvas-bearing");
|
||||
bearing.setAttribute("cx", 0);
|
||||
bearing.setAttribute("cy", 0);
|
||||
bearing.setAttribute("r", 3);
|
||||
var hand = createSvgElement("line");
|
||||
hand.setAttribute("x1", 0);
|
||||
hand.setAttribute("y1", 0);
|
||||
var bg = createSvgElement("circle");
|
||||
bg.setAttribute("class", "clockpicker-canvas-bg");
|
||||
bg.setAttribute("r", tickRadius);
|
||||
var fg = createSvgElement("circle");
|
||||
fg.setAttribute("class", "clockpicker-canvas-fg");
|
||||
fg.setAttribute("r", 3.5);
|
||||
g.appendChild(hand);
|
||||
g.appendChild(bg);
|
||||
g.appendChild(fg);
|
||||
g.appendChild(bearing);
|
||||
svg.appendChild(g);
|
||||
canvas.append(svg);
|
||||
|
||||
this.hand = hand;
|
||||
this.bg = bg;
|
||||
this.fg = fg;
|
||||
this.bearing = bearing;
|
||||
this.g = g;
|
||||
this.canvas = canvas;
|
||||
}
|
||||
|
||||
this.raiseCallback(this.options.init, "init");
|
||||
}
|
||||
|
||||
function raiseCallback(callbackFunction, triggerName) {
|
||||
if (
|
||||
callbackFunction &&
|
||||
typeof callbackFunction === "function" &&
|
||||
this.element
|
||||
) {
|
||||
var time = this.getTime() || null;
|
||||
callbackFunction.call(this.element, time);
|
||||
}
|
||||
if (triggerName) {
|
||||
this.element.trigger("clockpicker." + triggerName || "NoName");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Find most suitable vertical placement, doing our best to ensure it is inside of the viewport.
|
||||
*
|
||||
* First try to place the element according with preferredPlacement, then try the opposite
|
||||
* placement and as a last resort, popover will be placed on the very top of the viewport.
|
||||
*
|
||||
* @param {jQuery} element
|
||||
* @param {jQuery} popover
|
||||
* @param preferredPlacement Preferred placement, if there is enough room for it.
|
||||
* @returns {string} One of: 'top', 'bottom' or 'viewport-top'.
|
||||
*/
|
||||
function resolveAdaptiveVerticalPlacement(
|
||||
element,
|
||||
popover,
|
||||
preferredPlacement
|
||||
) {
|
||||
var popoverHeight = popover.outerHeight(),
|
||||
elementHeight = element.outerHeight(),
|
||||
elementTopOffset = element.offset().top,
|
||||
elementBottomOffset = element.offset().top + elementHeight,
|
||||
minVisibleY = elementTopOffset - element[0].getBoundingClientRect().top,
|
||||
maxVisibleY = minVisibleY + document.documentElement.clientHeight,
|
||||
isEnoughRoomAbove = elementTopOffset - popoverHeight >= minVisibleY,
|
||||
isEnoughRoomBelow = elementBottomOffset + popoverHeight <= maxVisibleY;
|
||||
|
||||
if (preferredPlacement === "top") {
|
||||
if (isEnoughRoomAbove) {
|
||||
return "top";
|
||||
} else if (isEnoughRoomBelow) {
|
||||
return "bottom";
|
||||
}
|
||||
} else {
|
||||
if (isEnoughRoomBelow) {
|
||||
return "bottom";
|
||||
} else if (isEnoughRoomAbove) {
|
||||
return "top";
|
||||
}
|
||||
}
|
||||
|
||||
return "viewport-top";
|
||||
}
|
||||
|
||||
ClockPicker.prototype.parseStep = function(givenStepSize, wholeSize) {
|
||||
return wholeSize % givenStepSize === 0 ? givenStepSize : 1;
|
||||
};
|
||||
|
||||
// Default options
|
||||
ClockPicker.DEFAULTS = {
|
||||
default: "", // default time, 'now' or '13:14' e.g.
|
||||
fromnow: 0, // set default time to * milliseconds from now (using with default = 'now')
|
||||
placement: "bottom", // clock popover placement
|
||||
align: "left", // popover arrow align
|
||||
donetext: "OK", // done button text
|
||||
canceltext: "Cancel", // cancel button text
|
||||
autoclose: false, // auto close when minute is selected
|
||||
twelvehour: false, // change to 12 hour AM/PM clock from 24 hour
|
||||
vibrate: true, // vibrate the device when dragging clock hand
|
||||
hourstep: 1, // allow to multi increment the hour
|
||||
minutestep: 1, // allow to multi increment the minute
|
||||
ampmSubmit: false, // allow submit with AM and PM buttons instead of the minute selection/picker
|
||||
addonOnly: false, // only open on clicking on the input-addon
|
||||
disabledhours: null // disabled hours (only 24 hour mode)
|
||||
};
|
||||
|
||||
// Show or hide popover
|
||||
ClockPicker.prototype.toggle = function() {
|
||||
this[this.isShown ? "hide" : "show"]();
|
||||
};
|
||||
|
||||
// Set new placement class for popover and remove the old one, if any.
|
||||
ClockPicker.prototype.updatePlacementClass = function(newClass) {
|
||||
if (this.currentPlacementClass) {
|
||||
this.popover.removeClass(this.currentPlacementClass);
|
||||
}
|
||||
if (newClass) {
|
||||
this.popover.addClass(newClass);
|
||||
}
|
||||
|
||||
this.currentPlacementClass = newClass;
|
||||
};
|
||||
|
||||
// Set popover position and update placement class, if needed
|
||||
ClockPicker.prototype.locate = function() {
|
||||
var element = this.element,
|
||||
popover = this.popover,
|
||||
offset = element.offset(),
|
||||
width = element.outerWidth(),
|
||||
height = element.outerHeight(),
|
||||
placement = this.options.placement,
|
||||
align = this.options.align,
|
||||
windowHeight = $win.height(),
|
||||
windowWidth = $win.width(),
|
||||
popoverHeight = popover.height(),
|
||||
popoverWidth = popover.width(),
|
||||
styles = {},
|
||||
self = this;
|
||||
|
||||
if (placement === "top-adaptive" || placement === "bottom-adaptive") {
|
||||
var preferredPlacement = placement.substr(0, placement.indexOf("-"));
|
||||
// Adaptive placement should be resolved into one of the "static" placement
|
||||
// options, that is best suitable for the current window scroll position.
|
||||
placement = resolveAdaptiveVerticalPlacement(
|
||||
element,
|
||||
popover,
|
||||
preferredPlacement
|
||||
);
|
||||
|
||||
this.updatePlacementClass(placement !== "viewport-top" ? placement : "");
|
||||
}
|
||||
|
||||
popover.show();
|
||||
|
||||
// Place the popover
|
||||
switch (placement) {
|
||||
case "bottom":
|
||||
styles.top = offset.top + height;
|
||||
break;
|
||||
case "right":
|
||||
styles.left = offset.left + width;
|
||||
break;
|
||||
case "top":
|
||||
styles.top = offset.top - popover.outerHeight();
|
||||
break;
|
||||
case "left":
|
||||
styles.left = offset.left - popover.outerWidth();
|
||||
break;
|
||||
case "viewport-top":
|
||||
styles.top = offset.top - element[0].getBoundingClientRect().top;
|
||||
break;
|
||||
}
|
||||
|
||||
// Align the popover arrow
|
||||
switch (align) {
|
||||
case "left":
|
||||
styles.left = offset.left;
|
||||
break;
|
||||
case "right":
|
||||
styles.left = offset.left + width - popover.outerWidth();
|
||||
break;
|
||||
case "top":
|
||||
styles.top = offset.top;
|
||||
break;
|
||||
case "bottom":
|
||||
styles.top = offset.top + height - popover.outerHeight();
|
||||
break;
|
||||
}
|
||||
|
||||
// Correct the popover position outside the window
|
||||
if (popoverHeight + styles.top > windowHeight) {
|
||||
styles.top = windowHeight - popoverHeight;
|
||||
}
|
||||
if (popoverWidth + styles.left > windowWidth) {
|
||||
styles.left = windowWidth - popoverWidth;
|
||||
}
|
||||
|
||||
popover.css(styles);
|
||||
};
|
||||
|
||||
// The input can be changed by the user
|
||||
// So before we can use this.hours/this.minutes we must update it
|
||||
ClockPicker.prototype.parseInputValue = function() {
|
||||
var value = this.input.prop("value") || this.options["default"] || "";
|
||||
|
||||
if (value === "now") {
|
||||
value = new Date(+new Date() + this.options.fromnow);
|
||||
}
|
||||
if (value instanceof Date) {
|
||||
value = value.getHours() + ":" + value.getMinutes();
|
||||
}
|
||||
|
||||
value = value.split(":");
|
||||
|
||||
// Minutes can have AM/PM that needs to be removed
|
||||
this.hours = +value[0] || 0;
|
||||
this.minutes = +(value[1] + "").replace(/\D/g, "") || 0;
|
||||
|
||||
this.hours =
|
||||
Math.round(this.hours / this.options.hourstep) * this.options.hourstep;
|
||||
this.minutes =
|
||||
Math.round(this.minutes / this.options.minutestep) *
|
||||
this.options.minutestep;
|
||||
|
||||
if (this.options.twelvehour) {
|
||||
var period = (value[1] + "").replace(/\d+/g, "").toLowerCase();
|
||||
//this.amOrPm = this.hours > 12 || period === "pm" ? "PM" : "AM";
|
||||
this.amOrPm = this.hours < 12 || period === "am" ? "AM" : "PM";
|
||||
}
|
||||
};
|
||||
|
||||
// Show popover
|
||||
ClockPicker.prototype.show = function(e) {
|
||||
// Not show again
|
||||
if (this.isShown) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.raiseCallback(this.options.beforeShow, "beforeShow");
|
||||
|
||||
var self = this;
|
||||
|
||||
// Initialize
|
||||
if (!this.isAppended) {
|
||||
// Append popover to body
|
||||
$body = $(document.body).append(this.popover);
|
||||
|
||||
// Reset position when resize
|
||||
$win.on("resize.clockpicker" + this.id, function() {
|
||||
if (self.isShown) {
|
||||
self.locate();
|
||||
}
|
||||
});
|
||||
|
||||
this.isAppended = true;
|
||||
}
|
||||
|
||||
// Get the time from the input field
|
||||
this.parseInputValue();
|
||||
|
||||
this.spanHours.html(leadingZero(this.hours));
|
||||
this.spanMinutes.html(leadingZero(this.minutes));
|
||||
|
||||
// Toggle to hours view
|
||||
this.toggleView("hours");
|
||||
|
||||
// Set position
|
||||
this.locate();
|
||||
|
||||
this.isShown = true;
|
||||
|
||||
// Hide when clicking or tabbing on any element except the clock, input and addon
|
||||
$doc.on(
|
||||
"click.clockpicker." + this.id + " focusin.clockpicker." + this.id,
|
||||
function(e) {
|
||||
var target = $(e.target);
|
||||
if (
|
||||
target.closest(self.popover).length === 0 &&
|
||||
target.closest(self.addon).length === 0 &&
|
||||
target.closest(self.input).length === 0
|
||||
) {
|
||||
self.hide();
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// Hide when ESC is pressed
|
||||
$doc.on("keyup.clockpicker." + this.id, function(e) {
|
||||
if (e.keyCode === 27) {
|
||||
self.hide();
|
||||
}
|
||||
});
|
||||
|
||||
this.raiseCallback(this.options.afterShow, "afterShow");
|
||||
};
|
||||
|
||||
// Hide popover
|
||||
ClockPicker.prototype.hide = function() {
|
||||
this.raiseCallback(this.options.beforeHide, "beforeHide");
|
||||
|
||||
this.isShown = false;
|
||||
|
||||
// Unbinding events on document
|
||||
$doc.off(
|
||||
"click.clockpicker." + this.id + " focusin.clockpicker." + this.id
|
||||
);
|
||||
$doc.off("keyup.clockpicker." + this.id);
|
||||
|
||||
this.popover.hide();
|
||||
|
||||
this.raiseCallback(this.options.afterHide, "afterHide");
|
||||
};
|
||||
|
||||
// Toggle to hours or minutes view
|
||||
ClockPicker.prototype.toggleView = function(view, delay) {
|
||||
var raiseAfterHourSelect = false;
|
||||
if (
|
||||
view === "minutes" &&
|
||||
$(this.hoursView).css("visibility") === "visible"
|
||||
) {
|
||||
this.raiseCallback(this.options.beforeHourSelect, "beforeHourSelect");
|
||||
raiseAfterHourSelect = true;
|
||||
}
|
||||
var isHours = view === "hours",
|
||||
nextView = isHours ? this.hoursView : this.minutesView,
|
||||
hideView = isHours ? this.minutesView : this.hoursView;
|
||||
|
||||
this.currentView = view;
|
||||
|
||||
this.spanHours.toggleClass("text-white-50", !isHours);
|
||||
this.spanMinutes.toggleClass("text-white-50", isHours);
|
||||
|
||||
// Let's make transitions
|
||||
hideView.addClass("clockpicker-dial-out");
|
||||
nextView.css("visibility", "visible").removeClass("clockpicker-dial-out");
|
||||
|
||||
// Reset clock hand
|
||||
this.resetClock(delay);
|
||||
|
||||
// After transitions ended
|
||||
clearTimeout(this.toggleViewTimer);
|
||||
this.toggleViewTimer = setTimeout(function() {
|
||||
hideView.css("visibility", "hidden");
|
||||
}, duration);
|
||||
|
||||
if (raiseAfterHourSelect) {
|
||||
this.raiseCallback(this.options.afterHourSelect, "afterHourSelect");
|
||||
}
|
||||
};
|
||||
|
||||
// Reset clock hand
|
||||
ClockPicker.prototype.resetClock = function(delay) {
|
||||
var view = this.currentView,
|
||||
value = this[view],
|
||||
isHours = view === "hours",
|
||||
unit = Math.PI / (isHours ? 6 : 30),
|
||||
radian = value * unit,
|
||||
radius = isHours && value > 0 && value < 13 ? innerRadius : outerRadius,
|
||||
x = Math.sin(radian) * radius,
|
||||
y = -Math.cos(radian) * radius,
|
||||
self = this;
|
||||
if (svgSupported && delay) {
|
||||
self.canvas.addClass("clockpicker-canvas-out");
|
||||
setTimeout(function() {
|
||||
self.canvas.removeClass("clockpicker-canvas-out");
|
||||
self.setHand(x, y);
|
||||
}, delay);
|
||||
} else {
|
||||
this.setHand(x, y);
|
||||
}
|
||||
};
|
||||
|
||||
// Set clock hand to (x, y)
|
||||
ClockPicker.prototype.setHand = function(x, y, dragging) {
|
||||
var radian = Math.atan2(x, -y),
|
||||
isHours = this.currentView === "hours",
|
||||
z = Math.sqrt(x * x + y * y),
|
||||
options = this.options,
|
||||
inner = isHours && z < (outerRadius + innerRadius) / 2,
|
||||
radius = inner ? innerRadius : outerRadius,
|
||||
unit,
|
||||
value;
|
||||
|
||||
// Calculate the unit
|
||||
if (isHours) {
|
||||
unit = (options.hourstep / 6) * Math.PI;
|
||||
} else {
|
||||
unit = (options.minutestep / 30) * Math.PI;
|
||||
}
|
||||
|
||||
if (options.twelvehour) {
|
||||
radius = outerRadius;
|
||||
}
|
||||
|
||||
// Radian should in range [0, 2PI]
|
||||
if (radian < 0) {
|
||||
radian = Math.PI * 2 + radian;
|
||||
}
|
||||
|
||||
// Get the round value
|
||||
value = Math.round(radian / unit);
|
||||
|
||||
// Get the round radian
|
||||
radian = value * unit;
|
||||
|
||||
// Correct the hours or minutes
|
||||
if (isHours) {
|
||||
value *= options.hourstep;
|
||||
|
||||
if (!options.twelvehour && !inner == value > 0) {
|
||||
value += 12;
|
||||
}
|
||||
if (options.twelvehour && value === 0) {
|
||||
value = 12;
|
||||
}
|
||||
if (value === 24) {
|
||||
value = 0;
|
||||
}
|
||||
if (
|
||||
dragging &&
|
||||
!options.twelvehour &&
|
||||
options.disabledhours &&
|
||||
$.inArray(value, options.disabledhours) != -1
|
||||
) {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
value *= options.minutestep;
|
||||
if (value === 60) {
|
||||
value = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Once hours or minutes changed, vibrate the device
|
||||
if (this[this.currentView] !== value) {
|
||||
if (vibrate && this.options.vibrate) {
|
||||
// Do not vibrate too frequently
|
||||
if (!this.vibrateTimer) {
|
||||
navigator[vibrate](10);
|
||||
this.vibrateTimer = setTimeout(
|
||||
$.proxy(function() {
|
||||
this.vibrateTimer = null;
|
||||
}, this),
|
||||
100
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this[this.currentView] = value;
|
||||
this[isHours ? "spanHours" : "spanMinutes"].html(leadingZero(value));
|
||||
|
||||
// If svg is not supported, just add an active class to the tick
|
||||
if (!svgSupported) {
|
||||
this[isHours ? "hoursView" : "minutesView"]
|
||||
.find(".clockpicker-tick")
|
||||
.each(function() {
|
||||
var tick = $(this);
|
||||
tick.toggleClass("active", value === +tick.html());
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Place clock hand at the top when dragging
|
||||
if (dragging || (!isHours && value % 5)) {
|
||||
this.g.insertBefore(this.hand, this.bearing);
|
||||
this.g.insertBefore(this.bg, this.fg);
|
||||
this.bg.setAttribute(
|
||||
"class",
|
||||
"clockpicker-canvas-bg clockpicker-canvas-bg-trans"
|
||||
);
|
||||
} else {
|
||||
// Or place it at the bottom
|
||||
this.g.insertBefore(this.hand, this.bg);
|
||||
this.g.insertBefore(this.fg, this.bg);
|
||||
this.bg.setAttribute("class", "clockpicker-canvas-bg");
|
||||
}
|
||||
|
||||
// Set clock hand and others' position
|
||||
var cx = Math.sin(radian) * radius,
|
||||
cy = -Math.cos(radian) * radius;
|
||||
this.hand.setAttribute("x2", cx);
|
||||
this.hand.setAttribute("y2", cy);
|
||||
this.bg.setAttribute("cx", cx);
|
||||
this.bg.setAttribute("cy", cy);
|
||||
this.fg.setAttribute("cx", cx);
|
||||
this.fg.setAttribute("cy", cy);
|
||||
};
|
||||
|
||||
// Allow user to get time time as Date object
|
||||
ClockPicker.prototype.getTime = function(callback) {
|
||||
var hours = this.hours;
|
||||
if (this.options.twelvehour && hours < 12 && this.amOrPm === "PM") {
|
||||
hours += 12;
|
||||
}
|
||||
|
||||
var selectedTime = new Date();
|
||||
selectedTime.setMinutes(this.minutes);
|
||||
selectedTime.setHours(hours);
|
||||
selectedTime.setSeconds(0);
|
||||
|
||||
return (
|
||||
(callback && callback.apply(this.element, selectedTime)) || selectedTime
|
||||
);
|
||||
};
|
||||
|
||||
// Hours and minutes are selected
|
||||
ClockPicker.prototype.done = function() {
|
||||
this.raiseCallback(this.options.beforeDone, "beforeDone");
|
||||
this.hide();
|
||||
var last = this.input.prop("value"),
|
||||
outHours = this.hours,
|
||||
value = ":" + leadingZero(this.minutes);
|
||||
|
||||
if (this.isHTML5 && this.options.twelvehour) {
|
||||
if (this.hours < 12 && this.amOrPm === "PM") {
|
||||
outHours += 12;
|
||||
}
|
||||
if (this.hours === 12 && this.amOrPm === "AM") {
|
||||
outHours = 0;
|
||||
}
|
||||
}
|
||||
|
||||
value = leadingZero(outHours) + value;
|
||||
|
||||
if (!this.isHTML5 && this.options.twelvehour) {
|
||||
value = value + this.amOrPm;
|
||||
}
|
||||
|
||||
this.input.prop("value", value);
|
||||
if (value !== last) {
|
||||
this.input.trigger("change");
|
||||
if (!this.isInput) {
|
||||
this.element.trigger("change");
|
||||
}
|
||||
}
|
||||
|
||||
if (this.options.autoclose) {
|
||||
this.input.trigger("blur");
|
||||
}
|
||||
|
||||
this.raiseCallback(this.options.afterDone, "afterDone");
|
||||
};
|
||||
|
||||
// Remove clockpicker from input
|
||||
ClockPicker.prototype.remove = function() {
|
||||
this.element.removeData("clockpicker");
|
||||
this.input.off("focus.clockpicker click.clockpicker");
|
||||
this.addon.off("click.clockpicker");
|
||||
if (this.isShown) {
|
||||
this.hide();
|
||||
}
|
||||
if (this.isAppended) {
|
||||
$win.off("resize.clockpicker" + this.id);
|
||||
this.popover.remove();
|
||||
}
|
||||
};
|
||||
|
||||
// Extends $.fn.clockpicker
|
||||
$.fn.clockpicker = function(option) {
|
||||
var args = Array.prototype.slice.call(arguments, 1);
|
||||
|
||||
function handleClockPickerRequest() {
|
||||
var $this = $(this),
|
||||
data = $this.data("clockpicker");
|
||||
if (!data) {
|
||||
var options = $.extend(
|
||||
{},
|
||||
ClockPicker.DEFAULTS,
|
||||
$this.data(),
|
||||
typeof option == "object" && option
|
||||
);
|
||||
$this.data("clockpicker", new ClockPicker($this, options));
|
||||
} else {
|
||||
// Manual operations. show, hide, remove, getTime, e.g.
|
||||
if (typeof data[option] === "function") {
|
||||
return data[option].apply(data, args);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If we explicitly do a call on a single element then we can return the value (if needed)
|
||||
// This allows us, for example, to return the value of getTime
|
||||
if (this.length == 1) {
|
||||
var returnValue = handleClockPickerRequest.apply(this[0]);
|
||||
|
||||
// If we do not have any return value then return the object itself so you can chain
|
||||
return returnValue !== undefined ? returnValue : this;
|
||||
}
|
||||
|
||||
// If we do have a list then we do not care about return values
|
||||
return this.each(handleClockPickerRequest);
|
||||
};
|
||||
})(jQuery);
|
||||
7
html/webpage/assets/js/bootstrap4-clockpicker.min.js
vendored
Normal file
7
html/webpage/assets/js/bootstrap4-clockpicker.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
2
html/webpage/assets/js/flatpickr.js
Normal file
2
html/webpage/assets/js/flatpickr.js
Normal file
File diff suppressed because one or more lines are too long
729
html/webpage/assets/js/jquery-clockpicker.js
vendored
Normal file
729
html/webpage/assets/js/jquery-clockpicker.js
vendored
Normal file
@@ -0,0 +1,729 @@
|
||||
/*!
|
||||
* ClockPicker v0.0.7 (http://weareoutman.github.io/clockpicker/)
|
||||
* Copyright 2014 Wang Shenwei.
|
||||
* Licensed under MIT (https://github.com/weareoutman/clockpicker/blob/gh-pages/LICENSE)
|
||||
*/
|
||||
|
||||
;(function(){
|
||||
var $ = window.jQuery,
|
||||
$win = $(window),
|
||||
$doc = $(document),
|
||||
$body;
|
||||
|
||||
// Can I use inline svg ?
|
||||
var svgNS = 'http://www.w3.org/2000/svg',
|
||||
svgSupported = 'SVGAngle' in window && (function(){
|
||||
var supported,
|
||||
el = document.createElement('div');
|
||||
el.innerHTML = '<svg/>';
|
||||
supported = (el.firstChild && el.firstChild.namespaceURI) == svgNS;
|
||||
el.innerHTML = '';
|
||||
return supported;
|
||||
})();
|
||||
|
||||
// Can I use transition ?
|
||||
var transitionSupported = (function(){
|
||||
var style = document.createElement('div').style;
|
||||
return 'transition' in style ||
|
||||
'WebkitTransition' in style ||
|
||||
'MozTransition' in style ||
|
||||
'msTransition' in style ||
|
||||
'OTransition' in style;
|
||||
})();
|
||||
|
||||
// Listen touch events in touch screen device, instead of mouse events in desktop.
|
||||
var touchSupported = 'ontouchstart' in window,
|
||||
mousedownEvent = 'mousedown' + ( touchSupported ? ' touchstart' : ''),
|
||||
mousemoveEvent = 'mousemove.clockpicker' + ( touchSupported ? ' touchmove.clockpicker' : ''),
|
||||
mouseupEvent = 'mouseup.clockpicker' + ( touchSupported ? ' touchend.clockpicker' : '');
|
||||
|
||||
// Vibrate the device if supported
|
||||
var vibrate = navigator.vibrate ? 'vibrate' : navigator.webkitVibrate ? 'webkitVibrate' : null;
|
||||
|
||||
function createSvgElement(name) {
|
||||
return document.createElementNS(svgNS, name);
|
||||
}
|
||||
|
||||
function leadingZero(num) {
|
||||
return (num < 10 ? '0' : '') + num;
|
||||
}
|
||||
|
||||
// Get a unique id
|
||||
var idCounter = 0;
|
||||
function uniqueId(prefix) {
|
||||
var id = ++idCounter + '';
|
||||
return prefix ? prefix + id : id;
|
||||
}
|
||||
|
||||
// Clock size
|
||||
var dialRadius = 100,
|
||||
outerRadius = 80,
|
||||
// innerRadius = 80 on 12 hour clock
|
||||
innerRadius = 54,
|
||||
tickRadius = 13,
|
||||
diameter = dialRadius * 2,
|
||||
duration = transitionSupported ? 350 : 1;
|
||||
|
||||
// Popover template
|
||||
var tpl = [
|
||||
'<div class="popover clockpicker-popover">',
|
||||
'<div class="arrow"></div>',
|
||||
'<div class="popover-title">',
|
||||
'<span class="clockpicker-span-hours text-primary"></span>',
|
||||
' : ',
|
||||
'<span class="clockpicker-span-minutes"></span>',
|
||||
'<span class="clockpicker-span-am-pm"></span>',
|
||||
'</div>',
|
||||
'<div class="popover-content">',
|
||||
'<div class="clockpicker-plate">',
|
||||
'<div class="clockpicker-canvas"></div>',
|
||||
'<div class="clockpicker-dial clockpicker-hours"></div>',
|
||||
'<div class="clockpicker-dial clockpicker-minutes clockpicker-dial-out"></div>',
|
||||
'</div>',
|
||||
'<span class="clockpicker-am-pm-block">',
|
||||
'</span>',
|
||||
'</div>',
|
||||
'</div>'
|
||||
].join('');
|
||||
|
||||
// ClockPicker
|
||||
function ClockPicker(element, options) {
|
||||
var popover = $(tpl),
|
||||
plate = popover.find('.clockpicker-plate'),
|
||||
hoursView = popover.find('.clockpicker-hours'),
|
||||
minutesView = popover.find('.clockpicker-minutes'),
|
||||
amPmBlock = popover.find('.clockpicker-am-pm-block'),
|
||||
isInput = element.prop('tagName') === 'INPUT',
|
||||
input = isInput ? element : element.find('input'),
|
||||
addon = element.find('.input-group-addon'),
|
||||
self = this,
|
||||
timer;
|
||||
|
||||
this.id = uniqueId('cp');
|
||||
this.element = element;
|
||||
this.options = options;
|
||||
this.isAppended = false;
|
||||
this.isShown = false;
|
||||
this.currentView = 'hours';
|
||||
this.isInput = isInput;
|
||||
this.input = input;
|
||||
this.addon = addon;
|
||||
this.popover = popover;
|
||||
this.plate = plate;
|
||||
this.hoursView = hoursView;
|
||||
this.minutesView = minutesView;
|
||||
this.amPmBlock = amPmBlock;
|
||||
this.spanHours = popover.find('.clockpicker-span-hours');
|
||||
this.spanMinutes = popover.find('.clockpicker-span-minutes');
|
||||
this.spanAmPm = popover.find('.clockpicker-span-am-pm');
|
||||
this.amOrPm = "PM";
|
||||
|
||||
// Setup for for 12 hour clock if option is selected
|
||||
if (options.twelvehour) {
|
||||
|
||||
var amPmButtonsTemplate = ['<div class="clockpicker-am-pm-block">',
|
||||
'<button type="button" class="btn btn-sm btn-default clockpicker-button clockpicker-am-button">',
|
||||
'AM</button>',
|
||||
'<button type="button" class="btn btn-sm btn-default clockpicker-button clockpicker-pm-button">',
|
||||
'PM</button>',
|
||||
'</div>'].join('');
|
||||
|
||||
var amPmButtons = $(amPmButtonsTemplate);
|
||||
//amPmButtons.appendTo(plate);
|
||||
|
||||
////Not working b/c they are not shown when this runs
|
||||
//$('clockpicker-am-button')
|
||||
// .on("click", function() {
|
||||
// self.amOrPm = "AM";
|
||||
// $('.clockpicker-span-am-pm').empty().append('AM');
|
||||
// });
|
||||
//
|
||||
//$('clockpicker-pm-button')
|
||||
// .on("click", function() {
|
||||
// self.amOrPm = "PM";
|
||||
// $('.clockpicker-span-am-pm').empty().append('PM');
|
||||
// });
|
||||
|
||||
$('<button type="button" class="btn btn-sm btn-default clockpicker-button am-button">' + "AM" + '</button>')
|
||||
.on("click", function() {
|
||||
self.amOrPm = "AM";
|
||||
$('.clockpicker-span-am-pm').empty().append('AM');
|
||||
}).appendTo(this.amPmBlock);
|
||||
|
||||
|
||||
$('<button type="button" class="btn btn-sm btn-default clockpicker-button pm-button">' + "PM" + '</button>')
|
||||
.on("click", function() {
|
||||
self.amOrPm = 'PM';
|
||||
$('.clockpicker-span-am-pm').empty().append('PM');
|
||||
}).appendTo(this.amPmBlock);
|
||||
|
||||
}
|
||||
|
||||
if (! options.autoclose) {
|
||||
// If autoclose is not setted, append a button
|
||||
$('<button type="button" class="btn btn-sm btn-default btn-block clockpicker-button">' + options.donetext + '</button>')
|
||||
.click($.proxy(this.done, this))
|
||||
.appendTo(popover);
|
||||
}
|
||||
|
||||
// Placement and arrow align - make sure they make sense.
|
||||
if ((options.placement === 'top' || options.placement === 'bottom') && (options.align === 'top' || options.align === 'bottom')) options.align = 'left';
|
||||
if ((options.placement === 'left' || options.placement === 'right') && (options.align === 'left' || options.align === 'right')) options.align = 'top';
|
||||
|
||||
popover.addClass(options.placement);
|
||||
popover.addClass('clockpicker-align-' + options.align);
|
||||
|
||||
this.spanHours.click($.proxy(this.toggleView, this, 'hours'));
|
||||
this.spanMinutes.click($.proxy(this.toggleView, this, 'minutes'));
|
||||
|
||||
// Show or toggle
|
||||
input.on('focus.clockpicker click.clockpicker', $.proxy(this.show, this));
|
||||
addon.on('click.clockpicker', $.proxy(this.toggle, this));
|
||||
|
||||
// Build ticks
|
||||
var tickTpl = $('<div class="clockpicker-tick"></div>'),
|
||||
i, tick, radian, radius;
|
||||
|
||||
// Hours view
|
||||
if (options.twelvehour) {
|
||||
for (i = 1; i < 13; i += 1) {
|
||||
tick = tickTpl.clone();
|
||||
radian = i / 6 * Math.PI;
|
||||
radius = outerRadius;
|
||||
tick.css('font-size', '120%');
|
||||
tick.css({
|
||||
left: dialRadius + Math.sin(radian) * radius - tickRadius,
|
||||
top: dialRadius - Math.cos(radian) * radius - tickRadius
|
||||
});
|
||||
tick.html(i === 0 ? '00' : i);
|
||||
hoursView.append(tick);
|
||||
tick.on(mousedownEvent, mousedown);
|
||||
}
|
||||
} else {
|
||||
for (i = 0; i < 24; i += 1) {
|
||||
tick = tickTpl.clone();
|
||||
radian = i / 6 * Math.PI;
|
||||
var inner = i > 0 && i < 13;
|
||||
radius = inner ? innerRadius : outerRadius;
|
||||
tick.css({
|
||||
left: dialRadius + Math.sin(radian) * radius - tickRadius,
|
||||
top: dialRadius - Math.cos(radian) * radius - tickRadius
|
||||
});
|
||||
if (inner) {
|
||||
tick.css('font-size', '120%');
|
||||
}
|
||||
tick.html(i === 0 ? '00' : i);
|
||||
hoursView.append(tick);
|
||||
tick.on(mousedownEvent, mousedown);
|
||||
}
|
||||
}
|
||||
|
||||
// Minutes view
|
||||
for (i = 0; i < 60; i += 5) {
|
||||
tick = tickTpl.clone();
|
||||
radian = i / 30 * Math.PI;
|
||||
tick.css({
|
||||
left: dialRadius + Math.sin(radian) * outerRadius - tickRadius,
|
||||
top: dialRadius - Math.cos(radian) * outerRadius - tickRadius
|
||||
});
|
||||
tick.css('font-size', '120%');
|
||||
tick.html(leadingZero(i));
|
||||
minutesView.append(tick);
|
||||
tick.on(mousedownEvent, mousedown);
|
||||
}
|
||||
|
||||
// Clicking on minutes view space
|
||||
plate.on(mousedownEvent, function(e){
|
||||
if ($(e.target).closest('.clockpicker-tick').length === 0) {
|
||||
mousedown(e, true);
|
||||
}
|
||||
});
|
||||
|
||||
// Mousedown or touchstart
|
||||
function mousedown(e, space) {
|
||||
var offset = plate.offset(),
|
||||
isTouch = /^touch/.test(e.type),
|
||||
x0 = offset.left + dialRadius,
|
||||
y0 = offset.top + dialRadius,
|
||||
dx = (isTouch ? e.originalEvent.touches[0] : e).pageX - x0,
|
||||
dy = (isTouch ? e.originalEvent.touches[0] : e).pageY - y0,
|
||||
z = Math.sqrt(dx * dx + dy * dy),
|
||||
moved = false;
|
||||
|
||||
// When clicking on minutes view space, check the mouse position
|
||||
if (space && (z < outerRadius - tickRadius || z > outerRadius + tickRadius)) {
|
||||
return;
|
||||
}
|
||||
e.preventDefault();
|
||||
|
||||
// Set cursor style of body after 200ms
|
||||
var movingTimer = setTimeout(function(){
|
||||
$body.addClass('clockpicker-moving');
|
||||
}, 200);
|
||||
|
||||
// Place the canvas to top
|
||||
if (svgSupported) {
|
||||
plate.append(self.canvas);
|
||||
}
|
||||
|
||||
// Clock
|
||||
self.setHand(dx, dy, ! space, true);
|
||||
|
||||
// Mousemove on document
|
||||
$doc.off(mousemoveEvent).on(mousemoveEvent, function(e){
|
||||
e.preventDefault();
|
||||
var isTouch = /^touch/.test(e.type),
|
||||
x = (isTouch ? e.originalEvent.touches[0] : e).pageX - x0,
|
||||
y = (isTouch ? e.originalEvent.touches[0] : e).pageY - y0;
|
||||
if (! moved && x === dx && y === dy) {
|
||||
// Clicking in chrome on windows will trigger a mousemove event
|
||||
return;
|
||||
}
|
||||
moved = true;
|
||||
self.setHand(x, y, false, true);
|
||||
});
|
||||
|
||||
// Mouseup on document
|
||||
$doc.off(mouseupEvent).on(mouseupEvent, function(e){
|
||||
$doc.off(mouseupEvent);
|
||||
e.preventDefault();
|
||||
var isTouch = /^touch/.test(e.type),
|
||||
x = (isTouch ? e.originalEvent.changedTouches[0] : e).pageX - x0,
|
||||
y = (isTouch ? e.originalEvent.changedTouches[0] : e).pageY - y0;
|
||||
if ((space || moved) && x === dx && y === dy) {
|
||||
self.setHand(x, y);
|
||||
}
|
||||
if (self.currentView === 'hours') {
|
||||
self.toggleView('minutes', duration / 2);
|
||||
} else {
|
||||
if (options.autoclose) {
|
||||
self.minutesView.addClass('clockpicker-dial-out');
|
||||
setTimeout(function(){
|
||||
self.done();
|
||||
}, duration / 2);
|
||||
}
|
||||
}
|
||||
plate.prepend(canvas);
|
||||
|
||||
// Reset cursor style of body
|
||||
clearTimeout(movingTimer);
|
||||
$body.removeClass('clockpicker-moving');
|
||||
|
||||
// Unbind mousemove event
|
||||
$doc.off(mousemoveEvent);
|
||||
});
|
||||
}
|
||||
|
||||
if (svgSupported) {
|
||||
// Draw clock hands and others
|
||||
var canvas = popover.find('.clockpicker-canvas'),
|
||||
svg = createSvgElement('svg');
|
||||
svg.setAttribute('class', 'clockpicker-svg');
|
||||
svg.setAttribute('width', diameter);
|
||||
svg.setAttribute('height', diameter);
|
||||
var g = createSvgElement('g');
|
||||
g.setAttribute('transform', 'translate(' + dialRadius + ',' + dialRadius + ')');
|
||||
var bearing = createSvgElement('circle');
|
||||
bearing.setAttribute('class', 'clockpicker-canvas-bearing');
|
||||
bearing.setAttribute('cx', 0);
|
||||
bearing.setAttribute('cy', 0);
|
||||
bearing.setAttribute('r', 2);
|
||||
var hand = createSvgElement('line');
|
||||
hand.setAttribute('x1', 0);
|
||||
hand.setAttribute('y1', 0);
|
||||
var bg = createSvgElement('circle');
|
||||
bg.setAttribute('class', 'clockpicker-canvas-bg');
|
||||
bg.setAttribute('r', tickRadius);
|
||||
var fg = createSvgElement('circle');
|
||||
fg.setAttribute('class', 'clockpicker-canvas-fg');
|
||||
fg.setAttribute('r', 3.5);
|
||||
g.appendChild(hand);
|
||||
g.appendChild(bg);
|
||||
g.appendChild(fg);
|
||||
g.appendChild(bearing);
|
||||
svg.appendChild(g);
|
||||
canvas.append(svg);
|
||||
|
||||
this.hand = hand;
|
||||
this.bg = bg;
|
||||
this.fg = fg;
|
||||
this.bearing = bearing;
|
||||
this.g = g;
|
||||
this.canvas = canvas;
|
||||
}
|
||||
|
||||
raiseCallback(this.options.init);
|
||||
}
|
||||
|
||||
function raiseCallback(callbackFunction) {
|
||||
if (callbackFunction && typeof callbackFunction === "function") {
|
||||
callbackFunction();
|
||||
}
|
||||
}
|
||||
|
||||
// Default options
|
||||
ClockPicker.DEFAULTS = {
|
||||
'default': '', // default time, 'now' or '13:14' e.g.
|
||||
fromnow: 0, // set default time to * milliseconds from now (using with default = 'now')
|
||||
placement: 'bottom', // clock popover placement
|
||||
align: 'left', // popover arrow align
|
||||
donetext: '完成', // done button text
|
||||
autoclose: false, // auto close when minute is selected
|
||||
twelvehour: false, // change to 12 hour AM/PM clock from 24 hour
|
||||
vibrate: true // vibrate the device when dragging clock hand
|
||||
};
|
||||
|
||||
// Show or hide popover
|
||||
ClockPicker.prototype.toggle = function(){
|
||||
this[this.isShown ? 'hide' : 'show']();
|
||||
};
|
||||
|
||||
// Set popover position
|
||||
ClockPicker.prototype.locate = function(){
|
||||
var element = this.element,
|
||||
popover = this.popover,
|
||||
offset = element.offset(),
|
||||
width = element.outerWidth(),
|
||||
height = element.outerHeight(),
|
||||
placement = this.options.placement,
|
||||
align = this.options.align,
|
||||
styles = {},
|
||||
self = this;
|
||||
|
||||
popover.show();
|
||||
|
||||
// Place the popover
|
||||
switch (placement) {
|
||||
case 'bottom':
|
||||
styles.top = offset.top + height;
|
||||
break;
|
||||
case 'right':
|
||||
styles.left = offset.left + width;
|
||||
break;
|
||||
case 'top':
|
||||
styles.top = offset.top - popover.outerHeight();
|
||||
break;
|
||||
case 'left':
|
||||
styles.left = offset.left - popover.outerWidth();
|
||||
break;
|
||||
}
|
||||
|
||||
// Align the popover arrow
|
||||
switch (align) {
|
||||
case 'left':
|
||||
styles.left = offset.left;
|
||||
break;
|
||||
case 'right':
|
||||
styles.left = offset.left + width - popover.outerWidth();
|
||||
break;
|
||||
case 'top':
|
||||
styles.top = offset.top;
|
||||
break;
|
||||
case 'bottom':
|
||||
styles.top = offset.top + height - popover.outerHeight();
|
||||
break;
|
||||
}
|
||||
|
||||
popover.css(styles);
|
||||
};
|
||||
|
||||
// Show popover
|
||||
ClockPicker.prototype.show = function(e){
|
||||
// Not show again
|
||||
if (this.isShown) {
|
||||
return;
|
||||
}
|
||||
|
||||
raiseCallback(this.options.beforeShow);
|
||||
|
||||
var self = this;
|
||||
|
||||
// Initialize
|
||||
if (! this.isAppended) {
|
||||
// Append popover to body
|
||||
$body = $(document.body).append(this.popover);
|
||||
|
||||
// Reset position when resize
|
||||
$win.on('resize.clockpicker' + this.id, function(){
|
||||
if (self.isShown) {
|
||||
self.locate();
|
||||
}
|
||||
});
|
||||
|
||||
this.isAppended = true;
|
||||
}
|
||||
|
||||
// Get the time
|
||||
var value = ((this.input.prop('value') || this.options['default'] || '') + '').split(':');
|
||||
if (value[0] === 'now') {
|
||||
var now = new Date(+ new Date() + this.options.fromnow);
|
||||
value = [
|
||||
now.getHours(),
|
||||
now.getMinutes()
|
||||
];
|
||||
}
|
||||
this.hours = + value[0] || 0;
|
||||
this.minutes = + value[1] || 0;
|
||||
this.spanHours.html(leadingZero(this.hours));
|
||||
this.spanMinutes.html(leadingZero(this.minutes));
|
||||
|
||||
// Toggle to hours view
|
||||
this.toggleView('hours');
|
||||
|
||||
// Set position
|
||||
this.locate();
|
||||
|
||||
this.isShown = true;
|
||||
|
||||
// Hide when clicking or tabbing on any element except the clock, input and addon
|
||||
$doc.on('click.clockpicker.' + this.id + ' focusin.clockpicker.' + this.id, function(e){
|
||||
var target = $(e.target);
|
||||
if (target.closest(self.popover).length === 0 &&
|
||||
target.closest(self.addon).length === 0 &&
|
||||
target.closest(self.input).length === 0) {
|
||||
self.hide();
|
||||
}
|
||||
});
|
||||
|
||||
// Hide when ESC is pressed
|
||||
$doc.on('keyup.clockpicker.' + this.id, function(e){
|
||||
if (e.keyCode === 27) {
|
||||
self.hide();
|
||||
}
|
||||
});
|
||||
|
||||
raiseCallback(this.options.afterShow);
|
||||
};
|
||||
|
||||
// Hide popover
|
||||
ClockPicker.prototype.hide = function(){
|
||||
raiseCallback(this.options.beforeHide);
|
||||
|
||||
this.isShown = false;
|
||||
|
||||
// Unbinding events on document
|
||||
$doc.off('click.clockpicker.' + this.id + ' focusin.clockpicker.' + this.id);
|
||||
$doc.off('keyup.clockpicker.' + this.id);
|
||||
|
||||
this.popover.hide();
|
||||
|
||||
raiseCallback(this.options.afterHide);
|
||||
};
|
||||
|
||||
// Toggle to hours or minutes view
|
||||
ClockPicker.prototype.toggleView = function(view, delay){
|
||||
var raiseAfterHourSelect = false;
|
||||
if (view === 'minutes' && $(this.hoursView).css("visibility") === "visible") {
|
||||
raiseCallback(this.options.beforeHourSelect);
|
||||
raiseAfterHourSelect = true;
|
||||
}
|
||||
var isHours = view === 'hours',
|
||||
nextView = isHours ? this.hoursView : this.minutesView,
|
||||
hideView = isHours ? this.minutesView : this.hoursView;
|
||||
|
||||
this.currentView = view;
|
||||
|
||||
this.spanHours.toggleClass('text-primary', isHours);
|
||||
this.spanMinutes.toggleClass('text-primary', ! isHours);
|
||||
|
||||
// Let's make transitions
|
||||
hideView.addClass('clockpicker-dial-out');
|
||||
nextView.css('visibility', 'visible').removeClass('clockpicker-dial-out');
|
||||
|
||||
// Reset clock hand
|
||||
this.resetClock(delay);
|
||||
|
||||
// After transitions ended
|
||||
clearTimeout(this.toggleViewTimer);
|
||||
this.toggleViewTimer = setTimeout(function(){
|
||||
hideView.css('visibility', 'hidden');
|
||||
}, duration);
|
||||
|
||||
if (raiseAfterHourSelect) {
|
||||
raiseCallback(this.options.afterHourSelect);
|
||||
}
|
||||
};
|
||||
|
||||
// Reset clock hand
|
||||
ClockPicker.prototype.resetClock = function(delay){
|
||||
var view = this.currentView,
|
||||
value = this[view],
|
||||
isHours = view === 'hours',
|
||||
unit = Math.PI / (isHours ? 6 : 30),
|
||||
radian = value * unit,
|
||||
radius = isHours && value > 0 && value < 13 ? innerRadius : outerRadius,
|
||||
x = Math.sin(radian) * radius,
|
||||
y = - Math.cos(radian) * radius,
|
||||
self = this;
|
||||
if (svgSupported && delay) {
|
||||
self.canvas.addClass('clockpicker-canvas-out');
|
||||
setTimeout(function(){
|
||||
self.canvas.removeClass('clockpicker-canvas-out');
|
||||
self.setHand(x, y);
|
||||
}, delay);
|
||||
} else {
|
||||
this.setHand(x, y);
|
||||
}
|
||||
};
|
||||
|
||||
// Set clock hand to (x, y)
|
||||
ClockPicker.prototype.setHand = function(x, y, roundBy5, dragging){
|
||||
var radian = Math.atan2(x, - y),
|
||||
isHours = this.currentView === 'hours',
|
||||
unit = Math.PI / (isHours || roundBy5 ? 6 : 30),
|
||||
z = Math.sqrt(x * x + y * y),
|
||||
options = this.options,
|
||||
inner = isHours && z < (outerRadius + innerRadius) / 2,
|
||||
radius = inner ? innerRadius : outerRadius,
|
||||
value;
|
||||
|
||||
if (options.twelvehour) {
|
||||
radius = outerRadius;
|
||||
}
|
||||
|
||||
// Radian should in range [0, 2PI]
|
||||
if (radian < 0) {
|
||||
radian = Math.PI * 2 + radian;
|
||||
}
|
||||
|
||||
// Get the round value
|
||||
value = Math.round(radian / unit);
|
||||
|
||||
// Get the round radian
|
||||
radian = value * unit;
|
||||
|
||||
// Correct the hours or minutes
|
||||
if (options.twelvehour) {
|
||||
if (isHours) {
|
||||
if (value === 0) {
|
||||
value = 12;
|
||||
}
|
||||
} else {
|
||||
if (roundBy5) {
|
||||
value *= 5;
|
||||
}
|
||||
if (value === 60) {
|
||||
value = 0;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (isHours) {
|
||||
if (value === 12) {
|
||||
value = 0;
|
||||
}
|
||||
value = inner ? (value === 0 ? 12 : value) : value === 0 ? 0 : value + 12;
|
||||
} else {
|
||||
if (roundBy5) {
|
||||
value *= 5;
|
||||
}
|
||||
if (value === 60) {
|
||||
value = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Once hours or minutes changed, vibrate the device
|
||||
if (this[this.currentView] !== value) {
|
||||
if (vibrate && this.options.vibrate) {
|
||||
// Do not vibrate too frequently
|
||||
if (! this.vibrateTimer) {
|
||||
navigator[vibrate](10);
|
||||
this.vibrateTimer = setTimeout($.proxy(function(){
|
||||
this.vibrateTimer = null;
|
||||
}, this), 100);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this[this.currentView] = value;
|
||||
this[isHours ? 'spanHours' : 'spanMinutes'].html(leadingZero(value));
|
||||
|
||||
// If svg is not supported, just add an active class to the tick
|
||||
if (! svgSupported) {
|
||||
this[isHours ? 'hoursView' : 'minutesView'].find('.clockpicker-tick').each(function(){
|
||||
var tick = $(this);
|
||||
tick.toggleClass('active', value === + tick.html());
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Place clock hand at the top when dragging
|
||||
if (dragging || (! isHours && value % 5)) {
|
||||
this.g.insertBefore(this.hand, this.bearing);
|
||||
this.g.insertBefore(this.bg, this.fg);
|
||||
this.bg.setAttribute('class', 'clockpicker-canvas-bg clockpicker-canvas-bg-trans');
|
||||
} else {
|
||||
// Or place it at the bottom
|
||||
this.g.insertBefore(this.hand, this.bg);
|
||||
this.g.insertBefore(this.fg, this.bg);
|
||||
this.bg.setAttribute('class', 'clockpicker-canvas-bg');
|
||||
}
|
||||
|
||||
// Set clock hand and others' position
|
||||
var cx = Math.sin(radian) * radius,
|
||||
cy = - Math.cos(radian) * radius;
|
||||
this.hand.setAttribute('x2', cx);
|
||||
this.hand.setAttribute('y2', cy);
|
||||
this.bg.setAttribute('cx', cx);
|
||||
this.bg.setAttribute('cy', cy);
|
||||
this.fg.setAttribute('cx', cx);
|
||||
this.fg.setAttribute('cy', cy);
|
||||
};
|
||||
|
||||
// Hours and minutes are selected
|
||||
ClockPicker.prototype.done = function() {
|
||||
raiseCallback(this.options.beforeDone);
|
||||
this.hide();
|
||||
var last = this.input.prop('value'),
|
||||
value = leadingZero(this.hours) + ':' + leadingZero(this.minutes);
|
||||
if (this.options.twelvehour) {
|
||||
value = value + this.amOrPm;
|
||||
}
|
||||
|
||||
this.input.prop('value', value);
|
||||
if (value !== last) {
|
||||
this.input.triggerHandler('change');
|
||||
if (! this.isInput) {
|
||||
this.element.trigger('change');
|
||||
}
|
||||
}
|
||||
|
||||
if (this.options.autoclose) {
|
||||
this.input.trigger('blur');
|
||||
}
|
||||
|
||||
raiseCallback(this.options.afterDone);
|
||||
};
|
||||
|
||||
// Remove clockpicker from input
|
||||
ClockPicker.prototype.remove = function() {
|
||||
this.element.removeData('clockpicker');
|
||||
this.input.off('focus.clockpicker click.clockpicker');
|
||||
this.addon.off('click.clockpicker');
|
||||
if (this.isShown) {
|
||||
this.hide();
|
||||
}
|
||||
if (this.isAppended) {
|
||||
$win.off('resize.clockpicker' + this.id);
|
||||
this.popover.remove();
|
||||
}
|
||||
};
|
||||
|
||||
// Extends $.fn.clockpicker
|
||||
$.fn.clockpicker = function(option){
|
||||
var args = Array.prototype.slice.call(arguments, 1);
|
||||
return this.each(function(){
|
||||
var $this = $(this),
|
||||
data = $this.data('clockpicker');
|
||||
if (! data) {
|
||||
var options = $.extend({}, ClockPicker.DEFAULTS, $this.data(), typeof option == 'object' && option);
|
||||
$this.data('clockpicker', new ClockPicker($this, options));
|
||||
} else {
|
||||
// Manual operatsions. show, hide, remove, e.g.
|
||||
if (typeof data[option] === 'function') {
|
||||
data[option].apply(data, args);
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
}());
|
||||
@@ -78,6 +78,7 @@ $(document).ready(function () {
|
||||
$('#languagebanktablebody').empty();
|
||||
window.selectedlanguagerow = null;
|
||||
let $btnClear = $('#btnClear');
|
||||
let $btndefaultinit = $('#btnDefaultInit');
|
||||
let $btnAdd = $('#btnAdd');
|
||||
let $btnRemove = $('#btnRemove');
|
||||
let $btnEdit = $('#btnEdit');
|
||||
@@ -86,7 +87,6 @@ $(document).ready(function () {
|
||||
$btnRemove.prop('disabled', true);
|
||||
$btnEdit.prop('disabled', true);
|
||||
let APIURL = "LanguageLink/";
|
||||
let $findlanguage = $('#findlanguage');
|
||||
let $modal = $('#languagemodal');
|
||||
let $langid = $modal.find('#languagelinkindex');
|
||||
let $langtag = $modal.find('#languagelinktag');
|
||||
@@ -121,18 +121,6 @@ $(document).ready(function () {
|
||||
$cbChi.prop('checked', false);
|
||||
}
|
||||
|
||||
// $findlanguage.on('input', function () {
|
||||
// let searchTerm = $findlanguage.val().toLowerCase();
|
||||
// if (searchTerm.length > 0) {
|
||||
// window.selectedlanguagerow = null;
|
||||
// let filtered = window.languagebankdata.filter(item => item.tag.toLowerCase().includes(searchTerm) || item.language.toLowerCase().includes(searchTerm));
|
||||
// fill_languagebanktablebody(filtered);
|
||||
// } else {
|
||||
// window.selectedlanguagerow = null;
|
||||
// fill_languagebanktablebody(window.languagebankdata);
|
||||
// }
|
||||
// });
|
||||
|
||||
reloadLanguageBank(APIURL);
|
||||
$btnClear.click(() => {
|
||||
DoClear(APIURL, "LanguageLink", (okdata) => {
|
||||
@@ -141,8 +129,18 @@ $(document).ready(function () {
|
||||
}, (errdata) => {
|
||||
alert("Error clear languageLink : " + errdata.message);
|
||||
});
|
||||
|
||||
});
|
||||
$btndefaultinit.click(() => {
|
||||
if (confirm("Default Init will clear existing data and create default language link data. Cotinue ?")){
|
||||
fetchAPI(APIURL + "DefaultInit", "POST", {}, null, (okdata) => {
|
||||
reloadLanguageBank(APIURL);
|
||||
alert("Success default init languageLink : " + okdata.message);
|
||||
}, (errdata) => {
|
||||
alert("Error default init languageLink : " + errdata.message);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
$btnAdd.click(() => {
|
||||
// show modal with id 'languagemodal'
|
||||
$modal.modal('show');
|
||||
|
||||
12
html/webpage/assets/js/litepicker.js
Normal file
12
html/webpage/assets/js/litepicker.js
Normal file
File diff suppressed because one or more lines are too long
@@ -21,7 +21,7 @@ dtLog = null;
|
||||
function fill_logtablebody(vv) {
|
||||
dtLog.clear();
|
||||
if (!Array.isArray(vv) || vv.length === 0) {
|
||||
$('#btnExport').prop('disabled', true);
|
||||
//$('#btnExport').prop('disabled', true);
|
||||
return;
|
||||
}
|
||||
dtLog.rows.add(vv);
|
||||
@@ -29,7 +29,7 @@ function fill_logtablebody(vv) {
|
||||
|
||||
|
||||
$('#tablesize').text("Table Size: " + vv.length);
|
||||
$('#btnExport').prop('disabled', false);
|
||||
//$('#btnExport').prop('disabled', false);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -43,8 +43,10 @@ function reloadLogs(APIURL = "Log/", date, filter) {
|
||||
date: date,
|
||||
filter: filter
|
||||
})
|
||||
console.log("Loading logs with params: " + params.toString());
|
||||
window.logdata = [];
|
||||
fetchAPI(APIURL + "List?" + params.toString(), "GET", {}, null, (okdata) => {
|
||||
console.log("Logs loaded: " + okdata.length);
|
||||
if (Array.isArray(okdata)) {
|
||||
window.logdata.push(...okdata);
|
||||
fill_logtablebody(window.logdata);
|
||||
@@ -54,16 +56,33 @@ function reloadLogs(APIURL = "Log/", date, filter) {
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
datepicker = null;
|
||||
$btnGet = null;
|
||||
|
||||
$(document).ready(function () {
|
||||
console.log("log.js ready");
|
||||
let selectedlogdate = "";
|
||||
let logfilter = "";
|
||||
let APIURL = "Log/";
|
||||
$btnGet = $('#btnGet');
|
||||
|
||||
datepicker = new Litepicker({
|
||||
element: document.getElementById('logdate'),
|
||||
format: 'DD/MM/YYYY',
|
||||
lang: 'en-US',
|
||||
autoApply: true,
|
||||
singleMode: true,
|
||||
startDate: new Date(),
|
||||
onSelect: (date) => {
|
||||
selectedlogdate = date.format('DD/MM/YYYY');
|
||||
console.log("Selected date: " + selectedlogdate);
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
if (dtLog === null) {
|
||||
dtLog = new DataTable('#logtable', {
|
||||
dom: 'Bfrtip',
|
||||
data: [],
|
||||
pageLength: 25,
|
||||
columns: [
|
||||
@@ -72,36 +91,42 @@ $(document).ready(function () {
|
||||
{ title: "Time", data: "timenya" },
|
||||
{ title: "Machine", data: "machine" },
|
||||
{ title: "Description", data: "description" }
|
||||
]
|
||||
],
|
||||
buttons: ['print', 'pdf', 'excel']
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
if (!$('#logdate').val()) {
|
||||
const today = new Date();
|
||||
const dd = String(today.getDate()).padStart(2, '0');
|
||||
const mm = String(today.getMonth() + 1).padStart(2, '0');
|
||||
const yyyy = today.getFullYear();
|
||||
$('#logdate').val(`${yyyy}-${mm}-${dd}`);
|
||||
selectedlogdate = `${dd}-${mm}-${yyyy}`;
|
||||
reloadLogs(APIURL, selectedlogdate, logfilter);
|
||||
}
|
||||
$('#logdate').off('change').on('change', function () {
|
||||
const selected = $(this).val();
|
||||
if (selected) {
|
||||
const [year, month, day] = selected.split('-');
|
||||
selectedlogdate = `${day}-${month}-${year}`;
|
||||
reloadLogs(APIURL, selectedlogdate, logfilter);
|
||||
// findalldate is checkbox, if checked will disable datepicker
|
||||
$('#findalldate').off('change').on('change', function () {
|
||||
if ($(this).is(':checked')) {
|
||||
datepicker.disabled = true;
|
||||
selectedlogdate = "alldate";
|
||||
console.log("Find all date checked, omitting date filter");
|
||||
} else {
|
||||
datepicker.disabled = false;
|
||||
const date = datepicker.getDate();
|
||||
selectedlogdate = date.format('DD/MM/YYYY');
|
||||
console.log("Find all date unchecked, selected date: " + selectedlogdate);
|
||||
}
|
||||
});
|
||||
|
||||
$('#searchfilter').off('input').on('input', function () {
|
||||
logfilter = $(this).val();
|
||||
//reloadLogs(APIURL, selectedlogdate, logfilter);
|
||||
});
|
||||
$btnGet.click(function () {
|
||||
let checked = $('#findalldate').is(':checked');
|
||||
if (checked && logfilter.trim() === "") {
|
||||
alert("Please enter a filter when 'Find All Date' is checked to avoid large data load.");
|
||||
return;
|
||||
}
|
||||
//$(this).data('selectedlogdate', selectedlogdate);
|
||||
//$(this).data('logfilter', logfilter);
|
||||
reloadLogs(APIURL, selectedlogdate, logfilter);
|
||||
});
|
||||
$('#btnExport').off('click').on('click', function () {
|
||||
DoExport(APIURL, "log.xlsx", { date: selectedlogdate, filter: logfilter });
|
||||
});
|
||||
|
||||
selectedlogdate = datepicker.getDate().format('DD/MM/YYYY');
|
||||
console.log("Initial selected date: " + selectedlogdate);
|
||||
$btnGet.trigger('click'); // load logs on page load
|
||||
|
||||
});
|
||||
@@ -25,22 +25,38 @@
|
||||
* @property {JQuery<HTMLElement> | null} ip - The jQuery result should be <h6> element.
|
||||
* @property {JQuery<HTMLElement> | null} buffer - The jQuery result should be <h6> element.
|
||||
* @property {JQuery<HTMLElement> | null} status - The jQuery result should be <p> element.
|
||||
* @property {JQuery<HTMLElement> | null} filename - The jQuery result should be <h6> element.
|
||||
* @property {JQuery<HTMLElement> | null} duration - The jQuery result should be <h6> element.
|
||||
* @property {JQuery<HTMLElement> | null} elapsed - The jQuery result should be <h6> element.
|
||||
* @property {JQuery<HTMLElement> | null} broadcastzones - The jQuery result should be <h6> element.
|
||||
* @property {JQuery<HTMLElement> | null} vu - The jQuery result should be <progress-bar> element.
|
||||
*/
|
||||
|
||||
function getCardByIndex(index) {
|
||||
let cardname = "ch" + index.toString().padStart(2, '0');
|
||||
let obj = {
|
||||
// title is <h4> element wiht id `streamertitle${index}`, with index as two digit number, e.g. 01, 02, 03
|
||||
title: $(`#streamertitle${index.toString().padStart(2, '0')}`),
|
||||
//title: $(`#streamertitle${index.toString().padStart(2, '0')}`),
|
||||
// ip is <h6> element with id `streamerip${index}`, with index as two digit number, e.g. 01, 02, 03
|
||||
ip: $(`#streamerip${index.toString().padStart(2, '0')}`),
|
||||
//ip: $(`#streamerip${index.toString().padStart(2, '0')}`),
|
||||
// buffer is <h6> element with id `streamerbuffer${index}`, with index as two digit number, e.g. 01, 02, 03
|
||||
buffer: $(`#streamerbuffer${index.toString().padStart(2, '0')}`),
|
||||
//buffer: $(`#streamerbuffer${index.toString().padStart(2, '0')}`),
|
||||
// status is <p> element with id `streamerstatus${index}`, with index as two digit number, e.g. 01, 02, 03
|
||||
status: $(`#streamerstatus${index.toString().padStart(2, '0')}`),
|
||||
//status: $(`#streamerstatus${index.toString().padStart(2, '0')}`),
|
||||
// vu is <progress-bar> element with id `streamervu${index}`, with index as two digit number, e.g. 01, 02, 03
|
||||
vu: $(`#streamervu${index.toString().padStart(2, '0')} .progress-bar`),
|
||||
//vu: $(`#streamervu${index.toString().padStart(2, '0')} .progress-bar`),
|
||||
card: $(`#${cardname}`),
|
||||
title: $(`#${cardname} .streamertitle`),
|
||||
ip: $(`#${cardname} .streamerip`),
|
||||
buffer: $(`#${cardname} .streamerbuffer`),
|
||||
status: $(`#${cardname} .streamerstatus`),
|
||||
vu: $(`#${cardname} .streamervu .progress-bar`),
|
||||
filename: $(`#${cardname} .streamerfile`),
|
||||
duration: $(`#${cardname} .streamerduration`),
|
||||
elapsed: $(`#${cardname} .streamerelapsed`),
|
||||
broadcastzones: $(`#${cardname} .streamerzones`),
|
||||
}
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
@@ -64,29 +80,71 @@ function UpdateStreamerCard(values) {
|
||||
values = [];
|
||||
}
|
||||
|
||||
let visiblilitychanged = false;
|
||||
|
||||
for (let i = 1; i <= 64; i++) {
|
||||
let vv = values.find(v => v.index === i);
|
||||
let card = getCardByIndex(i);
|
||||
const vv = values.find(v => v.index === i);
|
||||
const cardname = "ch" + i.toString().padStart(2, '0');
|
||||
const $card = $(`#${cardname}`);
|
||||
|
||||
|
||||
if (vv) {
|
||||
// there is value for this index
|
||||
if (card.title) card.title.text(vv.channel ? vv.channel : `Channel ${i.toString().padStart(2, '0')}`);
|
||||
if (card.ip) card.ip.text(`IP Address: ${vv.ipaddress ? vv.ipaddress : 'N/A'}`);
|
||||
if (card.buffer) card.buffer.text(`Buffer: ${vv.bufferRemain !== undefined && vv.bufferRemain !== null ? vv.bufferRemain.toString() : 'N/A'}`);
|
||||
if (card.status) card.status.text(`Status: ${vv.isPlaying ? 'Playing' : 'Idle'}`);
|
||||
if (card.vu) {
|
||||
setProgress(i, card.vu, vv.vu, 100);
|
||||
// ada data untuk index i
|
||||
if ($card.length > 0) {
|
||||
// ada card untuk index i, show card
|
||||
if ($card.hasClass('d-none')) {
|
||||
visiblilitychanged = true;
|
||||
$card.removeClass('d-none');
|
||||
$card.closest('.streamercol').removeClass('d-none'); // show the column as well
|
||||
}
|
||||
const $title = $(`#${cardname} .streamertitle`);
|
||||
const $ip = $(`#${cardname} .streamerip`);
|
||||
const $buffer = $(`#${cardname} .streamerbuffer`);
|
||||
const $status = $(`#${cardname} .streamerstatus`);
|
||||
const $vu = $(`#${cardname} .streamervu .progress-bar`);
|
||||
const $filename = $(`#${cardname} .streamerfile`);
|
||||
const $duration = $(`#${cardname} .streamerduration`);
|
||||
const $elapsed = $(`#${cardname} .streamerelapsed`);
|
||||
const $broadcastzones = $(`#${cardname} .streamerzones`);
|
||||
|
||||
//console.log(`Updating card for index ${i}`, vv);
|
||||
$title.text(vv.channel ? vv.channel : `Channel ${i.toString().padStart(2, '0')}`);
|
||||
$ip.text(vv.ipaddress ? vv.ipaddress : 'N/A');
|
||||
$buffer.text(vv.bufferRemain !== undefined && vv.bufferRemain !== null ? vv.bufferRemain.toString() : 'N/A');
|
||||
$status.text(vv.isPlaying ? 'Playing' : 'Idle');
|
||||
setProgress(i, $vu, vv.vu ? vv.vu : 0, 100);
|
||||
$filename.text(vv.filename ? vv.filename : 'N/A');
|
||||
$duration.text(vv.duration ? vv.duration : 'N/A');
|
||||
$elapsed.text(vv.elapsed ? vv.elapsed : 'N/A');
|
||||
$broadcastzones.text(vv.broadcastzones ? vv.broadcastzones : 'N/A');
|
||||
}
|
||||
} else {
|
||||
// no value for this index, disable the card
|
||||
if (card.title) card.title.text(`Channel ${i.toString().padStart(2, '0')}`);
|
||||
if (card.ip) card.ip.text(`IP Address: N/A`);
|
||||
if (card.buffer) card.buffer.text(`Buffer: N/A`);
|
||||
if (card.status) card.status.text(`Status: Disconnected`);
|
||||
if (card.vu) {
|
||||
setProgress(i, card.vu, 0, 100);
|
||||
// tidak ada data untuk index i, hide card
|
||||
if ($card.length > 0) {
|
||||
// ada card untuk index i, hide card
|
||||
if (!$card.hasClass('d-none')) {
|
||||
visiblilitychanged = true;
|
||||
$card.addClass('d-none');
|
||||
$card.closest('.streamercol').addClass('d-none'); // hide the column as well
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// hide rows that have all cards hidden
|
||||
if (visiblilitychanged) {
|
||||
$('.streamerrow').each(function () {
|
||||
const $row = $(this);
|
||||
const visiblecards = $row.find('.streamercard:not(.d-none)');
|
||||
if (visiblecards.length === 0) {
|
||||
$row.addClass('d-none');
|
||||
} else {
|
||||
$row.removeClass('d-none');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -22,6 +22,15 @@ window.schedulebankdata = [];
|
||||
window.selectedschedulerow = null;
|
||||
|
||||
dtScheduleBank = null;
|
||||
dtTodaySchedule = null;
|
||||
|
||||
function fill_todayscheduletablebody(vv) {
|
||||
dtTodaySchedule.clear();
|
||||
if (!Array.isArray(vv) || vv.length === 0) return;
|
||||
|
||||
dtTodaySchedule.rows.add(vv);
|
||||
dtTodaySchedule.draw();
|
||||
}
|
||||
|
||||
/**
|
||||
* Fill schedulebank table body with values
|
||||
@@ -34,7 +43,6 @@ function fill_schedulebanktablebody(vv) {
|
||||
dtScheduleBank.rows.add(vv);
|
||||
dtScheduleBank.draw();
|
||||
|
||||
|
||||
$('#schedulebanktable tbody').off('click').on('click', 'tr', function () {
|
||||
// if no data
|
||||
if (!dtScheduleBank) return;
|
||||
@@ -118,6 +126,22 @@ function reloadTimerBank(APIURL = "ScheduleBank/") {
|
||||
});
|
||||
}
|
||||
|
||||
function reloadTodaySchedule(APIURL = "ScheduleBank/") {
|
||||
fetchAPI(APIURL + "TodaySchedule", "GET", {}, null, (okdata) => {
|
||||
if (Array.isArray(okdata)) {
|
||||
console.log("Today's Schedule: ", okdata);
|
||||
fill_todayscheduletablebody(okdata);
|
||||
}
|
||||
}, (errdata) => {
|
||||
alert("Error loading today's schedule : " + errdata.message);
|
||||
});
|
||||
}
|
||||
|
||||
dayViewMode = 'all'; // all, everyday, monday, tuesday, wednesday, thursday, friday, saturday, sunday
|
||||
scheduledate = null; // Litepicker instance for schedule date selection
|
||||
scheduletime = null; // time picker instance for schedule time selection
|
||||
$schedulemodal = null; // schedule modal jQuery object
|
||||
|
||||
$(document).ready(function () {
|
||||
console.log("schedulebank.js loaded successfully");
|
||||
$('#schedulebanktablebody').empty();
|
||||
@@ -132,8 +156,11 @@ $(document).ready(function () {
|
||||
$btnRemove.prop('disabled', true);
|
||||
let APIURL = "ScheduleBank/";
|
||||
|
||||
|
||||
|
||||
if (dtScheduleBank === null) {
|
||||
dtScheduleBank = new DataTable('#schedulebanktable', {
|
||||
dom: 'Bfrtip',
|
||||
data: [],
|
||||
pageLength: 25,
|
||||
columns: [
|
||||
@@ -141,25 +168,99 @@ $(document).ready(function () {
|
||||
{ title: "Description", data: "description" },
|
||||
{ title: "Day", data: "day" },
|
||||
{ title: "Time", data: "time" },
|
||||
{ title: "Sound Path", data: "soundpath" },
|
||||
{ title: "Message", data: "soundpath" },
|
||||
{ title: "Repeat", data: "repeat" },
|
||||
{ title: "Enable", data: "enable" },
|
||||
{ title: "Broadcast Zones", data: "broadcastZones" },
|
||||
{ title: "Language", data: "language" }
|
||||
],
|
||||
buttons: ['print', 'pdf', {
|
||||
extend: 'collection',
|
||||
text: 'View',
|
||||
className: 'btn btn-outline-secondary',
|
||||
buttons: [
|
||||
{text: 'All', className: 'btn btn-outline-primary', action() { dayViewMode = 'all'; dtScheduleBank.draw(); } },
|
||||
{ text: 'Everyday', className: 'btn btn-outline-primary', action() { dayViewMode = 'everyday'; dtScheduleBank.draw(); } },
|
||||
{ text: 'Monday', className: 'btn btn-outline-primary', action() { dayViewMode = 'monday'; dtScheduleBank.draw(); } },
|
||||
{ text: 'Tuesday', className: 'btn btn-outline-primary', action() { dayViewMode = 'tuesday'; dtScheduleBank.draw(); } },
|
||||
{ text: 'Wednesday', className: 'btn btn-outline-primary', action() { dayViewMode = 'wednesday'; dtScheduleBank.draw(); } },
|
||||
{ text: 'Thursday', className: 'btn btn-outline-primary', action() { dayViewMode = 'thursday'; dtScheduleBank.draw(); } },
|
||||
{ text: 'Friday', className: 'btn btn-outline-primary', action() { dayViewMode = 'friday'; dtScheduleBank.draw(); } },
|
||||
{ text: 'Saturday', className: 'btn btn-outline-primary', action() { dayViewMode = 'saturday'; dtScheduleBank.draw(); } },
|
||||
{ text: 'Sunday', className: 'btn btn-outline-primary', action() { dayViewMode = 'sunday'; dtScheduleBank.draw(); } },
|
||||
{text: 'Special Date', className: 'btn btn-outline-primary', action() { dayViewMode = 'specialdate'; dtScheduleBank.draw(); } }
|
||||
]
|
||||
}]
|
||||
});
|
||||
}
|
||||
|
||||
if (dtTodaySchedule === null) {
|
||||
dtTodaySchedule = new DataTable('#todaytable', {
|
||||
dom: 'Bfrtip',
|
||||
data: [],
|
||||
pageLength: 25,
|
||||
columns: [
|
||||
{ title: "No", data: "index" },
|
||||
{ title: "Description", data: "description" },
|
||||
{ title: "Day", data: "day" },
|
||||
{ title: "Time", data: "time" },
|
||||
{ title: "Message", data: "soundpath" },
|
||||
{ title: "Repeat", data: "repeat" },
|
||||
{ title: "Enable", data: "enable" },
|
||||
{ title: "Broadcast Zones", data: "broadcastZones" },
|
||||
{ title: "Language", data: "language" }
|
||||
],
|
||||
buttons: ['print', 'pdf']
|
||||
});
|
||||
}
|
||||
|
||||
let $schedulemodal = $('#schedulemodal');
|
||||
$.fn.dataTable.ext.search.push(function (settings, data, dataIndex, rowData) {
|
||||
if (settings.nTable.id !== 'schedulebanktable') return true;
|
||||
switch (dayViewMode) {
|
||||
case 'everyday':
|
||||
return rowData.day.toLowerCase() === 'everyday';
|
||||
case 'monday':
|
||||
return rowData.day.toLowerCase() === 'monday';
|
||||
case 'tuesday':
|
||||
return rowData.day.toLowerCase() === 'tuesday';
|
||||
case 'wednesday':
|
||||
return rowData.day.toLowerCase() === 'wednesday';
|
||||
case 'thursday':
|
||||
return rowData.day.toLowerCase() === 'thursday';
|
||||
case 'friday':
|
||||
return rowData.day.toLowerCase() === 'friday';
|
||||
case 'saturday':
|
||||
return rowData.day.toLowerCase() === 'saturday';
|
||||
case 'sunday':
|
||||
return rowData.day.toLowerCase() === 'sunday';
|
||||
case 'specialdate':
|
||||
// match dd/mm/yyyy format
|
||||
return /^\d{2}\/\d{2}\/\d{4}$/.test(rowData.day);
|
||||
default:
|
||||
// 'all' include in here
|
||||
return true;
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
$schedulemodal = $('#schedulemodal');
|
||||
// text input
|
||||
let $scheduleid = $schedulemodal.find('#scheduleid');
|
||||
// text input
|
||||
let $scheduledescription = $schedulemodal.find('#scheduledescription');
|
||||
|
||||
// number input 0-23
|
||||
let $schedulehour = $schedulemodal.find('#schedulehour');
|
||||
//let $schedulehour = $schedulemodal.find('#schedulehour');
|
||||
// number input 0-59
|
||||
let $scheduleminute = $schedulemodal.find('#scheduleminute');
|
||||
//let $scheduleminute = $schedulemodal.find('#scheduleminute');
|
||||
scheduletime = flatpickr("#scheduletime",{
|
||||
enableTime: true,
|
||||
noCalendar: true, // time only
|
||||
dateFormat: "H:i", // HH:mm format
|
||||
time_24hr: true, // firce 24-hour format
|
||||
minuteIncrement: 1,
|
||||
defaultDate: new Date()
|
||||
});
|
||||
// select2 for message
|
||||
let $schedulemessage = $schedulemodal.find('#schedulemessage');
|
||||
// number input 0-5
|
||||
@@ -176,24 +277,41 @@ $(document).ready(function () {
|
||||
let $weeklyselect = $schedulemodal.find('#weeklyselect');
|
||||
// radio button for specific date
|
||||
let $schedulespecialdate = $schedulemodal.find('#schedulespecialdate');
|
||||
|
||||
|
||||
scheduledate = new Litepicker({
|
||||
element: document.getElementById('scheduledate'),
|
||||
format: 'DD/MM/YYYY',
|
||||
lang: 'en-US',
|
||||
autoApply: true,
|
||||
singleMode: true,
|
||||
startDate: new Date(),
|
||||
onSelect: (date) => {
|
||||
console.log("Selected special date: " + date.format('DD/MM/YYYY'));
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
// date input
|
||||
let $scheduledate = $schedulemodal.find('#scheduledate');
|
||||
//let $scheduledate = $schedulemodal.find('#scheduledate');
|
||||
// select2 for language
|
||||
let $languageselect = $schedulemodal.find('#languageselect');
|
||||
|
||||
$schedulespecialdate.off('change').on('change', function () {
|
||||
if ($(this).is(':checked')) {
|
||||
$scheduledate.prop('disabled', false);
|
||||
//$scheduledate.prop('disabled', false);
|
||||
scheduledate.disabled = false
|
||||
} else {
|
||||
$scheduledate.prop('disabled', true);
|
||||
//$scheduledate.prop('disabled', true);
|
||||
scheduledate.disabled = true
|
||||
}
|
||||
});
|
||||
|
||||
function clearScheduleModal() {
|
||||
$scheduleid.prop('disabled', true).val('');
|
||||
$scheduledescription.val('');
|
||||
$schedulehour.val('0');
|
||||
$scheduleminute.val('0');
|
||||
//$schedulehour.val('0');
|
||||
//$scheduleminute.val('0');
|
||||
$schedulerepeat.val('1');
|
||||
$scheduleenable.prop('checked', true);
|
||||
$scheduleeveryday.prop('checked', false);
|
||||
@@ -214,7 +332,8 @@ $(document).ready(function () {
|
||||
dropdownParent: $('#schedulemodal')
|
||||
});
|
||||
|
||||
$scheduledate.prop('disabled', true).val('');
|
||||
//$scheduledate.prop('disabled', true).val('');
|
||||
scheduledate.disabled = true;
|
||||
$schedulezones.empty().select2({
|
||||
data: window.BroadcastZoneList.map(zone => ({ id: zone.description, text: zone.description })),
|
||||
placeholder: 'Select broadcast zones',
|
||||
@@ -237,46 +356,28 @@ $(document).ready(function () {
|
||||
$scheduleeveryday.off('change').on('change', function () {
|
||||
if ($(this).is(':checked')) {
|
||||
$weeklyselect.prop('disabled', true);
|
||||
$scheduledate.prop('disabled', true);
|
||||
//$scheduledate.prop('disabled', true);
|
||||
scheduledate.disabled = true;
|
||||
}
|
||||
});
|
||||
$scheduleweekly.off('change').on('change', function () {
|
||||
if ($(this).is(':checked')) {
|
||||
$weeklyselect.prop('disabled', false);
|
||||
$scheduledate.prop('disabled', true);
|
||||
} else {
|
||||
$weeklyselect.prop('disabled', true);
|
||||
//$scheduledate.prop('disabled', true);
|
||||
scheduledate.disabled = true;
|
||||
}
|
||||
});
|
||||
$schedulespecialdate.off('change').on('change', function () {
|
||||
if ($(this).is(':checked')) {
|
||||
$weeklyselect.prop('disabled', true);
|
||||
$scheduledate.prop('disabled', false);
|
||||
} else {
|
||||
$scheduledate.prop('disabled', true);
|
||||
//$scheduledate.prop('disabled', false);
|
||||
scheduledate.disabled = false;
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
let $findschedule = $('#findschedule');
|
||||
// $findschedule.off('input').on('input', function () {
|
||||
// let searchTerm = $findschedule.val().toLowerCase();
|
||||
// if (searchTerm.length > 0) {
|
||||
// window.selectedschedulerow = null;
|
||||
// let filtered = window.schedulebankdata.filter(item =>
|
||||
// item.description.toLowerCase().includes(searchTerm)
|
||||
// || item.soundpath.toLowerCase().includes(searchTerm)
|
||||
// || item.broadcastZones.toLowerCase().includes(searchTerm));
|
||||
// fill_schedulebanktablebody(filtered);
|
||||
// } else {
|
||||
// window.selectedschedulerow = null;
|
||||
// fill_schedulebanktablebody(window.schedulebankdata);
|
||||
// }
|
||||
// });
|
||||
|
||||
|
||||
reloadTimerBank(APIURL);
|
||||
reloadTodaySchedule(APIURL);
|
||||
reloadBroadcastZones();
|
||||
getLanguages();
|
||||
getScheduledDays();
|
||||
@@ -284,6 +385,7 @@ $(document).ready(function () {
|
||||
$btnClear.click(() => {
|
||||
DoClear(APIURL, "Timerbank", (okdata) => {
|
||||
reloadTimerBank(APIURL);
|
||||
reloadTodaySchedule(APIURL);
|
||||
alert("Success clear schedulebank : " + okdata.message);
|
||||
}, (errdata) => {
|
||||
alert("Error clear schedulebank : " + errdata.message);
|
||||
@@ -292,7 +394,9 @@ $(document).ready(function () {
|
||||
});
|
||||
$btnAdd.click(() => {
|
||||
$schedulemodal.modal('show');
|
||||
|
||||
clearScheduleModal();
|
||||
$scheduleeveryday.prop('checked', true).trigger('click');
|
||||
|
||||
$schedulemodal.off('click.scheduleclose').on('click.scheduleclose', '#scheduleclose', function () {
|
||||
$schedulemodal.modal('hide');
|
||||
@@ -308,16 +412,16 @@ $(document).ready(function () {
|
||||
if ($scheduleeveryday.is(':checked')) {
|
||||
_Day = "Everyday";
|
||||
} else if ($schedulespecialdate.is(':checked')) {
|
||||
_Day = Convert_input_date_to_string($scheduledate.val());
|
||||
_Day = Convert_input_date_to_string(scheduledate.getDate().format('DD/MM/YYYY'));
|
||||
} else if ($scheduleweekly.is(':checked')) {
|
||||
_Day = $weeklyselect.val();
|
||||
}
|
||||
const Language = $languageselect.val().join(';');
|
||||
const broadcastZones = $schedulezones.val().join(';');
|
||||
// Format time as HH:mm
|
||||
const hour = parseInt($schedulehour.val(), 10);
|
||||
const minute = parseInt($scheduleminute.val(), 10);
|
||||
const _Time = `${hour.toString().padStart(2, '0')}:${minute.toString().padStart(2, '0')}`;
|
||||
//const hour = parseInt($schedulehour.val(), 10);
|
||||
//const minute = parseInt($scheduleminute.val(), 10);
|
||||
const _Time = $('#scheduletime').val();
|
||||
if (Description.length > 0) {
|
||||
if (_Day.length > 0) {
|
||||
if (Message.length > 0) {
|
||||
@@ -334,10 +438,12 @@ $(document).ready(function () {
|
||||
BroadcastZones: broadcastZones,
|
||||
Language: Language
|
||||
};
|
||||
console.log("Adding schedule: ", scheduleObj);
|
||||
|
||||
fetchAPI(APIURL + "Add", "POST", {}, scheduleObj, (okdata) => {
|
||||
alert("Success add schedule: " + okdata.message);
|
||||
reloadTimerBank(APIURL);
|
||||
reloadTodaySchedule(APIURL);
|
||||
alert("Success add schedule: " + okdata.message);
|
||||
}, (errdata) => {
|
||||
alert("Error add schedule: " + errdata.message);
|
||||
});
|
||||
@@ -363,9 +469,11 @@ $(document).ready(function () {
|
||||
broadcastZones: window.selectedschedulerow.broadcastZones,
|
||||
language: window.selectedschedulerow.language
|
||||
}
|
||||
|
||||
if (confirm(`Are you sure to delete schedule [${sr.index}] Description=${sr.description}?`)) {
|
||||
fetchAPI(APIURL + "DeleteByIndex/" + sr.index, "DELETE", {}, null, (okdata) => {
|
||||
reloadTimerBank(APIURL);
|
||||
reloadTodaySchedule(APIURL);
|
||||
alert("Success delete schedule : " + okdata.message);
|
||||
}, (errdata) => {
|
||||
alert("Error delete schedule : " + errdata.message);
|
||||
@@ -387,6 +495,7 @@ $(document).ready(function () {
|
||||
BroadcastZones: window.selectedschedulerow.broadcastZones,
|
||||
Language: window.selectedschedulerow.language
|
||||
}
|
||||
console.log("Editing schedule: ", sr);
|
||||
if (confirm(`Are you sure to edit schedule [${sr.index}] Description=${sr.Description}?`)) {
|
||||
$schedulemodal.modal('show');
|
||||
clearScheduleModal();
|
||||
@@ -395,16 +504,24 @@ $(document).ready(function () {
|
||||
$scheduleid.val(sr.index);
|
||||
$scheduledescription.val(sr.Description);
|
||||
let [hour, minute] = sr.Time.split(':').map(num => parseInt(num, 10));
|
||||
$schedulehour.val(hour.toString());
|
||||
$scheduleminute.val(minute.toString());
|
||||
//$schedulehour.val(hour.toString());
|
||||
//$scheduleminute.val(minute.toString());
|
||||
scheduletime.setDate(sr.Time,true, "H:i");
|
||||
$schedulemessage.val(sr.Soundpath).trigger('change');
|
||||
$schedulerepeat.val(sr.Repeat);
|
||||
$scheduleenable.prop('checked', sr.Enable.toLowerCase() === 'true');
|
||||
$scheduleenable.prop('checked', sr.Enable);
|
||||
$languageselect.val(sr.Language.split(';')).trigger('change');
|
||||
$schedulezones.val(sr.BroadcastZones.split(';')).trigger('change');
|
||||
switch (sr.Day) {
|
||||
case 'Everyday':
|
||||
$scheduleeveryday.click();
|
||||
// make the everyday radio button checked
|
||||
$scheduleeveryday.prop('checked', true);
|
||||
|
||||
$weeklyselect.val(null).trigger('change');
|
||||
$weeklyselect.prop('disabled', true);
|
||||
|
||||
//$scheduledate.prop('disabled', true);
|
||||
scheduledate.disabled = true;
|
||||
break;
|
||||
case 'Sunday':
|
||||
case 'Monday':
|
||||
@@ -413,15 +530,28 @@ $(document).ready(function () {
|
||||
case 'Thursday':
|
||||
case 'Friday':
|
||||
case 'Saturday':
|
||||
$scheduleweekly.click();
|
||||
$scheduleweekly.prop('checked', true);
|
||||
|
||||
$weeklyselect.val(sr.Day).trigger('change');
|
||||
$weeklyselect.prop('disabled', false);
|
||||
|
||||
//$scheduledate.prop('disabled', true);
|
||||
scheduledate.disabled = true;
|
||||
break;
|
||||
default:
|
||||
console.log("Assuming special date for Day: ", sr.Day);
|
||||
// check if the day is in format dd/mm/yyyy
|
||||
// and set the special date radio button and date input
|
||||
if (/^\d{2}\/\d{2}\/\d{4}$/.test(sr.Day)) {
|
||||
$schedulespecialdate.click();
|
||||
$scheduledate.val(Convert_string_to_input_date(sr.Day));
|
||||
$schedulespecialdate.prop('checked', true);
|
||||
|
||||
//$scheduledate.val(Convert_string_to_input_date(sr.Day));
|
||||
// $scheduledate.prop('disabled', false);
|
||||
scheduledate.setDate(dayjs(sr.Day,'DD/MM/YYYY'));
|
||||
scheduledate.disabled = false;
|
||||
|
||||
$weeklyselect.val(null).trigger('change');
|
||||
$weeklyselect.prop('disabled', true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -440,7 +570,8 @@ $(document).ready(function () {
|
||||
Day = "Everyday";
|
||||
} else if ($schedulespecialdate.is(':checked')) {
|
||||
// convert date from yyyy-mm-dd to dd/mm/yyyy
|
||||
Day = Convert_input_date_to_string($scheduledate.val());
|
||||
//Day = Convert_input_date_to_string($scheduledate.val());
|
||||
Day = scheduledate.getDate().format('DD/MM/YYYY');
|
||||
} else if ($scheduleweekly.is(':checked')) {
|
||||
Day = $weeklyselect.val();
|
||||
}
|
||||
@@ -448,9 +579,9 @@ $(document).ready(function () {
|
||||
const BroadcastZones = $schedulezones.val().join(';');
|
||||
const Language = $languageselect.val().join(';');
|
||||
// Format time as HH:mm
|
||||
const hour = parseInt($schedulehour.val(), 10);
|
||||
const minute = parseInt($scheduleminute.val(), 10);
|
||||
const Time = `${hour.toString().padStart(2, '0')}:${minute.toString().padStart(2, '0')}`;
|
||||
// const hour = parseInt($schedulehour.val(), 10);
|
||||
//const minute = parseInt($scheduleminute.val(), 10);
|
||||
const Time = $('#scheduletime').val();
|
||||
if (Description && Description.length > 0) {
|
||||
if (Day && Day.length > 0) {
|
||||
if (Soundpath && Soundpath.length > 0) {
|
||||
@@ -472,6 +603,7 @@ $(document).ready(function () {
|
||||
fetchAPI(APIURL + "UpdateByIndex/" + sr.index, "PATCH", {}, scheduleObj, (okdata) => {
|
||||
alert("Success edit schedule: " + okdata.message);
|
||||
reloadTimerBank(APIURL);
|
||||
reloadTodaySchedule(APIURL);
|
||||
}, (errdata) => {
|
||||
alert("Error edit schedule: " + errdata.message);
|
||||
});
|
||||
@@ -493,6 +625,7 @@ $(document).ready(function () {
|
||||
$btnImport.click(() => {
|
||||
DoImport(APIURL, (okdata) => {
|
||||
reloadTimerBank(APIURL);
|
||||
reloadTodaySchedule(APIURL);
|
||||
alert("Success import schedulebank from XLSX : " + okdata.message);
|
||||
}, (errdata) => {
|
||||
alert("Error importing schedulebank from XLSX : " + errdata.message);
|
||||
|
||||
@@ -67,6 +67,275 @@ function load_default_voice(){
|
||||
});
|
||||
}
|
||||
|
||||
function ValidLatitude(lat){
|
||||
const num = parseFloat(lat);
|
||||
return !isNaN(num) && num >= -90 && num <= 90;
|
||||
}
|
||||
|
||||
function ValidLongitude(lon){
|
||||
const num = parseFloat(lon);
|
||||
return !isNaN(num) && num >= -180 && num <= 180;
|
||||
}
|
||||
|
||||
function ValidTimezone(tz){
|
||||
try{
|
||||
Intl.DateTimeFormat(undefined, { timeZone: tz });
|
||||
return true;
|
||||
} catch(e){
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function ValidHHMM(time){
|
||||
const regex = /^([01]\d|2[0-3]):([0-5]\d)$/;
|
||||
return regex.test(time);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a date string is valid in DD/MM/YYYY format
|
||||
* @param {string} dateStr date to check in DD/MM/YYYY format
|
||||
* @returns {boolean} true if valid date in DD/MM/YYYY format, false otherwise
|
||||
*/
|
||||
function ValidDateDDMMYYYY(dateStr){
|
||||
const regex = /^(0[1-9]|[12][0-9]|3[01])\/(0[1-9]|1[0-2])\/\d{4}$/;
|
||||
if (!regex.test(dateStr)) return false;
|
||||
const [day, month, year] = dateStr.split('/').map(Number);
|
||||
const date = new Date(year, month - 1, day);
|
||||
return date.getFullYear() === year && date.getMonth() === month - 1 && date.getDate() === day;
|
||||
}
|
||||
|
||||
function ValidFilePath(path){
|
||||
if (typeof path !== 'string' || path.trim() === '') return false;
|
||||
// test if ends with .wav or .mp3
|
||||
const regex = /\.(wav|mp3)$/i;
|
||||
return regex.test(path);
|
||||
}
|
||||
|
||||
function IsEnabled(value){
|
||||
// accept "true", "false", true, false, 1, 0
|
||||
// detect if value is null or undefined
|
||||
if (value === null || value === undefined) return false;
|
||||
if (typeof value === 'boolean') return value;
|
||||
if (typeof value === 'number') return value === 1;
|
||||
if (typeof value === 'string') return value.toLowerCase() === "true" || value === "1";
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @typedef {Object} AdzanSetting
|
||||
* @property {string} latitude
|
||||
* @property {string} longitude
|
||||
* @property {string} timezone
|
||||
* @property {string} fajar_sound
|
||||
* @property {string} dzuhur_sound
|
||||
* @property {string} ashar_sound
|
||||
* @property {string} maghrib_sound
|
||||
* @property {string} isya_sound
|
||||
* @property {string} fajar_time
|
||||
* @property {string} dzuhur_time
|
||||
* @property {string} ashar_time
|
||||
* @property {string} maghrib_time
|
||||
* @property {string} isya_time
|
||||
* @property {boolean} fajar_enable
|
||||
* @property {boolean} dzuhur_enable
|
||||
* @property {boolean} ashar_enable
|
||||
* @property {boolean} maghrib_enable
|
||||
* @property {boolean} isya_enable
|
||||
*/
|
||||
|
||||
function Get_AdzanSetting(){
|
||||
fetchAPI("Settings/AdzanSetting", "GET", {}, null,
|
||||
/**
|
||||
* returned AdzanSetting data
|
||||
* @param {AdzanSetting} okdata
|
||||
*/
|
||||
(okdata) => {
|
||||
// text input for latitude, longitude, timezone
|
||||
if (ValidLatitude(okdata.latitude)) {
|
||||
$('#adzanlatitude').val(okdata.latitude);
|
||||
} else {
|
||||
$('#adzanlatitude').val("N/A");
|
||||
}
|
||||
if (ValidLongitude(okdata.longitude)) {
|
||||
$('#adzanlongitude').val(okdata.longitude);
|
||||
} else {
|
||||
$('#adzanlongitude').val("N/A");
|
||||
}
|
||||
if (ValidTimezone(okdata.timezone)) {
|
||||
$('#adzantimezone').val(okdata.timezone);
|
||||
} else {
|
||||
$('#adzantimezone').val("N/A");
|
||||
}
|
||||
// text input for adzan sound file for each prayer time
|
||||
if (ValidFilePath(okdata.fajar_sound)) {
|
||||
$('#fajar .adzanfile').val(okdata.fajar_sound);
|
||||
} else {
|
||||
$('#fajar .adzanfile').val("N/A");
|
||||
}
|
||||
if (ValidFilePath(okdata.dzuhur_sound)) {
|
||||
$('#dzuhur .adzanfile').val(okdata.dzuhur_sound);
|
||||
} else {
|
||||
$('#dzuhur .adzanfile').val("N/A");
|
||||
}
|
||||
if (ValidFilePath(okdata.ashar_sound)) {
|
||||
$('#ashar .adzanfile').val(okdata.ashar_sound);
|
||||
} else {
|
||||
$('#ashar .adzanfile').val("N/A");
|
||||
}
|
||||
if (ValidFilePath(okdata.maghrib_sound)) {
|
||||
$('#maghrib .adzanfile').val(okdata.maghrib_sound);
|
||||
} else {
|
||||
$('#maghrib .adzanfile').val("N/A");
|
||||
}
|
||||
if (ValidFilePath(okdata.isya_sound)) {
|
||||
$('#isya .adzanfile').val(okdata.isya_sound);
|
||||
} else {
|
||||
$('#isya .adzanfile').val("N/A");
|
||||
}
|
||||
// checkbox adzanenable for each prayer time
|
||||
$('#fajar .adzanenable').prop('checked', IsEnabled(okdata.fajar_enable));
|
||||
$('#dzuhur .adzanenable').prop('checked', IsEnabled(okdata.dzuhur_enable));
|
||||
$('#ashar .adzanenable').prop('checked', IsEnabled(okdata.ashar_enable));
|
||||
$('#maghrib .adzanenable').prop('checked', IsEnabled(okdata.maghrib_enable));
|
||||
$('#isya .adzanenable').prop('checked', IsEnabled(okdata.isya_enable));
|
||||
|
||||
// adzantime for each prayer time
|
||||
// if valid HH:MM will set <input type="time"> value, else set to undefined
|
||||
if (ValidHHMM(okdata.fajar_time)) {
|
||||
$('#fajar .adzantime').val(okdata.fajar_time);
|
||||
} else {
|
||||
$('#fajar .adzantime').val("");
|
||||
}
|
||||
if (ValidHHMM(okdata.dzuhur_time)) {
|
||||
$('#dzuhur .adzantime').val(okdata.dzuhur_time);
|
||||
} else {
|
||||
$('#dzuhur .adzantime').val("");
|
||||
}
|
||||
if (ValidHHMM(okdata.ashar_time)) {
|
||||
$('#ashar .adzantime').val(okdata.ashar_time);
|
||||
} else {
|
||||
$('#ashar .adzantime').val("");
|
||||
}
|
||||
if (ValidHHMM(okdata.maghrib_time)) {
|
||||
$('#maghrib .adzantime').val(okdata.maghrib_time);
|
||||
} else {
|
||||
$('#maghrib .adzantime').val("");
|
||||
}
|
||||
if (ValidHHMM(okdata.isya_time)) {
|
||||
$('#isya .adzantime').val(okdata.isya_time);
|
||||
} else {
|
||||
$('#isya .adzantime').val("");
|
||||
}
|
||||
|
||||
}, (errdata) => {
|
||||
alert("Error getting Adzan settings : " + errdata.message);
|
||||
});
|
||||
}
|
||||
|
||||
function Set_AdzanSetting(){
|
||||
let latitude = $('#adzanlatitude').val();
|
||||
if (!ValidLatitude(latitude)){
|
||||
alert("Please enter a valid latitude between -90 and 90.");
|
||||
return;
|
||||
}
|
||||
let longitude = $('#adzanlongitude').val();
|
||||
if (!ValidLongitude(longitude)){
|
||||
alert("Please enter a valid longitude between -180 and 180.");
|
||||
return;
|
||||
}
|
||||
let timezone = $('#adzantimezone').val();
|
||||
if (!ValidTimezone(timezone)){
|
||||
alert("Please enter a valid timezone.");
|
||||
return;
|
||||
}
|
||||
let fajar_sound = $('#fajar .adzanfile').val();
|
||||
if (!ValidFilePath(fajar_sound)){
|
||||
alert("Please enter a valid file path for Fajar adzan sound (must end with .wav or .mp3).");
|
||||
return;
|
||||
}
|
||||
let dzuhur_sound = $('#dzuhur .adzanfile').val();
|
||||
if (!ValidFilePath(dzuhur_sound)){
|
||||
alert("Please enter a valid file path for Dzuhur adzan sound (must end with .wav or .mp3).");
|
||||
return;
|
||||
}
|
||||
let ashar_sound = $('#ashar .adzanfile').val();
|
||||
if (!ValidFilePath(ashar_sound)){
|
||||
alert("Please enter a valid file path for Ashar adzan sound (must end with .wav or .mp3).");
|
||||
return;
|
||||
}
|
||||
let maghrib_sound = $('#maghrib .adzanfile').val();
|
||||
if (!ValidFilePath(maghrib_sound)){
|
||||
alert("Please enter a valid file path for Maghrib adzan sound (must end with .wav or .mp3).");
|
||||
return;
|
||||
}
|
||||
let isya_sound = $('#isya .adzanfile').val();
|
||||
if (!ValidFilePath(isya_sound)){
|
||||
alert("Please enter a valid file path for Isya adzan sound (must end with .wav or .mp3).");
|
||||
return;
|
||||
}
|
||||
let fajar_enable = $('#fajar .adzanenable').prop('checked');
|
||||
let dzuhur_enable = $('#dzuhur .adzanenable').prop('checked');
|
||||
let ashar_enable = $('#ashar .adzanenable').prop('checked');
|
||||
let maghrib_enable = $('#maghrib .adzanenable').prop('checked');
|
||||
let isya_enable = $('#isya .adzanenable').prop('checked');
|
||||
let fajar_time = $('#fajar .adzantime').val();
|
||||
if (!ValidHHMM(fajar_time)){
|
||||
alert("Please enter a valid time for Fajar adzan (HH:MM).");
|
||||
return;
|
||||
}
|
||||
let dzuhur_time = $('#dzuhur .adzantime').val();
|
||||
if (!ValidHHMM(dzuhur_time)){
|
||||
alert("Please enter a valid time for Dzuhur adzan (HH:MM).");
|
||||
return;
|
||||
}
|
||||
let ashar_time = $('#ashar .adzantime').val();
|
||||
if (!ValidHHMM(ashar_time)){
|
||||
alert("Please enter a valid time for Ashar adzan (HH:MM).");
|
||||
return;
|
||||
}
|
||||
let maghrib_time = $('#maghrib .adzantime').val();
|
||||
if (!ValidHHMM(maghrib_time)){
|
||||
alert("Please enter a valid time for Maghrib adzan (HH:MM).");
|
||||
return;
|
||||
}
|
||||
let isya_time = $('#isya .adzantime').val();
|
||||
if (!ValidHHMM(isya_time)){
|
||||
alert("Please enter a valid time for Isya adzan (HH:MM).");
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* @type {AdzanSetting}
|
||||
*/
|
||||
let data = {
|
||||
latitude: latitude,
|
||||
longitude: longitude,
|
||||
timezone: timezone,
|
||||
fajar_sound: fajar_sound,
|
||||
dzuhur_sound: dzuhur_sound,
|
||||
ashar_sound: ashar_sound,
|
||||
maghrib_sound: maghrib_sound,
|
||||
isya_sound: isya_sound,
|
||||
fajar_enable: fajar_enable,
|
||||
dzuhur_enable: dzuhur_enable,
|
||||
ashar_enable: ashar_enable,
|
||||
maghrib_enable: maghrib_enable,
|
||||
isya_enable: isya_enable,
|
||||
fajar_time: fajar_time,
|
||||
dzuhur_time: dzuhur_time,
|
||||
ashar_time: ashar_time,
|
||||
maghrib_time: maghrib_time,
|
||||
isya_time: isya_time
|
||||
};
|
||||
fetchAPI("Settings/AdzanSetting", "POST", {}, data, (okdata) => {
|
||||
alert("Adzan settings updated successfully.");
|
||||
}, (errdata) => {
|
||||
alert("Error updating Adzan settings : " + errdata.message);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
function Get_WebAccessSetting(){
|
||||
fetchAPI("Settings/WebAccess", "GET", {}, null, (okdata) => {
|
||||
let adminpass = okdata.adminpass || "password";
|
||||
@@ -109,8 +378,10 @@ $(document).ready(function () {
|
||||
load_default_voice();
|
||||
Get_OldResultDays();
|
||||
Get_WebAccessSetting();
|
||||
Get_AdzanSetting();
|
||||
load_messagebank(() => load_remark_selection());
|
||||
$("#fiscodesave").off('click').on('click', function () {
|
||||
|
||||
$('#fiscodesave').click(function () {
|
||||
Set_OldResultDays();
|
||||
let gop = $("#input_GOP").val();
|
||||
let gbd = $("#input_GBD").val();
|
||||
@@ -133,12 +404,15 @@ $(document).ready(function () {
|
||||
} else {
|
||||
alert("Please select all FIS codes (GOP, GBD, GFC, FLD) and Default Voice before saving.");
|
||||
}
|
||||
|
||||
});
|
||||
$("#webaccesssave").off('click').on('click', function () {
|
||||
$('#webaccesssave').click(function () {
|
||||
Set_WebAccessSetting();
|
||||
});
|
||||
|
||||
$('#adzansave').click(function () {
|
||||
Set_AdzanSetting();
|
||||
});
|
||||
|
||||
|
||||
|
||||
});
|
||||
@@ -37,6 +37,11 @@ dtSoundbank = null;
|
||||
*/
|
||||
window.select2data = [];
|
||||
|
||||
$btnRemove = null;
|
||||
$btnEdit = null;
|
||||
$btnExport = null;
|
||||
$btnfilecheck = null;
|
||||
|
||||
/**
|
||||
* Reload sound bank from server
|
||||
* @param {String} APIURL API URL endpoint, default "SoundBank/"
|
||||
@@ -62,7 +67,18 @@ function reloadSoundBank(APIURL = "SoundBank/") {
|
||||
*/
|
||||
function fill_soundbanktablebody(vv) {
|
||||
dtSoundbank.clear();
|
||||
if (!Array.isArray(vv) || vv.length === 0) return;
|
||||
if (!Array.isArray(vv) || vv.length === 0) {
|
||||
// kalau kosong, tidak bisa remove, edit, export dan filecheck
|
||||
$btnRemove.prop('disabled', true);
|
||||
$btnEdit.prop('disabled', true);
|
||||
$btnExport.prop('disabled', true);
|
||||
$btnfilecheck.prop('disabled', true);
|
||||
return;
|
||||
} else {
|
||||
// kalau ada isi, bisa export dan filecheck, tapi remove dan edit tetap tergantung selection
|
||||
$btnExport.prop('disabled', false);
|
||||
$btnfilecheck.prop('disabled', false);
|
||||
}
|
||||
dtSoundbank.rows.add(vv);
|
||||
dtSoundbank.draw();
|
||||
|
||||
@@ -77,8 +93,8 @@ function fill_soundbanktablebody(vv) {
|
||||
if ($(this).hasClass('row-selected')) {
|
||||
$(this).removeClass('row-selected').find('td').css('background-color', '');
|
||||
window.selectedsoundrow = null;
|
||||
$('#btnRemove').prop('disabled', true);
|
||||
$('#btnEdit').prop('disabled', true);
|
||||
$btnRemove.prop('disabled', true);
|
||||
$btnEdit.prop('disabled', true);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -87,8 +103,8 @@ function fill_soundbanktablebody(vv) {
|
||||
|
||||
$(this).addClass('row-selected').find('td').css('background-color', '#ffeeba');
|
||||
window.selectedsoundrow = selected.data();
|
||||
$('#btnRemove').prop('disabled', false);
|
||||
$('#btnEdit').prop('disabled', false);
|
||||
$btnRemove.prop('disabled', false);
|
||||
$btnEdit.prop('disabled', false);
|
||||
})
|
||||
|
||||
$('#tablesize').text("Table Size: " + vv.length);
|
||||
@@ -146,7 +162,7 @@ function getFilenameFromPath(path) {
|
||||
}
|
||||
|
||||
|
||||
|
||||
fileViewMode = 'all'; // all, valid, invalid
|
||||
|
||||
$(document).ready(function () {
|
||||
console.log("soundbank.js loaded successfully");
|
||||
@@ -154,9 +170,9 @@ $(document).ready(function () {
|
||||
window.selectedsoundrow = null;
|
||||
let $btnClear = $('#btnClear');
|
||||
let $btnAdd = $('#btnAdd');
|
||||
let $btnRemove = $('#btnRemove');
|
||||
let $btnEdit = $('#btnEdit');
|
||||
let $btnExport = $('#btnExport');
|
||||
$btnRemove = $('#btnRemove');
|
||||
$btnEdit = $('#btnEdit');
|
||||
$btnExport = $('#btnExport');
|
||||
let $btnImport = $('#btnImport');
|
||||
$btnRemove.prop('disabled', true);
|
||||
$btnEdit.prop('disabled', true);
|
||||
@@ -171,9 +187,11 @@ $(document).ready(function () {
|
||||
let selected_category = null;
|
||||
let selected_language = null;
|
||||
let selected_voicetype = null;
|
||||
$btnfilecheck = $('#btnFileCheck');
|
||||
|
||||
if (dtSoundbank === null) {
|
||||
dtSoundbank = new DataTable('#soundbanktable', {
|
||||
dom: 'Bfrtip',
|
||||
data: [],
|
||||
pageLength: 25,
|
||||
columns: [
|
||||
@@ -184,9 +202,29 @@ $(document).ready(function () {
|
||||
{ title: "Language", data: "language" },
|
||||
{ title: "Type", data: "voiceType" },
|
||||
{ title: "Filename", data: "path" }
|
||||
],
|
||||
rowCallback: function (row, data) {
|
||||
row.classList.toggle('table-danger', !!data.filemissing);
|
||||
},
|
||||
buttons: ['print', 'pdf', {
|
||||
extend: 'collection',
|
||||
text: 'View',
|
||||
className: 'btn btn-outline-secondary',
|
||||
buttons: [
|
||||
{ text: 'All Files', className: 'btn btn-outline-primary', action() { fileViewMode = 'all'; dtSoundbank.draw(); } },
|
||||
{ text: 'Valid Files', className: 'btn btn-outline-success', action() { fileViewMode = 'valid'; dtSoundbank.draw(); } },
|
||||
{ text: 'Invalid Files', className: 'btn btn-outline-danger', action() { fileViewMode = 'invalid'; dtSoundbank.draw(); } }
|
||||
]
|
||||
}]
|
||||
});
|
||||
}
|
||||
$.fn.dataTable.ext.search.push(function (settings, data, dataIndex, rowData) {
|
||||
if (settings.nTable.id !== 'soundbanktable') return true;
|
||||
const isInvalid = !!rowData.filemissing;
|
||||
if (fileViewMode === 'invalid') return isInvalid;
|
||||
if (fileViewMode === 'valid') return !isInvalid;
|
||||
return true;
|
||||
})
|
||||
|
||||
|
||||
/**
|
||||
@@ -219,17 +257,6 @@ $(document).ready(function () {
|
||||
}
|
||||
|
||||
reloadSoundBank(APIURL);
|
||||
// $('#findsoundbank').on('input', function () {
|
||||
// let searchTerm = $(this).val().trim().toLowerCase();
|
||||
// if (searchTerm.length > 0) {
|
||||
// window.selectedsoundrow = null;
|
||||
// let filtered = window.soundbankdata.filter(item => item.description.toLowerCase().includes(searchTerm) || item.tag.toLowerCase().includes(searchTerm) || item.path.toLowerCase().includes(searchTerm));
|
||||
// fill_soundbanktablebody(filtered);
|
||||
// } else {
|
||||
// window.selectedsoundrow = null;
|
||||
// fill_soundbanktablebody(window.soundbankdata);
|
||||
// }
|
||||
// });
|
||||
$btnClear.click(() => {
|
||||
DoClear(APIURL, "Soundbank", (okdata) => {
|
||||
reloadSoundBank(APIURL);
|
||||
@@ -447,4 +474,37 @@ $(document).ready(function () {
|
||||
alert("Error importing soundbank from XLSX : " + errdata.message);
|
||||
});
|
||||
});
|
||||
$btnfilecheck.click(() => {
|
||||
fetchAPI(APIURL + "FileCheck", "GET", {}, null, (okdata) => {
|
||||
console.log(okdata);
|
||||
let invalidList = Array.isArray(okdata.invalidfile) ? okdata.invalidfile : [];
|
||||
let validList = Array.isArray(okdata.validfile) ? okdata.validfile : [];
|
||||
const invalidSet = new Set(invalidList.map(f => f.path));
|
||||
dtSoundbank.rows().every(function () {
|
||||
const d = this.data();
|
||||
d.filemissing = invalidSet.has(d.path);
|
||||
this.data(d); // update row data
|
||||
});
|
||||
|
||||
dtSoundbank.draw(false);
|
||||
console.log(`File Check completed. ${validList.length} valid files, ${invalidList.length} invalid files.`);
|
||||
if (validList.length === 0) {
|
||||
if (invalidList.length === 0) {
|
||||
alert("No soundbank files found on server.");
|
||||
} else {
|
||||
alert(`File Check completed. All ${invalidList.length} soundbank files are missing on server.`);
|
||||
|
||||
}
|
||||
} else {
|
||||
if (invalidList.length === 0) {
|
||||
alert(`File Check completed. All ${validList.length} soundbank files are present on server.`);
|
||||
} else {
|
||||
alert(`File Check completed. ${validList.length} soundbank files are present, ${invalidList.length} soundbank files are missing on server.`);
|
||||
}
|
||||
}
|
||||
|
||||
}, (errdata) => {
|
||||
alert("Error checking soundbank files : " + errdata.message);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -14,7 +14,6 @@
|
||||
<link rel="stylesheet" href="assets/css/Font%20Awesome%206%20Pro.css">
|
||||
<link rel="stylesheet" href="assets/css/FontAwesome.css">
|
||||
<link rel="stylesheet" href="assets/css/bss-overrides.css">
|
||||
<link rel="stylesheet" href="assets/css/datatables.css">
|
||||
<link rel="stylesheet" href="assets/css/Login-Form-Basic-icons.css">
|
||||
<link rel="stylesheet" href="assets/css/styles.css">
|
||||
</head>
|
||||
@@ -294,7 +293,6 @@
|
||||
<script src="assets/js/bs-init.js"></script>
|
||||
<script src="assets/js/soundchannel.js"></script>
|
||||
<script src="assets/js/broadcastzones.js"></script>
|
||||
<script src="assets/js/datatables.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -14,7 +14,6 @@
|
||||
<link rel="stylesheet" href="assets/css/Font%20Awesome%206%20Pro.css">
|
||||
<link rel="stylesheet" href="assets/css/FontAwesome.css">
|
||||
<link rel="stylesheet" href="assets/css/bss-overrides.css">
|
||||
<link rel="stylesheet" href="assets/css/datatables.css">
|
||||
<link rel="stylesheet" href="assets/css/Login-Form-Basic-icons.css">
|
||||
<link rel="stylesheet" href="assets/css/styles.css">
|
||||
</head>
|
||||
@@ -156,7 +155,6 @@
|
||||
<div><audio class="invisible" id="audioplayer" controls=""></audio></div>
|
||||
<script src="assets/bootstrap/js/bootstrap.min.js"></script>
|
||||
<script src="assets/js/bs-init.js"></script>
|
||||
<script src="assets/js/datatables.js"></script>
|
||||
<script src="assets/js/filemanagement.js"></script>
|
||||
</body>
|
||||
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
<link rel="stylesheet" href="assets/css/bss-overrides.css">
|
||||
<link rel="stylesheet" href="assets/css/all.min.css">
|
||||
<link rel="stylesheet" href="assets/css/datatables.css">
|
||||
<link rel="stylesheet" href="assets/css/flatpickr.min.css">
|
||||
<link rel="stylesheet" href="assets/css/litepicker.css">
|
||||
<link rel="stylesheet" href="assets/css/select2.min.css">
|
||||
<link rel="stylesheet" href="assets/css/Login-Form-Basic-icons.css">
|
||||
<link rel="stylesheet" href="assets/css/styles.css">
|
||||
@@ -40,26 +42,26 @@
|
||||
<div>
|
||||
<hr class="mt-0">
|
||||
<ul class="nav nav-pills flex-column mb-auto">
|
||||
<li class="nav-item"><a class="nav-link active link-light text-menu" id="homelink" href="#" aria-current="page"><svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" viewBox="0 0 16 16" class="bi bi-house-door me-2" style="font-size: 20px;">
|
||||
<li class="nav-item"><a class="nav-link active link-light text-menu" id="homelink" href="#" aria-current="page"><svg class="bi bi-house-door me-2" xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" viewBox="0 0 16 16" style="font-size: 20px;">
|
||||
<path d="M8.354 1.146a.5.5 0 0 0-.708 0l-6 6A.5.5 0 0 0 1.5 7.5v7a.5.5 0 0 0 .5.5h4.5a.5.5 0 0 0 .5-.5v-4h2v4a.5.5 0 0 0 .5.5H14a.5.5 0 0 0 .5-.5v-7a.5.5 0 0 0-.146-.354L13 5.793V2.5a.5.5 0 0 0-.5-.5h-1a.5.5 0 0 0-.5.5v1.293zM2.5 14V7.707l5.5-5.5 5.5 5.5V14H10v-4a.5.5 0 0 0-.5-.5h-3a.5.5 0 0 0-.5.5v4z"></path>
|
||||
</svg> Overview</a></li>
|
||||
<li class="nav-item"><a class="nav-link link-body-emphasis text-menu" id="soundbanklink" href="#"><svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" viewBox="0 0 16 16" class="bi bi-soundwave me-2 icon-menu pad-icon-menu" style="font-size: 20px;">
|
||||
<li class="nav-item"><a class="nav-link link-body-emphasis text-menu" id="soundbanklink" href="#"><svg class="bi bi-soundwave me-2 icon-menu pad-icon-menu" xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" viewBox="0 0 16 16" style="font-size: 20px;">
|
||||
<path fill-rule="evenodd" d="M8.5 2a.5.5 0 0 1 .5.5v11a.5.5 0 0 1-1 0v-11a.5.5 0 0 1 .5-.5m-2 2a.5.5 0 0 1 .5.5v7a.5.5 0 0 1-1 0v-7a.5.5 0 0 1 .5-.5m4 0a.5.5 0 0 1 .5.5v7a.5.5 0 0 1-1 0v-7a.5.5 0 0 1 .5-.5m-6 1.5A.5.5 0 0 1 5 6v4a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5m8 0a.5.5 0 0 1 .5.5v4a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5m-10 1A.5.5 0 0 1 3 7v2a.5.5 0 0 1-1 0V7a.5.5 0 0 1 .5-.5m12 0a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-1 0V7a.5.5 0 0 1 .5-.5"></path>
|
||||
</svg> Sound Bank</a></li>
|
||||
<li class="nav-item"><a class="nav-link link-body-emphasis text-menu" id="messagebanklink" href="#"><svg xmlns="http://www.w3.org/2000/svg" height="1em" viewBox="0 0 24 24" width="1em" fill="currentColor" class="me-2 icon-menu" style="font-size: 20px;">
|
||||
<li class="nav-item"><a class="nav-link link-body-emphasis text-menu" id="messagebanklink" href="#"><svg class="me-2 icon-menu" xmlns="http://www.w3.org/2000/svg" height="1em" viewBox="0 0 24 24" width="1em" fill="currentColor" style="font-size: 20px;">
|
||||
<path d="M0 0h24v24H0z" fill="none"></path>
|
||||
<path d="M20 2H4c-1.1 0-1.99.9-1.99 2L2 22l4-4h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm-2 12H6v-2h12v2zm0-3H6V9h12v2zm0-3H6V6h12v2z"></path>
|
||||
</svg> Message Bank</a></li>
|
||||
<li class="nav-item"><a class="nav-link link-body-emphasis text-menu" id="languagelink" href="#"><svg xmlns="http://www.w3.org/2000/svg" height="1em" viewBox="0 0 24 24" width="1em" fill="currentColor" class="me-2 icon-menu" style="font-size: 20px;">
|
||||
<li class="nav-item"><a class="nav-link link-body-emphasis text-menu" id="languagelink" href="#"><svg class="me-2 icon-menu" xmlns="http://www.w3.org/2000/svg" height="1em" viewBox="0 0 24 24" width="1em" fill="currentColor" style="font-size: 20px;">
|
||||
<path d="M0 0h24v24H0z" fill="none"></path>
|
||||
<path d="M3.9 12c0-1.71 1.39-3.1 3.1-3.1h4V7H7c-2.76 0-5 2.24-5 5s2.24 5 5 5h4v-1.9H7c-1.71 0-3.1-1.39-3.1-3.1zM8 13h8v-2H8v2zm9-6h-4v1.9h4c1.71 0 3.1 1.39 3.1 3.1s-1.39 3.1-3.1 3.1h-4V17h4c2.76 0 5-2.24 5-5s-2.24-5-5-5z"></path>
|
||||
</svg> Language Link</a></li>
|
||||
<li class="nav-item .icon-menu"><a class="nav-link link-body-emphasis text-menu" id="timerlink" href="#"><svg xmlns="http://www.w3.org/2000/svg" height="1em" viewBox="0 0 24 24" width="1em" fill="currentColor" class="me-2 icon-menu pad-icon-menu" style="font-size: 20px;">
|
||||
<li class="nav-item .icon-menu"><a class="nav-link link-body-emphasis text-menu" id="timerlink" href="#"><svg class="me-2 icon-menu pad-icon-menu" xmlns="http://www.w3.org/2000/svg" height="1em" viewBox="0 0 24 24" width="1em" fill="currentColor" style="font-size: 20px;">
|
||||
<path d="M0 0h24v24H0z" fill="none"></path>
|
||||
<path d="M11.99 2C6.47 2 2 6.48 2 12s4.47 10 9.99 10C17.52 22 22 17.52 22 12S17.52 2 11.99 2zM12 20c-4.42 0-8-3.58-8-8s3.58-8 8-8 8 3.58 8 8-3.58 8-8 8z"></path>
|
||||
<path d="M12.5 7H11v6l5.25 3.15.75-1.23-4.5-2.67z"></path>
|
||||
</svg> Timer</a></li>
|
||||
<li class="nav-item .icon-menu"><a class="nav-link link-body-emphasis text-menu" id="broadcastzonelink" href="#"><svg xmlns="http://www.w3.org/2000/svg" height="1em" viewBox="0 0 24 24" width="1em" fill="currentColor" class="me-2 icon-menu" style="font-size: 20px;">
|
||||
<li class="nav-item .icon-menu"><a class="nav-link link-body-emphasis text-menu" id="broadcastzonelink" href="#"><svg class="me-2 icon-menu" xmlns="http://www.w3.org/2000/svg" height="1em" viewBox="0 0 24 24" width="1em" fill="currentColor" style="font-size: 20px;">
|
||||
<path d="M0 0h24v24H0z" fill="none"></path>
|
||||
<path d="M18.2 1H9.8C8.81 1 8 1.81 8 2.8v14.4c0 .99.81 1.79 1.8 1.79l8.4.01c.99 0 1.8-.81 1.8-1.8V2.8c0-.99-.81-1.8-1.8-1.8zM14 3c1.1 0 2 .89 2 2s-.9 2-2 2-2-.89-2-2 .9-2 2-2zm0 13.5c-2.21 0-4-1.79-4-4s1.79-4 4-4 4 1.79 4 4-1.79 4-4 4z"></path>
|
||||
<circle cx="14" cy="12.5" r="2.5"></circle>
|
||||
@@ -71,11 +73,11 @@
|
||||
<path d="M19,7H9C7.9,7,7,7.9,7,9v10c0,1.1,0.9,2,2,2h10c1.1,0,2-0.9,2-2V9C21,7.9,20.1,7,19,7z M19,9v2H9V9H19z M13,15v-2h2v2H13z M15,17v2h-2v-2H15z M11,15H9v-2h2V15z M17,13h2v2h-2V13z M9,17h2v2H9V17z M17,19v-2h2v2H17z M6,17H5c-1.1,0-2-0.9-2-2V5 c0-1.1,0.9-2,2-2h10c1.1,0,2,0.9,2,2v1h-2V5H5v10h1V17z"></path>
|
||||
</g>
|
||||
</svg> Log</a></li>
|
||||
<li class="nav-item"><a class="nav-link link-body-emphasis text-menu" id="usermanagement" href="#"><svg xmlns="http://www.w3.org/2000/svg" height="1em" viewBox="0 0 24 24" width="1em" fill="currentColor" class="me-2 icon-menu pad-icon-menu" style="font-size: 20px;">
|
||||
<li class="nav-item"><a class="nav-link link-body-emphasis text-menu" id="usermanagement" href="#"><svg class="me-2 icon-menu pad-icon-menu" xmlns="http://www.w3.org/2000/svg" height="1em" viewBox="0 0 24 24" width="1em" fill="currentColor" style="font-size: 20px;">
|
||||
<path d="M0 0h24v24H0z" fill="none"></path>
|
||||
<path d="M16 11c1.66 0 2.99-1.34 2.99-3S17.66 5 16 5c-1.66 0-3 1.34-3 3s1.34 3 3 3zm-8 0c1.66 0 2.99-1.34 2.99-3S9.66 5 8 5C6.34 5 5 6.34 5 8s1.34 3 3 3zm0 2c-2.33 0-7 1.17-7 3.5V19h14v-2.5c0-2.33-4.67-3.5-7-3.5zm8 0c-.29 0-.62.02-.97.05 1.16.84 1.97 1.97 1.97 3.45V19h6v-2.5c0-2.33-4.67-3.5-7-3.5z"></path>
|
||||
</svg> User Management</a></li>
|
||||
<li class="nav-item"><a class="nav-link link-body-emphasis text-menu" id="filemanagement" href="#"><svg xmlns="http://www.w3.org/2000/svg" enable-background="new 0 0 24 24" height="1em" viewBox="0 0 24 24" width="1em" fill="currentColor" class="me-2 icon-menu pad-icon-menu" style="font-size: 20px;">
|
||||
<li class="nav-item"><a class="nav-link link-body-emphasis text-menu" id="filemanagement" href="#"><svg class="me-2 icon-menu pad-icon-menu" xmlns="http://www.w3.org/2000/svg" enable-background="new 0 0 24 24" height="1em" viewBox="0 0 24 24" width="1em" fill="currentColor" style="font-size: 20px;">
|
||||
<g>
|
||||
<rect fill="none" height="24" width="24"></rect>
|
||||
</g>
|
||||
@@ -83,13 +85,13 @@
|
||||
<path d="M14,2H6C4.9,2,4.01,2.9,4.01,4L4,20c0,1.1,0.89,2,1.99,2H18c1.1,0,2-0.9,2-2V8L14,2z M16,13h-3v3.75 c0,1.24-1.01,2.25-2.25,2.25S8.5,17.99,8.5,16.75c0-1.24,1.01-2.25,2.25-2.25c0.46,0,0.89,0.14,1.25,0.38V11h4V13z M13,9V3.5 L18.5,9H13z"></path>
|
||||
</g>
|
||||
</svg> File Management</a></li>
|
||||
<li class="nav-item"><a class="nav-link link-body-emphasis text-menu" id="settinglink" href="#"><svg xmlns="http://www.w3.org/2000/svg" enable-background="new 0 0 24 24" height="1em" viewBox="0 0 24 24" width="1em" fill="currentColor" class="me-2 icon-menu pad-icon-menu" style="font-size: 20px;">
|
||||
<li class="nav-item"><a class="nav-link link-body-emphasis text-menu" id="settinglink" href="#"><svg class="me-2 icon-menu pad-icon-menu" xmlns="http://www.w3.org/2000/svg" enable-background="new 0 0 24 24" height="1em" viewBox="0 0 24 24" width="1em" fill="currentColor" style="font-size: 20px;">
|
||||
<g>
|
||||
<path d="M0,0h24v24H0V0z" fill="none"></path>
|
||||
<path d="M19.14,12.94c0.04-0.3,0.06-0.61,0.06-0.94c0-0.32-0.02-0.64-0.07-0.94l2.03-1.58c0.18-0.14,0.23-0.41,0.12-0.61 l-1.92-3.32c-0.12-0.22-0.37-0.29-0.59-0.22l-2.39,0.96c-0.5-0.38-1.03-0.7-1.62-0.94L14.4,2.81c-0.04-0.24-0.24-0.41-0.48-0.41 h-3.84c-0.24,0-0.43,0.17-0.47,0.41L9.25,5.35C8.66,5.59,8.12,5.92,7.63,6.29L5.24,5.33c-0.22-0.08-0.47,0-0.59,0.22L2.74,8.87 C2.62,9.08,2.66,9.34,2.86,9.48l2.03,1.58C4.84,11.36,4.8,11.69,4.8,12s0.02,0.64,0.07,0.94l-2.03,1.58 c-0.18,0.14-0.23,0.41-0.12,0.61l1.92,3.32c0.12,0.22,0.37,0.29,0.59,0.22l2.39-0.96c0.5,0.38,1.03,0.7,1.62,0.94l0.36,2.54 c0.05,0.24,0.24,0.41,0.48,0.41h3.84c0.24,0,0.44-0.17,0.47-0.41l0.36-2.54c0.59-0.24,1.13-0.56,1.62-0.94l2.39,0.96 c0.22,0.08,0.47,0,0.59-0.22l1.92-3.32c0.12-0.22,0.07-0.47-0.12-0.61L19.14,12.94z M12,15.6c-1.98,0-3.6-1.62-3.6-3.6 s1.62-3.6,3.6-3.6s3.6,1.62,3.6,3.6S13.98,15.6,12,15.6z"></path>
|
||||
</g>
|
||||
</svg> Setting</a></li>
|
||||
<li class="nav-item"><a class="nav-link link-body-emphasis text-menu" id="logoutlink" href="#"><svg xmlns="http://www.w3.org/2000/svg" height="1em" viewBox="0 0 24 24" width="1em" fill="currentColor" class="me-2 icon-menu" style="font-size: 20px;">
|
||||
<li class="nav-item"><a class="nav-link link-body-emphasis text-menu" id="logoutlink" href="#"><svg class="me-2 icon-menu" xmlns="http://www.w3.org/2000/svg" height="1em" viewBox="0 0 24 24" width="1em" fill="currentColor" style="font-size: 20px;">
|
||||
<path d="M0 0h24v24H0z" fill="none"></path>
|
||||
<path d="M17 7l-1.41 1.41L18.17 11H8v2h10.17l-2.58 2.58L17 17l5-5zM4 5h8V3H4c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h8v-2H4V5z"></path>
|
||||
</svg> Logout</a></li>
|
||||
@@ -159,10 +161,12 @@
|
||||
<script src="assets/bootstrap/js/bootstrap.min.js"></script>
|
||||
<script src="assets/js/bs-init.js"></script>
|
||||
<script src="assets/js/jquery-3.7.1.min.js"></script>
|
||||
<script src="assets/js/script.js"></script>
|
||||
<script src="assets/js/all.min.js"></script>
|
||||
<script src="assets/js/datatables.js"></script>
|
||||
<script src="assets/js/select2.min.js"></script>
|
||||
<script src="assets/js/litepicker.js"></script>
|
||||
<script src="assets/js/datatables.js"></script>
|
||||
<script src="assets/js/all.min.js"></script>
|
||||
<script src="assets/js/script.js"></script>
|
||||
<script src="assets/js/flatpickr.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -16,6 +16,8 @@
|
||||
<link rel="stylesheet" href="assets/css/bss-overrides.css">
|
||||
<link rel="stylesheet" href="assets/css/all.min.css">
|
||||
<link rel="stylesheet" href="assets/css/datatables.css">
|
||||
<link rel="stylesheet" href="assets/css/flatpickr.min.css">
|
||||
<link rel="stylesheet" href="assets/css/litepicker.css">
|
||||
<link rel="stylesheet" href="assets/css/select2.min.css">
|
||||
<link rel="stylesheet" href="assets/css/Login-Form-Basic-icons.css">
|
||||
<link rel="stylesheet" href="assets/css/styles.css">
|
||||
@@ -40,7 +42,7 @@
|
||||
<div>
|
||||
<hr class="mt-0">
|
||||
<ul class="nav nav-pills flex-column mb-auto">
|
||||
<li class="nav-item"><a class="nav-link active link-light text-menu" id="homelink" href="#" aria-current="page"><svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" viewBox="0 0 16 16" class="bi bi-house-door me-2" style="font-size: 20px;">
|
||||
<li class="nav-item"><a class="nav-link active link-light text-menu" id="homelink" href="#" aria-current="page"><svg class="bi bi-house-door me-2" xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" viewBox="0 0 16 16" style="font-size: 20px;">
|
||||
<path d="M8.354 1.146a.5.5 0 0 0-.708 0l-6 6A.5.5 0 0 0 1.5 7.5v7a.5.5 0 0 0 .5.5h4.5a.5.5 0 0 0 .5-.5v-4h2v4a.5.5 0 0 0 .5.5H14a.5.5 0 0 0 .5-.5v-7a.5.5 0 0 0-.146-.354L13 5.793V2.5a.5.5 0 0 0-.5-.5h-1a.5.5 0 0 0-.5.5v1.293zM2.5 14V7.707l5.5-5.5 5.5 5.5V14H10v-4a.5.5 0 0 0-.5-.5h-3a.5.5 0 0 0-.5.5v4z"></path>
|
||||
</svg> Overview</a></li>
|
||||
<li class="nav-item"><a class="nav-link link-body-emphasis text-menu" id="loglink" href="#"><svg xmlns="http://www.w3.org/2000/svg" enable-background="new 0 0 24 24" height="1em" viewBox="0 0 24 24" width="1em" fill="currentColor" class="icon-menu pad-icon-menu" style="font-size: 20px;">
|
||||
@@ -49,7 +51,7 @@
|
||||
<path d="M19,7H9C7.9,7,7,7.9,7,9v10c0,1.1,0.9,2,2,2h10c1.1,0,2-0.9,2-2V9C21,7.9,20.1,7,19,7z M19,9v2H9V9H19z M13,15v-2h2v2H13z M15,17v2h-2v-2H15z M11,15H9v-2h2V15z M17,13h2v2h-2V13z M9,17h2v2H9V17z M17,19v-2h2v2H17z M6,17H5c-1.1,0-2-0.9-2-2V5 c0-1.1,0.9-2,2-2h10c1.1,0,2,0.9,2,2v1h-2V5H5v10h1V17z"></path>
|
||||
</g>
|
||||
</svg> Log</a></li>
|
||||
<li class="nav-item"><a class="nav-link link-body-emphasis text-menu" id="logoutlink" href="#"><svg xmlns="http://www.w3.org/2000/svg" height="1em" viewBox="0 0 24 24" width="1em" fill="currentColor" class="me-2 icon-menu" style="font-size: 20px;">
|
||||
<li class="nav-item"><a class="nav-link link-body-emphasis text-menu" id="logoutlink" href="#"><svg class="me-2 icon-menu" xmlns="http://www.w3.org/2000/svg" height="1em" viewBox="0 0 24 24" width="1em" fill="currentColor" style="font-size: 20px;">
|
||||
<path d="M0 0h24v24H0z" fill="none"></path>
|
||||
<path d="M17 7l-1.41 1.41L18.17 11H8v2h10.17l-2.58 2.58L17 17l5-5zM4 5h8V3H4c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h8v-2H4V5z"></path>
|
||||
</svg> Logout</a></li>
|
||||
@@ -119,10 +121,12 @@
|
||||
<script src="assets/bootstrap/js/bootstrap.min.js"></script>
|
||||
<script src="assets/js/bs-init.js"></script>
|
||||
<script src="assets/js/jquery-3.7.1.min.js"></script>
|
||||
<script src="assets/js/script.js"></script>
|
||||
<script src="assets/js/all.min.js"></script>
|
||||
<script src="assets/js/datatables.js"></script>
|
||||
<script src="assets/js/select2.min.js"></script>
|
||||
<script src="assets/js/litepicker.js"></script>
|
||||
<script src="assets/js/datatables.js"></script>
|
||||
<script src="assets/js/all.min.js"></script>
|
||||
<script src="assets/js/script.js"></script>
|
||||
<script src="assets/js/flatpickr.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -14,7 +14,6 @@
|
||||
<link rel="stylesheet" href="assets/css/Font%20Awesome%206%20Pro.css">
|
||||
<link rel="stylesheet" href="assets/css/FontAwesome.css">
|
||||
<link rel="stylesheet" href="assets/css/bss-overrides.css">
|
||||
<link rel="stylesheet" href="assets/css/datatables.css">
|
||||
<link rel="stylesheet" href="assets/css/Login-Form-Basic-icons.css">
|
||||
<link rel="stylesheet" href="assets/css/styles.css">
|
||||
</head>
|
||||
@@ -34,11 +33,12 @@
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-3 col-sm-3 col-md-3 col-lg-2 col-xl-2"><button class="btn w-100 pad-button btn-round-basic color-add" id="btnClear" type="button">Clear</button></div>
|
||||
<div class="col-3 col-sm-3 col-md-3 col-lg-2 col-xl-2"><button class="btn w-100 pad-button btn-round-basic color-add" id="btnDefaultInit" type="button">Initialize Default</button></div>
|
||||
<div class="col-3 col-sm-3 col-md-3 col-lg-2 col-xl-2"><button class="btn w-100 pad-button btn-round-basic color-add" id="btnAdd" type="button">Add</button></div>
|
||||
<div class="col-3 col-sm-3 col-md-3 col-lg-2 col-xl-2"><button class="btn w-100 pad-button btn-round-basic color-remove" id="btnRemove" type="button">Remove</button></div>
|
||||
<div class="col-3 col-sm-3 col-md-3 col-lg-2 col-xl-2"><button class="btn w-100 pad-button btn-round-basic color-edit" id="btnEdit" type="button">Edit</button></div>
|
||||
<div class="col-6 col-sm-6 col-md-6 col-lg-2 col-xl-2"><button class="btn w-100 pad-button btn-round-basic color-import" id="btnExport" type="button">Export</button></div>
|
||||
<div class="col-6 col-sm-6 col-md-6 col-lg-2 col-xl-2"><button class="btn w-100 pad-button btn-round-basic color-import" id="btnImport" type="button">Import</button></div>
|
||||
<div class="col-3 col-sm-3 col-md-3 col-lg-2 col-xl-2"><button class="btn w-100 pad-button btn-round-basic color-import" id="btnExport" type="button">Export</button></div>
|
||||
<div class="col-3 col-sm-3 col-md-3 col-lg-2 col-xl-2"><button class="btn w-100 pad-button btn-round-basic color-import" id="btnImport" type="button">Import</button></div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
@@ -99,7 +99,6 @@
|
||||
<script src="assets/bootstrap/js/bootstrap.min.js"></script>
|
||||
<script src="assets/js/bs-init.js"></script>
|
||||
<script src="assets/js/languagelink.js"></script>
|
||||
<script src="assets/js/datatables.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -14,7 +14,6 @@
|
||||
<link rel="stylesheet" href="assets/css/Font%20Awesome%206%20Pro.css">
|
||||
<link rel="stylesheet" href="assets/css/FontAwesome.css">
|
||||
<link rel="stylesheet" href="assets/css/bss-overrides.css">
|
||||
<link rel="stylesheet" href="assets/css/datatables.css">
|
||||
<link rel="stylesheet" href="assets/css/Login-Form-Basic-icons.css">
|
||||
<link rel="stylesheet" href="assets/css/styles.css">
|
||||
</head>
|
||||
@@ -26,16 +25,26 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-4 col-sm-4 col-md-2 col-lg-2 col-xl-2">
|
||||
<div class="col-5 ms-1 me-1">
|
||||
<div class="row">
|
||||
<div class="col-auto">
|
||||
<p class="text-add">Select Log Date</p>
|
||||
</div>
|
||||
<div class="col-4 col-sm-4 col-md-2 col-lg-2 col-xl-2"><input id="logdate" class="form-control" type="date"></div>
|
||||
<div class="col-4 col-sm-4 col-md-2 col-lg-2 col-xl-2"><button class="btn w-100 pad-button btn-round-basic color-import" id="btnExport" type="button">Export</button></div>
|
||||
<div class="col-md-2 col-lg-2 col-xl-2"></div>
|
||||
<div class="col-4 col-sm-4 col-md-2 col-lg-2 col-xl-2 d-none">
|
||||
<div class="col-auto"><input type="text" id="logdate" class="form-control"></div>
|
||||
<div class="col">
|
||||
<div class="form-check w-100 h-100 align-content-center"><input class="form-check-input" type="checkbox" id="findalldate"><label class="form-check-label" for="formCheck-1">All Date</label></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-5 ms-1 me-1">
|
||||
<div class="row">
|
||||
<div class="col-auto">
|
||||
<p class="text-add">Search</p>
|
||||
</div>
|
||||
<div class="col-4 col-sm-4 col-md-2 col-lg-2 col-xl-2 d-none"><input type="text" id="searchfilter" class="form-control" placeholder="Search Filter"></div>
|
||||
<div class="col"><input type="text" id="searchfilter" class="form-control" placeholder="Search Filter"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col ms-1 me-1"><button class="btn w-100 pad-button btn-round-basic color-import" id="btnGet" type="button">Get</button></div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
@@ -61,7 +70,6 @@
|
||||
<script src="assets/bootstrap/js/bootstrap.min.js"></script>
|
||||
<script src="assets/js/bs-init.js"></script>
|
||||
<script src="assets/js/log.js"></script>
|
||||
<script src="assets/js/datatables.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -14,7 +14,6 @@
|
||||
<link rel="stylesheet" href="assets/css/Font%20Awesome%206%20Pro.css">
|
||||
<link rel="stylesheet" href="assets/css/FontAwesome.css">
|
||||
<link rel="stylesheet" href="assets/css/bss-overrides.css">
|
||||
<link rel="stylesheet" href="assets/css/datatables.css">
|
||||
<link rel="stylesheet" href="assets/css/Login-Form-Basic-icons.css">
|
||||
<link rel="stylesheet" href="assets/css/styles.css">
|
||||
</head>
|
||||
@@ -27,7 +26,7 @@
|
||||
<div class="col-md-6 col-xl-4">
|
||||
<div class="card mb-5 card-login">
|
||||
<div class="card-body d-flex flex-column align-items-center">
|
||||
<div class="bs-icon-xl bs-icon-circle bs-icon-primary my-4 bs-icon bg-icon-login"><svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" viewBox="0 0 16 16" class="bi bi-person bg-icon-login">
|
||||
<div class="bs-icon-xl bs-icon-circle bs-icon-primary my-4 bs-icon bg-icon-login"><svg class="bi bi-person bg-icon-login" xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" viewBox="0 0 16 16">
|
||||
<path d="M8 8a3 3 0 1 0 0-6 3 3 0 0 0 0 6m2-3a2 2 0 1 1-4 0 2 2 0 0 1 4 0m4 8c0 1-1 1-1 1H3s-1 0-1-1 1-4 6-4 6 3 6 4m-1-.004c-.001-.246-.154-.986-.832-1.664C11.516 10.68 10.289 10 8 10c-2.29 0-3.516.68-4.168 1.332-.678.678-.83 1.418-.832 1.664z"></path>
|
||||
</svg></div>
|
||||
<h2 class="mb-3 h-login">Login</h2>
|
||||
@@ -46,8 +45,6 @@
|
||||
</section>
|
||||
<script src="assets/bootstrap/js/bootstrap.min.js"></script>
|
||||
<script src="assets/js/bs-init.js"></script>
|
||||
<script src="assets/js/jquery-3.7.1.min.js"></script>
|
||||
<script src="assets/js/datatables.js"></script>
|
||||
<script src="assets/js/login.js"></script>
|
||||
</body>
|
||||
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
<link rel="stylesheet" href="assets/css/Font%20Awesome%206%20Pro.css">
|
||||
<link rel="stylesheet" href="assets/css/FontAwesome.css">
|
||||
<link rel="stylesheet" href="assets/css/bss-overrides.css">
|
||||
<link rel="stylesheet" href="assets/css/datatables.css">
|
||||
<link rel="stylesheet" href="assets/css/Login-Form-Basic-icons.css">
|
||||
<link rel="stylesheet" href="assets/css/styles.css">
|
||||
</head>
|
||||
@@ -114,16 +113,13 @@
|
||||
<div class="row">
|
||||
<div class="col bg-light"><select class="w-100 h-100 overflow-scroll" id="messageavailablevariables" size="10"></select></div>
|
||||
<div class="col-3 col-sm-3 col-md-3 col-lg-2 col-xl-2">
|
||||
<div class="row pad-row-btn"><button class="btn btn-round-basic color-remove" data-bs-toggle="tooltip" data-bss-tooltip="" id="btnclearlist" type="button" title="Clear List"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" width="1em" height="1em" fill="currentColor" style="font-size: 32;">
|
||||
<!--! Font Awesome Free 6.4.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2023 Fonticons, Inc. -->
|
||||
<div class="row pad-row-btn"><button class="btn btn-round-basic color-remove" data-bs-toggle="tooltip" data-bss-tooltip="" id="btnclearlist" type="button" title="Clear List"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" width="1em" height="1em" fill="currentColor" style="font-size: 32;"><!--! Font Awesome Free 6.4.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2023 Fonticons, Inc. -->
|
||||
<path d="M256 48a208 208 0 1 1 0 416 208 208 0 1 1 0-416zm0 464A256 256 0 1 0 256 0a256 256 0 1 0 0 512zM175 175c-9.4 9.4-9.4 24.6 0 33.9l47 47-47 47c-9.4 9.4-9.4 24.6 0 33.9s24.6 9.4 33.9 0l47-47 47 47c9.4 9.4 24.6 9.4 33.9 0s9.4-24.6 0-33.9l-47-47 47-47c9.4-9.4 9.4-24.6 0-33.9s-24.6-9.4-33.9 0l-47 47-47-47c-9.4-9.4-24.6-9.4-33.9 0z"></path>
|
||||
</svg></button></div>
|
||||
<div class="row pad-row-btn"><button class="btn btn-round-basic color-edit" data-bs-toggle="tooltip" data-bss-tooltip="" data-bs-placement="right" id="btnremovefromlist" type="button" title="Remove"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" width="1em" height="1em" fill="currentColor" style="font-size: 32;">
|
||||
<!--! Font Awesome Free 6.4.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2023 Fonticons, Inc. -->
|
||||
<div class="row pad-row-btn"><button class="btn btn-round-basic color-edit" data-bs-toggle="tooltip" data-bss-tooltip="" data-bs-placement="right" id="btnremovefromlist" type="button" title="Remove"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" width="1em" height="1em" fill="currentColor" style="font-size: 32;"><!--! Font Awesome Free 6.4.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2023 Fonticons, Inc. -->
|
||||
<path d="M48 256a208 208 0 1 1 416 0A208 208 0 1 1 48 256zm464 0A256 256 0 1 0 0 256a256 256 0 1 0 512 0zM217.4 376.9c4.2 4.5 10.1 7.1 16.3 7.1c12.3 0 22.3-10 22.3-22.3V304h96c17.7 0 32-14.3 32-32V240c0-17.7-14.3-32-32-32H256V150.3c0-12.3-10-22.3-22.3-22.3c-6.2 0-12.1 2.6-16.3 7.1L117.5 242.2c-3.5 3.8-5.5 8.7-5.5 13.8s2 10.1 5.5 13.8l99.9 107.1z"></path>
|
||||
</svg></button></div>
|
||||
<div class="row pad-row-btn"><button class="btn btn-round-basic color-import" data-bs-toggle="tooltip" data-bss-tooltip="" data-bs-placement="right" id="btnaddtolist" type="button" title="Add"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" width="1em" height="1em" fill="currentColor" style="font-size: 32;">
|
||||
<!--! Font Awesome Free 6.4.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2023 Fonticons, Inc. -->
|
||||
<div class="row pad-row-btn"><button class="btn btn-round-basic color-import" data-bs-toggle="tooltip" data-bss-tooltip="" data-bs-placement="right" id="btnaddtolist" type="button" title="Add"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" width="1em" height="1em" fill="currentColor" style="font-size: 32;"><!--! Font Awesome Free 6.4.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2023 Fonticons, Inc. -->
|
||||
<path d="M464 256A208 208 0 1 1 48 256a208 208 0 1 1 416 0zM0 256a256 256 0 1 0 512 0A256 256 0 1 0 0 256zM294.6 135.1c-4.2-4.5-10.1-7.1-16.3-7.1C266 128 256 138 256 150.3V208H160c-17.7 0-32 14.3-32 32v32c0 17.7 14.3 32 32 32h96v57.7c0 12.3 10 22.3 22.3 22.3c6.2 0 12.1-2.6 16.3-7.1l99.9-107.1c3.5-3.8 5.5-8.7 5.5-13.8s-2-10.1-5.5-13.8L294.6 135.1z"></path>
|
||||
</svg></button></div>
|
||||
</div>
|
||||
@@ -137,7 +133,6 @@
|
||||
<script src="assets/bootstrap/js/bootstrap.min.js"></script>
|
||||
<script src="assets/js/bs-init.js"></script>
|
||||
<script src="assets/js/messagebank.js"></script>
|
||||
<script src="assets/js/datatables.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
File diff suppressed because it is too large
Load Diff
@@ -14,7 +14,6 @@
|
||||
<link rel="stylesheet" href="assets/css/Font%20Awesome%206%20Pro.css">
|
||||
<link rel="stylesheet" href="assets/css/FontAwesome.css">
|
||||
<link rel="stylesheet" href="assets/css/bss-overrides.css">
|
||||
<link rel="stylesheet" href="assets/css/datatables.css">
|
||||
<link rel="stylesheet" href="assets/css/Login-Form-Basic-icons.css">
|
||||
<link rel="stylesheet" href="assets/css/styles.css">
|
||||
</head>
|
||||
@@ -123,9 +122,76 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h4 class="card-title">Adzan Setting</h4>
|
||||
<hr>
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<div class="container">
|
||||
<div class="row py-1">
|
||||
<div class="col-auto"><label class="col-form-label">Latitude</label></div>
|
||||
<div class="col-2"><input class="w-100 h-100" type="text" id="adzanlatitude"></div>
|
||||
<div class="col-auto"><label class="col-form-label">Longitude</label></div>
|
||||
<div class="col-2"><input class="w-100 h-100" type="text" id="adzanlongitude"></div>
|
||||
<div class="col-auto"><label class="col-form-label">TimeZone</label></div>
|
||||
<div class="col"><input class="w-100 h-100" type="text" id="adzantimezone"></div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<p class="py-1" id="adzantoday">Today's Adzan</p>
|
||||
</div>
|
||||
<div class="row py-1" id="fajar">
|
||||
<div class="col-2"><label class="col-form-label w-100 h-100 align-content-center">Fajar</label></div>
|
||||
<div class="col-2"><input class="w-100 h-100 align-content-center adzantime" id="fajar_time" type="time"></div>
|
||||
<div class="col d-flex"><input class="flex-grow-1 me-1 adzanfile" type="text"><button class="btn btn-primary col-auto adzanbrowse" type="button">Browse</button></div>
|
||||
<div class="col-2">
|
||||
<div class="form-check w-100 h-100 align-content-center"><input class="form-check-input adzanenable" type="checkbox" id="enable_fajr"><label class="form-check-label" for="enable_fajr">Enable</label></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row py-1" id="dzuhur">
|
||||
<div class="col-2"><label class="col-form-label w-100 h-100 align-content-center">Dzuhur</label></div>
|
||||
<div class="col-2"><input class="w-100 h-100 align-content-center adzantime" id="fajar_time-4" type="time"></div>
|
||||
<div class="col d-flex"><input class="flex-grow-1 me-1 adzanfile" type="text"><button class="btn btn-primary col-auto adzanbrowse" type="button">Browse</button></div>
|
||||
<div class="col-2">
|
||||
<div class="form-check w-100 h-100 align-content-center"><input class="form-check-input adzanenable" type="checkbox" id="enable_fajr-4"><label class="form-check-label" for="enable_fajr-4">Enable</label></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row py-1" id="ashar">
|
||||
<div class="col-2"><label class="col-form-label w-100 h-100 align-content-center">Ashar</label></div>
|
||||
<div class="col-2"><input class="w-100 h-100 align-content-center adzantime" id="fajar_time-3" type="time"></div>
|
||||
<div class="col d-flex"><input class="flex-grow-1 me-1 adzanfile" type="text"><button class="btn btn-primary col-auto adzanbrowse" type="button">Browse</button></div>
|
||||
<div class="col-2">
|
||||
<div class="form-check w-100 h-100 align-content-center"><input class="form-check-input adzanenable" type="checkbox" id="enable_fajr-3"><label class="form-check-label" for="enable_fajr-3">Enable</label></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row py-1" id="magrib">
|
||||
<div class="col-2"><label class="col-form-label w-100 h-100 align-content-center">Magrib</label></div>
|
||||
<div class="col-2"><input class="w-100 h-100 align-content-center adzantime" id="fajar_time-2" type="time"></div>
|
||||
<div class="col d-flex"><input class="flex-grow-1 me-1 adzanfile" type="text"><button class="btn btn-primary col-auto adzanbrowse" type="button">Browse</button></div>
|
||||
<div class="col-2">
|
||||
<div class="form-check w-100 h-100 align-content-center"><input class="form-check-input adzanenable" type="checkbox" id="enable_fajr-2"><label class="form-check-label" for="enable_fajr-2">Enable</label></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row py-1" id="isya">
|
||||
<div class="col-2"><label class="col-form-label w-100 h-100 align-content-center">Isya</label></div>
|
||||
<div class="col-2"><input class="w-100 h-100 align-content-center adzantime" id="fajar_time-1" type="time"></div>
|
||||
<div class="col d-flex"><input class="flex-grow-1 me-1 adzanfile" type="text"><button class="btn btn-primary col-auto adzanbrowse" type="button">Browse</button></div>
|
||||
<div class="col-2">
|
||||
<div class="form-check w-100 h-100 align-content-center"><input class="form-check-input adzanenable" type="checkbox" id="enable_fajr-1"><label class="form-check-label" for="enable_fajr-1">Enable</label></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-4 col-sm-3 col-md-2 col-lg-2 col-xl-2"><button class="btn w-100 h-100 pad-button btn-round-basic color-add" id="adzansave" type="button">Save</button></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script src="assets/bootstrap/js/bootstrap.min.js"></script>
|
||||
<script src="assets/js/bs-init.js"></script>
|
||||
<script src="assets/js/datatables.js"></script>
|
||||
<script src="assets/js/setting.js"></script>
|
||||
</body>
|
||||
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
<link rel="stylesheet" href="assets/css/Font%20Awesome%206%20Pro.css">
|
||||
<link rel="stylesheet" href="assets/css/FontAwesome.css">
|
||||
<link rel="stylesheet" href="assets/css/bss-overrides.css">
|
||||
<link rel="stylesheet" href="assets/css/datatables.css">
|
||||
<link rel="stylesheet" href="assets/css/Login-Form-Basic-icons.css">
|
||||
<link rel="stylesheet" href="assets/css/styles.css">
|
||||
</head>
|
||||
@@ -42,8 +41,9 @@
|
||||
<div class="col-3 col-sm-3 col-md-3 col-lg-2 col-xl-2"><button class="btn w-100 pad-button btn-round-basic color-add" id="btnAdd" type="button">Add</button></div>
|
||||
<div class="col-3 col-sm-3 col-md-3 col-lg-2 col-xl-2"><button class="btn w-100 pad-button btn-round-basic color-remove" id="btnRemove" type="button">Remove</button></div>
|
||||
<div class="col-3 col-sm-3 col-md-3 col-lg-2 col-xl-2"><button class="btn w-100 pad-button btn-round-basic color-edit" id="btnEdit" type="button">Edit</button></div>
|
||||
<div class="col-6 col-sm-6 col-md-6 col-lg-2 col-xl-2"><button class="btn w-100 pad-button btn-round-basic color-import" id="btnExport" type="button">Export</button></div>
|
||||
<div class="col-6 col-sm-6 col-md-6 col-lg-2 col-xl-2"><button class="btn w-100 pad-button btn-round-basic color-import" id="btnImport" type="button">Import</button></div>
|
||||
<div class="col-3 col-sm-3 col-md-3 col-lg-2 col-xl-2"><button class="btn w-100 pad-button btn-round-basic color-import" id="btnExport" type="button">Export</button></div>
|
||||
<div class="col-3 col-sm-3 col-md-3 col-lg-2 col-xl-2"><button class="btn w-100 pad-button btn-round-basic color-import" id="btnImport" type="button">Import</button></div>
|
||||
<div class="col-3 col-sm-3 col-md-3 col-lg-2 col-xl-2"><button class="btn w-100 pad-button btn-round-basic color-import" id="btnFileCheck" type="button">File Check</button></div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="table-responsive">
|
||||
@@ -120,7 +120,6 @@
|
||||
<script src="assets/bootstrap/js/bootstrap.min.js"></script>
|
||||
<script src="assets/js/bs-init.js"></script>
|
||||
<script src="assets/js/soundbank.js"></script>
|
||||
<script src="assets/js/datatables.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -14,32 +14,82 @@
|
||||
<link rel="stylesheet" href="assets/css/Font%20Awesome%206%20Pro.css">
|
||||
<link rel="stylesheet" href="assets/css/FontAwesome.css">
|
||||
<link rel="stylesheet" href="assets/css/bss-overrides.css">
|
||||
<link rel="stylesheet" href="assets/css/datatables.css">
|
||||
<link rel="stylesheet" href="assets/css/Login-Form-Basic-icons.css">
|
||||
<link rel="stylesheet" href="assets/css/styles.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="card" id="streamercard">
|
||||
<div class="card streamercard" id="streamercard">
|
||||
<div class="card-body card-channel">
|
||||
<h4 class="card-title" id="streamertitle">Channel 01</h4>
|
||||
<h4 class="card-title streamertitle" id="streamertitle">Channel 01</h4>
|
||||
<div class="row">
|
||||
<div class="col-12 col-sm-12 col-md-12 col-lg-6 col-xl-6 col-xxl-6">
|
||||
<h6 class="text-muted mb-2" id="streamerip">IP : 192.168.10.10</h6>
|
||||
<div class="col-3">
|
||||
<p class="w-100 h-100 align-content-center">IP</p>
|
||||
</div>
|
||||
<div class="col-12 col-sm-12 col-md-12 col-lg-6 col-xl-6 col-xxl-6">
|
||||
<h6 class="text-muted mb-2" id="streamerbuffer">Free : 64KB</h6>
|
||||
<div class="col">
|
||||
<p class="w-100 h-100 align-content-center streamerip" id="streamerip">N/A</p>
|
||||
</div>
|
||||
</div>
|
||||
<p class="card-text" id="streamerstatus">Status : Idle</p>
|
||||
<div class="progress" id="streamervu">
|
||||
<div class="row">
|
||||
<div class="col-3">
|
||||
<p class="w-100 h-100 align-content-center">Buffer</p>
|
||||
</div>
|
||||
<div class="col">
|
||||
<p class="w-100 h-100 align-content-center streamerbuffer" id="streamerbuffer">N/A</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-3">
|
||||
<p class="w-100 h-100 align-content-center">Status</p>
|
||||
</div>
|
||||
<div class="col">
|
||||
<p class="w-100 h-100 align-content-center streamerstatus" id="streamerstatus">N/A</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-3">
|
||||
<p class="w-100 h-100 align-content-center">File</p>
|
||||
</div>
|
||||
<div class="col">
|
||||
<p class="w-100 h-100 align-content-center streamerfile" id="streamerfile">N/A</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-3">
|
||||
<p class="w-100 h-100 align-content-center">Zones</p>
|
||||
</div>
|
||||
<div class="col">
|
||||
<p class="w-100 h-100 align-content-center streamerzones" id="streamerzones">N/A</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-3">
|
||||
<p class="w-100 h-100 align-content-center">Duration</p>
|
||||
</div>
|
||||
<div class="col">
|
||||
<p class="w-100 h-100 align-content-center streamerduration" id="streamerduration">N/A</p>
|
||||
</div>
|
||||
<div class="col-3">
|
||||
<p class="w-100 h-100 align-content-center">Elapsed</p>
|
||||
</div>
|
||||
<div class="col">
|
||||
<p class="w-100 h-100 align-content-center streamerelapsed" id="streamerelapsed">N/A</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-3">
|
||||
<p class="w-100 h-100 align-content-center">VU</p>
|
||||
</div>
|
||||
<div class="col">
|
||||
<div class="progress w-100 h-50 streamervu" id="streamervu">
|
||||
<div class="progress-bar" aria-valuenow="50" aria-valuemin="0" aria-valuemax="100" style="width: 50%;">50%</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script src="assets/bootstrap/js/bootstrap.min.js"></script>
|
||||
<script src="assets/js/bs-init.js"></script>
|
||||
<script src="assets/js/datatables.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -14,7 +14,6 @@
|
||||
<link rel="stylesheet" href="assets/css/Font%20Awesome%206%20Pro.css">
|
||||
<link rel="stylesheet" href="assets/css/FontAwesome.css">
|
||||
<link rel="stylesheet" href="assets/css/bss-overrides.css">
|
||||
<link rel="stylesheet" href="assets/css/datatables.css">
|
||||
<link rel="stylesheet" href="assets/css/Login-Form-Basic-icons.css">
|
||||
<link rel="stylesheet" href="assets/css/styles.css">
|
||||
</head>
|
||||
@@ -25,46 +24,6 @@
|
||||
<h2 style="text-align: center;">Schedule Bank</h2>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row d-none pad-row-search">
|
||||
<div class="col-md-7 col-lg-7 col-xl-7"></div>
|
||||
<div class="col-2 col-sm-2 col-md-2 col-lg-2 col-xl-2 search">
|
||||
<p class="text-add">Search</p>
|
||||
</div>
|
||||
<div class="col-10 col-sm-10 col-md-3 col-lg-3 col-xl-3"><input class="w-100 form-control" type="text" id="findschedule" placeholder="Search keyword" name="findsoundbank"></div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-3 col-sm-3 col-md-3 col-lg-2 col-xl-2"><button class="btn w-100 pad-button btn-round-basic color-add" id="btnClear" type="button">Clear</button></div>
|
||||
<div class="col-3 col-sm-3 col-md-3 col-lg-2 col-xl-2"><button class="btn w-100 pad-button btn-round-basic color-add" id="btnAdd" type="button">Add</button></div>
|
||||
<div class="col-3 col-sm-3 col-md-3 col-lg-2 col-xl-2"><button class="btn w-100 pad-button btn-round-basic color-remove" id="btnRemove" type="button">Remove</button></div>
|
||||
<div class="col-3 col-sm-3 col-md-3 col-lg-2 col-xl-2"><button class="btn w-100 pad-button btn-round-basic color-edit" id="btnEdit" type="button">Edit</button></div>
|
||||
<div class="col-6 col-sm-6 col-md-6 col-lg-2 col-xl-2"><button class="btn w-100 pad-button btn-round-basic color-import" id="btnExport" type="button">Export</button></div>
|
||||
<div class="col-6 col-sm-6 col-md-6 col-lg-2 col-xl-2"><button class="btn w-100 pad-button btn-round-basic color-import" id="btnImport" type="button">Import</button></div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<p id="tablesize" class="text-add" style="text-align: center;">Table Length : N/A</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="table-responsive">
|
||||
<table class="table" id="schedulebanktable">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="class05">No</th>
|
||||
<th class="class15">Description</th>
|
||||
<th class="class15">Day</th>
|
||||
<th class="class10">Time</th>
|
||||
<th class="class15">Sound Path</th>
|
||||
<th class="class10">Repeat</th>
|
||||
<th class="class05">Enable</th>
|
||||
<th class="class15">Broadcast Zones</th>
|
||||
<th class="class10">Language</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="schedulebanktablebody"></tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal fade" role="dialog" tabindex="-1" id="schedulemodal">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
@@ -106,10 +65,10 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="row py-2">
|
||||
<div class="col-7 col-sm-7 col-md-7 col-lg-6 col-xl-6 pad-day">
|
||||
<div class="col-auto pad-day">
|
||||
<div class="form-check"><input class="form-check-input" type="radio" id="schedulespecialdate" name="dayselect"><label class="form-check-label" for="schedulespecialdate">Special Date</label></div>
|
||||
</div>
|
||||
<div class="col-sm-5 col-md-5 col-lg-6 col-xl-6"><input id="scheduledate" class="form-control" type="date"></div>
|
||||
<div class="col"><input type="text" id="scheduledate" class="form-control"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -119,13 +78,8 @@
|
||||
</div>
|
||||
<div class="col">
|
||||
<div class="row w-100 h-100">
|
||||
<div class="col-4 col-sm-4 col-md-4 col-lg-4 col-xl-4"><input type="number" id="schedulehour" class="input-add form-control class100" value="0" min="0" max="23" step="1"></div>
|
||||
<div class="col-2 col-sm-2 col-md-2 col-lg-2 col-xl-2">
|
||||
<p class="pad-time">(H)</p>
|
||||
</div>
|
||||
<div class="col-4 col-sm-4 col-md-4 col-lg-4 col-xl-4"><input class="w-100 input-add form-control" type="number" id="scheduleminute" value="0" min="0" max="59" step="1"></div>
|
||||
<div class="col-2 col-sm-2 col-md-2 col-lg-2 col-xl-2">
|
||||
<p class="pad-time">(M)</p>
|
||||
<div class="row w-100 pad-day">
|
||||
<div class="col w-100"><input type="text" id="scheduletime" placeholder="HH:mm"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -181,10 +135,78 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="accordion" role="tablist" id="accordion-1">
|
||||
<div class="accordion-item">
|
||||
<h2 class="accordion-header" role="tab"><button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#accordion-1 .item-1" aria-expanded="false" aria-controls="accordion-1 .item-1">Schedule Table</button></h2>
|
||||
<div class="accordion-collapse collapse item-1" role="tabpanel" data-bs-parent="#accordion-1">
|
||||
<div class="accordion-body">
|
||||
<div class="row">
|
||||
<div class="col-3 col-sm-3 col-md-3 col-lg-2 col-xl-2"><button class="btn w-100 pad-button btn-round-basic color-add" id="btnClear" type="button">Clear</button></div>
|
||||
<div class="col-3 col-sm-3 col-md-3 col-lg-2 col-xl-2"><button class="btn w-100 pad-button btn-round-basic color-add" id="btnAdd" type="button">Add</button></div>
|
||||
<div class="col-3 col-sm-3 col-md-3 col-lg-2 col-xl-2"><button class="btn w-100 pad-button btn-round-basic color-remove" id="btnRemove" type="button">Remove</button></div>
|
||||
<div class="col-3 col-sm-3 col-md-3 col-lg-2 col-xl-2"><button class="btn w-100 pad-button btn-round-basic color-edit" id="btnEdit" type="button">Edit</button></div>
|
||||
<div class="col-6 col-sm-6 col-md-6 col-lg-2 col-xl-2"><button class="btn w-100 pad-button btn-round-basic color-import" id="btnExport" type="button">Export</button></div>
|
||||
<div class="col-6 col-sm-6 col-md-6 col-lg-2 col-xl-2"><button class="btn w-100 pad-button btn-round-basic color-import" id="btnImport" type="button">Import</button></div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<p id="tablesize" class="text-add" style="text-align: center;">Table Length : N/A</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="table-responsive">
|
||||
<table class="table" id="schedulebanktable">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="class05">No</th>
|
||||
<th class="class15">Description</th>
|
||||
<th class="class15">Day</th>
|
||||
<th class="class10">Time</th>
|
||||
<th class="class15">Sound Path</th>
|
||||
<th class="class10">Repeat</th>
|
||||
<th class="class05">Enable</th>
|
||||
<th class="class15">Broadcast Zones</th>
|
||||
<th class="class10">Language</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="schedulebanktablebody"></tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="accordion-item">
|
||||
<h2 class="accordion-header" role="tab"><button class="accordion-button" type="button" data-bs-toggle="collapse" data-bs-target="#accordion-1 .item-2" aria-expanded="true" aria-controls="accordion-1 .item-2">Today's Schedule</button></h2>
|
||||
<div class="accordion-collapse collapse show item-2" role="tabpanel" data-bs-parent="#accordion-1">
|
||||
<div class="accordion-body">
|
||||
<div class="row">
|
||||
<div class="table-responsive">
|
||||
<table class="table" id="todaytable">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="class05">No</th>
|
||||
<th class="class15">Description</th>
|
||||
<th class="class15">Day</th>
|
||||
<th class="class10">Time</th>
|
||||
<th class="class15">Sound Path</th>
|
||||
<th class="class10">Repeat</th>
|
||||
<th class="class05">Enable</th>
|
||||
<th class="class15">Broadcast Zones</th>
|
||||
<th class="class10">Language</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="schedulebanktablebody-1"></tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script src="assets/bootstrap/js/bootstrap.min.js"></script>
|
||||
<script src="assets/js/bs-init.js"></script>
|
||||
<script src="assets/js/schedulebank.js"></script>
|
||||
<script src="assets/js/datatables.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -14,7 +14,6 @@
|
||||
<link rel="stylesheet" href="assets/css/Font%20Awesome%206%20Pro.css">
|
||||
<link rel="stylesheet" href="assets/css/FontAwesome.css">
|
||||
<link rel="stylesheet" href="assets/css/bss-overrides.css">
|
||||
<link rel="stylesheet" href="assets/css/datatables.css">
|
||||
<link rel="stylesheet" href="assets/css/Login-Form-Basic-icons.css">
|
||||
<link rel="stylesheet" href="assets/css/styles.css">
|
||||
</head>
|
||||
@@ -124,7 +123,6 @@
|
||||
</div>
|
||||
<script src="assets/bootstrap/js/bootstrap.min.js"></script>
|
||||
<script src="assets/js/bs-init.js"></script>
|
||||
<script src="assets/js/datatables.js"></script>
|
||||
<script src="assets/js/tts.js"></script>
|
||||
</body>
|
||||
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
<link rel="stylesheet" href="assets/css/Font%20Awesome%206%20Pro.css">
|
||||
<link rel="stylesheet" href="assets/css/FontAwesome.css">
|
||||
<link rel="stylesheet" href="assets/css/bss-overrides.css">
|
||||
<link rel="stylesheet" href="assets/css/datatables.css">
|
||||
<link rel="stylesheet" href="assets/css/Login-Form-Basic-icons.css">
|
||||
<link rel="stylesheet" href="assets/css/styles.css">
|
||||
</head>
|
||||
@@ -238,7 +237,6 @@
|
||||
</div>
|
||||
<script src="assets/bootstrap/js/bootstrap.min.js"></script>
|
||||
<script src="assets/js/bs-init.js"></script>
|
||||
<script src="assets/js/datatables.js"></script>
|
||||
<script src="assets/js/usermanagement.js"></script>
|
||||
</body>
|
||||
|
||||
|
||||
98
src/Main.kt
98
src/Main.kt
@@ -14,8 +14,16 @@ import commandServer.TCP_Android_Command_Server
|
||||
import content.Category
|
||||
import content.Language
|
||||
import content.VoiceType
|
||||
import database.Log
|
||||
import database.data.Log
|
||||
import database.MariaDB
|
||||
import database.data.QueueTable
|
||||
import database.table.Table_Adzan
|
||||
import database.table.Table_BroadcastZones
|
||||
import database.table.Table_Logs
|
||||
import database.table.Table_Messagebank
|
||||
import database.table.Table_QueuePaging
|
||||
import database.table.Table_QueueSoundbank
|
||||
import database.table.Table_SoundChannel
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.delay
|
||||
@@ -29,6 +37,7 @@ import web.WebApp
|
||||
import java.io.File
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.Paths
|
||||
import java.util.TimeZone
|
||||
import kotlin.concurrent.fixedRateTimer
|
||||
import kotlin.io.path.absolutePathString
|
||||
import kotlin.io.path.exists
|
||||
@@ -39,22 +48,21 @@ lateinit var audioPlayer: AudioPlayer
|
||||
val StreamerOutputs: MutableMap<String, BarixConnection> = HashMap()
|
||||
lateinit var udpreceiver: UDPReceiver
|
||||
lateinit var tcpreceiver: TCPReceiver
|
||||
const val version = "0.0.28 (04/02/2026)"
|
||||
const val version = "0.0.30 (12/02/2026)"
|
||||
// AAS 64 channels
|
||||
const val max_channel = 64
|
||||
|
||||
val apptick : Long = System.currentTimeMillis()
|
||||
// dipakai untuk ambil messagebank berdasarkan id
|
||||
val urutan_bahasa = listOf(
|
||||
Language.INDONESIA.name,
|
||||
Language.LOCAL.name,
|
||||
Language.ENGLISH.name,
|
||||
Language.CHINESE.name,
|
||||
Language.JAPANESE.name,
|
||||
Language.ARABIC.name
|
||||
)
|
||||
|
||||
// 4 tabel utama , kepakai dimana-mana, ditaruh di root biar gampang aksesnya
|
||||
lateinit var broadcastDB: Table_BroadcastZones
|
||||
lateinit var soundchannelDB: Table_SoundChannel
|
||||
lateinit var messageDB: Table_Messagebank
|
||||
lateinit var queuetableDB: Table_QueueSoundbank
|
||||
lateinit var queuepagingDB: Table_QueuePaging
|
||||
lateinit var logDB: Table_Logs
|
||||
|
||||
lateinit var adzanTable : Table_Adzan
|
||||
val contentCache = ContentCache()
|
||||
/**
|
||||
* Create necessary folders if not exist
|
||||
@@ -136,6 +144,12 @@ fun main(args: Array<String>) {
|
||||
if ("--bypass-dongle" == str.lowercase()){
|
||||
sdx.BypassDongle = true
|
||||
}
|
||||
if (str.startsWith("--default-soundbank=")){
|
||||
val defaultsb = str.substringAfter("=")
|
||||
val lang = Language.entries.find { it.value.equals(defaultsb, ignoreCase = true) }
|
||||
Language.DEFAULT = lang ?: Language.INDONESIA
|
||||
Logger.info { "Default soundbank language set to ${Language.DEFAULT.value} from command line argument" }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -169,6 +183,12 @@ fun main(args: Array<String>) {
|
||||
|
||||
db = MariaDB()
|
||||
|
||||
adzanTable = Table_Adzan(latitude = config.Get(configKeys.LATITUDE.key).toDouble(),
|
||||
longitude = config.Get(configKeys.LONGITUDE.key).toDouble(),
|
||||
timezone = TimeZone.getTimeZone(config.Get(configKeys.TIMEZONE.key))
|
||||
)
|
||||
adzanTable.GetTodayPrayerTimes()
|
||||
|
||||
|
||||
val subcode01 = MainExtension01()
|
||||
|
||||
@@ -176,14 +196,47 @@ fun main(args: Array<String>) {
|
||||
CoroutineScope(Dispatchers.Default).launch {
|
||||
while (isActive) {
|
||||
delay(1000)
|
||||
|
||||
adzanTable.CheckTime()?.let{ adz ->
|
||||
Logger.info { "It's time for Adzan ${adz.prayerName} at ${adz.timeString}" }
|
||||
val qt = QueueTable(
|
||||
Source = "AAS",
|
||||
Type = "ADZAN",
|
||||
Message = adz.message,
|
||||
Language = adzanTable.adzan_language,
|
||||
SB_TAGS = adz.sb_tags,
|
||||
BroadcastZones = adzanTable.adzan_broadcastzones
|
||||
)
|
||||
queuetableDB.Add(qt)
|
||||
}
|
||||
|
||||
|
||||
// prioritas 1 , habisin queue paging
|
||||
subcode01.Read_Queue_Paging()
|
||||
// prioritas 2, habisin queue shalat
|
||||
subcode01.Read_Queue_Shalat()
|
||||
if (subcode01.Read_Queue_Paging()){
|
||||
// processing paging, skip selanjutnya
|
||||
delay(2000)
|
||||
continue
|
||||
}
|
||||
// // prioritas 2, habisin queue shalat
|
||||
// if (subcode01.Read_Queue_Shalat()){
|
||||
// // processing shalat, skip selanjutnya
|
||||
// delay(2000)
|
||||
// continue
|
||||
// }
|
||||
|
||||
|
||||
// prioritas 3, habisin queue timer
|
||||
subcode01.Read_Queue_Timer()
|
||||
if (subcode01.Read_Queue_Timer()){
|
||||
// processing timer, skip selanjutnya
|
||||
delay(2000)
|
||||
continue
|
||||
}
|
||||
// prioritas 4, habisin queue soundbank
|
||||
subcode01.Read_Queue_Soundbank()
|
||||
if (subcode01.Read_Queue_Soundbank()){
|
||||
// processing soundbank, skip selanjutnya
|
||||
delay(2000)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -220,14 +273,14 @@ fun main(args: Array<String>) {
|
||||
val androidserver = TCP_Android_Command_Server()
|
||||
androidserver.StartTcpServer(5003){
|
||||
Logger.info { it }
|
||||
db.logDB.Add(Log.NewLog("ANDROID", it))
|
||||
logDB.Add(Log.NewLog("ANDROID", it))
|
||||
}
|
||||
|
||||
val barixserver = TCP_Barix_Command_Server()
|
||||
barixserver.StartTcpServer { cmd ->
|
||||
val _tcp = barixserver.getSocket(cmd.ipaddress)
|
||||
val _streamer = StreamerOutputs[cmd.ipaddress]
|
||||
val _sc = db.soundchannelDB.List.find { it.ip == cmd.ipaddress }
|
||||
val _sc = soundchannelDB.List.find { it.ip == cmd.ipaddress }
|
||||
if (_streamer == null) {
|
||||
|
||||
// belum create BarixConnection untuk ipaddress ini
|
||||
@@ -240,6 +293,7 @@ fun main(args: Array<String>) {
|
||||
_bc.bufferRemain = cmd.buffremain
|
||||
_bc.statusData = cmd.statusdata
|
||||
_bc.commandsocket = _tcp
|
||||
_bc.BarixMode = cmd.isBarix
|
||||
|
||||
StreamerOutputs[cmd.ipaddress] = _bc
|
||||
Logger.info { "Created new Streamer Output for channel ${_sc.channel} with IP ${cmd.ipaddress}" }
|
||||
@@ -281,13 +335,17 @@ fun main(args: Array<String>) {
|
||||
}
|
||||
|
||||
|
||||
db.logDB.Add("AAS"," Application started")
|
||||
adzanTable = Table_Adzan(latitude = config.Get(configKeys.LATITUDE.key).toDouble(),
|
||||
longitude = config.Get(configKeys.LONGITUDE.key).toDouble(),
|
||||
timezone = TimeZone.getTimeZone(config.Get(configKeys.TIMEZONE.key))
|
||||
)
|
||||
|
||||
logDB.Add("AAS"," Application started")
|
||||
|
||||
// shutdown hook
|
||||
Runtime.getRuntime().addShutdownHook(Thread ({
|
||||
|
||||
db.logDB.Add("AAS"," Application stopping")
|
||||
logDB.Add("AAS"," Application stopping")
|
||||
Logger.info { "Shutdown hook called, stopping services..." }
|
||||
barixserver.StopTcpCommand()
|
||||
androidserver.StopTcpCommand()
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -18,4 +18,39 @@ class AudioFileInfo {
|
||||
fun isValid() : Boolean {
|
||||
return fileName.isNotBlank() && fileSize > 0 && duration > 0.0 && bytes.isNotEmpty()
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the duration to a human-readable string format.
|
||||
* @return Duration as a string in HH:MM:SS or MM:SS format or SS if less than a minute.
|
||||
*/
|
||||
fun DurationToString() : String {
|
||||
val totalSeconds = duration.toInt()
|
||||
val hours = totalSeconds / 3600
|
||||
val minutes = (totalSeconds % 3600) / 60
|
||||
val seconds = totalSeconds % 60
|
||||
|
||||
return when {
|
||||
hours > 0 -> String.format("%02d h:%02d m:%02d s", hours, minutes, seconds)
|
||||
minutes > 0 -> String.format("%02d m:%02d s", minutes, seconds)
|
||||
else -> String.format("00:%02d s", seconds)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the file size to a human-readable string format.
|
||||
* @return File size as a string in Bytes, KB, MB, or GB.
|
||||
*/
|
||||
fun FileSizeToString() : String {
|
||||
val kb = 1024
|
||||
val mb = kb * 1024
|
||||
val gb = mb * 1024
|
||||
|
||||
return when {
|
||||
fileSize >= gb -> String.format("%.2f GB", fileSize.toDouble() / gb)
|
||||
fileSize >= mb -> String.format("%.2f MB", fileSize.toDouble() / mb)
|
||||
fileSize >= kb -> String.format("%.2f KB", fileSize.toDouble() / kb)
|
||||
else -> "$fileSize Bytes"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,7 @@
|
||||
package barix
|
||||
|
||||
import audio.AudioFileInfo
|
||||
import audio.Mp3Encoder
|
||||
import codes.Somecodes
|
||||
import com.fasterxml.jackson.databind.JsonNode
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.delay
|
||||
@@ -14,8 +13,8 @@ import java.net.InetSocketAddress
|
||||
import java.net.Socket
|
||||
import java.nio.ByteBuffer
|
||||
import java.util.function.Consumer
|
||||
import kotlin.experimental.or
|
||||
|
||||
@Suppress("unused")
|
||||
class BarixConnection(val index: UInt, var channel: String, val ipaddress: String, val port: Int = 5002) : AutoCloseable {
|
||||
private var _bR: Int = 0
|
||||
private var _sd: Int = 0
|
||||
@@ -27,6 +26,99 @@ class BarixConnection(val index: UInt, var channel: String, val ipaddress: Strin
|
||||
private val mp3encoder = Mp3Encoder()
|
||||
private val mp3Consumer = mutableMapOf<String, Consumer<ByteArray>>()
|
||||
private val udp = DatagramSocket()
|
||||
private var _barixmode: Boolean = false
|
||||
private var _usedbybroadcastzone = mutableSetOf<String>()
|
||||
private var afi : AudioFileInfo? = null
|
||||
private var starttick: Long? = null
|
||||
|
||||
/**
|
||||
* Set audio file information used for playback
|
||||
* @param value The AudioFileInfo object containing audio file details
|
||||
*/
|
||||
fun SetAudioFileInfo(value: AudioFileInfo?){
|
||||
afi = value
|
||||
}
|
||||
|
||||
/**
|
||||
* Get audio file information used for playback
|
||||
* @return The AudioFileInfo object containing audio file details, or null if not set
|
||||
*/
|
||||
fun GetAudioFileInfo() : AudioFileInfo?{
|
||||
return afi
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the start tick for playback timing
|
||||
* @param tick The start tick in milliseconds
|
||||
*/
|
||||
fun SetStartTick(tick: Long?){
|
||||
starttick = tick
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the elapsed time since the start tick
|
||||
* @return Elapsed time as a formatted string or "N/A" if start tick is not set
|
||||
*/
|
||||
fun GetElapsed() : String {
|
||||
if (starttick != null){
|
||||
val elapsedMs = System.currentTimeMillis() - starttick!!
|
||||
val totalSeconds = elapsedMs / 1000
|
||||
val hours = totalSeconds / 3600
|
||||
val minutes = (totalSeconds % 3600) / 60
|
||||
val seconds = totalSeconds % 60
|
||||
|
||||
return when {
|
||||
hours > 0 -> String.format("%02d h:%02d m:%02d s", hours, minutes, seconds)
|
||||
minutes > 0 -> String.format("%02d m:%02d s", minutes, seconds)
|
||||
else -> String.format("00:%02d s", seconds)
|
||||
}
|
||||
} else {
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a broadcast zone that uses this Barix device
|
||||
* @param zoneName The name of the broadcast zone
|
||||
*/
|
||||
fun AddUsedByBroadcastZone(zoneName: String){
|
||||
_usedbybroadcastzone.add(zoneName)
|
||||
println("Added used by broadcast zone: $zoneName to Barix device $ipaddress")
|
||||
}
|
||||
|
||||
/**
|
||||
* Add multiple broadcast zones that use this Barix device
|
||||
* @param zoneNames The list of broadcast zone names
|
||||
*/
|
||||
fun AddUsedByBroadcastZone(zoneNames: List<String>){
|
||||
for (zoneName in zoneNames){
|
||||
AddUsedByBroadcastZone(zoneName)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear all broadcast zones that use this Barix device
|
||||
*/
|
||||
fun ClearUsedByBroadcastZones(){
|
||||
_usedbybroadcastzone.clear()
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a comma-separated string of broadcast zones that use this Barix device
|
||||
* @return Comma-separated string of broadcast zones
|
||||
*/
|
||||
fun GetUsedByBroadcastZones() : String{
|
||||
return _usedbybroadcastzone.joinToString(", ")
|
||||
}
|
||||
|
||||
/**
|
||||
* Barix mode flag
|
||||
*/
|
||||
var BarixMode: Boolean
|
||||
get() = _barixmode
|
||||
set(value) {
|
||||
_barixmode = value
|
||||
}
|
||||
|
||||
init {
|
||||
mp3encoder.Start { data ->
|
||||
@@ -42,6 +134,7 @@ class BarixConnection(val index: UInt, var channel: String, val ipaddress: Strin
|
||||
mp3Consumer.remove(key)
|
||||
}
|
||||
|
||||
@Suppress("unused")
|
||||
fun Exists_Mp3_Consumer(key: String) : Boolean{
|
||||
return mp3Consumer.containsKey(key)
|
||||
}
|
||||
@@ -118,136 +211,143 @@ class BarixConnection(val index: UInt, var channel: String, val ipaddress: Strin
|
||||
return statusData == 1
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if buffer has enough space (more than 10,000 bytes)
|
||||
* @return true if buffer has enough space
|
||||
*/
|
||||
fun bufferEnough(): Boolean{
|
||||
return isOnline() && (bufferRemain > 10000)
|
||||
}
|
||||
|
||||
/**
|
||||
* Send data to Barix device via UDP
|
||||
* @param data The data to send
|
||||
* @param cbOK Callback function if sending is successful
|
||||
* @param cbFail Callback function if sending fails
|
||||
* @param cbPlaying Callback function to indicate if device is playing
|
||||
*/
|
||||
fun SendData(data: ByteArray, cbOK: Consumer<String>, cbFail: Consumer<String>) {
|
||||
fun SendData(data: ByteArray, cbOK: Consumer<String>, cbFail: Consumer<String>, cbPlaying: Consumer<Boolean>) {
|
||||
if (data.isNotEmpty()) {
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
val bb = ByteBuffer.wrap(data)
|
||||
while(!bufferEnough()){
|
||||
delay(20)
|
||||
Logger.info{"Waiting for StreamerOutput $ipaddress buffer to have enough space: $bufferRemain bytes available, need more than 10000 bytes"}
|
||||
}
|
||||
val bufmax = bufferRemain
|
||||
val bufkosong = 0.2 * bufmax
|
||||
val bufpenuh = 0.8 * bufmax
|
||||
Logger.info{"Starting to send data to StreamerOutput $ipaddress on channel $channel, total data size: ${data.size} bytes, bufferRemain: $bufferRemain bytes, bufkosong: $bufkosong bytes, bufpenuh: $bufpenuh bytes"}
|
||||
// Ide 07/02/2026, kasih buffer dummy 10x1000 byte pertama biar barix kebacanya stabil
|
||||
for (i in 1..10){
|
||||
val chunk = ByteArray(1000){0}
|
||||
udp.send(DatagramPacket(chunk, chunk.size, inet))
|
||||
delay(5)
|
||||
println("Sending dummy buffer $i to $ipaddress")
|
||||
}
|
||||
|
||||
// delay interval awal = 5 ms untuk streamer output dan 10 ms untuk barix
|
||||
// slow down interval = 5 ms untuk streamer output dan 10 ms untuk barix
|
||||
// speed up interval = 2 ms untuk streamer output dan 5 ms untuk barix
|
||||
val slowdowninterval = if (BarixMode) 8L else 5L
|
||||
val speedupinterval = if (BarixMode) 5L else 2L
|
||||
var delayinterval = if (BarixMode) 8L else 5L
|
||||
|
||||
// buat hitung elapsed
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
SetStartTick(System.currentTimeMillis())
|
||||
cbPlaying.accept(true)
|
||||
do{
|
||||
delay(1000)
|
||||
} while (isPlaying())
|
||||
cbPlaying.accept(false)
|
||||
SetStartTick(null)
|
||||
}
|
||||
|
||||
|
||||
while(bb.hasRemaining()){
|
||||
try {
|
||||
val chunk = ByteArray(if (bb.remaining() > maxUDPsize) maxUDPsize else bb.remaining())
|
||||
bb.get(chunk)
|
||||
while(bufferRemain<chunk.size){
|
||||
delay(5)
|
||||
delay(10)
|
||||
// gas-rem pengiriman
|
||||
when{
|
||||
bufferRemain <= bufkosong -> {
|
||||
if (delayinterval!=slowdowninterval){
|
||||
delayinterval = slowdowninterval
|
||||
Logger.info{"Sending to $ipaddress on channel $channel, bufferRemain low: $bufferRemain bytes, slowing down to $delayinterval ms"}
|
||||
}
|
||||
}
|
||||
bufferRemain >= bufpenuh -> {
|
||||
if (delayinterval!=speedupinterval){
|
||||
delayinterval = speedupinterval
|
||||
Logger.info{"Sending to $ipaddress on channel $channel, bufferRemain high: $bufferRemain bytes, speeding up to $delayinterval ms"}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
udp.send(DatagramPacket(chunk, chunk.size, inet))
|
||||
mp3encoder.PushData(chunk)
|
||||
delay(1)
|
||||
|
||||
|
||||
delay(delayinterval)
|
||||
} catch (e: Exception) {
|
||||
Logger.error{"SendData to $ipaddress failed, message: ${e.message}"}
|
||||
cbFail.accept("SendData to $ipaddress failed, message: ${e.message}")
|
||||
cbPlaying.accept(false)
|
||||
return@launch
|
||||
}
|
||||
}
|
||||
cbOK.accept("SendData to $channel ($ipaddress) succeeded, ${data.size} bytes sent")
|
||||
Logger.info{"SendData to $channel ($ipaddress) ended, ${data.size} bytes sent"}
|
||||
cbOK.accept("SendData to $channel ($ipaddress) ended, ${data.size} bytes sent")
|
||||
}
|
||||
|
||||
} else cbFail.accept("SendData to $ipaddress failed, data is empty")
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert BarixConnection to JsonNode
|
||||
* @return JsonNode representation of BarixConnection
|
||||
*/
|
||||
fun toJsonNode(): JsonNode {
|
||||
// make json node from index, channel, ipaddress, port, bufferRemain, statusData, vu
|
||||
return Somecodes.objectmapper.createObjectNode().apply {
|
||||
put("index", index.toInt())
|
||||
put("channel", channel)
|
||||
put("ipaddress", ipaddress)
|
||||
put("port", port)
|
||||
put("bufferRemain", bufferRemain)
|
||||
put("statusData", statusData)
|
||||
put("vu", vu)
|
||||
put("isOnline", isOnline())
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert BarixConnection to JSON string
|
||||
* @return JSON string representation of BarixConnection
|
||||
*/
|
||||
fun toJsonString(): String {
|
||||
return Somecodes.toJsonString(toJsonNode())
|
||||
}
|
||||
|
||||
/**
|
||||
* Activate relay on Barix device
|
||||
* @param relays The relay numbers to activate (1-8)
|
||||
* @return true if successful
|
||||
*/
|
||||
fun ActivateRelay(vararg relays: Int){
|
||||
val command = StringBuilder("RELAY;")
|
||||
var binary = 0
|
||||
relays.forEach {
|
||||
if (it in 1..8) {
|
||||
binary = binary or (1 shl (it - 1))
|
||||
}
|
||||
}
|
||||
command.append(binary.toString()).append("@")
|
||||
SendCommand(command.toString())
|
||||
}
|
||||
|
||||
fun ActivateRelay(relays: List<Int>){
|
||||
val command = StringBuilder("RELAY;")
|
||||
var binary = 0
|
||||
relays.forEach {
|
||||
if (it in 1..8) {
|
||||
binary = binary or (1 shl (it - 1))
|
||||
if (relays.isNotEmpty()){
|
||||
var value : Byte = 0
|
||||
for (r in relays){
|
||||
if (r in 1..8){
|
||||
value = value or (1 shl (r - 1)).toByte()
|
||||
}
|
||||
}
|
||||
command.append(binary.toString()).append("@")
|
||||
SendCommand(command.toString())
|
||||
SendSimpleCommand(byteArrayOf(0x1A, value, 0x61))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deactivate relay on Barix device
|
||||
*/
|
||||
fun DeactivateRelay(){
|
||||
SendCommand("RELAY;0@")
|
||||
SendSimpleCommand(byteArrayOf(0x1A, 0, 0x61))
|
||||
}
|
||||
|
||||
/**
|
||||
* Send command to Barix device
|
||||
* Send simple command to Barix device
|
||||
* @param command The command to send
|
||||
* @return true if successful
|
||||
*/
|
||||
fun SendCommand(command: String): Boolean {
|
||||
fun SendSimpleCommand(command: ByteArray) : Boolean {
|
||||
try {
|
||||
if (_tcp !=null){
|
||||
if (!_tcp!!.isClosed){
|
||||
val bb = command.toByteArray()
|
||||
val size = bb.size + 4
|
||||
val b4 = byteArrayOf(
|
||||
(size shr 24 and 0xFF).toByte(),
|
||||
(size shr 16 and 0xFF).toByte(),
|
||||
(size shr 8 and 0xFF).toByte(),
|
||||
(size and 0xFF).toByte()
|
||||
)
|
||||
if (_tcp!!.isConnected){
|
||||
val out = _tcp!!.getOutputStream()
|
||||
out.write(b4)
|
||||
out.write(bb)
|
||||
out.write(command)
|
||||
out.flush()
|
||||
Logger.info { "SendCommand to $ipaddress : $command" }
|
||||
return true
|
||||
}else {
|
||||
Logger.error { "Socket to $ipaddress is not connected" }
|
||||
}
|
||||
} else {
|
||||
Logger.error { "Socket to $ipaddress is null" }
|
||||
}
|
||||
|
||||
} else throw Exception("Socket to $ipaddress is not connected")
|
||||
} else throw Exception("Socket to $ipaddress is null")
|
||||
} catch (e: Exception) {
|
||||
Logger.error { "Failed to SendCommand to $ipaddress, Message : ${e.message}" }
|
||||
if (e.message != null && e.message!!.isNotEmpty()) {
|
||||
Logger.error { "Failed to send command, message : ${e.message}" }
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
override fun close() {
|
||||
try{
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package barix
|
||||
@Suppress("unused")
|
||||
data class BarixStatus(val ipaddress: String, val vu: Int, val buffremain: Int, val statusdata: Int){
|
||||
data class BarixStatus(val ipaddress: String, val vu: Int, val buffremain: Int, val statusdata: Int, val isBarix: Boolean){
|
||||
override fun toString(): String {
|
||||
return "BarixStatus(ipaddress='$ipaddress', vu=$vu, buffremain=$buffremain, statusdata=$statusdata)"
|
||||
}
|
||||
|
||||
@@ -1,22 +1,21 @@
|
||||
package barix
|
||||
|
||||
import codes.Somecodes.Companion.LitteEndianToInt
|
||||
import codes.Somecodes.Companion.ValidString
|
||||
import kotlinx.coroutines.*
|
||||
import org.tinylog.Logger
|
||||
import java.io.DataInputStream
|
||||
import java.net.ServerSocket
|
||||
import java.net.Socket
|
||||
import java.nio.ByteBuffer
|
||||
import java.util.function.Consumer
|
||||
|
||||
@Suppress("unused")
|
||||
class TCP_Barix_Command_Server {
|
||||
lateinit var tcpserver: ServerSocket
|
||||
lateinit var job: Job
|
||||
private val socketMap = mutableMapOf<String, Socket>()
|
||||
|
||||
private val regex = """STATUSBARIX;(\d+);(\d+);?(\d)?"""
|
||||
private val pattern = Regex(regex)
|
||||
//private val regex = """STATUSBARIX;(\d+);(\d+)(;(\d+))?"""
|
||||
//private val pattern = Regex(regex)
|
||||
|
||||
/**
|
||||
* Start TCP Command Server
|
||||
@@ -40,33 +39,77 @@ class TCP_Barix_Command_Server {
|
||||
socketMap[key] = socket
|
||||
Logger.info { "Start communicating with Streamer Output with IP : $key" }
|
||||
try{
|
||||
|
||||
val din = DataInputStream(socket.getInputStream())
|
||||
|
||||
var VuZeroCounter = 0L
|
||||
while (isActive) {
|
||||
val length = ByteArray(4)
|
||||
din.readFully(length)
|
||||
val readlength = ByteBuffer.wrap(length).getInt()
|
||||
//println("Read Length : $readlength")
|
||||
val bb = ByteArray(readlength)
|
||||
din.readFully(bb)
|
||||
// B4A format, 4 bytes di depan adalah size
|
||||
val str = String(bb)
|
||||
//println("Received from $key : $str")
|
||||
if (ValidString(str)) {
|
||||
// Valid command from Barix is in format $"STATUSBARIX;VU;BuffRemain;StatusData"$
|
||||
pattern.find(str)?.let { matchResult ->
|
||||
val (vu, buffremain, statusdata) = matchResult.destructured
|
||||
val status = BarixStatus(
|
||||
socket.inetAddress.hostAddress,
|
||||
vu.toInt(),
|
||||
buffremain.toInt(),
|
||||
statusdata.toIntOrNull() ?: 0
|
||||
)
|
||||
//Logger.info { "Received valid command from $key : $status" }
|
||||
cb.accept(status)
|
||||
} ?: run {
|
||||
Logger.warn { "Invalid command format from $key : $str" }
|
||||
|
||||
val bb = ByteArray(128)
|
||||
val readbytes = din.read(bb)
|
||||
if (readbytes == -1) {
|
||||
Logger.info { "Connection closed by Streamer Output with IP $key" }
|
||||
break
|
||||
}
|
||||
|
||||
if (readbytes == 0) continue
|
||||
var stringlength = 0
|
||||
try{
|
||||
stringlength = LitteEndianToInt(bb[0], bb[1], bb[2], bb[3])
|
||||
if (stringlength<1 || stringlength>bb.size-4) throw Exception("Invalid string length $stringlength")
|
||||
} catch (ex:Exception){
|
||||
Logger.error { "Error reading length from Streamer Output with IP $key, Message : ${ex.message}" }
|
||||
continue
|
||||
}
|
||||
var str = String(bb,4, stringlength).trim()
|
||||
if (str.isBlank()) continue
|
||||
if (!str.startsWith("STATUSBARIX")) continue
|
||||
if (str.endsWith("@")) str = str.removeSuffix("@")
|
||||
if (ValidString(str)) {
|
||||
// Valid command from StreamerOutput is in format $"STATUSBARIX;VU;BuffRemain;StatusData"$
|
||||
// Valid command from Barix is in format $"STATUSBARIX;VU;BuffRemain"$
|
||||
val values = str.split(";")
|
||||
if (values.size<3) continue
|
||||
if ("STATUSBARIX" != values[0]) continue
|
||||
val vu = values[1].toIntOrNull() ?: continue
|
||||
|
||||
val buffremain = values[2].toIntOrNull() ?: continue
|
||||
var status: BarixStatus
|
||||
when(values.size){
|
||||
3 ->{
|
||||
// mode barix
|
||||
// kadang vu stuck tidak di 0 saat idle,
|
||||
// jadi kalau vu <512 selama 10 kali berturut2
|
||||
// dan buffer lebih dari 16000, anggap idle
|
||||
if ((vu < 512) && (buffremain>=16000)){
|
||||
VuZeroCounter++
|
||||
} else {
|
||||
VuZeroCounter = 0
|
||||
}
|
||||
// statusdata = isplaying = , if VuZeroCounter >=10 then idle (0) else playing (1)
|
||||
val statusdata = if (VuZeroCounter>=10) 0 else 1
|
||||
status = BarixStatus(
|
||||
socket.inetAddress.hostAddress,
|
||||
vu,
|
||||
buffremain,
|
||||
statusdata,
|
||||
true
|
||||
)
|
||||
}
|
||||
4 ->{
|
||||
// mode Q-AG1
|
||||
val statusdata = values[3].toIntOrNull() ?: 0
|
||||
status = BarixStatus(
|
||||
socket.inetAddress.hostAddress,
|
||||
vu,
|
||||
buffremain,
|
||||
statusdata,
|
||||
false
|
||||
)
|
||||
}
|
||||
else -> continue
|
||||
}
|
||||
cb.accept(status)
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -24,11 +24,13 @@ import java.nio.file.Files
|
||||
import java.nio.file.Path
|
||||
import java.nio.file.StandardCopyOption
|
||||
import java.time.LocalDateTime
|
||||
import java.time.ZoneId
|
||||
import java.time.format.DateTimeFormatter
|
||||
import java.util.function.BiConsumer
|
||||
import java.util.function.Consumer
|
||||
import kotlin.io.path.exists
|
||||
import kotlin.io.path.name
|
||||
import kotlin.streams.asSequence
|
||||
|
||||
|
||||
@Suppress("unused")
|
||||
@@ -109,6 +111,14 @@ class Somecodes {
|
||||
} else cb.accept(-1,-1)
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a string of numbers separated by commas or semicolons into a list of integers.
|
||||
*/
|
||||
fun StringToListInt(value : String?) : List<Int>{
|
||||
if (value.isNullOrBlank()) return listOf()
|
||||
return value.split(",",";").mapNotNull { it.trim().toIntOrNull() }
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear all files in PagingResult directory.
|
||||
* If PagingResult directory does not exist, callback with -1,-1.
|
||||
@@ -344,6 +354,7 @@ class Somecodes {
|
||||
// and find them recursively
|
||||
if (Files.exists(p) && Files.isDirectory(p)){
|
||||
Files.walk(p)
|
||||
.asSequence()
|
||||
// cari file regular saja
|
||||
.filter { Files.isRegularFile(it)}
|
||||
// size lebih dari 1KB
|
||||
@@ -522,6 +533,22 @@ class Somecodes {
|
||||
return value is String && value.isNotBlank()
|
||||
}
|
||||
|
||||
fun LitteEndianToInt(vararg bb: Byte): Int {
|
||||
var result = 0
|
||||
for (i in bb.indices) {
|
||||
result = result or ((bb[i].toInt() and 0xFF) shl (8 * i))
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
fun BigEndianToInt(vararg bb: Byte): Int {
|
||||
var result = 0
|
||||
for (i in bb.indices) {
|
||||
result = result or ((bb[i].toInt() and 0xFF) shl (8 * (bb.size - 1 - i)))
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if all strings in a list are valid non-blank strings.
|
||||
* @param values The list of strings to check.
|
||||
@@ -640,6 +667,14 @@ class Somecodes {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get today's date as a string in the format "dd/MM/yyyy".
|
||||
* @return A string representing today's date.
|
||||
*/
|
||||
fun Today_to_DateString() : String {
|
||||
return dateformat1.format(LocalDateTime.now())
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a string is a valid time in the format "hh:mm:ss".
|
||||
* @param value The string to check.
|
||||
@@ -675,6 +710,14 @@ class Somecodes {
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of all available time zones.
|
||||
* @return A list of strings representing the available time zones.
|
||||
*/
|
||||
fun Get_TimeZones() : List<String>{
|
||||
return ZoneId.getAvailableZoneIds().sorted()
|
||||
}
|
||||
|
||||
/**
|
||||
* Find a schedule day by its name.
|
||||
* @param value The name of the schedule day to find.
|
||||
@@ -685,6 +728,21 @@ class Somecodes {
|
||||
return sd?.name
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a string is a valid language code.
|
||||
* A valid language code is one that matches any of the Language enum values.
|
||||
* @param value The string to check.
|
||||
* @return True if the string is a valid language code, false otherwise.
|
||||
*/
|
||||
fun ValidLanguage(value: String) : Boolean{
|
||||
value.split(";",",").forEach { ll ->
|
||||
Language.entries.forEach { l ->
|
||||
if (l.value.equals(ll,true)) return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a string is a valid schedule day or a valid date.
|
||||
* A valid schedule day is either one of the ScheduleDay enum names or a date in the format "dd/MM/yyyy".
|
||||
@@ -705,6 +763,56 @@ class Somecodes {
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a string is a valid latitude.
|
||||
* @param value The string to check.
|
||||
* @return True if the string is a valid latitude, false otherwise.
|
||||
*/
|
||||
fun ValidLatitude(value: String) : Boolean {
|
||||
return try {
|
||||
val lat = value.toDouble()
|
||||
lat in -90.0..90.0
|
||||
} catch (_: Exception) {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
fun ValidLatitude(value: Double) : Boolean {
|
||||
return value in -90.0..90.0
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a string is a valid longitude.
|
||||
* @param value The string to check.
|
||||
* @return True if the string is a valid longitude, false otherwise.
|
||||
*/
|
||||
fun ValidLongitude(value: String) : Boolean {
|
||||
return try {
|
||||
val lon = value.toDouble()
|
||||
lon in -180.0..180.0
|
||||
} catch (_: Exception) {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
fun ValidLongitude(value: Double) : Boolean {
|
||||
return value in -180.0..180.0
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a string is a valid time zone.
|
||||
* @param value The string to check.
|
||||
* @return True if the string is a valid time zone, false otherwise.
|
||||
*/
|
||||
fun ValidTimeZone(value: String) : Boolean {
|
||||
return try {
|
||||
ZoneId.of(value)
|
||||
true
|
||||
} catch (_: Exception) {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a WAV file name with the current date and time.
|
||||
* The file name format is: [prefix]_ddMMyyyy_HHmmss_[postfix].wav
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package codes
|
||||
|
||||
import content.VoiceType
|
||||
import database.table.AdzanSetting
|
||||
import org.tinylog.Logger
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.Paths
|
||||
@@ -44,6 +44,44 @@ class configFile {
|
||||
}
|
||||
}
|
||||
|
||||
class ChangedSetting(
|
||||
val key: String,
|
||||
val oldValue: String,
|
||||
val newValue: String
|
||||
)
|
||||
|
||||
/**
|
||||
* Compare current Adzan settings with the provided AdzanSetting object.
|
||||
* @param adz The AdzanSetting object to compare with.
|
||||
* @return true if some settings are different, false if all are the same.
|
||||
*/
|
||||
fun CompareWithAdzanSetting(adz : AdzanSetting) : List<ChangedSetting>{
|
||||
val changes = mutableListOf<ChangedSetting>()
|
||||
configKeys.entries.forEach { ck ->
|
||||
val currentValue = Get(ck.key)
|
||||
val newValue = when (ck) {
|
||||
configKeys.ADZAN_FAJR_ENABLED -> adz.fajar_enable.toString()
|
||||
configKeys.ADZAN_DHUHR_ENABLED -> adz.dzuhur_enable.toString()
|
||||
configKeys.ADZAN_ASR_ENABLED -> adz.ashar_enable.toString()
|
||||
configKeys.ADZAN_MAGHRIB_ENABLED -> adz.maghrib_enable.toString()
|
||||
configKeys.ADZAN_ISHA_ENABLED -> adz.isya_enable.toString()
|
||||
configKeys.ADZAN_FAJR_SOUND -> adz.fajar_sound
|
||||
configKeys.ADZAN_DHUHR_SOUND -> adz.dzuhur_sound
|
||||
configKeys.ADZAN_ASR_SOUND -> adz.ashar_sound
|
||||
configKeys.ADZAN_MAGHRIB_SOUND -> adz.maghrib_sound
|
||||
configKeys.ADZAN_ISHA_SOUND -> adz.isya_sound
|
||||
configKeys.LATITUDE -> adz.latitude.toString()
|
||||
configKeys.LONGITUDE -> adz.longitude.toString()
|
||||
configKeys.TIMEZONE -> adz.timezone
|
||||
else -> return@forEach
|
||||
}
|
||||
if (currentValue != newValue){
|
||||
changes.add(ChangedSetting(key=ck.key, oldValue = currentValue, newValue = newValue))
|
||||
}
|
||||
}
|
||||
return changes
|
||||
}
|
||||
|
||||
private fun HaveAllKeys() : Boolean{
|
||||
return configKeys.entries.all { config.containsKey(it.key) }
|
||||
|
||||
@@ -52,23 +90,9 @@ class configFile {
|
||||
private fun CreateDefaultConfig(){
|
||||
config.clear()
|
||||
// create default config file
|
||||
config[configKeys.DATABASE_HOST.key] = "localhost"
|
||||
config[configKeys.DATABASE_PORT.key] = "3306"
|
||||
config[configKeys.DATABASE_USER.key] = "admin"
|
||||
config[configKeys.DATABASE_PASSWORD.key] = "admin"
|
||||
config[configKeys.DATABASE_NAME.key] = "aas"
|
||||
config[configKeys.SOUNDBANK_DIRECTORY.key] = Paths.get(Somecodes.current_directory, "soundbank").toString()
|
||||
config[configKeys.REMARK_GOP.key] = ""
|
||||
config[configKeys.REMARK_GBD.key] = ""
|
||||
config[configKeys.REMARK_GFC.key] = ""
|
||||
config[configKeys.REMARK_FLD.key] = ""
|
||||
config[configKeys.WEBAPP_ADMIN_USERNAME.key] = "admin"
|
||||
config[configKeys.WEBAPP_ADMIN_PASSWORD.key] = "password"
|
||||
config[configKeys.WEBAPP_VIEWER_USERNAME.key] = "viewer"
|
||||
config[configKeys.WEBAPP_VIEWER_PASSWORD.key] = "password"
|
||||
config[configKeys.WEBAPP_PORT.key] = "3030"
|
||||
config[configKeys.DEFAULT_VOICE_TYPE.key] = VoiceType.VOICE_1.name
|
||||
config[configKeys.AUTO_DELETE_RESULT_DAYS.key] = "7"
|
||||
configKeys.entries.forEach { ck ->
|
||||
config[ck.key] = ck.defaultValue
|
||||
}
|
||||
Save()
|
||||
}
|
||||
}
|
||||
@@ -1,21 +1,42 @@
|
||||
package codes
|
||||
|
||||
enum class configKeys(val key: String) {
|
||||
DATABASE_HOST("database.host"),
|
||||
DATABASE_PORT("database.port"),
|
||||
DATABASE_USER("database.user"),
|
||||
DATABASE_PASSWORD("database.password"),
|
||||
DATABASE_NAME("database.name"),
|
||||
SOUNDBANK_DIRECTORY("soundbank.directory"),
|
||||
REMARK_GOP("remark.GOP"),
|
||||
REMARK_GBD("remark.GBD"),
|
||||
REMARK_GFC("remark.GFC"),
|
||||
REMARK_FLD("remark.FLD"),
|
||||
DEFAULT_VOICE_TYPE("default.voice.type"),
|
||||
WEBAPP_ADMIN_USERNAME("webapp.admin.username"),
|
||||
WEBAPP_ADMIN_PASSWORD("webapp.admin.password"),
|
||||
WEBAPP_VIEWER_USERNAME("webapp.viewer.username"),
|
||||
WEBAPP_VIEWER_PASSWORD("webapp.viewer.password"),
|
||||
WEBAPP_PORT("webapp.port"),
|
||||
AUTO_DELETE_RESULT_DAYS("auto.delete.result.days")
|
||||
import content.VoiceType
|
||||
import kotlin.io.path.Path
|
||||
|
||||
enum class configKeys(val key: String, val defaultValue: String) {
|
||||
DATABASE_HOST("database.host", "localhost"),
|
||||
DATABASE_PORT("database.port", "3306"),
|
||||
DATABASE_USER("database.user", "admin"),
|
||||
DATABASE_PASSWORD("database.password", "admin"),
|
||||
DATABASE_NAME("database.name", "aas"),
|
||||
SOUNDBANK_DIRECTORY("soundbank.directory", Path(Somecodes.current_directory, "soundbank").toString()),
|
||||
REMARK_GOP("remark.GOP", "GOP"),
|
||||
REMARK_GBD("remark.GBD", "GBD"),
|
||||
REMARK_GFC("remark.GFC", "GFC"),
|
||||
REMARK_FLD("remark.FLD", "FLD"),
|
||||
DEFAULT_VOICE_TYPE("default.voice.type", VoiceType.VOICE_1.name),
|
||||
WEBAPP_ADMIN_USERNAME("webapp.admin.username", "admin"),
|
||||
WEBAPP_ADMIN_PASSWORD("webapp.admin.password", "password"),
|
||||
WEBAPP_VIEWER_USERNAME("webapp.viewer.username", "viewer"),
|
||||
WEBAPP_VIEWER_PASSWORD("webapp.viewer.password", "password"),
|
||||
WEBAPP_PORT("webapp.port", "3030"),
|
||||
AUTO_DELETE_RESULT_DAYS("auto.delete.result.days", "7"),
|
||||
LATITUDE("latitude", "-6.1751"),
|
||||
LONGITUDE("longitude", "106.8272"),
|
||||
TIMEZONE("timezone", "Asia/Jakarta"),
|
||||
ADZAN_FAJR_ENABLED("adzan.fajr.enabled", "false"),
|
||||
ADZAN_DHUHR_ENABLED("adzan.dhuhr.enabled", "false"),
|
||||
ADZAN_ASR_ENABLED("adzan.asr.enabled", "false"),
|
||||
ADZAN_MAGHRIB_ENABLED("adzan.maghrib.enabled", "false"),
|
||||
ADZAN_ISHA_ENABLED("adzan.isha.enabled", "false"),
|
||||
ADZAN_FAJR_SOUND("adzan.fajr.sound", "adzan_fajr.mp3"),
|
||||
ADZAN_DHUHR_SOUND("adzan.dhuhr.sound", "adzan_dhuhr.mp3"),
|
||||
ADZAN_ASR_SOUND("adzan.asr.sound", "adzan_asr.mp3"),
|
||||
ADZAN_MAGHRIB_SOUND("adzan.maghrib.sound", "adzan_maghrib.mp3"),
|
||||
ADZAN_ISHA_SOUND("adzan.isha.sound", "adzan_isha.mp3"),
|
||||
ADZAN_FAJR_MINUTE_OFFSET("adzan.fajr.minute.offset", "0"),
|
||||
ADZAN_DHUHR_MINUTE_OFFSET("adzan.dhuhr.minute.offset", "0"),
|
||||
ADZAN_ASR_MINUTE_OFFSET("adzan.asr.minute.offset", "0"),
|
||||
ADZAN_MAGHRIB_MINUTE_OFFSET("adzan.maghrib.minute.offset", "0"),
|
||||
ADZAN_ISHA_MINUTE_OFFSET("adzan.isha.minute.offset", "0")
|
||||
}
|
||||
@@ -3,12 +3,11 @@ package commandServer
|
||||
import audioPlayer
|
||||
import codes.Somecodes.Companion.ValidString
|
||||
import codes.Somecodes.Companion.datetimeformat1
|
||||
import content.Category
|
||||
import content.Language
|
||||
import database.Messagebank
|
||||
import database.QueuePaging
|
||||
import database.QueueTable
|
||||
import database.Soundbank
|
||||
import database.data.Messagebank
|
||||
import database.data.QueuePaging
|
||||
import database.data.QueueTable
|
||||
import database.data.Soundbank
|
||||
import db
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
@@ -16,7 +15,10 @@ import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.isActive
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import messageDB
|
||||
import org.tinylog.Logger
|
||||
import queuepagingDB
|
||||
import queuetableDB
|
||||
import tcpreceiver
|
||||
import udpreceiver
|
||||
import java.net.ServerSocket
|
||||
@@ -72,14 +74,13 @@ class TCP_Android_Command_Server {
|
||||
//println("Received command from $key : $str")
|
||||
str.split("@").map { it.trim() }.filter { ValidString(it) }
|
||||
.forEach {
|
||||
Logger.info{"Receive command from $key : $it"}
|
||||
process_command(key,it) { reply ->
|
||||
try {
|
||||
val cc = String_to_Byte_Android(reply)
|
||||
if (cc.isNotEmpty()){
|
||||
dout.write(cc)
|
||||
dout.flush()
|
||||
Logger.info{"Sent reply ${cc.size} bytes to $key : $reply"}
|
||||
//Logger.info{"Sent reply ${cc.size} bytes to $key : $reply"}
|
||||
} else Logger.error { "Empty reply to send to $key" }
|
||||
|
||||
} catch (e: Exception) {
|
||||
@@ -149,7 +150,7 @@ class TCP_Android_Command_Server {
|
||||
* @param cb Callback to send reply string
|
||||
*/
|
||||
private fun process_command(key: String, cmd: String, cb: Consumer<String>) {
|
||||
Logger.info { "Command from $key : $cmd" }
|
||||
if ("PING" != cmd) Logger.info { "Command from $key : $cmd" }
|
||||
val parts = cmd.split(";").map { it.trim() }.filter { it.isNotBlank() }
|
||||
when (parts[0]) {
|
||||
"GETLOGIN" -> {
|
||||
@@ -235,8 +236,8 @@ class TCP_Android_Command_Server {
|
||||
pj.broadcastzones
|
||||
)
|
||||
Logger.info{"Inserting paging audio to queue paging table from Android $key, data=$qp"}
|
||||
if (db.queuepagingDB.Add(qp)) {
|
||||
db.queuepagingDB.Resort()
|
||||
if (queuepagingDB.Add(qp)) {
|
||||
queuepagingDB.Resort()
|
||||
logcb.accept("Paging audio inserted to queue paging table from Android $key, file ${pj.filePath.absolutePathString()}")
|
||||
cb.accept("PCMFILE_STOP;OK@")
|
||||
return
|
||||
@@ -290,8 +291,8 @@ class TCP_Android_Command_Server {
|
||||
pj.filePath.absolutePathString(),
|
||||
pj.broadcastzones
|
||||
)
|
||||
if (db.queuepagingDB.Add(qp)){
|
||||
db.queuepagingDB.Resort()
|
||||
if (queuepagingDB.Add(qp)){
|
||||
queuepagingDB.Resort()
|
||||
logcb.accept("Paging audio inserted to queue paging table from IPM $key, file ${pj.filePath.absolutePathString()}")
|
||||
cb.accept("STOPPAGINGAND;OK@")
|
||||
return
|
||||
@@ -349,7 +350,7 @@ class TCP_Android_Command_Server {
|
||||
// iterasi setiap ANN_ID
|
||||
.forEach { annid ->
|
||||
// masukin ke VARMESSAGES yang unik secara ANN_ID dan Language
|
||||
val xx = db.messageDB.List
|
||||
val xx = messageDB.List
|
||||
.asSequence()
|
||||
.filter{it.ANN_ID == annid.toUInt()}
|
||||
.distinctBy { it.ANN_ID }
|
||||
@@ -368,11 +369,7 @@ class TCP_Android_Command_Server {
|
||||
.map { it.trim() }
|
||||
.filter { it.isNotBlank() }
|
||||
.forEach { al ->
|
||||
val sb = db.soundDB.List
|
||||
.filter { it.Category.equals(Category.Airplane_Name.name, true) }
|
||||
.filter { it.TAG.equals(al, true)}
|
||||
.distinctBy { it.TAG }
|
||||
VARAPTOTAL.addAll(sb)
|
||||
VARAPTOTAL.addAll(db.soundDB.Find_AirlineName_By_TAG(al))
|
||||
}
|
||||
result.append(VARAPTOTAL.size).append("@")
|
||||
cb.accept(result.toString())
|
||||
@@ -386,11 +383,7 @@ class TCP_Android_Command_Server {
|
||||
.map { it.trim() }
|
||||
.filter { it.isNotBlank() }
|
||||
.forEach { ct ->
|
||||
val sb = db.soundDB.List
|
||||
.filter { it.Category.equals(Category.City.name, true) }
|
||||
.filter { it.TAG.equals(ct, true)}
|
||||
.distinctBy { it.TAG }
|
||||
VARCITYTOTAL.addAll(sb)
|
||||
VARCITYTOTAL.addAll(db.soundDB.Find_City_By_TAG(ct))
|
||||
}
|
||||
result.append(VARCITYTOTAL.size).append("@")
|
||||
cb.accept(result.toString())
|
||||
@@ -398,104 +391,56 @@ class TCP_Android_Command_Server {
|
||||
// kirim VARPLACESTOTAL
|
||||
result.clear()
|
||||
result.append("VARPLACESTOTAL;")
|
||||
val VARPLACESTOTAL = mutableListOf<Soundbank>()
|
||||
db.soundDB.List
|
||||
.filter { it.Category.equals(Category.Places.name, true) }
|
||||
.distinctBy { it.TAG }
|
||||
.forEach {
|
||||
VARPLACESTOTAL.add(it)
|
||||
}
|
||||
val VARPLACESTOTAL = db.soundDB.Get_Places()
|
||||
result.append(VARPLACESTOTAL.size).append("@")
|
||||
cb.accept(result.toString())
|
||||
|
||||
// kirim VARSHALATTOTAL
|
||||
result.clear()
|
||||
result.append("VARSHALATTOTAL;")
|
||||
val VARSHALATTOTAL = mutableListOf<Soundbank>()
|
||||
db.soundDB.List
|
||||
.filter { it.Category.equals(Category.Shalat.name, true) }
|
||||
.distinctBy { it.TAG }
|
||||
.forEach {
|
||||
VARSHALATTOTAL.add(it)
|
||||
}
|
||||
val VARSHALATTOTAL = db.soundDB.Get_Shalat()
|
||||
result.append(VARSHALATTOTAL.size).append("@")
|
||||
cb.accept(result.toString())
|
||||
|
||||
// kirim VARSEQUENCETOTAL
|
||||
result.clear()
|
||||
result.append("VARSEQUENCETOTAL;")
|
||||
val VARSEQUENCETOTAL = mutableListOf<Soundbank>()
|
||||
db.soundDB.List
|
||||
.filter { it.Category.equals(Category.Sequence.name, true) }
|
||||
.distinctBy { it.TAG }
|
||||
.forEach {
|
||||
VARSEQUENCETOTAL.add(it)
|
||||
}
|
||||
val VARSEQUENCETOTAL = db.soundDB.Get_Sequences()
|
||||
result.append(VARSEQUENCETOTAL.size).append("@")
|
||||
cb.accept(result.toString())
|
||||
|
||||
// kirim VARREASONTOTAL
|
||||
result.clear()
|
||||
result.append("VARREASONTOTAL;")
|
||||
val VARREASONTOTAL = mutableListOf<Soundbank>()
|
||||
db.soundDB.List
|
||||
.filter { it.Category.equals(Category.Reason.name, true) }
|
||||
.distinctBy { it.TAG }
|
||||
.forEach {
|
||||
VARREASONTOTAL.add(it)
|
||||
}
|
||||
val VARREASONTOTAL = db.soundDB.Get_Reasons()
|
||||
result.append(VARREASONTOTAL.size).append("@")
|
||||
cb.accept(result.toString())
|
||||
|
||||
// kirim VARPROCEDURETOTAL
|
||||
val VARPROCEDURETOTAL = mutableListOf<Soundbank>()
|
||||
result.clear()
|
||||
result.append("VARPROCEDURETOTAL;")
|
||||
db.soundDB.List
|
||||
.filter { it.Category.equals(Category.Procedure.name, true) }
|
||||
.distinctBy { it.TAG }
|
||||
.forEach {
|
||||
VARPROCEDURETOTAL.add(it)
|
||||
}
|
||||
val VARPROCEDURETOTAL = db.soundDB.Get_Procedures()
|
||||
result.append(VARPROCEDURETOTAL.size).append("@")
|
||||
cb.accept(result.toString())
|
||||
|
||||
// kirim VARGATETOTAL
|
||||
val VARGATETOTAL = mutableListOf<Soundbank>()
|
||||
result.clear()
|
||||
result.append("VARGATETOTAL;")
|
||||
db.soundDB.List
|
||||
.filter { it.Category.equals(Category.Gate.name, true) }
|
||||
.distinctBy { it.TAG }
|
||||
.forEach {
|
||||
VARGATETOTAL.add(it)
|
||||
}
|
||||
val VARGATETOTAL = db.soundDB.Get_Gates()
|
||||
result.append(VARGATETOTAL.size).append("@")
|
||||
cb.accept(result.toString())
|
||||
|
||||
// kirim VARCOMPENSATIONTOTAL
|
||||
result.clear()
|
||||
result.append("VARCOMPENSATIONTOTAL;")
|
||||
val VARCOMPENSATIONTOTAL = mutableListOf<Soundbank>()
|
||||
db.soundDB.List
|
||||
.filter { it.Category.equals(Category.Compensation.name, true) }
|
||||
.distinctBy { it.TAG }
|
||||
.forEach {
|
||||
VARCOMPENSATIONTOTAL.add(it)
|
||||
}
|
||||
val VARCOMPENSATIONTOTAL = db.soundDB.Get_Compensation()
|
||||
result.append(VARCOMPENSATIONTOTAL.size).append("@")
|
||||
cb.accept(result.toString())
|
||||
|
||||
// kirim VARGREETINGTOTAL
|
||||
result.clear()
|
||||
result.append("VARGREETINGTOTAL;")
|
||||
val VARGREETINGTOTAL = mutableListOf<Soundbank>()
|
||||
db.soundDB.List
|
||||
.filter { it.Category.equals(Category.Greeting.name, true) }
|
||||
.distinctBy { it.TAG }
|
||||
.forEach {
|
||||
VARGREETINGTOTAL.add(it)
|
||||
}
|
||||
val VARGREETINGTOTAL = db.soundDB.Get_Greeting()
|
||||
result.append(VARGREETINGTOTAL.size).append("@")
|
||||
cb.accept(result.toString())
|
||||
|
||||
@@ -505,13 +450,13 @@ class TCP_Android_Command_Server {
|
||||
VARMESSAGES.forEachIndexed { index, msg ->
|
||||
|
||||
val ann_id = msg.ANN_ID
|
||||
val msg_indo = db.messageDB.List.find {
|
||||
val msg_indo = messageDB.List.find {
|
||||
it.ANN_ID == ann_id && it.Language.equals(
|
||||
Language.INDONESIA.name,
|
||||
true
|
||||
)
|
||||
}
|
||||
val msg_eng = db.messageDB.List.find {
|
||||
val msg_eng = messageDB.List.find {
|
||||
it.ANN_ID == ann_id && it.Language.equals(
|
||||
Language.ENGLISH.name,
|
||||
true
|
||||
@@ -636,18 +581,15 @@ class TCP_Android_Command_Server {
|
||||
if (ValidString(tags)){
|
||||
if (ValidString(zone)){
|
||||
val qt = QueueTable(
|
||||
0u,
|
||||
LocalDateTime.now().format(datetimeformat1),
|
||||
"ANDROID",
|
||||
"SOUNDBANK",
|
||||
desc,
|
||||
tags,
|
||||
zone,
|
||||
1u,
|
||||
lang
|
||||
Source="ANDROID",
|
||||
Type="SOUNDBANK",
|
||||
Message=desc,
|
||||
SB_TAGS = tags,
|
||||
BroadcastZones = zone,
|
||||
Language = lang
|
||||
)
|
||||
if (db.queuetableDB.Add(qt)){
|
||||
db.queuetableDB.Resort()
|
||||
if (queuetableDB.Add(qt)){
|
||||
queuetableDB.Resort()
|
||||
logcb.accept("Broadcast request from Android $key username=${listUserLogin.find { it.ip==key }?.username ?: "UNKNOWN"} inserted. Message: $desc;$lang;$tags;$zone")
|
||||
cb.accept("BROADCASTAND;OK@")
|
||||
return
|
||||
|
||||
@@ -5,16 +5,37 @@ package content
|
||||
*
|
||||
* @property name The name of the language, as in Soundbank Database
|
||||
*/
|
||||
@Suppress("unused")
|
||||
enum class Language(name: String) {
|
||||
INDONESIA("INDONESIA"),
|
||||
ENGLISH("ENGLISH"),
|
||||
LOCAL("LOCAL"),
|
||||
JAPANESE("JAPANESE"),
|
||||
CHINESE("CHINESE"),
|
||||
ARABIC("ARABIC");
|
||||
enum class Language(val value: String, val googletts: String) {
|
||||
INDONESIA(value="INDONESIA", googletts="id-ID"),
|
||||
ENGLISH(value="ENGLISH", googletts="en-US"),
|
||||
LOCAL(value="LOCAL", googletts=""),
|
||||
JAPANESE(value="JAPANESE", googletts="ja-JP"),
|
||||
CHINESE(value="CHINESE", googletts="zh-CN"),
|
||||
ARABIC(value="ARABIC", googletts="ar-SA");
|
||||
|
||||
companion object{
|
||||
var DEFAULT: Language = INDONESIA
|
||||
|
||||
/**
|
||||
* Default language link string
|
||||
*/
|
||||
fun DefaultLanguageLink() : String {
|
||||
return DEFAULT.value+";"+ENGLISH.value
|
||||
}
|
||||
|
||||
/**
|
||||
* Default language order
|
||||
*/
|
||||
fun LanguageOrder() : List<String> {
|
||||
return listOf(
|
||||
INDONESIA.value,
|
||||
LOCAL.value,
|
||||
ENGLISH.value,
|
||||
CHINESE.value,
|
||||
JAPANESE.value,
|
||||
ARABIC.value
|
||||
)
|
||||
}
|
||||
fun from_GoogleTTSLanguage(lang: google.GoogleTTSLanguage) : Language {
|
||||
return when(lang) {
|
||||
google.GoogleTTSLanguage.Indonesia -> INDONESIA
|
||||
@@ -24,15 +45,9 @@ enum class Language(name: String) {
|
||||
google.GoogleTTSLanguage.Arabic -> ARABIC
|
||||
}
|
||||
}
|
||||
fun from_GoogleTTSLanguage(code: String) : Language {
|
||||
return when(code) {
|
||||
"id-ID" -> INDONESIA
|
||||
"en-US" -> ENGLISH
|
||||
"ja-JP" -> JAPANESE
|
||||
"zh-CN" -> CHINESE
|
||||
"ar-SA" -> ARABIC
|
||||
else -> INDONESIA
|
||||
}
|
||||
|
||||
fun from_GoogleTTSLanguage(code: String) : Language? {
|
||||
return entries.find { it.googletts == code }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
package content
|
||||
|
||||
import java.time.DayOfWeek
|
||||
|
||||
@Suppress("unused")
|
||||
enum class ScheduleDay(val day: String) {
|
||||
Sunday("Sunday"),
|
||||
@@ -9,5 +11,22 @@ enum class ScheduleDay(val day: String) {
|
||||
Thursday("Thursday"),
|
||||
Friday("Friday"),
|
||||
Saturday("Saturday"),
|
||||
Everyday("Everyday")
|
||||
Everyday("Everyday");
|
||||
|
||||
companion object {
|
||||
|
||||
/**
|
||||
* Converts a DayOfWeek to a ScheduleDay
|
||||
*/
|
||||
fun from_LocalDate_DOW(value : DayOfWeek) : ScheduleDay{
|
||||
return when(value){
|
||||
DayOfWeek.SUNDAY -> Sunday
|
||||
DayOfWeek.MONDAY -> Monday
|
||||
DayOfWeek.TUESDAY -> Tuesday
|
||||
DayOfWeek.WEDNESDAY -> Wednesday
|
||||
DayOfWeek.THURSDAY -> Thursday
|
||||
DayOfWeek.FRIDAY -> Friday
|
||||
DayOfWeek.SATURDAY -> Saturday
|
||||
}}
|
||||
}
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
package database
|
||||
|
||||
@Suppress("unused")
|
||||
data class BroadcastZones(var index: UInt, var description: String, var SoundChannel: String, var id: String, var bp: String){
|
||||
|
||||
/**
|
||||
* Check if all fields are not empty
|
||||
*/
|
||||
fun isNotEmpty(): Boolean{
|
||||
if (description.isNotEmpty()){
|
||||
if (SoundChannel.isNotEmpty()){
|
||||
if (id.isNotEmpty()){
|
||||
if (bp.isNotEmpty()){
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a string representation of the BroadcastZones object.
|
||||
*/
|
||||
override fun toString(): String {
|
||||
return "BroadcastZones(index=$index, description='$description', SoundChannel='$SoundChannel', id='$id', bp='$bp')"
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,11 +0,0 @@
|
||||
package database
|
||||
|
||||
data class QueuePaging(var index: UInt, var Date_Time: String, var Source: String, var Type: String, var Message: String, var BroadcastZones: String){
|
||||
fun isNotEmpty(): Boolean {
|
||||
return Date_Time.isNotEmpty() && Source.isNotEmpty() && Type.isNotEmpty() && Message.isNotEmpty() && BroadcastZones.isNotEmpty()
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return "QueuePaging(index=$index, Date_Time='$Date_Time', Source='$Source', Type='$Type', Message='$Message', BroadcastZones='$BroadcastZones')"
|
||||
}
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
package database
|
||||
|
||||
data class QueueTable(var index: UInt, var Date_Time: String, var Source: String, var Type: String, var Message: String, var SB_TAGS: String, var BroadcastZones: String, var Repeat: UInt, var Language: String){
|
||||
|
||||
/**
|
||||
* Check if all fields are not empty
|
||||
*/
|
||||
fun isNotEmpty(): Boolean {
|
||||
return Date_Time.isNotEmpty() && Source.isNotEmpty() && Type.isNotEmpty() && Message.isNotEmpty() && SB_TAGS.isNotEmpty() && BroadcastZones.isNotEmpty() && Language.isNotEmpty()
|
||||
}
|
||||
override fun toString(): String {
|
||||
return "QueueTable(index=$index, Date_Time='$Date_Time', Source='$Source', Type='$Type', Message='$Message', SB_TAGS='$SB_TAGS', BroadcastZones='$BroadcastZones', Repeat=$Repeat, Language='$Language')"
|
||||
}
|
||||
}
|
||||
@@ -1,235 +0,0 @@
|
||||
package database
|
||||
|
||||
import org.apache.poi.xssf.usermodel.XSSFWorkbook
|
||||
import org.tinylog.Logger
|
||||
import java.sql.Connection
|
||||
import java.time.LocalDate
|
||||
import java.time.format.DateTimeFormatter
|
||||
import java.util.function.Consumer
|
||||
|
||||
class Table_Logs(connection: Connection) : dbFunctions<Log> ("logs", connection,listOf("index", "datenya", "timenya", "machine", "description")) {
|
||||
/**
|
||||
* dateformat1 is regex for DD/MM/YYYY
|
||||
*/
|
||||
val dateformat1 = """^(0[1-9]|[12][0-9]|3[01])/(0[1-9]|1[0-2])/\d{4}$""".toRegex()
|
||||
|
||||
/**
|
||||
* dateformat2 is regex for DD-MM-YYYY
|
||||
*/
|
||||
val dateformat2 = """^(0[1-9]|[12][0-9]|3[01])-(0[1-9]|1[0-2])-\d{4}$""".toRegex()
|
||||
|
||||
/**
|
||||
* dateformat3 is regex for YYYY/MM/DD
|
||||
*/
|
||||
val dateformat3 = """^\d{4}/(0[1-9]|1[0-2])/(0[1-9]|[12][0-9]|3[01])$""".toRegex()
|
||||
|
||||
/**
|
||||
* dateformat4 is regex for YYYY-MM-DD
|
||||
*/
|
||||
val dateformat4 = """^\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$""".toRegex()
|
||||
|
||||
|
||||
override fun Create() {
|
||||
val tabledefinition = "CREATE TABLE IF NOT EXISTS ${super.dbName} (" +
|
||||
"`index` INT AUTO_INCREMENT PRIMARY KEY," +
|
||||
"datenya VARCHAR(20) NOT NULL," + // format DD/MM/YYYY
|
||||
"timenya VARCHAR(20) NOT NULL," + // format HH:MM:SS
|
||||
"machine VARCHAR(45) NOT NULL," +
|
||||
"description TEXT NOT NULL" +
|
||||
")"
|
||||
|
||||
super.Create(tabledefinition)
|
||||
|
||||
}
|
||||
|
||||
fun GetLogForHtml(date: String, filter: String?, cbOK: Consumer<ArrayList<Log>>?, cbFail: Consumer<String>?){
|
||||
try{
|
||||
val valid_date : java.sql.Date? = when{
|
||||
dateformat1.matches(date) -> {
|
||||
java.sql.Date.valueOf(LocalDate.parse(date, DateTimeFormatter.ofPattern("dd/MM/yyyy")))
|
||||
}
|
||||
dateformat2.matches(date) -> {
|
||||
java.sql.Date.valueOf(LocalDate.parse(date, DateTimeFormatter.ofPattern("dd-MM-yyyy")))
|
||||
}
|
||||
dateformat3.matches(date) -> {
|
||||
java.sql.Date.valueOf(LocalDate.parse(date, DateTimeFormatter.ofPattern("yyyy/MM/dd")))
|
||||
}
|
||||
dateformat4.matches(date) -> {
|
||||
java.sql.Date.valueOf(LocalDate.parse(date, DateTimeFormatter.ofPattern("yyyy-MM-dd")))
|
||||
}
|
||||
else -> null
|
||||
}
|
||||
if (valid_date!=null){
|
||||
// use coalescing for different datenya formats
|
||||
val statement = if (filter.isNullOrEmpty()){
|
||||
connection.prepareStatement("SELECT * FROM ${super.dbName} WHERE COALESCE(STR_TO_DATE(datenya,'%d/%m/%Y'), STR_TO_DATE(datenya,'%d-%m-%Y'), STR_TO_DATE(datenya,'%Y/%m/%d'), STR_TO_DATE(datenya,'%Y-%m-%d')) = ?")
|
||||
} else {
|
||||
connection.prepareStatement("SELECT * FROM ${super.dbName} WHERE COALESCE(STR_TO_DATE(datenya,'%d/%m/%Y'), STR_TO_DATE(datenya,'%d-%m-%Y'), STR_TO_DATE(datenya,'%Y/%m/%d'), STR_TO_DATE(datenya,'%Y-%m-%d')) = ? AND description LIKE ?")
|
||||
}
|
||||
statement?.setDate(1, valid_date)
|
||||
if (!filter.isNullOrEmpty()){
|
||||
statement?.setString(2, "%$filter%")
|
||||
}
|
||||
val resultSet = statement?.executeQuery()
|
||||
val tempList = ArrayList<Log>()
|
||||
while (resultSet?.next() == true) {
|
||||
val log = Log(
|
||||
resultSet.getLong("index").toULong(),
|
||||
resultSet.getString("datenya"),
|
||||
resultSet.getString("timenya"),
|
||||
resultSet.getString("machine"),
|
||||
resultSet.getString("description")
|
||||
)
|
||||
tempList.add(log)
|
||||
}
|
||||
cbOK?.accept(tempList)
|
||||
} else throw Exception("Invalid date")
|
||||
} catch (e : Exception){
|
||||
if (filter.isNullOrEmpty()){
|
||||
cbFail?.accept("Failed to Get logs for date $date: ${e.message}")
|
||||
} else {
|
||||
cbFail?.accept("Failed to Get logs for date $date with filter $filter: ${e.message}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun Get(cbOK: Consumer<Unit>?, cbFail: Consumer<String>?) {
|
||||
List.clear()
|
||||
try {
|
||||
val statement = connection.createStatement()
|
||||
val resultSet = statement?.executeQuery("SELECT * FROM ${super.dbName}")
|
||||
while (resultSet?.next() == true) {
|
||||
val log = Log(
|
||||
resultSet.getLong("index").toULong(),
|
||||
resultSet.getString("datenya"),
|
||||
resultSet.getString("timenya"),
|
||||
resultSet.getString("machine"),
|
||||
resultSet.getString("description")
|
||||
)
|
||||
List.add(log)
|
||||
}
|
||||
cbOK?.accept(Unit)
|
||||
} catch (e: Exception) {
|
||||
cbFail?.accept("Error fetching ${super.dbName}: ${e.message}")
|
||||
Logger.error("Error fetching ${super.dbName}: ${e.message}" as Any)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
fun Add(machine: String, description: String): Boolean {
|
||||
val log = Log.NewLog(machine, description)
|
||||
return Add(log)
|
||||
}
|
||||
|
||||
override fun Add(data: Log): Boolean {
|
||||
try {
|
||||
val statement =
|
||||
connection.prepareStatement("INSERT INTO logs (datenya, timenya, machine, description) VALUES (?, ?, ?, ?)")
|
||||
statement?.setString(1, data.datenya)
|
||||
statement?.setString(2, data.timenya)
|
||||
statement?.setString(3, data.machine)
|
||||
statement?.setString(4, data.description)
|
||||
val rowsAffected = statement?.executeUpdate()
|
||||
if (rowsAffected != null && rowsAffected > 0) {
|
||||
return true
|
||||
} else {
|
||||
Logger.warn{"Failed to add log entry : $data"}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Logger.error{"Error adding log entry: ${e.message}"}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
override fun AddAll(data: ArrayList<Log>): Boolean {
|
||||
return try {
|
||||
connection.autoCommit = false
|
||||
val sql = "INSERT INTO logs (datenya, timenya, machine, description) VALUES (?, ?, ?, ?)"
|
||||
val statement = connection.prepareStatement(sql)
|
||||
for (log in data) {
|
||||
statement.setString(1, log.datenya)
|
||||
statement.setString(2, log.timenya)
|
||||
statement.setString(3, log.machine)
|
||||
statement.setString(4, log.description)
|
||||
statement.addBatch()
|
||||
}
|
||||
statement.executeBatch()
|
||||
connection.commit()
|
||||
Logger.info("Bulk log insert successful: ${data.size} entries" as Any)
|
||||
connection.autoCommit = true
|
||||
true
|
||||
} catch (e: Exception) {
|
||||
Logger.error("Error adding log entries: ${e.message}" as Any)
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
override fun UpdateByIndex(index: Int, data: Log): Boolean {
|
||||
throw Exception("Update not supported")
|
||||
}
|
||||
|
||||
override fun Resort(): Boolean {
|
||||
try {
|
||||
val statement = connection.createStatement()
|
||||
val tempdb_name = "temp_${super.dbName}"
|
||||
// use a temporary table to reorder the index
|
||||
statement?.executeUpdate("CREATE TABLE IF NOT EXISTS $tempdb_name LIKE ${super.dbName}")
|
||||
statement?.executeUpdate("TRUNCATE TABLE $tempdb_name")
|
||||
statement?.executeUpdate(
|
||||
"INSERT INTO $tempdb_name (datenya, timenya, machine, description) " +
|
||||
"SELECT datenya, timenya, machine, description FROM ${super.dbName} " +
|
||||
"ORDER BY datenya , timenya , machine "
|
||||
)
|
||||
statement?.executeUpdate("TRUNCATE TABLE ${super.dbName}")
|
||||
statement?.executeUpdate(
|
||||
"INSERT INTO ${super.dbName} (datenya, timenya, machine, description) " +
|
||||
"SELECT datenya, timenya, machine, description FROM $tempdb_name"
|
||||
)
|
||||
statement?.executeUpdate("DROP TABLE $tempdb_name")
|
||||
Logger.info("${super.dbName} table resorted by datenya, timenya, machine" as Any)
|
||||
// reload the local list
|
||||
Get()
|
||||
return true
|
||||
} catch (e: Exception) {
|
||||
Logger.error("Error resorting ${super.dbName} table by datenya, timenya, machine: ${e.message}" as Any)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
override fun Import_XLSX(workbook: XSSFWorkbook): Boolean {
|
||||
throw Exception("Importing Logs from XLSX is not supported")
|
||||
}
|
||||
|
||||
override fun Export_XLSX(): XSSFWorkbook? {
|
||||
try {
|
||||
val statement = connection.createStatement()
|
||||
val resultSet = statement?.executeQuery("SELECT * FROM ${super.dbName}")
|
||||
val workbook = XSSFWorkbook()
|
||||
val sheet = workbook.createSheet("Log")
|
||||
val headerRow = sheet.createRow(0)
|
||||
val headers = arrayOf("Index", "datenya", "timenya", "machine", "description")
|
||||
for ((colIndex, header) in headers.withIndex()) {
|
||||
val cell = headerRow.createCell(colIndex)
|
||||
cell.setCellValue(header)
|
||||
}
|
||||
var rowIndex = 1
|
||||
while (resultSet?.next() == true) {
|
||||
val row = sheet.createRow(rowIndex++)
|
||||
row.createCell(0).setCellValue(resultSet.getString("index"))
|
||||
row.createCell(1).setCellValue(resultSet.getString("datenya"))
|
||||
row.createCell(2).setCellValue(resultSet.getString("timenya"))
|
||||
row.createCell(3).setCellValue(resultSet.getString("machine"))
|
||||
row.createCell(4).setCellValue(resultSet.getString("description"))
|
||||
}
|
||||
for (i in headers.indices) {
|
||||
sheet.autoSizeColumn(i)
|
||||
}
|
||||
return workbook
|
||||
} catch (e: Exception) {
|
||||
Logger.error { "Error exporting Log, Msg: ${e.message}" }
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
||||
52
src/database/data/BroadcastZones.kt
Normal file
52
src/database/data/BroadcastZones.kt
Normal file
@@ -0,0 +1,52 @@
|
||||
package database.data
|
||||
|
||||
@Suppress("unused")
|
||||
data class BroadcastZones(var index: UInt, var description: String, var SoundChannel: String, var id: String, var bp: String){
|
||||
|
||||
/**
|
||||
* Check if all fields are not empty
|
||||
*/
|
||||
fun isNotEmpty(): Boolean{
|
||||
if (description.isNotEmpty()){
|
||||
if (SoundChannel.isNotEmpty()){
|
||||
if (id.isNotEmpty()){
|
||||
if (bp.isNotEmpty()){
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a string representation of the BroadcastZones object.
|
||||
*/
|
||||
override fun toString(): String {
|
||||
return "BroadcastZones(index=$index, description='$description', SoundChannel='$SoundChannel', id='$id', bp='$bp')"
|
||||
}
|
||||
|
||||
companion object{
|
||||
|
||||
/**
|
||||
* Get a list of relay numbers from the broadcast zone's bp field.
|
||||
* Currently, supports relays 1 to 8.
|
||||
* @param bz The BroadcastZones object
|
||||
* @return List of relay numbers (Int) extracted from the bp field
|
||||
*/
|
||||
fun getRelaysFromBroadcastZone(bz : BroadcastZones) : List<Int>{
|
||||
val result = ArrayList<Int>()
|
||||
// delimiters either comma or semicolon
|
||||
val parts = bz.bp.split(",", ";")
|
||||
for (part in parts){
|
||||
val relay = part.trim().toIntOrNull()
|
||||
if (relay != null){
|
||||
if (relay in 1..8){
|
||||
result.add(relay)
|
||||
}
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package database
|
||||
package database.data
|
||||
|
||||
data class LanguageLink(var index: UInt, var TAG: String, var Language: String){
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package database
|
||||
package database.data
|
||||
|
||||
import java.time.LocalDate
|
||||
import java.time.LocalTime
|
||||
@@ -1,4 +1,4 @@
|
||||
package database
|
||||
package database.data
|
||||
|
||||
import java.time.LocalDate
|
||||
import java.time.LocalTime
|
||||
@@ -1,4 +1,4 @@
|
||||
package database
|
||||
package database.data
|
||||
|
||||
data class Messagebank(
|
||||
var index : UInt,
|
||||
@@ -1,4 +1,4 @@
|
||||
package database
|
||||
package database.data
|
||||
|
||||
@Suppress("unused")
|
||||
data class QueueFids(var index: UInt, var ALCODE: String, var FLNUM: String, var ORIGIN: String, var ETAD: String, var FREMARK: String){
|
||||
33
src/database/data/QueuePaging.kt
Normal file
33
src/database/data/QueuePaging.kt
Normal file
@@ -0,0 +1,33 @@
|
||||
package database.data
|
||||
|
||||
import codes.Somecodes
|
||||
import java.time.Duration
|
||||
import java.time.LocalDateTime
|
||||
|
||||
data class QueuePaging(var index: UInt, var Date_Time: String, var Source: String, var Type: String, var Message: String, var BroadcastZones: String){
|
||||
fun isNotEmpty(): Boolean {
|
||||
return Date_Time.isNotEmpty() && Source.isNotEmpty() && Type.isNotEmpty() && Message.isNotEmpty() && BroadcastZones.isNotEmpty()
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return "QueuePaging(index=$index, Date_Time='$Date_Time', Source='$Source', Type='$Type', Message='$Message', BroadcastZones='$BroadcastZones')"
|
||||
}
|
||||
|
||||
companion object{
|
||||
|
||||
/**
|
||||
* Check if the QueuePaging entry is expired (older than 5 seconds)
|
||||
* @param qt QueuePaging entry to check
|
||||
* @return true if expired, false otherwise
|
||||
*/
|
||||
fun isExpired(qt: QueuePaging) : Boolean{
|
||||
try{
|
||||
val t1 = LocalDateTime.parse(qt.Date_Time, Somecodes.datetimeformat1)
|
||||
val delta = Duration.between(t1, LocalDateTime.now())
|
||||
return delta.seconds > 5
|
||||
} catch (_: Exception){
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
37
src/database/data/QueueTable.kt
Normal file
37
src/database/data/QueueTable.kt
Normal file
@@ -0,0 +1,37 @@
|
||||
package database.data
|
||||
|
||||
import codes.Somecodes.Companion.datetimeformat1
|
||||
import java.time.Duration
|
||||
import java.time.LocalDateTime
|
||||
|
||||
data class QueueTable(var index: UInt=0u, var Date_Time: String =LocalDateTime.now().format(datetimeformat1), var Source: String, var Type: String, var Message: String, var SB_TAGS: String, var BroadcastZones: String, var Repeat: UInt=1u, var Language: String){
|
||||
|
||||
/**
|
||||
* Check if all fields are not empty
|
||||
*/
|
||||
fun isNotEmpty(): Boolean {
|
||||
return Date_Time.isNotEmpty() && Source.isNotEmpty() && Type.isNotEmpty() && Message.isNotEmpty() && SB_TAGS.isNotEmpty() && BroadcastZones.isNotEmpty() && Language.isNotEmpty()
|
||||
}
|
||||
override fun toString(): String {
|
||||
return "QueueTable(index=$index, Date_Time='$Date_Time', Source='$Source', Type='$Type', Message='$Message', SB_TAGS='$SB_TAGS', BroadcastZones='$BroadcastZones', Repeat=$Repeat, Language='$Language')"
|
||||
}
|
||||
|
||||
companion object{
|
||||
|
||||
/**
|
||||
* Check if the QueueTable entry is expired (older than 5 seconds)
|
||||
* @param qt QueueTable entry to check
|
||||
* @return true if expired, false otherwise
|
||||
*/
|
||||
fun isExpired(qt: QueueTable) : Boolean{
|
||||
try{
|
||||
val t1 = LocalDateTime.parse(qt.Date_Time, datetimeformat1)
|
||||
val delta = Duration.between(t1, LocalDateTime.now())
|
||||
// expired if more than 5 seconds
|
||||
return delta.seconds > 5
|
||||
} catch (_: Exception){
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package database
|
||||
package database.data
|
||||
|
||||
@Suppress("unused")
|
||||
data class ScheduleBank(
|
||||
@@ -1,4 +1,4 @@
|
||||
package database
|
||||
package database.data
|
||||
|
||||
data class SoundChannel(val index: UInt, val channel: String, val ip: String) {
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
package database
|
||||
|
||||
package database.data
|
||||
|
||||
data class Soundbank(
|
||||
var index: UInt,
|
||||
@@ -1,4 +1,4 @@
|
||||
package database
|
||||
package database.data
|
||||
|
||||
@Suppress("unused")
|
||||
data class UserDB(var index: UInt, var username: String, var password: String, var location: String, var airline_tags: String, var city_tags: String, var messagebank_ann_id: String, var broadcastzones: String){
|
||||
@@ -26,6 +26,3 @@ data class UserDB(var index: UInt, var username: String, var password: String, v
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -6,8 +6,30 @@ import java.sql.Connection
|
||||
import java.util.function.Consumer
|
||||
|
||||
@Suppress("unused", "SqlDialectInspection", "SqlSourceToSinkFlow")
|
||||
abstract class dbFunctions<T>(val dbName: String, val connection: Connection, requiredcolumns: List<String>) {
|
||||
abstract class dbFunctions<T>(val dbName: String, conn: Connection, requiredcolumns: List<String>) {
|
||||
var List : ArrayList<T> = ArrayList()
|
||||
var connection = conn
|
||||
|
||||
/**
|
||||
* Check if the database connection is valid
|
||||
* @return true if valid, false otherwise
|
||||
*/
|
||||
fun IsConnected() : Boolean{
|
||||
return try {
|
||||
connection.isValid(2)
|
||||
} catch (e: Exception) {
|
||||
Logger.error("Database connection is not valid: ${e.message}" as Any)
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Change the database connection
|
||||
* @param newcon The new Connection object
|
||||
*/
|
||||
fun ChangeConnection(newcon: Connection){
|
||||
connection = newcon
|
||||
}
|
||||
|
||||
init{
|
||||
val columns = GetColumnInfo()
|
||||
|
||||
12
src/database/table/AdzanPrayerTime.kt
Normal file
12
src/database/table/AdzanPrayerTime.kt
Normal file
@@ -0,0 +1,12 @@
|
||||
package database.table
|
||||
|
||||
/**
|
||||
* Data class representing Adzan prayer times for a specific date.
|
||||
* @param date The date for the prayer times in format DD/MM/YYYY.
|
||||
* @param fajr The Fajr prayer time in format HH:MM.
|
||||
* @param dhuhr The Dhuhr prayer time in format HH:MM.
|
||||
* @param asr The Asr prayer time in format HH:MM.
|
||||
* @param maghrib The Maghrib prayer time in format HH:MM.
|
||||
* @param isha The Isha prayer time in format HH:MM.
|
||||
*/
|
||||
data class AdzanPrayerTime(val date: String, val fajr: String, val dhuhr: String, val asr: String, val maghrib: String, val isha: String)
|
||||
69
src/database/table/AdzanSetting.kt
Normal file
69
src/database/table/AdzanSetting.kt
Normal file
@@ -0,0 +1,69 @@
|
||||
package database.table
|
||||
|
||||
import codes.Somecodes.Companion.ValidLatitude
|
||||
import codes.Somecodes.Companion.ValidLongitude
|
||||
import codes.Somecodes.Companion.ValidScheduleTime
|
||||
import codes.Somecodes.Companion.ValidTimeZone
|
||||
import com.fasterxml.jackson.databind.JsonNode
|
||||
|
||||
data class AdzanSetting(
|
||||
val latitude: Double,
|
||||
val longitude: Double,
|
||||
val timezone: String,
|
||||
val fajar_sound: String,
|
||||
val fajar_enable : Boolean,
|
||||
val fajar_time : String,
|
||||
val dzuhur_sound: String,
|
||||
val dzuhur_enable : Boolean,
|
||||
val dzuhur_time : String,
|
||||
val ashar_sound: String,
|
||||
val ashar_enable : Boolean,
|
||||
val ashar_time : String,
|
||||
val maghrib_sound: String,
|
||||
val maghrib_enable : Boolean,
|
||||
val maghrib_time : String,
|
||||
val isya_sound: String,
|
||||
val isya_enable : Boolean,
|
||||
val isya_time : String
|
||||
) {
|
||||
companion object{
|
||||
/**
|
||||
* Create AdzanSetting from JsonNode
|
||||
* @param json JsonNode object
|
||||
* @return AdzanSetting object
|
||||
* @throws Exception if any required field is missing
|
||||
*/
|
||||
fun FromJsonNode(json: JsonNode): AdzanSetting{
|
||||
val xx = AdzanSetting(
|
||||
latitude = json.get("latitude")?.asDouble() ?: throw Exception("latitude is missing"),
|
||||
longitude = json.get("longitude")?.asDouble() ?: throw Exception("longitude is missing"),
|
||||
timezone = json.get("timezone")?.asText() ?: throw Exception("timezone is missing"),
|
||||
fajar_sound = json.get("fajar_sound")?.asText("") ?: throw Exception("fajar_sound is missing"),
|
||||
fajar_enable = json.get("fajar_enable")?.asBoolean(false) ?: throw Exception("fajar_enable is missing"),
|
||||
fajar_time = json.get("fajar_time")?.asText() ?: throw Exception("fajar_time is missing"),
|
||||
dzuhur_sound = json.get("dzuhur_sound")?.asText() ?: throw Exception("dzuhur_sound is missing"),
|
||||
dzuhur_enable = json.get("dzuhur_enable")?.asBoolean() ?: throw Exception("dzuhur_enable is missing"),
|
||||
dzuhur_time = json.get("dzuhur_time")?.asText() ?: throw Exception("dzuhur_time is missing"),
|
||||
ashar_sound = json.get("ashar_sound")?.asText() ?: throw Exception("ashar_sound is missing"),ashar_enable = json.get("ashar_enable").asBoolean(false),
|
||||
ashar_time = json.get("ashar_time")?.asText() ?: throw Exception("ashar_time is missing"),
|
||||
maghrib_sound = json.get("maghrib_sound")?.asText() ?: throw Exception("maghrib_sound is missing"),
|
||||
maghrib_enable = json.get("maghrib_enable")?.asBoolean() ?: throw Exception("maghrib_enable is missing"),
|
||||
maghrib_time = json.get("maghrib_time")?.asText() ?: throw Exception("maghrib_time is missing"),
|
||||
isya_sound = json.get("isya_sound")?.asText() ?: throw Exception("isya_sound is missing"),
|
||||
isya_enable = json.get("isya_enable")?.asBoolean() ?: throw Exception("isya_enable is missing"),
|
||||
isya_time = json.get("isya_time")?.asText() ?: throw Exception("isya_time is missing")
|
||||
)
|
||||
if (!ValidLatitude(xx.latitude)) throw Exception("Invalid latitude value")
|
||||
if (!ValidLongitude(xx.longitude)) throw Exception("Invalid longitude value")
|
||||
if (!ValidTimeZone(xx.timezone)) throw Exception("Invalid timezone value")
|
||||
if (!ValidScheduleTime(xx.fajar_time)) throw Exception("Invalid fajar_time value")
|
||||
if (!ValidScheduleTime(xx.dzuhur_time)) throw Exception("Invalid dzuhur_time value")
|
||||
if (!ValidScheduleTime(xx.ashar_time)) throw Exception("Invalid ashar_time value")
|
||||
if (!ValidScheduleTime(xx.maghrib_time)) throw Exception("Invalid maghrib_time value")
|
||||
if (!ValidScheduleTime(xx.isya_time)) throw Exception("Invalid isya_time value")
|
||||
return xx
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
15
src/database/table/AdzanTimeZone.kt
Normal file
15
src/database/table/AdzanTimeZone.kt
Normal file
@@ -0,0 +1,15 @@
|
||||
package database.table
|
||||
|
||||
enum class AdzanTimeZone {
|
||||
WIB,
|
||||
WITA,
|
||||
WIT;
|
||||
|
||||
fun toTimeZoneString(): String {
|
||||
return when (this) {
|
||||
WIB -> "Asia/Jakarta"
|
||||
WITA -> "Asia/Makassar"
|
||||
WIT -> "Asia/Jayapura"
|
||||
}
|
||||
}
|
||||
}
|
||||
255
src/database/table/Table_Adzan.kt
Normal file
255
src/database/table/Table_Adzan.kt
Normal file
@@ -0,0 +1,255 @@
|
||||
package database.table
|
||||
|
||||
import codes.Somecodes.Companion.timeformat2
|
||||
import com.batoulapps.adhan.CalculationMethod
|
||||
import com.batoulapps.adhan.CalculationParameters
|
||||
import com.batoulapps.adhan.Coordinates
|
||||
import com.batoulapps.adhan.Madhab
|
||||
import com.batoulapps.adhan.PrayerTimes
|
||||
import com.batoulapps.adhan.data.DateComponents
|
||||
import content.Language
|
||||
import messageDB
|
||||
import org.tinylog.Logger
|
||||
import java.text.SimpleDateFormat
|
||||
import java.time.LocalTime
|
||||
import java.util.Date
|
||||
import java.util.TimeZone
|
||||
|
||||
/**
|
||||
* Class for calculating prayer times (Adzan) based on latitude, longitude, and time zone.
|
||||
* @param latitude The latitude of the location. Default is Monas, Jakarta (latitude: -6.1751).
|
||||
* @param longitude The longitude of the location. Default is Monas, Jakarta (longitude: 106.8272).
|
||||
* @param timezone The time zone for formatting prayer times. Default is "Asia/Jakarta".
|
||||
*/
|
||||
class Table_Adzan(val latitude: Double = -6.1751, val longitude: Double = 106.8272, val timezone : TimeZone = TimeZone.getTimeZone("Asia/Jakarta")) {
|
||||
|
||||
var coordinate: Coordinates = Coordinates(latitude, longitude)
|
||||
val params : CalculationParameters = CalculationMethod.OTHER.parameters
|
||||
val timeformatter = SimpleDateFormat("HH:mm")
|
||||
val dateformatter = SimpleDateFormat("dd/MM/yyyy")
|
||||
|
||||
var fajar_enable = false
|
||||
var dzuhur_enable = false
|
||||
var ashar_enable = false
|
||||
var maghrib_enable = false
|
||||
var isya_enable = false
|
||||
|
||||
var fajar_message = ""
|
||||
var dzuhur_message = ""
|
||||
var ashar_message = ""
|
||||
var maghrib_message = ""
|
||||
var isya_message = ""
|
||||
|
||||
var fajar_time = ""
|
||||
var dzuhur_time = ""
|
||||
var ashar_time = ""
|
||||
var maghrib_time = ""
|
||||
var isya_time = ""
|
||||
|
||||
var adzan_broadcastzones = ""
|
||||
|
||||
var adzan_language = Language.DEFAULT.value
|
||||
|
||||
init{
|
||||
// sumber chatgpt Kemenag
|
||||
params.fajrAngle = 20.0
|
||||
params.ishaAngle = 18.0
|
||||
params.madhab = Madhab.SHAFI
|
||||
timeformatter.timeZone = timezone
|
||||
}
|
||||
|
||||
data class AdzanTask(
|
||||
val prayerName : String,
|
||||
val timeString : String,
|
||||
val message : String,
|
||||
val sb_tags : String
|
||||
)
|
||||
|
||||
fun CheckTime() : AdzanTask?{
|
||||
val hhmm = LocalTime.now().format(timeformat2)
|
||||
if (hhmm == fajar_time){
|
||||
if (fajar_enable){
|
||||
val mb = messageDB.Find_Messagebank(fajar_message, adzan_language)
|
||||
if (mb!=null){
|
||||
return AdzanTask(
|
||||
"Fajar",
|
||||
fajar_time,
|
||||
mb.Description,
|
||||
mb.Message_TAGS
|
||||
)
|
||||
} else Logger.error { "Skipped Adzan Fajar because Unable to find $fajar_message in messageDB" }
|
||||
} else Logger.info{"Skipped Adzan Fajar because fajr_enable is false"}
|
||||
}
|
||||
if (hhmm == dzuhur_time){
|
||||
if (dzuhur_enable){
|
||||
val mb = messageDB.Find_Messagebank(dzuhur_message, adzan_language)
|
||||
if (mb!=null){
|
||||
return AdzanTask(
|
||||
"Dzuhur",
|
||||
dzuhur_time,
|
||||
mb.Description,
|
||||
mb.Message_TAGS
|
||||
)
|
||||
} else Logger.error { "Skipped Adzan Dzuhur because Unable to find $dzuhur_message in messageDB" }
|
||||
} else Logger.info{"Skipped Adzan Dzuhur because dzuhur_enable is false"}
|
||||
}
|
||||
if (hhmm == ashar_time) {
|
||||
if (ashar_enable){
|
||||
val mb = messageDB.Find_Messagebank(ashar_message, adzan_language)
|
||||
if (mb!=null){
|
||||
return AdzanTask(
|
||||
"Ashar",
|
||||
ashar_time,
|
||||
mb.Description,
|
||||
mb.Message_TAGS
|
||||
)
|
||||
} else Logger.error { "Skipped Adzan Ashar because Unable to find $ashar_message in messageDB" }
|
||||
} else Logger.info{"Skipped Adzan Ashar because ashar_enable is false"}
|
||||
}
|
||||
if (hhmm == maghrib_time){
|
||||
if (maghrib_enable){
|
||||
val mb = messageDB.Find_Messagebank(maghrib_message, adzan_language)
|
||||
if (mb!=null){
|
||||
return AdzanTask(
|
||||
"Maghrib",
|
||||
maghrib_time,
|
||||
mb.Description,
|
||||
mb.Message_TAGS
|
||||
)
|
||||
} else Logger.error { "Skipped Adzan Maghrib because Unable to find $maghrib_message in messageDB" }
|
||||
|
||||
} else Logger.info{"Skipped Adzan Maghrib because maghrib_enable is false"}
|
||||
}
|
||||
if (hhmm == isya_time) {
|
||||
if (isya_enable){
|
||||
val mb = messageDB.Find_Messagebank(isya_message, adzan_language)
|
||||
if (mb!=null){
|
||||
return AdzanTask(
|
||||
"Isya",
|
||||
isya_time,
|
||||
mb.Description,
|
||||
mb.Message_TAGS
|
||||
)
|
||||
} else Logger.error { "Skipped Adzan Isya because Unable to find $isya_message in messageDB" }
|
||||
|
||||
} else Logger.info{"Skipped Adzan Isya because isya_enable is false"}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
// /**
|
||||
// * Change the time zone used for formatting prayer times.
|
||||
// * @param timezone The new AdzanTimeZone.
|
||||
// */
|
||||
// fun ChangeTimezone(timezone: AdzanTimeZone) {
|
||||
// ChangeTimeZone(timezone.toTimeZoneString())
|
||||
// }
|
||||
|
||||
/**
|
||||
* Change the time zone used for formatting prayer times.
|
||||
* @param timezoneString The new time zone string (e.g., "Asia/Jakarta").
|
||||
*/
|
||||
fun ChangeTimeZone(timezoneString: String) {
|
||||
timeformatter.timeZone = TimeZone.getTimeZone(timezoneString)
|
||||
}
|
||||
|
||||
// /**
|
||||
// * Set prayer time adjustments in minutes.
|
||||
// * @param fajrMinute Adjustment for Fajr prayer time in minutes.
|
||||
// * @param dhuhrMinute Adjustment for Dhuhr prayer time in minutes.
|
||||
// * @param asrMinute Adjustment for Asr prayer time in minutes.
|
||||
// * @param maghribMinute Adjustment for Maghrib prayer time in minutes.
|
||||
// * @param ishaMinute Adjustment for Isha prayer time in minutes.
|
||||
// */
|
||||
// fun SetPrayerAdjustment(fajrMinute: Int = 0, dhuhrMinute: Int = 0, asrMinute: Int = 0,maghribMinute: Int = 0, ishaMinute: Int = 0) {
|
||||
// params.adjustments.fajr = fajrMinute
|
||||
// params.adjustments.dhuhr = dhuhrMinute
|
||||
// params.adjustments.asr = asrMinute
|
||||
// params.adjustments.maghrib = maghribMinute
|
||||
// params.adjustments.isha = ishaMinute
|
||||
// }
|
||||
// /**
|
||||
// * Change the coordinates used for Adzan calculations.
|
||||
// * @param lat The new latitude.
|
||||
// * @param long The new longitude.
|
||||
// */
|
||||
// fun ChangeCoordinate(lat: Double, long: Double) {
|
||||
// coordinate = Coordinates(lat, long)
|
||||
// }
|
||||
|
||||
fun ChangeLatitude(lat: Double) {
|
||||
coordinate = Coordinates(lat, coordinate.longitude)
|
||||
}
|
||||
|
||||
fun ChangeLongitude(long: Double) {
|
||||
coordinate = Coordinates(coordinate.latitude, long)
|
||||
}
|
||||
|
||||
// /**
|
||||
// * Get prayer times for a specific date string in the format "dd/MM/yyyy".
|
||||
// * @param date_string The date string for which to get prayer times.
|
||||
// * @return An AdzanPrayerTime object containing the prayer times, or null if the date string is invalid.
|
||||
// */
|
||||
// fun GetPrayerTimes(date_string: String) : AdzanPrayerTime?{
|
||||
// try{
|
||||
// val date = dateformatter.parse(date_string)
|
||||
// return GetPrayerTimes(date)
|
||||
// } catch (_: Exception){
|
||||
// return null
|
||||
// }
|
||||
// }
|
||||
|
||||
/**
|
||||
* Get prayer times for a specific date.
|
||||
* @param date The date for which to get prayer times.
|
||||
* @return An AdzanPrayerTime object containing the prayer times.
|
||||
*/
|
||||
fun GetPrayerTimes(date: Date) : AdzanPrayerTime{
|
||||
val prayer = PrayerTimes(coordinate, DateComponents.from(date), params)
|
||||
return AdzanPrayerTime(
|
||||
dateformatter.format(date),
|
||||
timeformatter.format(prayer.fajr),
|
||||
timeformatter.format(prayer.dhuhr),
|
||||
timeformatter.format(prayer.asr),
|
||||
timeformatter.format(prayer.maghrib),
|
||||
timeformatter.format(prayer.isha)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get prayer times for the current date.
|
||||
* this will update fajar_time, dzuhur_time, ashar_time, maghrib_time, isya_time properties
|
||||
* @return An AdzanPrayerTime object containing the prayer times for today.
|
||||
*/
|
||||
fun GetTodayPrayerTimes() : AdzanPrayerTime{
|
||||
val result = GetPrayerTimes(Date())
|
||||
fajar_time = result.fajr
|
||||
dzuhur_time = result.dhuhr
|
||||
ashar_time = result.asr
|
||||
maghrib_time = result.maghrib
|
||||
isya_time = result.isha
|
||||
return result
|
||||
}
|
||||
|
||||
// /**
|
||||
// * Get prayer times for all days in a specific month and year.
|
||||
// * @param month The month (1-12) for which to get prayer times.
|
||||
// * @param year The year for which to get prayer times.
|
||||
// * @return A list of AdzanPrayerTime objects for each day in the specified month and year.
|
||||
// */
|
||||
// fun GetMonthlyPrayerTimes(month: Int, year: Int) : List<AdzanPrayerTime>{
|
||||
// val prayerTimesList = mutableListOf<AdzanPrayerTime>()
|
||||
// val calendar = java.util.Calendar.getInstance()
|
||||
// calendar.set(year, month - 1, 1) // Month is 0-based in Calendar
|
||||
// val daysInMonth = calendar.getActualMaximum(java.util.Calendar.DAY_OF_MONTH)
|
||||
//
|
||||
// for (day in 1..daysInMonth) {
|
||||
// calendar.set(year, month - 1, day)
|
||||
// val date = calendar.time
|
||||
// val prayerTimes = GetPrayerTimes(date)
|
||||
// prayerTimesList.add(prayerTimes)
|
||||
// }
|
||||
//
|
||||
// return prayerTimesList
|
||||
// }
|
||||
}
|
||||
332
src/database/table/Table_BroadcastZones.kt
Normal file
332
src/database/table/Table_BroadcastZones.kt
Normal file
@@ -0,0 +1,332 @@
|
||||
package database.table
|
||||
|
||||
import StreamerOutputs
|
||||
import codes.Somecodes.Companion.ValidIPV4
|
||||
import codes.Somecodes.Companion.ValidString
|
||||
import database.data.BroadcastZones
|
||||
import database.dbFunctions
|
||||
import org.apache.poi.xssf.usermodel.XSSFWorkbook
|
||||
import org.tinylog.Logger
|
||||
import soundchannelDB
|
||||
import java.sql.Connection
|
||||
import java.util.function.Consumer
|
||||
|
||||
class Table_BroadcastZones(connection: Connection) : dbFunctions<BroadcastZones>("broadcastzones", connection, listOf("index", "description", "SoundChannel", "id", "bp")) {
|
||||
override fun Create() {
|
||||
val tabledefinition = "CREATE TABLE IF NOT EXISTS ${super.dbName} (" +
|
||||
"`index` INT AUTO_INCREMENT PRIMARY KEY," +
|
||||
"description VARCHAR(512) NOT NULL," + // Description of the broadcast zone
|
||||
"SoundChannel VARCHAR(45) NOT NULL," + // Sound channel of the broadcast zone
|
||||
"id VARCHAR(45) NOT NULL," + // Box of the broadcast zone
|
||||
"bp VARCHAR(45) NOT NULL" + // Relay of the broadcast zone
|
||||
")"
|
||||
super.Create(tabledefinition)
|
||||
}
|
||||
|
||||
override fun Get(cbOK: Consumer<Unit>?, cbFail: Consumer<String>?) {
|
||||
List.clear()
|
||||
try {
|
||||
val statement = connection.createStatement()
|
||||
val resultSet = statement?.executeQuery("SELECT * FROM ${super.dbName}")
|
||||
while (resultSet?.next() == true) {
|
||||
val zone = BroadcastZones(
|
||||
resultSet.getLong("index").toUInt(),
|
||||
resultSet.getString("description"),
|
||||
resultSet.getString("SoundChannel"),
|
||||
resultSet.getString("id"),
|
||||
resultSet.getString("bp")
|
||||
)
|
||||
List.add(zone)
|
||||
}
|
||||
cbOK?.accept(Unit)
|
||||
} catch (e: Exception) {
|
||||
Logger.error("Error fetching ${super.dbName} : ${e.message}" as Any)
|
||||
cbFail?.accept("Error fetching ${super.dbName} : ${e.message}" )
|
||||
}
|
||||
}
|
||||
|
||||
override fun Add(data: BroadcastZones): Boolean {
|
||||
try {
|
||||
val statement =
|
||||
connection.prepareStatement("INSERT INTO ${super.dbName} (description, SoundChannel, id, bp) VALUES (?, ?, ?, ?)")
|
||||
statement?.setString(1, data.description)
|
||||
statement?.setString(2, data.SoundChannel)
|
||||
statement?.setString(3, data.id)
|
||||
statement?.setString(4, data.bp)
|
||||
val rowsAffected = statement?.executeUpdate()
|
||||
if (rowsAffected != null && rowsAffected > 0) {
|
||||
Logger.info("Broadcast zone added: ${data.description}" as Any)
|
||||
return true
|
||||
} else {
|
||||
Logger.warn("No broadcast zone entry added for: ${data.description}" as Any)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Logger.error("Error adding broadcast zone entry: ${e.message}" as Any)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
override fun AddAll(data: ArrayList<BroadcastZones>): Boolean {
|
||||
try {
|
||||
connection.autoCommit = false
|
||||
val sql =
|
||||
"INSERT INTO ${super.dbName} (description, SoundChannel, id, bp) VALUES (?, ?, ?, ?)"
|
||||
val statement = connection.prepareStatement(sql)
|
||||
for (bz in data) {
|
||||
statement.setString(1, bz.description)
|
||||
statement.setString(2, bz.SoundChannel)
|
||||
statement.setString(3, bz.id)
|
||||
statement.setString(4, bz.bp)
|
||||
statement.addBatch()
|
||||
}
|
||||
statement.executeBatch()
|
||||
connection.commit()
|
||||
Logger.info("Bulk ${super.dbName} insert successful: ${data.size} entries" as Any)
|
||||
connection.autoCommit = true
|
||||
return true
|
||||
} catch (e: Exception) {
|
||||
Logger.error("Error adding ${super.dbName} entries: ${e.message}" as Any)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
override fun UpdateByIndex(index: Int, data: BroadcastZones): Boolean {
|
||||
try {
|
||||
val statement =
|
||||
connection.prepareStatement("UPDATE ${super.dbName} SET description = ?, SoundChannel = ?, id = ?, bp = ? WHERE `index` = ?")
|
||||
statement?.setString(1, data.description)
|
||||
statement?.setString(2, data.SoundChannel)
|
||||
statement?.setString(3, data.id)
|
||||
statement?.setString(4, data.bp)
|
||||
statement?.setLong(5, index.toLong())
|
||||
val rowsAffected = statement?.executeUpdate()
|
||||
if (rowsAffected != null && rowsAffected > 0) {
|
||||
Logger.info("Broadcast zone updated at index $index: ${data.description}" as Any)
|
||||
return true
|
||||
} else {
|
||||
Logger.warn("No broadcast zone entry updated at index $index for: ${data.description}" as Any)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Logger.error("Error updating broadcast zone entry at index $index: ${e.message}" as Any)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
override fun Resort(): Boolean {
|
||||
try {
|
||||
val statement = connection.createStatement()
|
||||
val tempdb_name = "temp_${super.dbName}"
|
||||
// use a temporary table to reorder the index
|
||||
statement?.executeUpdate("CREATE TABLE IF NOT EXISTS $tempdb_name LIKE ${super.dbName}")
|
||||
statement?.executeUpdate("TRUNCATE TABLE $tempdb_name")
|
||||
statement?.executeUpdate("INSERT INTO $tempdb_name (description, SoundChannel, id, bp) SELECT description, SoundChannel, id, bp FROM ${super.dbName} ORDER BY description ")
|
||||
statement?.executeUpdate("TRUNCATE TABLE ${super.dbName}")
|
||||
statement?.executeUpdate("INSERT INTO ${super.dbName} (description, SoundChannel, id, bp) SELECT description, SoundChannel, id, bp FROM $tempdb_name")
|
||||
statement?.executeUpdate("DROP TABLE $tempdb_name")
|
||||
Logger.info("${super.dbName} table resorted by description" as Any)
|
||||
// reload the local list
|
||||
Get()
|
||||
return true
|
||||
} catch (e: Exception) {
|
||||
Logger.error("Error resorting ${super.dbName} table by description: ${e.message}" as Any)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
override fun Import_XLSX(workbook: XSSFWorkbook): Boolean {
|
||||
try {
|
||||
val sheet = workbook.getSheet("BroadcastZones")
|
||||
?: throw Exception("No sheet named 'BroadcastZones' found")
|
||||
val headerRow = sheet.getRow(0) ?: throw Exception("No header row found")
|
||||
val headers = arrayOf("Index", "description", "SoundChannel", "id", "bp")
|
||||
for ((colIndex, header) in headers.withIndex()) {
|
||||
val cell = headerRow.getCell(colIndex) ?: throw Exception("Header '$header' not found")
|
||||
if (cell.stringCellValue != header) throw Exception("Header '$header' not found")
|
||||
}
|
||||
// clear existing broadcast_zones
|
||||
Clear()
|
||||
// read each row and insert into database
|
||||
val _broadcastZonesList = ArrayList<BroadcastZones>()
|
||||
for (rowIndex in 1..sheet.lastRowNum) {
|
||||
val row = sheet.getRow(rowIndex) ?: continue
|
||||
val description = row.getCell(1)?.stringCellValue ?: continue
|
||||
val soundChannel = row.getCell(2)?.stringCellValue ?: continue
|
||||
val id = row.getCell(3)?.stringCellValue ?: continue
|
||||
val bp = row.getCell(4)?.stringCellValue ?: continue
|
||||
val broadcastZone = BroadcastZones(0u, description, soundChannel, id, bp)
|
||||
_broadcastZonesList.add(broadcastZone)
|
||||
}
|
||||
return AddAll(_broadcastZonesList)
|
||||
} catch (e: Exception) {
|
||||
Logger.error { "Error importing BroadcastZones, Msg: ${e.message}" }
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
override fun Export_XLSX(): XSSFWorkbook? {
|
||||
try {
|
||||
val statement = connection.createStatement()
|
||||
val resultSet = statement?.executeQuery("SELECT * FROM ${super.dbName}")
|
||||
val workbook = XSSFWorkbook()
|
||||
val sheet = workbook.createSheet("BroadcastZones")
|
||||
val headerRow = sheet.createRow(0)
|
||||
val headers = arrayOf("Index", "description", "SoundChannel", "id", "bp")
|
||||
for ((colIndex, header) in headers.withIndex()) {
|
||||
val cell = headerRow.createCell(colIndex)
|
||||
cell.setCellValue(header)
|
||||
}
|
||||
var rowIndex = 1
|
||||
while (resultSet?.next() == true) {
|
||||
val row = sheet.createRow(rowIndex++)
|
||||
row.createCell(0).setCellValue(resultSet.getString("index"))
|
||||
row.createCell(1).setCellValue(resultSet.getString("description"))
|
||||
row.createCell(2).setCellValue(resultSet.getString("SoundChannel"))
|
||||
row.createCell(3).setCellValue(resultSet.getString("id"))
|
||||
row.createCell(4).setCellValue(resultSet.getString("bp"))
|
||||
}
|
||||
for (i in headers.indices) {
|
||||
sheet.autoSizeColumn(i)
|
||||
}
|
||||
return workbook
|
||||
} catch (e: Exception) {
|
||||
Logger.error { "Error exporting BroadcastZones, Msg: ${e.message}" }
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all distinct broadcast zone descriptions from broadcastDB
|
||||
* @return a list of distinct broadcast zone descriptions sorted alphabetically
|
||||
*/
|
||||
fun Get_BroadcastZone_List(): List<String> {
|
||||
return List
|
||||
.distinctBy { it.description }
|
||||
.map { it.description }
|
||||
.sorted()
|
||||
}
|
||||
|
||||
data class InvalidZoneDetail(val zonename: String, val reason: String)
|
||||
data class ValidZoneDetail(
|
||||
val zonename: String,
|
||||
val soundchanel: String,
|
||||
val ip: String,
|
||||
val boxid: String,
|
||||
val contacts: String
|
||||
)
|
||||
|
||||
class CheckBroadcastZoneResult {
|
||||
var allvalid: Boolean = false
|
||||
var message: String? = null
|
||||
var validzones = mutableListOf<ValidZoneDetail>()
|
||||
var invalidzones = mutableListOf<InvalidZoneDetail>()
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if all broadcast zones in a comma-separated string are valid,
|
||||
* means the broadcast zone name exists in the BroadcastZones table, its SoundChannel exists in the SoundChannel table, and its IP is valid
|
||||
* @param zones Comma-separated string of broadcast zones
|
||||
* @param checkOnline Whether to check if the sound channel is really online, recorded in StreamerOutputs map
|
||||
* @return true if all broadcast zones are valid, false otherwise
|
||||
*/
|
||||
fun AllBroadcastZonesValid(zones: String, checkOnline: Boolean = true) : Boolean{
|
||||
val bzlist = zones.split(",",";").map { it.trim() }.filter { it.isNotEmpty() }
|
||||
//println("Checking all broadcast zones validity for: $bzlist")
|
||||
AllBroadcastZonesValid(bzlist, checkOnline).let { result ->
|
||||
if (result.allvalid) {
|
||||
//Logger.info("All broadcast zones are valid: ${bzlist.joinToString(", ")}" as Any)
|
||||
return true
|
||||
} else {
|
||||
//Logger.warn("Some broadcast zones are invalid:" as Any)
|
||||
result.invalidzones.forEach { iz ->
|
||||
Logger.warn(" - Zone '${iz.zonename}' is invalid: ${iz.reason}" as Any)
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
//return AllBroadcastZonesValid(bzlist).allvalid
|
||||
}
|
||||
|
||||
/**
|
||||
* Fungsi untuk cek apakah semua broadcast zone valid
|
||||
* Valid berarti nama broadcast zone ada di tabel BroadcastZones, dan SoundChannel-nya ada di tabel SoundChannel, dan IP-nya valid
|
||||
* @param bz List of broadcast zone (SoundChannel)
|
||||
* @param checkOnline Whether to check if the sound channel is really online, recorded in StreamerOutputs map
|
||||
* @return CheckBroadcastZoneResult object containing allvalid flag and list of invalid zones
|
||||
*/
|
||||
fun AllBroadcastZonesValid(bz: List<String>, checkOnline: Boolean = true): CheckBroadcastZoneResult {
|
||||
val result = CheckBroadcastZoneResult()
|
||||
if (bz.isNotEmpty()) {
|
||||
bz.forEach { zz ->
|
||||
if (ValidString(zz)) { // string tidak kosong
|
||||
val findzone = List.find{
|
||||
ValidString(it.description) && ValidString(it.SoundChannel) && it.description.equals(zz,true)
|
||||
}
|
||||
|
||||
if (findzone != null) { // ketemu zona dengan deskripsi sesuai
|
||||
val findsc = soundchannelDB.List.find {
|
||||
findzone.SoundChannel.equals(
|
||||
it.channel,
|
||||
true
|
||||
) && ValidIPV4(it.ip)
|
||||
}
|
||||
if (findsc != null) { // ketemu soundchannel dengan channel sesuai dan IP valid
|
||||
// check apakah offline atau online
|
||||
if (checkOnline){
|
||||
if (StreamerOutputs.containsKey(findsc.ip)) {
|
||||
val bc = StreamerOutputs[findsc.ip]
|
||||
if (bc != null && bc.isOnline()) {
|
||||
result.validzones.add(
|
||||
ValidZoneDetail(
|
||||
zz,
|
||||
findzone.SoundChannel,
|
||||
findsc.ip,
|
||||
findzone.id,
|
||||
findzone.bp
|
||||
)
|
||||
)
|
||||
} else result.invalidzones.add(
|
||||
InvalidZoneDetail(
|
||||
zz,
|
||||
"SoundChannel ${findzone.SoundChannel} with IP ${findsc.ip} is offline"
|
||||
)
|
||||
)
|
||||
} else result.invalidzones.add(
|
||||
InvalidZoneDetail(
|
||||
zz,
|
||||
"SoundChannel ${findzone.SoundChannel} with IP ${findsc.ip} is not connected in StreamerOutputs"
|
||||
)
|
||||
)
|
||||
} else {
|
||||
result.validzones.add(
|
||||
ValidZoneDetail(
|
||||
zz,
|
||||
findzone.SoundChannel,
|
||||
findsc.ip,
|
||||
findzone.id,
|
||||
findzone.bp
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
} else result.invalidzones.add(
|
||||
InvalidZoneDetail(
|
||||
zz,
|
||||
"SoundChannel ${findzone.SoundChannel} not found or has invalid IP in SoundChannel table"
|
||||
)
|
||||
)
|
||||
} else result.invalidzones.add(InvalidZoneDetail(zz, "Zone $zz not found in BroadcastZones table"))
|
||||
} else result.invalidzones.add(InvalidZoneDetail(zz, "Invalid broadcast zone string"))
|
||||
}
|
||||
|
||||
if (result.validzones.size == bz.size) {
|
||||
result.allvalid = true
|
||||
result.message = "All requested broadcast zones are valid"
|
||||
} else {
|
||||
result.message = "Some requested broadcast zones are not registered in BroadcastZone table"
|
||||
}
|
||||
} else {
|
||||
result.message = "No Broadcast Zones checked for validity"
|
||||
}
|
||||
return result
|
||||
}
|
||||
}
|
||||
180
src/database/table/Table_LanguageLink.kt
Normal file
180
src/database/table/Table_LanguageLink.kt
Normal file
@@ -0,0 +1,180 @@
|
||||
package database.table
|
||||
|
||||
import database.data.LanguageLink
|
||||
import database.dbFunctions
|
||||
import org.apache.poi.xssf.usermodel.XSSFWorkbook
|
||||
import org.tinylog.Logger
|
||||
import java.sql.Connection
|
||||
import java.util.function.Consumer
|
||||
|
||||
class Table_LanguageLink(connection: Connection) : dbFunctions<LanguageLink>("languagelinking", connection, listOf("index", "TAG", "Language")) {
|
||||
override fun Create() {
|
||||
val tabledefinition = "CREATE TABLE IF NOT EXISTS ${super.dbName} (" +
|
||||
"`index` INT AUTO_INCREMENT PRIMARY KEY," +
|
||||
"TAG VARCHAR(45) NOT NULL," + // Language tag (e.g., EN, FR)
|
||||
"Language VARCHAR(128) NOT NULL" + // Full language name (e.g., English, French)
|
||||
")"
|
||||
|
||||
super.Create(tabledefinition)
|
||||
}
|
||||
|
||||
override fun Get(cbOK: Consumer<Unit>?, cbFail: Consumer<String>?) {
|
||||
List.clear()
|
||||
try {
|
||||
val statement = connection.createStatement()
|
||||
val resultSet = statement?.executeQuery("SELECT * FROM ${super.dbName}")
|
||||
while (resultSet?.next() == true) {
|
||||
val languageLink = LanguageLink(
|
||||
resultSet.getLong("index").toUInt(),
|
||||
resultSet.getString("TAG"),
|
||||
resultSet.getString("Language")
|
||||
)
|
||||
List.add(languageLink)
|
||||
}
|
||||
cbOK?.accept(Unit)
|
||||
} catch (e: Exception) {
|
||||
Logger.error("Error fetching ${super.dbName} : ${e.message}" as Any)
|
||||
cbFail?.accept("Error fetching ${super.dbName} : ${e.message}" )
|
||||
}
|
||||
}
|
||||
|
||||
override fun Add(data: LanguageLink): Boolean {
|
||||
try {
|
||||
val statement = connection.prepareStatement("INSERT INTO ${super.dbName} (TAG, Language) VALUES (?, ?)")
|
||||
statement.setString(1, data.TAG)
|
||||
statement.setString(2, data.Language)
|
||||
val rowsAffected = statement.executeUpdate()
|
||||
if (rowsAffected > 0) {
|
||||
Logger.info("Language link added: ${data.TAG} -> ${data.Language}" as Any)
|
||||
return true
|
||||
} else {
|
||||
Logger.warn("No language link entry added for: ${data.TAG} -> ${data.Language}" as Any)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Logger.error("Error adding language link entry: ${e.message}" as Any)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
override fun AddAll(data: ArrayList<LanguageLink>): Boolean {
|
||||
try {
|
||||
connection.autoCommit = false
|
||||
val sql = "INSERT INTO ${super.dbName} (TAG, Language) VALUES (?, ?)"
|
||||
val statement = connection.prepareStatement(sql)
|
||||
|
||||
//for (ll in List) {
|
||||
for (ll in data) {
|
||||
statement.setString(1, ll.TAG)
|
||||
statement.setString(2, ll.Language)
|
||||
statement.addBatch()
|
||||
}
|
||||
statement.executeBatch()
|
||||
connection.commit()
|
||||
Logger.info("Bulk languagelinking insert successful: ${List.size} entries" as Any)
|
||||
connection.autoCommit = true
|
||||
return true
|
||||
} catch (e: Exception) {
|
||||
Logger.error("Error adding languagelinking entries: ${e.message}" as Any)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
override fun UpdateByIndex(index: Int, data: LanguageLink): Boolean {
|
||||
try {
|
||||
val statement =
|
||||
connection.prepareStatement("UPDATE ${super.dbName} SET TAG = ?, Language = ? WHERE `index` = ?")
|
||||
statement?.setString(1, data.TAG)
|
||||
statement?.setString(2, data.Language)
|
||||
statement?.setLong(3, index.toLong())
|
||||
val rowsAffected = statement?.executeUpdate()
|
||||
if (rowsAffected != null && rowsAffected > 0) {
|
||||
Logger.info("Language link updated at index $index: ${data.TAG} -> ${data.Language}" as Any)
|
||||
|
||||
return true
|
||||
} else {
|
||||
Logger.warn("No language link entry updated at index $index for: ${data.TAG} -> ${data.Language}" as Any)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Logger.error("Error updating language link entry at index $index: ${e.message}" as Any)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
override fun Resort(): Boolean {
|
||||
try {
|
||||
val statement = connection.createStatement()
|
||||
val tempdb_name = "temp_${super.dbName}"
|
||||
// use a temporary table to reorder the index
|
||||
statement?.executeUpdate("CREATE TABLE IF NOT EXISTS $tempdb_name LIKE ${super.dbName}")
|
||||
statement?.executeUpdate("TRUNCATE TABLE $tempdb_name")
|
||||
statement?.executeUpdate("INSERT INTO $tempdb_name (TAG, Language) SELECT TAG, Language FROM ${super.dbName} ORDER BY TAG")
|
||||
statement?.executeUpdate("TRUNCATE TABLE ${super.dbName}")
|
||||
statement?.executeUpdate("INSERT INTO ${super.dbName} (TAG, Language) SELECT TAG, Language FROM $tempdb_name")
|
||||
statement?.executeUpdate("DROP TABLE $tempdb_name")
|
||||
Logger.info("${super.dbName} table resorted by TAG" as Any)
|
||||
// reload the local list
|
||||
Get()
|
||||
return true
|
||||
} catch (e: Exception) {
|
||||
Logger.error("Error resorting ${super.dbName} table by TAG: ${e.message}" as Any)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
override fun Import_XLSX(workbook: XSSFWorkbook): Boolean {
|
||||
try {
|
||||
val sheet =
|
||||
workbook.getSheet("LanguageLink") ?: throw Exception("No sheet named 'LanguageLink' found")
|
||||
val headerRow = sheet.getRow(0) ?: throw Exception("No header row found")
|
||||
val headers = arrayOf("Index", "TAG", "Language")
|
||||
for ((colIndex, header) in headers.withIndex()) {
|
||||
val cell = headerRow.getCell(colIndex) ?: throw Exception("Header '$header' not found")
|
||||
if (cell.stringCellValue != header) throw Exception("Header '$header' not found")
|
||||
}
|
||||
// clear existing languagelink
|
||||
Clear()
|
||||
// read each row and insert into database
|
||||
val _languageLinkList = ArrayList<LanguageLink>()
|
||||
for (rowIndex in 1..sheet.lastRowNum) {
|
||||
val row = sheet.getRow(rowIndex) ?: continue
|
||||
val tag = row.getCell(1)?.stringCellValue ?: continue
|
||||
val language = row.getCell(2)?.stringCellValue ?: continue
|
||||
val languageLink = LanguageLink(0u, tag, language)
|
||||
_languageLinkList.add(languageLink)
|
||||
}
|
||||
return AddAll(_languageLinkList)
|
||||
} catch (e: Exception) {
|
||||
Logger.error { "Error importing LanguageLink, Msg: ${e.message}" }
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
override fun Export_XLSX(): XSSFWorkbook? {
|
||||
try {
|
||||
val statement = connection.createStatement()
|
||||
val resultSet = statement?.executeQuery("SELECT * FROM ${super.dbName}")
|
||||
val workbook = XSSFWorkbook()
|
||||
val sheet = workbook.createSheet("LanguageLink")
|
||||
val headerRow = sheet.createRow(0)
|
||||
val headers = arrayOf("Index", "TAG", "Language")
|
||||
for ((colIndex, header) in headers.withIndex()) {
|
||||
val cell = headerRow.createCell(colIndex)
|
||||
cell.setCellValue(header)
|
||||
}
|
||||
var rowIndex = 1
|
||||
while (resultSet?.next() == true) {
|
||||
val row = sheet.createRow(rowIndex++)
|
||||
row.createCell(0).setCellValue(resultSet.getString("index"))
|
||||
row.createCell(1).setCellValue(resultSet.getString("TAG"))
|
||||
row.createCell(2).setCellValue(resultSet.getString("Language"))
|
||||
}
|
||||
for (i in headers.indices) {
|
||||
sheet.autoSizeColumn(i)
|
||||
}
|
||||
return workbook
|
||||
} catch (e: Exception) {
|
||||
Logger.error { "Error exporting languagelinking, Msg: ${e.message}" }
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,11 @@
|
||||
package database
|
||||
package database.table
|
||||
|
||||
import database.data.LogSemiauto
|
||||
import database.dbFunctions
|
||||
import org.apache.poi.xssf.usermodel.XSSFWorkbook
|
||||
import org.tinylog.Logger
|
||||
import java.sql.Connection
|
||||
import java.sql.Date
|
||||
import java.time.LocalDate
|
||||
import java.time.format.DateTimeFormatter
|
||||
import java.util.function.Consumer
|
||||
@@ -44,26 +47,27 @@ class Table_LogSemiAuto(connection: Connection) : dbFunctions<LogSemiauto>("logs
|
||||
|
||||
|
||||
override fun Get(cbOK: Consumer<Unit>?, cbFail: Consumer<String>?) {
|
||||
List.clear()
|
||||
try {
|
||||
val statement = connection.createStatement()
|
||||
val resultSet = statement?.executeQuery("SELECT * FROM ${super.dbName}")
|
||||
while (resultSet?.next() == true) {
|
||||
val log = LogSemiauto(
|
||||
resultSet.getLong("index").toULong(),
|
||||
resultSet.getString("date"),
|
||||
resultSet.getString("time"),
|
||||
resultSet.getString("source"),
|
||||
resultSet.getString("description")
|
||||
)
|
||||
List.add(log)
|
||||
}
|
||||
cbOK?.accept(Unit)
|
||||
} catch (e: Exception) {
|
||||
cbFail?.accept("Error fetching ${super.dbName}: ${e.message}")
|
||||
Logger.error("Error fetching ${super.dbName}: ${e.message}" as Any)
|
||||
|
||||
}
|
||||
throw Exception("Getting all LogSemiauto entries is not supported")
|
||||
// List.clear()
|
||||
// try {
|
||||
// val statement = connection.createStatement()
|
||||
// val resultSet = statement?.executeQuery("SELECT * FROM ${super.dbName}")
|
||||
// while (resultSet?.next() == true) {
|
||||
// val log = LogSemiauto(
|
||||
// resultSet.getLong("index").toULong(),
|
||||
// resultSet.getString("date"),
|
||||
// resultSet.getString("time"),
|
||||
// resultSet.getString("source"),
|
||||
// resultSet.getString("description")
|
||||
// )
|
||||
// List.add(log)
|
||||
// }
|
||||
// cbOK?.accept(Unit)
|
||||
// } catch (e: Exception) {
|
||||
// cbFail?.accept("Error fetching ${super.dbName}: ${e.message}")
|
||||
// Logger.error("Error fetching ${super.dbName}: ${e.message}" as Any)
|
||||
//
|
||||
// }
|
||||
}
|
||||
|
||||
fun Add(source: String, description: String){
|
||||
@@ -92,18 +96,18 @@ class Table_LogSemiAuto(connection: Connection) : dbFunctions<LogSemiauto>("logs
|
||||
|
||||
fun GetLogSemiAutoForHtml(date: String, filter: String?, cbOK: Consumer<ArrayList<LogSemiauto>>?, cbFail: Consumer<String>?){
|
||||
try{
|
||||
val valid_date : java.sql.Date? = when{
|
||||
val valid_date : Date? = when{
|
||||
dateformat1.matches(date) -> {
|
||||
java.sql.Date.valueOf(LocalDate.parse(date, DateTimeFormatter.ofPattern("dd/MM/yyyy")))
|
||||
Date.valueOf(LocalDate.parse(date, DateTimeFormatter.ofPattern("dd/MM/yyyy")))
|
||||
}
|
||||
dateformat2.matches(date) -> {
|
||||
java.sql.Date.valueOf(LocalDate.parse(date, DateTimeFormatter.ofPattern("dd-MM-yyyy")))
|
||||
Date.valueOf(LocalDate.parse(date, DateTimeFormatter.ofPattern("dd-MM-yyyy")))
|
||||
}
|
||||
dateformat3.matches(date) -> {
|
||||
java.sql.Date.valueOf(LocalDate.parse(date, DateTimeFormatter.ofPattern("yyyy/MM/dd")))
|
||||
Date.valueOf(LocalDate.parse(date, DateTimeFormatter.ofPattern("yyyy/MM/dd")))
|
||||
}
|
||||
dateformat4.matches(date) -> {
|
||||
java.sql.Date.valueOf(LocalDate.parse(date, DateTimeFormatter.ofPattern("yyyy-MM-dd")))
|
||||
Date.valueOf(LocalDate.parse(date, DateTimeFormatter.ofPattern("yyyy-MM-dd")))
|
||||
}
|
||||
else -> null
|
||||
}
|
||||
@@ -179,31 +183,32 @@ class Table_LogSemiAuto(connection: Connection) : dbFunctions<LogSemiauto>("logs
|
||||
}
|
||||
|
||||
override fun Resort(): Boolean {
|
||||
try{
|
||||
val statement = connection.createStatement()
|
||||
val tempdb_name = "temp_${super.dbName}"
|
||||
// use a temporary table to reorder the index
|
||||
statement?.executeUpdate("CREATE TABLE IF NOT EXISTS $tempdb_name LIKE ${super.dbName}")
|
||||
statement?.executeUpdate("TRUNCATE TABLE $tempdb_name")
|
||||
statement?.executeUpdate(
|
||||
"INSERT INTO $tempdb_name (date, time, source, description) " +
|
||||
"SELECT date, time, source, description FROM ${super.dbName} " +
|
||||
"ORDER BY date , time , source "
|
||||
)
|
||||
statement?.executeUpdate("TRUNCATE TABLE ${super.dbName}")
|
||||
statement?.executeUpdate(
|
||||
"INSERT INTO ${super.dbName} (date, time, source, description) " +
|
||||
"SELECT date, time, source, description FROM $tempdb_name"
|
||||
)
|
||||
statement?.executeUpdate("DROP TABLE $tempdb_name")
|
||||
Logger.info("${super.dbName} table resorted by date, time, source" as Any)
|
||||
// reload the local list
|
||||
Get()
|
||||
return true
|
||||
} catch (e : Exception){
|
||||
Logger.error { "Error resorting logsemiauto table: ${e.message}" }
|
||||
return false
|
||||
}
|
||||
throw Exception("Resorting LogSemiauto table is not supported")
|
||||
// try{
|
||||
// val statement = connection.createStatement()
|
||||
// val tempdb_name = "temp_${super.dbName}"
|
||||
// // use a temporary table to reorder the index
|
||||
// statement?.executeUpdate("CREATE TABLE IF NOT EXISTS $tempdb_name LIKE ${super.dbName}")
|
||||
// statement?.executeUpdate("TRUNCATE TABLE $tempdb_name")
|
||||
// statement?.executeUpdate(
|
||||
// "INSERT INTO $tempdb_name (date, time, source, description) " +
|
||||
// "SELECT date, time, source, description FROM ${super.dbName} " +
|
||||
// "ORDER BY date , time , source "
|
||||
// )
|
||||
// statement?.executeUpdate("TRUNCATE TABLE ${super.dbName}")
|
||||
// statement?.executeUpdate(
|
||||
// "INSERT INTO ${super.dbName} (date, time, source, description) " +
|
||||
// "SELECT date, time, source, description FROM $tempdb_name"
|
||||
// )
|
||||
// statement?.executeUpdate("DROP TABLE $tempdb_name")
|
||||
// Logger.info("${super.dbName} table resorted by date, time, source" as Any)
|
||||
// // reload the local list
|
||||
// Get()
|
||||
// return true
|
||||
// } catch (e : Exception){
|
||||
// Logger.error { "Error resorting logsemiauto table: ${e.message}" }
|
||||
// return false
|
||||
// }
|
||||
}
|
||||
|
||||
override fun Import_XLSX(workbook: XSSFWorkbook): Boolean {
|
||||
327
src/database/table/Table_Logs.kt
Normal file
327
src/database/table/Table_Logs.kt
Normal file
@@ -0,0 +1,327 @@
|
||||
package database.table
|
||||
|
||||
import database.data.Log
|
||||
import database.dbFunctions
|
||||
import org.apache.poi.xssf.usermodel.XSSFWorkbook
|
||||
import org.tinylog.Logger
|
||||
import java.sql.Connection
|
||||
import java.sql.Date
|
||||
import java.sql.Statement
|
||||
import java.time.LocalDate
|
||||
import java.time.format.DateTimeFormatter
|
||||
import java.util.function.Consumer
|
||||
|
||||
class Table_Logs(connection: Connection) : dbFunctions<Log>("logs", connection,listOf("index", "datenya", "timenya", "machine", "description")) {
|
||||
/**
|
||||
* dateformat1 is regex for DD/MM/YYYY
|
||||
*/
|
||||
val dateformat1 = """^(0[1-9]|[12][0-9]|3[01])/(0[1-9]|1[0-2])/\d{4}$""".toRegex()
|
||||
|
||||
/**
|
||||
* dateformat2 is regex for DD-MM-YYYY
|
||||
*/
|
||||
val dateformat2 = """^(0[1-9]|[12][0-9]|3[01])-(0[1-9]|1[0-2])-\d{4}$""".toRegex()
|
||||
|
||||
/**
|
||||
* dateformat3 is regex for YYYY/MM/DD
|
||||
*/
|
||||
val dateformat3 = """^\d{4}/(0[1-9]|1[0-2])/(0[1-9]|[12][0-9]|3[01])$""".toRegex()
|
||||
|
||||
/**
|
||||
* dateformat4 is regex for YYYY-MM-DD
|
||||
*/
|
||||
val dateformat4 = """^\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$""".toRegex()
|
||||
|
||||
|
||||
override fun Create() {
|
||||
val tabledefinition = "CREATE TABLE IF NOT EXISTS ${super.dbName} (" +
|
||||
"`index` INT AUTO_INCREMENT PRIMARY KEY," +
|
||||
"datenya VARCHAR(20) NOT NULL," + // format DD/MM/YYYY
|
||||
"timenya VARCHAR(20) NOT NULL," + // format HH:MM:SS
|
||||
"machine VARCHAR(45) NOT NULL," +
|
||||
"description TEXT NOT NULL" +
|
||||
")"
|
||||
|
||||
super.Create(tabledefinition)
|
||||
|
||||
}
|
||||
|
||||
fun GetLogForHtml(date: String, filter: String?, cbOK: Consumer<ArrayList<Log>>?, cbFail: Consumer<String>?){
|
||||
try{
|
||||
var statement : Statement?
|
||||
if ("alldate" == date){
|
||||
if (filter.isNullOrEmpty()) throw Exception("Filter is required when date is 'alldate'")
|
||||
statement = connection.prepareStatement("SELECT * FROM ${super.dbName} WHERE description LIKE ?")
|
||||
statement?.setString(1, "%$filter%")
|
||||
} else {
|
||||
val valid_date : Date? = when{
|
||||
dateformat1.matches(date) -> {
|
||||
Date.valueOf(LocalDate.parse(date, DateTimeFormatter.ofPattern("dd/MM/yyyy")))
|
||||
}
|
||||
dateformat2.matches(date) -> {
|
||||
Date.valueOf(LocalDate.parse(date, DateTimeFormatter.ofPattern("dd-MM-yyyy")))
|
||||
}
|
||||
dateformat3.matches(date) -> {
|
||||
Date.valueOf(LocalDate.parse(date, DateTimeFormatter.ofPattern("yyyy/MM/dd")))
|
||||
}
|
||||
dateformat4.matches(date) -> {
|
||||
Date.valueOf(LocalDate.parse(date, DateTimeFormatter.ofPattern("yyyy-MM-dd")))
|
||||
}
|
||||
else -> null
|
||||
}
|
||||
if (valid_date!=null){
|
||||
// use coalescing for different datenya formats
|
||||
statement = if (filter.isNullOrEmpty()){
|
||||
connection.prepareStatement("SELECT * FROM ${super.dbName} WHERE COALESCE(STR_TO_DATE(datenya,'%d/%m/%Y'), STR_TO_DATE(datenya,'%d-%m-%Y'), STR_TO_DATE(datenya,'%Y/%m/%d'), STR_TO_DATE(datenya,'%Y-%m-%d')) = ?")
|
||||
} else {
|
||||
connection.prepareStatement("SELECT * FROM ${super.dbName} WHERE COALESCE(STR_TO_DATE(datenya,'%d/%m/%Y'), STR_TO_DATE(datenya,'%d-%m-%Y'), STR_TO_DATE(datenya,'%Y/%m/%d'), STR_TO_DATE(datenya,'%Y-%m-%d')) = ? AND description LIKE ?")
|
||||
}
|
||||
statement?.setDate(1, valid_date)
|
||||
if (!filter.isNullOrEmpty()){
|
||||
statement?.setString(2, "%$filter%")
|
||||
}
|
||||
} else throw Exception("Invalid date")
|
||||
}
|
||||
if (statement!=null){
|
||||
val resultSet = statement.executeQuery()
|
||||
val tempList = ArrayList<Log>()
|
||||
while (resultSet?.next() == true) {
|
||||
val log = Log(
|
||||
resultSet.getLong("index").toULong(),
|
||||
resultSet.getString("datenya"),
|
||||
resultSet.getString("timenya"),
|
||||
resultSet.getString("machine"),
|
||||
resultSet.getString("description")
|
||||
)
|
||||
tempList.add(log)
|
||||
}
|
||||
cbOK?.accept(tempList)
|
||||
} else throw Exception("Failed to prepare statement")
|
||||
} catch (e : Exception){
|
||||
if (filter.isNullOrEmpty()){
|
||||
cbFail?.accept("Failed to Get logs for date $date: ${e.message}")
|
||||
} else {
|
||||
cbFail?.accept("Failed to Get logs for date $date with filter $filter: ${e.message}")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/****
|
||||
* Fetches all log entries from the database and populates the local List.
|
||||
* @param cbOK Optional callback invoked upon successful retrieval.
|
||||
* @param cbFail Optional callback invoked upon failure with an error message.
|
||||
*/
|
||||
override fun Get(cbOK: Consumer<Unit>?, cbFail: Consumer<String>?) {
|
||||
throw Exception("Get all logs is not supported for Logs")
|
||||
// CoroutineScope(Dispatchers.IO).launch {
|
||||
// List.clear()
|
||||
// try {
|
||||
// val statement = connection.createStatement()
|
||||
// val resultSet = statement?.executeQuery("SELECT * FROM ${super.dbName}")
|
||||
// while (resultSet?.next() == true) {
|
||||
// val log = Log(
|
||||
// resultSet.getLong("index").toULong(),
|
||||
// resultSet.getString("datenya"),
|
||||
// resultSet.getString("timenya"),
|
||||
// resultSet.getString("machine"),
|
||||
// resultSet.getString("description")
|
||||
// )
|
||||
// List.add(log)
|
||||
// }
|
||||
// cbOK?.accept(Unit)
|
||||
// } catch (e: Exception) {
|
||||
// cbFail?.accept("Error fetching ${super.dbName}: ${e.message}")
|
||||
// Logger.error("Error fetching ${super.dbName}: ${e.message}" as Any)
|
||||
//
|
||||
// }
|
||||
// }
|
||||
|
||||
}
|
||||
|
||||
fun Add(machine: String, description: String): Boolean {
|
||||
val log = Log.NewLog(machine, description)
|
||||
return Add(log)
|
||||
}
|
||||
|
||||
override fun Add(data: Log): Boolean {
|
||||
try {
|
||||
val statement =
|
||||
connection.prepareStatement("INSERT INTO logs (datenya, timenya, machine, description) VALUES (?, ?, ?, ?)")
|
||||
statement?.setString(1, data.datenya)
|
||||
statement?.setString(2, data.timenya)
|
||||
statement?.setString(3, data.machine)
|
||||
statement?.setString(4, data.description)
|
||||
val rowsAffected = statement?.executeUpdate()
|
||||
if (rowsAffected != null && rowsAffected > 0) {
|
||||
return true
|
||||
} else {
|
||||
Logger.warn{"Failed to add log entry : $data"}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Logger.error{"Error adding log entry: ${e.message}"}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
override fun AddAll(data: ArrayList<Log>): Boolean {
|
||||
return try {
|
||||
connection.autoCommit = false
|
||||
val sql = "INSERT INTO logs (datenya, timenya, machine, description) VALUES (?, ?, ?, ?)"
|
||||
val statement = connection.prepareStatement(sql)
|
||||
for (log in data) {
|
||||
statement.setString(1, log.datenya)
|
||||
statement.setString(2, log.timenya)
|
||||
statement.setString(3, log.machine)
|
||||
statement.setString(4, log.description)
|
||||
statement.addBatch()
|
||||
}
|
||||
statement.executeBatch()
|
||||
connection.commit()
|
||||
Logger.info("Bulk log insert successful: ${data.size} entries" as Any)
|
||||
connection.autoCommit = true
|
||||
true
|
||||
} catch (e: Exception) {
|
||||
Logger.error("Error adding log entries: ${e.message}" as Any)
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
override fun UpdateByIndex(index: Int, data: Log): Boolean {
|
||||
throw Exception("Update not supported")
|
||||
}
|
||||
|
||||
override fun Resort(): Boolean {
|
||||
throw Exception("Resorting Logs is not supported")
|
||||
// try {
|
||||
// val statement = connection.createStatement()
|
||||
// val tempdb_name = "temp_${super.dbName}"
|
||||
// // use a temporary table to reorder the index
|
||||
// statement?.executeUpdate("CREATE TABLE IF NOT EXISTS $tempdb_name LIKE ${super.dbName}")
|
||||
// statement?.executeUpdate("TRUNCATE TABLE $tempdb_name")
|
||||
// statement?.executeUpdate(
|
||||
// "INSERT INTO $tempdb_name (datenya, timenya, machine, description) " +
|
||||
// "SELECT datenya, timenya, machine, description FROM ${super.dbName} " +
|
||||
// "ORDER BY datenya , timenya , machine "
|
||||
// )
|
||||
// statement?.executeUpdate("TRUNCATE TABLE ${super.dbName}")
|
||||
// statement?.executeUpdate(
|
||||
// "INSERT INTO ${super.dbName} (datenya, timenya, machine, description) " +
|
||||
// "SELECT datenya, timenya, machine, description FROM $tempdb_name"
|
||||
// )
|
||||
// statement?.executeUpdate("DROP TABLE $tempdb_name")
|
||||
// Logger.info("${super.dbName} table resorted by datenya, timenya, machine" as Any)
|
||||
// // reload the local list
|
||||
// Get()
|
||||
// return true
|
||||
// } catch (e: Exception) {
|
||||
// Logger.error("Error resorting ${super.dbName} table by datenya, timenya, machine: ${e.message}" as Any)
|
||||
// }
|
||||
// return false
|
||||
}
|
||||
|
||||
override fun Import_XLSX(workbook: XSSFWorkbook): Boolean {
|
||||
throw Exception("Importing Logs from XLSX is not supported")
|
||||
}
|
||||
|
||||
/**
|
||||
* Exports the log table to an XLSX workbook for a specific date and optional filter.
|
||||
* @param logDate The date string in format "dd/MM/yyyy".
|
||||
* @param logFilter The filter string for the description or machine. If empty, exports all logs for the date.
|
||||
* @return An XSSFWorkbook containing the filtered log data, or null if an error occurred.
|
||||
*/
|
||||
fun Export_Log_XLSX(logDate: String, logFilter: String): XSSFWorkbook? {
|
||||
try {
|
||||
val valid_date : Date? = when{
|
||||
dateformat1.matches(logDate) -> {
|
||||
Date.valueOf(LocalDate.parse(logDate, DateTimeFormatter.ofPattern("dd/MM/yyyy")))
|
||||
}
|
||||
dateformat2.matches(logDate) -> {
|
||||
Date.valueOf(LocalDate.parse(logDate, DateTimeFormatter.ofPattern("dd-MM-yyyy")))
|
||||
}
|
||||
dateformat3.matches(logDate) -> {
|
||||
Date.valueOf(LocalDate.parse(logDate, DateTimeFormatter.ofPattern("yyyy/MM/dd")))
|
||||
}
|
||||
dateformat4.matches(logDate) -> {
|
||||
Date.valueOf(LocalDate.parse(logDate, DateTimeFormatter.ofPattern("yyyy-MM-dd")))
|
||||
}
|
||||
else -> null
|
||||
}
|
||||
|
||||
if (valid_date!=null){
|
||||
// use coalescing for different datenya formats
|
||||
val statement = if (logFilter.isEmpty()){
|
||||
connection.prepareStatement("SELECT * FROM ${super.dbName} WHERE COALESCE(STR_TO_DATE(datenya,'%d/%m/%Y'), STR_TO_DATE(datenya,'%d-%m-%Y'), STR_TO_DATE(datenya,'%Y/%m/%d'), STR_TO_DATE(datenya,'%Y-%m-%d')) = ?")
|
||||
} else {
|
||||
connection.prepareStatement("SELECT * FROM ${super.dbName} WHERE COALESCE(STR_TO_DATE(datenya,'%d/%m/%Y'), STR_TO_DATE(datenya,'%d-%m-%Y'), STR_TO_DATE(datenya,'%Y/%m/%d'), STR_TO_DATE(datenya,'%Y-%m-%d')) = ? AND description LIKE ?")
|
||||
}
|
||||
statement?.setDate(1, valid_date)
|
||||
if (logFilter.isNotEmpty()){
|
||||
statement?.setString(2, "%$logFilter%")
|
||||
}
|
||||
val resultSet = statement?.executeQuery()
|
||||
|
||||
val workbook = XSSFWorkbook()
|
||||
val sheet = workbook.createSheet("Log")
|
||||
val headerRow = sheet.createRow(0)
|
||||
val headers = arrayOf("Index", "datenya", "timenya", "machine", "description")
|
||||
for ((colIndex, header) in headers.withIndex()) {
|
||||
val cell = headerRow.createCell(colIndex)
|
||||
cell.setCellValue(header)
|
||||
}
|
||||
var rowIndex = 1
|
||||
|
||||
while (resultSet?.next() == true) {
|
||||
val row = sheet.createRow(rowIndex++)
|
||||
row.createCell(0).setCellValue(resultSet.getString("index"))
|
||||
row.createCell(1).setCellValue(resultSet.getString("datenya"))
|
||||
row.createCell(2).setCellValue(resultSet.getString("timenya"))
|
||||
row.createCell(3).setCellValue(resultSet.getString("machine"))
|
||||
row.createCell(4).setCellValue(resultSet.getString("description"))
|
||||
}
|
||||
|
||||
for (i in headers.indices) {
|
||||
sheet.autoSizeColumn(i)
|
||||
}
|
||||
return workbook
|
||||
} else throw Exception("Invalid date")
|
||||
|
||||
} catch (e: Exception) {
|
||||
Logger.error { "Error exporting Log, Msg: ${e.message}" }
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
override fun Export_XLSX(): XSSFWorkbook? {
|
||||
try {
|
||||
val statement = connection.createStatement()
|
||||
val resultSet = statement?.executeQuery("SELECT * FROM ${super.dbName}")
|
||||
val workbook = XSSFWorkbook()
|
||||
val sheet = workbook.createSheet("Log")
|
||||
val headerRow = sheet.createRow(0)
|
||||
val headers = arrayOf("Index", "datenya", "timenya", "machine", "description")
|
||||
for ((colIndex, header) in headers.withIndex()) {
|
||||
val cell = headerRow.createCell(colIndex)
|
||||
cell.setCellValue(header)
|
||||
}
|
||||
var rowIndex = 1
|
||||
while (resultSet?.next() == true) {
|
||||
val row = sheet.createRow(rowIndex++)
|
||||
row.createCell(0).setCellValue(resultSet.getString("index"))
|
||||
row.createCell(1).setCellValue(resultSet.getString("datenya"))
|
||||
row.createCell(2).setCellValue(resultSet.getString("timenya"))
|
||||
row.createCell(3).setCellValue(resultSet.getString("machine"))
|
||||
row.createCell(4).setCellValue(resultSet.getString("description"))
|
||||
}
|
||||
for (i in headers.indices) {
|
||||
sheet.autoSizeColumn(i)
|
||||
}
|
||||
return workbook
|
||||
} catch (e: Exception) {
|
||||
Logger.error { "Error exporting Log, Msg: ${e.message}" }
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
||||
282
src/database/table/Table_Messagebank.kt
Normal file
282
src/database/table/Table_Messagebank.kt
Normal file
@@ -0,0 +1,282 @@
|
||||
package database.table
|
||||
|
||||
import database.data.Messagebank
|
||||
import database.dbFunctions
|
||||
import org.apache.poi.xssf.usermodel.XSSFWorkbook
|
||||
import org.tinylog.Logger
|
||||
import java.sql.Connection
|
||||
import java.util.function.Consumer
|
||||
|
||||
class Table_Messagebank(connection: Connection) : dbFunctions<Messagebank>("messagebank", connection, listOf("index", "Description", "Language", "ANN_ID", "Voice_Type", "Message_Detail", "Message_TAGS")) {
|
||||
override fun Create() {
|
||||
val tabledefinition = "CREATE TABLE IF NOT EXISTS ${super.dbName} (" +
|
||||
"`index` INT AUTO_INCREMENT PRIMARY KEY," +
|
||||
"Description VARCHAR(512) NOT NULL," + // Description of the message
|
||||
"Language VARCHAR(45) NOT NULL," + // Language of the message
|
||||
"ANN_ID INT NOT NULL," + // ANN ID of the message
|
||||
"Voice_Type VARCHAR(45) NOT NULL," + // Voice type of the message
|
||||
"Message_Detail VARCHAR(1024) NOT NULL," + // Full message text
|
||||
"Message_TAGS VARCHAR(1024)" + // Comma-separated tags for the message
|
||||
")"
|
||||
super.Create(tabledefinition)
|
||||
}
|
||||
|
||||
override fun Get(cbOK: Consumer<Unit>?, cbFail: Consumer<String>?) {
|
||||
List.clear()
|
||||
try {
|
||||
val statement = connection.createStatement()
|
||||
val resultSet = statement?.executeQuery("SELECT * FROM ${super.dbName}")
|
||||
while (resultSet?.next() == true) {
|
||||
val messagebank = Messagebank(
|
||||
resultSet.getLong("index").toUInt(),
|
||||
resultSet.getString("Description"),
|
||||
resultSet.getString("Language"),
|
||||
resultSet.getInt("ANN_ID").toUInt(),
|
||||
resultSet.getString("Voice_Type"),
|
||||
resultSet.getString("Message_Detail"),
|
||||
resultSet.getString("Message_TAGS")
|
||||
)
|
||||
|
||||
List.add(messagebank)
|
||||
}
|
||||
cbOK?.accept(Unit)
|
||||
} catch (e: Exception) {
|
||||
Logger.error("Error fetching ${super.dbName} : ${e.message}" as Any)
|
||||
cbFail?.accept("Error fetching ${super.dbName} : ${e.message}")
|
||||
}
|
||||
}
|
||||
|
||||
override fun Add(data: Messagebank): Boolean {
|
||||
try {
|
||||
val statement =
|
||||
connection.prepareStatement("INSERT INTO ${super.dbName} (Description, Language, ANN_ID, Voice_Type, Message_Detail, Message_TAGS) VALUES (?, ?, ?, ?, ?, ?)")
|
||||
statement?.setString(1, data.Description)
|
||||
statement?.setString(2, data.Language)
|
||||
statement?.setInt(3, data.ANN_ID.toInt())
|
||||
statement?.setString(4, data.Voice_Type)
|
||||
statement?.setString(5, data.Message_Detail)
|
||||
statement?.setString(6, data.Message_TAGS)
|
||||
val rowsAffected = statement?.executeUpdate()
|
||||
if (rowsAffected != null && rowsAffected > 0) {
|
||||
Logger.info("Messagebank added: ${data.Description}" as Any)
|
||||
return true
|
||||
} else {
|
||||
Logger.warn("No messagebank entry added for: ${data.Description}" as Any)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Logger.error("Error adding messagebank entry: ${e.message}" as Any)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
override fun AddAll(data: ArrayList<Messagebank>): Boolean {
|
||||
try {
|
||||
connection.autoCommit = false
|
||||
val sql =
|
||||
"INSERT INTO ${super.dbName} (Description, Language, ANN_ID, Voice_Type, Message_Detail, Message_TAGS) VALUES (?, ?, ?, ?, ?, ?)"
|
||||
val statement = connection.prepareStatement(sql)
|
||||
for (mb in data) {
|
||||
statement.setString(1, mb.Description)
|
||||
statement.setString(2, mb.Language)
|
||||
statement.setInt(3, mb.ANN_ID.toInt())
|
||||
statement.setString(4, mb.Voice_Type)
|
||||
statement.setString(5, mb.Message_Detail)
|
||||
statement.setString(6, mb.Message_TAGS)
|
||||
statement.addBatch()
|
||||
}
|
||||
statement.executeBatch()
|
||||
connection.commit()
|
||||
Logger.info("Bulk messagebank insert successful: ${data.size} entries" as Any)
|
||||
connection.autoCommit = true
|
||||
return true
|
||||
} catch (e: Exception) {
|
||||
Logger.error("Error adding messagebank entries: ${e.message}" as Any)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
override fun UpdateByIndex(index: Int, data: Messagebank): Boolean {
|
||||
try {
|
||||
val statement =
|
||||
connection.prepareStatement("UPDATE ${super.dbName} SET Description = ?, Language = ?, ANN_ID = ?, Voice_Type = ?, Message_Detail = ?, Message_TAGS = ? WHERE `index` = ?")
|
||||
statement?.setString(1, data.Description)
|
||||
statement?.setString(2, data.Language)
|
||||
statement?.setInt(3, data.ANN_ID.toInt())
|
||||
statement?.setString(4, data.Voice_Type)
|
||||
statement?.setString(5, data.Message_Detail)
|
||||
statement?.setString(6, data.Message_TAGS)
|
||||
statement?.setLong(7, index.toLong())
|
||||
val rowsAffected = statement?.executeUpdate()
|
||||
if (rowsAffected != null && rowsAffected > 0) {
|
||||
Logger.info("Messagebank updated at index $index: ${data.Description}" as Any)
|
||||
return true
|
||||
} else {
|
||||
Logger.warn("No messagebank entry updated at index $index for: ${data.Description}" as Any)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Logger.error("Error updating messagebank entry at index $index: ${e.message}" as Any)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
override fun Resort(): Boolean {
|
||||
try {
|
||||
val statement = connection.createStatement()
|
||||
val tempdb_name = "temp_${super.dbName}"
|
||||
// use a temporary table to reorder the index
|
||||
statement?.executeUpdate("CREATE TABLE IF NOT EXISTS $tempdb_name LIKE ${super.dbName}")
|
||||
statement?.executeUpdate("TRUNCATE TABLE $tempdb_name")
|
||||
statement?.executeUpdate("INSERT INTO $tempdb_name (Description, Language, ANN_ID, Voice_Type, Message_Detail, Message_TAGS) SELECT Description, Language, ANN_ID, Voice_Type, Message_Detail, Message_TAGS FROM ${super.dbName} ORDER BY ANN_ID ")
|
||||
statement?.executeUpdate("TRUNCATE TABLE ${super.dbName}")
|
||||
statement?.executeUpdate("INSERT INTO ${super.dbName} (Description, Language, ANN_ID, Voice_Type, Message_Detail, Message_TAGS) SELECT Description, Language, ANN_ID, Voice_Type, Message_Detail, Message_TAGS FROM $tempdb_name")
|
||||
statement?.executeUpdate("DROP TABLE $tempdb_name")
|
||||
Logger.info("${super.dbName} table resorted by Description" as Any)
|
||||
// reload the local list
|
||||
Get()
|
||||
return true
|
||||
} catch (e: Exception) {
|
||||
Logger.error("Error resorting ${super.dbName} table by Description: ${e.message}" as Any)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
override fun Import_XLSX(workbook: XSSFWorkbook): Boolean {
|
||||
try {
|
||||
// check if there is sheet named "Messagebank"
|
||||
val sheet =
|
||||
workbook.getSheet("Messagebank") ?: throw Exception("No sheet named 'Messagebank' found")
|
||||
// check if the sheet contains header named "Index", "Description", "Language", "ANN_ID", "Voice_Type", "Message_Detail", "Message_TAGS"
|
||||
val headerRow = sheet.getRow(0) ?: throw Exception("No header row found")
|
||||
val headers =
|
||||
arrayOf(
|
||||
"Index",
|
||||
"Description",
|
||||
"Language",
|
||||
"ANN_ID",
|
||||
"Voice_Type",
|
||||
"Message_Detail",
|
||||
"Message_TAGS"
|
||||
)
|
||||
for ((colIndex, header) in headers.withIndex()) {
|
||||
val cell = headerRow.getCell(colIndex) ?: throw Exception("Header '$header' not found")
|
||||
if (cell.stringCellValue != header) throw Exception("Header '$header' not found")
|
||||
}
|
||||
// clear existing messagebank
|
||||
Clear()
|
||||
// read each row and insert into database
|
||||
val _messagebankList = ArrayList<Messagebank>()
|
||||
for (rowIndex in 1..sheet.lastRowNum) {
|
||||
val row = sheet.getRow(rowIndex) ?: continue
|
||||
val description = row.getCell(1)?.stringCellValue ?: continue
|
||||
val language = row.getCell(2)?.stringCellValue ?: continue
|
||||
val annId = row.getCell(3)?.stringCellValue?.toUIntOrNull() ?: continue
|
||||
val voiceType = row.getCell(4)?.stringCellValue ?: continue
|
||||
val messageDetail = row.getCell(5)?.stringCellValue ?: continue
|
||||
val messageTags = row.getCell(6)?.stringCellValue ?: continue
|
||||
val messagebank =
|
||||
Messagebank(0u, description, language, annId, voiceType, messageDetail, messageTags)
|
||||
_messagebankList.add(messagebank)
|
||||
}
|
||||
return AddAll(_messagebankList)
|
||||
} catch (e: Exception) {
|
||||
Logger.error { "Error importing Messagebank, Msg: ${e.message}" }
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
override fun Export_XLSX(): XSSFWorkbook? {
|
||||
try {
|
||||
val statement = connection.createStatement()
|
||||
val resultSet = statement?.executeQuery("SELECT * FROM ${super.dbName}")
|
||||
val workbook = XSSFWorkbook()
|
||||
val sheet = workbook.createSheet("Messagebank")
|
||||
val headerRow = sheet.createRow(0)
|
||||
val headers =
|
||||
arrayOf(
|
||||
"Index",
|
||||
"Description",
|
||||
"Language",
|
||||
"ANN_ID",
|
||||
"Voice_Type",
|
||||
"Message_Detail",
|
||||
"Message_TAGS"
|
||||
)
|
||||
for ((colIndex, header) in headers.withIndex()) {
|
||||
val cell = headerRow.createCell(colIndex)
|
||||
cell.setCellValue(header)
|
||||
}
|
||||
var rowIndex = 1
|
||||
while (resultSet?.next() == true) {
|
||||
val row = sheet.createRow(rowIndex++)
|
||||
row.createCell(0).setCellValue(resultSet.getString("index"))
|
||||
row.createCell(1).setCellValue(resultSet.getString("Description"))
|
||||
row.createCell(2).setCellValue(resultSet.getString("Language"))
|
||||
row.createCell(3).setCellValue(resultSet.getString("ANN_ID"))
|
||||
row.createCell(4).setCellValue(resultSet.getString("Voice_Type"))
|
||||
row.createCell(5).setCellValue(resultSet.getString("Message_Detail"))
|
||||
row.createCell(6).setCellValue(resultSet.getString("Message_TAGS"))
|
||||
}
|
||||
for (i in headers.indices) {
|
||||
sheet.autoSizeColumn(i)
|
||||
}
|
||||
return workbook
|
||||
} catch (e: Exception) {
|
||||
Logger.error { "Error exporting Messagebank, Msg: ${e.message}" }
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all distinct message ID from messagebank
|
||||
* @return a list of distinct ANN_ID sorted numerically
|
||||
*/
|
||||
fun Get_MessageID_List(): List<UInt> {
|
||||
return List
|
||||
.distinctBy { it.ANN_ID }
|
||||
.map { it.ANN_ID }
|
||||
.sorted()
|
||||
}
|
||||
|
||||
// valid messagedetail is message_name [ann_id]
|
||||
// so we need regex to check if messagedetail matches that format
|
||||
private val messageDetailRegex = """^(.*?)\s*\[(\d+)]$""".toRegex()
|
||||
|
||||
/**
|
||||
* Check if a messagebank entry exists based on messagedetail and languages
|
||||
* @param messagedetail the messagedetail in format "message_name [ann_id]"
|
||||
* @param languages a comma or semicolon separated string of languages to check
|
||||
* @return true if the messagebank entry exists for all specified languages, false otherwise
|
||||
*/
|
||||
fun Messagebank_Exists(messagedetail: String, languages: String) : Boolean{
|
||||
val match = messageDetailRegex.find(messagedetail)
|
||||
val ll = languages.split(",",";").map { it.trim() }
|
||||
if (match != null){
|
||||
val msg = match.groupValues[1].trim()
|
||||
val annid = match.groupValues[2].toUIntOrNull() ?: return false // kalau bukan number, return false
|
||||
val ff = List.filter{ it.ANN_ID == annid && it.Description == msg }
|
||||
return ll.all{ lang -> ff.any{ it.Language.equals(lang, ignoreCase = true) } }
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* Find a messagebank entry based on messagedetail and language
|
||||
* @param messagedetail the messagedetail in format "message_name [ann_id]"
|
||||
* @param language the language to find
|
||||
* @return the messagebank entry if found, null otherwise
|
||||
*/
|
||||
fun Find_Messagebank(messagedetail: String, language: String) : Messagebank?{
|
||||
return try{
|
||||
val match = messageDetailRegex.find(messagedetail)
|
||||
if (match != null){
|
||||
val msg = match.groupValues[1].trim()
|
||||
val annid = match.groupValues[2].toUIntOrNull() ?: return null // kalau bukan number, return null
|
||||
List.firstOrNull{ it.ANN_ID == annid && it.Description == msg && it.Language.equals(language, ignoreCase = true) }
|
||||
} else {
|
||||
null
|
||||
}
|
||||
} catch (_: Exception){
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
156
src/database/table/Table_QueuePaging.kt
Normal file
156
src/database/table/Table_QueuePaging.kt
Normal file
@@ -0,0 +1,156 @@
|
||||
package database.table
|
||||
|
||||
import database.data.QueuePaging
|
||||
import database.dbFunctions
|
||||
import org.apache.poi.xssf.usermodel.XSSFWorkbook
|
||||
import org.tinylog.Logger
|
||||
import java.sql.Connection
|
||||
import java.util.function.Consumer
|
||||
|
||||
class Table_QueuePaging(connection : Connection) : dbFunctions<QueuePaging>("queue_paging", connection, listOf("index", "Date_Time", "Source", "Type", "Message", "BroadcastZones")) {
|
||||
override fun Create() {
|
||||
val tabledefinition ="CREATE TABLE IF NOT EXISTS ${super.dbName} (" +
|
||||
"`index` INT AUTO_INCREMENT PRIMARY KEY," +
|
||||
"Date_Time VARCHAR(45) NOT NULL," + // Date and time of the entry
|
||||
"Source VARCHAR(45) NOT NULL," + // Source of the entry
|
||||
"Type VARCHAR(45) NOT NULL," + // Type of the entry
|
||||
"Message VARCHAR(1024) NOT NULL," + // Message content
|
||||
"BroadcastZones VARCHAR(1024)" + // Comma-separated soundbank tags
|
||||
")"
|
||||
super.Create(tabledefinition)
|
||||
}
|
||||
|
||||
override fun Get(cbOK: Consumer<Unit>?, cbFail: Consumer<String>?) {
|
||||
List.clear()
|
||||
val queueList = ArrayList<QueuePaging>()
|
||||
try {
|
||||
val statement = connection.createStatement()
|
||||
val resultSet = statement?.executeQuery("SELECT * FROM ${super.dbName}")
|
||||
while (resultSet?.next() == true) {
|
||||
val queuePaging = QueuePaging(
|
||||
resultSet.getLong("index").toUInt(),
|
||||
resultSet.getString("Date_Time"),
|
||||
resultSet.getString("Source"),
|
||||
resultSet.getString("Type"),
|
||||
resultSet.getString("Message"),
|
||||
resultSet.getString("BroadcastZones"),
|
||||
)
|
||||
queueList.add(queuePaging)
|
||||
List.add(queuePaging)
|
||||
}
|
||||
cbOK?.accept(Unit)
|
||||
} catch (e: Exception) {
|
||||
Logger.error("Error fetching ${super.dbName} : ${e.message}" as Any)
|
||||
cbFail?.accept("Error fetching ${super.dbName} : ${e.message}" )
|
||||
}
|
||||
}
|
||||
|
||||
override fun Add(data: QueuePaging): Boolean {
|
||||
try {
|
||||
val statement = connection.prepareStatement(
|
||||
"INSERT INTO ${super.dbName} (Date_Time, Source, Type, Message, BroadcastZones) VALUES (?, ?, ?, ?, ?)"
|
||||
)
|
||||
statement?.setString(1, data.Date_Time)
|
||||
statement?.setString(2, data.Source)
|
||||
statement?.setString(3, data.Type)
|
||||
statement?.setString(4, data.Message)
|
||||
statement?.setString(5, data.BroadcastZones)
|
||||
val rowsAffected = statement?.executeUpdate()
|
||||
if (rowsAffected != null && rowsAffected > 0) {
|
||||
Logger.info("QueuePaging added: ${data.Message}" as Any)
|
||||
return true
|
||||
} else {
|
||||
Logger.warn("No QueuePaging entry added for: ${data.Message}" as Any)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Logger.error("Error adding QueuePaging entry: ${e.message}" as Any)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
override fun AddAll(data: ArrayList<QueuePaging>): Boolean {
|
||||
return try {
|
||||
connection.autoCommit = false
|
||||
val sql =
|
||||
"INSERT INTO ${super.dbName} (Date_Time, Source, Type, Message, BroadcastZones) VALUES (?, ?, ?, ?, ?)"
|
||||
val statement = connection.prepareStatement(sql)
|
||||
for (qp in data) {
|
||||
statement.setString(1, qp.Date_Time)
|
||||
statement.setString(2, qp.Source)
|
||||
statement.setString(3, qp.Type)
|
||||
statement.setString(4, qp.Message)
|
||||
statement.setString(5, qp.BroadcastZones)
|
||||
statement.addBatch()
|
||||
}
|
||||
statement.executeBatch()
|
||||
connection.commit()
|
||||
Logger.info("Bulk QueuePaging insert successful: ${data.size} entries" as Any)
|
||||
connection.autoCommit = true
|
||||
true
|
||||
} catch (e: Exception) {
|
||||
Logger.error("Error adding QueuePaging entries: ${e.message}" as Any)
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
override fun UpdateByIndex(index: Int, data: QueuePaging): Boolean {
|
||||
throw Exception("Update not supported")
|
||||
}
|
||||
|
||||
override fun Resort(): Boolean {
|
||||
try {
|
||||
val statement = connection.createStatement()
|
||||
val tempdb_name = "temp_${super.dbName}"
|
||||
// use a temporary table to reorder the index
|
||||
statement?.executeUpdate("CREATE TABLE IF NOT EXISTS $tempdb_name LIKE ${super.dbName}")
|
||||
statement?.executeUpdate("TRUNCATE TABLE $tempdb_name")
|
||||
statement?.executeUpdate("INSERT INTO $tempdb_name (Date_Time, Source, Type, Message, BroadcastZones) SELECT Date_Time, Source, Type, Message, BroadcastZones FROM ${super.dbName} ")
|
||||
statement?.executeUpdate("TRUNCATE TABLE ${super.dbName}")
|
||||
statement?.executeUpdate("INSERT INTO ${super.dbName} (Date_Time, Source, Type, Message, BroadcastZones) SELECT Date_Time, Source, Type, Message, BroadcastZones FROM $tempdb_name")
|
||||
statement?.executeUpdate("DROP TABLE $tempdb_name")
|
||||
Logger.info("${super.dbName} table resorted by index" as Any)
|
||||
// reload the local list
|
||||
Get()
|
||||
return true
|
||||
} catch (e: Exception) {
|
||||
Logger.error("Error resorting ${super.dbName} table by index: ${e.message}" as Any)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
override fun Import_XLSX(workbook: XSSFWorkbook): Boolean {
|
||||
throw Exception("Importing QueuePaging from XLSX is not supported")
|
||||
}
|
||||
|
||||
override fun Export_XLSX(): XSSFWorkbook? {
|
||||
try {
|
||||
val statement = connection.createStatement()
|
||||
val resultSet = statement?.executeQuery("SELECT * FROM ${super.dbName}")
|
||||
val workbook = XSSFWorkbook()
|
||||
val sheet = workbook.createSheet("QueuePaging")
|
||||
val headerRow = sheet.createRow(0)
|
||||
val headers = arrayOf("Index", "Date_Time", "Source", "Type", "Message", "BroadcastZones")
|
||||
for ((colIndex, header) in headers.withIndex()) {
|
||||
val cell = headerRow.createCell(colIndex)
|
||||
cell.setCellValue(header)
|
||||
}
|
||||
var rowIndex = 1
|
||||
while (resultSet?.next() == true) {
|
||||
val row = sheet.createRow(rowIndex++)
|
||||
row.createCell(0).setCellValue(resultSet.getString("index"))
|
||||
row.createCell(1).setCellValue(resultSet.getString("Date_Time"))
|
||||
row.createCell(2).setCellValue(resultSet.getString("Source"))
|
||||
row.createCell(3).setCellValue(resultSet.getString("Type"))
|
||||
row.createCell(4).setCellValue(resultSet.getString("Message"))
|
||||
row.createCell(5).setCellValue(resultSet.getString("BroadcastZones"))
|
||||
}
|
||||
for (i in headers.indices) {
|
||||
sheet.autoSizeColumn(i)
|
||||
}
|
||||
return workbook
|
||||
} catch (e: Exception) {
|
||||
Logger.error { "Error exporting QueuePaging, Msg: ${e.message}" }
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
||||
181
src/database/table/Table_QueueSoundbank.kt
Normal file
181
src/database/table/Table_QueueSoundbank.kt
Normal file
@@ -0,0 +1,181 @@
|
||||
package database.table
|
||||
|
||||
import database.data.QueueTable
|
||||
import database.dbFunctions
|
||||
import org.apache.poi.xssf.usermodel.XSSFWorkbook
|
||||
import org.tinylog.Logger
|
||||
import java.sql.Connection
|
||||
import java.util.function.Consumer
|
||||
|
||||
class Table_QueueSoundbank(connection: Connection) : dbFunctions<QueueTable>("queue_table", connection, listOf("index", "Date_Time", "Source", "Type", "Message", "SB_TAGS", "BroadcastZones", "Repeat", "Language")) {
|
||||
override fun Create() {
|
||||
val tabledefinition = "CREATE TABLE IF NOT EXISTS ${super.dbName} (" +
|
||||
"`index` INT AUTO_INCREMENT PRIMARY KEY," +
|
||||
"Date_Time VARCHAR(45) NOT NULL," + // Date and time of the entry
|
||||
"Source VARCHAR(45) NOT NULL," + // Source of the entry
|
||||
"Type VARCHAR(45) NOT NULL," + // Type of the entry
|
||||
"Message VARCHAR(1024) NOT NULL," + // Message content
|
||||
"SB_TAGS VARCHAR(1024)," + // Comma-separated soundbank tags
|
||||
"BroadcastZones VARCHAR(1024) NOT NULL," + // Comma-separated broadcast zones
|
||||
"`Repeat` INT NOT NULL," + // Number of repeats
|
||||
"Language VARCHAR(100) NOT NULL" + // Language of the message
|
||||
")"
|
||||
super.Create(tabledefinition)
|
||||
}
|
||||
|
||||
override fun Get(cbOK: Consumer<Unit>?, cbFail: Consumer<String>?) {
|
||||
List.clear()
|
||||
val queueList = ArrayList<QueueTable>()
|
||||
try {
|
||||
val statement = connection.createStatement()
|
||||
val resultSet = statement?.executeQuery("SELECT * FROM ${super.dbName}")
|
||||
while (resultSet?.next() == true) {
|
||||
val queueTable = QueueTable(
|
||||
resultSet.getLong("index").toUInt(),
|
||||
resultSet.getString("Date_Time"),
|
||||
resultSet.getString("Source"),
|
||||
resultSet.getString("Type"),
|
||||
resultSet.getString("Message"),
|
||||
resultSet.getString("SB_TAGS"),
|
||||
resultSet.getString("BroadcastZones"),
|
||||
resultSet.getInt("Repeat").toUInt(),
|
||||
resultSet.getString("Language")
|
||||
)
|
||||
queueList.add(queueTable)
|
||||
List.add(queueTable)
|
||||
}
|
||||
cbOK?.accept(Unit)
|
||||
} catch (e: Exception) {
|
||||
Logger.error("Error fetching ${super.dbName} : ${e.message}" as Any)
|
||||
cbFail?.accept("Error fetching ${super.dbName} : ${e.message}" )
|
||||
}
|
||||
}
|
||||
|
||||
override fun Add(data: QueueTable): Boolean {
|
||||
try {
|
||||
val statement = connection.prepareStatement(
|
||||
"INSERT INTO ${super.dbName} (`Date_Time`, `Source`, `Type`, `Message`, `SB_TAGS`, `BroadcastZones`, `Repeat`, `Language`) VALUES (?, ?, ?, ?, ?, ?, ?, ?)"
|
||||
)
|
||||
statement?.setString(1, data.Date_Time)
|
||||
statement?.setString(2, data.Source)
|
||||
statement?.setString(3, data.Type)
|
||||
statement?.setString(4, data.Message)
|
||||
statement?.setString(5, data.SB_TAGS)
|
||||
statement?.setString(6, data.BroadcastZones)
|
||||
statement?.setInt(7, data.Repeat.toInt())
|
||||
statement?.setString(8, data.Language)
|
||||
val rowsAffected = statement?.executeUpdate()
|
||||
if (rowsAffected != null && rowsAffected > 0) {
|
||||
Logger.info("QueueTable added Source=${data.Source} Type=${data.Type} Message=${data.Message}, Languages=${data.Language} Variables=${data.SB_TAGS}, BroadcastZones=${data.BroadcastZones}" as Any)
|
||||
return true
|
||||
} else {
|
||||
Logger.warn("No QueueTable entry added for: ${data.Message}" as Any)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Logger.error("Error adding QueueTable entry: ${e.message}" as Any)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
override fun AddAll(data: ArrayList<QueueTable>): Boolean {
|
||||
try {
|
||||
connection.autoCommit = false
|
||||
val sql =
|
||||
"INSERT INTO ${super.dbName} (`Date_Time`, `Source`, `Type`, `Message`, `SB_TAGS`, `BroadcastZones`, `Repeat`, `Language`) VALUES (?, ?, ?, ?, ?, ?, ?, ?)"
|
||||
val statement = connection.prepareStatement(sql)
|
||||
for (qt in data) {
|
||||
statement.setString(1, qt.Date_Time)
|
||||
statement.setString(2, qt.Source)
|
||||
statement.setString(3, qt.Type)
|
||||
statement.setString(4, qt.Message)
|
||||
statement.setString(5, qt.SB_TAGS)
|
||||
statement.setString(6, qt.BroadcastZones)
|
||||
statement.setInt(7, qt.Repeat.toInt())
|
||||
statement.setString(8, qt.Language)
|
||||
statement.addBatch()
|
||||
}
|
||||
statement.executeBatch()
|
||||
connection.commit()
|
||||
Logger.info("Bulk QueueTable insert successful: ${data.size} entries" as Any)
|
||||
connection.autoCommit = true
|
||||
return true
|
||||
} catch (e: Exception) {
|
||||
Logger.error("Error adding QueueTable entries: ${e.message}" as Any)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
override fun UpdateByIndex(index: Int, data: QueueTable): Boolean {
|
||||
throw Exception("Update not supported")
|
||||
}
|
||||
|
||||
override fun Resort(): Boolean {
|
||||
try {
|
||||
val statement = connection.createStatement()
|
||||
val tempdb_name = "temp_${super.dbName}"
|
||||
// use a temporary table to reorder the index
|
||||
statement?.executeUpdate("CREATE TABLE IF NOT EXISTS $tempdb_name LIKE ${super.dbName}")
|
||||
statement?.executeUpdate("TRUNCATE TABLE $tempdb_name")
|
||||
statement?.executeUpdate("INSERT INTO $tempdb_name (Date_Time, Source, Type, Message, SB_TAGS, BroadcastZones, `Repeat`, Language) SELECT Date_Time, Source, Type, Message, SB_TAGS, BroadcastZones, `Repeat`, Language FROM ${super.dbName} ORDER BY `index` ")
|
||||
statement?.executeUpdate("TRUNCATE TABLE ${super.dbName}")
|
||||
statement?.executeUpdate("INSERT INTO ${super.dbName} (Date_Time, Source, Type, Message, SB_TAGS, BroadcastZones, `Repeat`, Language) SELECT Date_Time, Source, Type, Message, SB_TAGS, BroadcastZones, `Repeat`, Language FROM $tempdb_name")
|
||||
statement?.executeUpdate("DROP TABLE $tempdb_name")
|
||||
Logger.info("${super.dbName} table resorted by index" as Any)
|
||||
// reload the local list
|
||||
Get()
|
||||
return true
|
||||
} catch (e: Exception) {
|
||||
Logger.error("Error resorting ${super.dbName} table by index: ${e.message}" as Any)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
override fun Import_XLSX(workbook: XSSFWorkbook): Boolean {
|
||||
throw Exception("Import XLSX not supported for QueueTable")
|
||||
}
|
||||
|
||||
override fun Export_XLSX(): XSSFWorkbook? {
|
||||
try {
|
||||
val statement = connection.createStatement()
|
||||
val resultSet = statement?.executeQuery("SELECT * FROM ${super.dbName}")
|
||||
val workbook = XSSFWorkbook()
|
||||
val sheet = workbook.createSheet("QueueTable")
|
||||
val headerRow = sheet.createRow(0)
|
||||
val headers = arrayOf(
|
||||
"Index",
|
||||
"Date_Time",
|
||||
"Source",
|
||||
"Type",
|
||||
"Message",
|
||||
"SB_TAGS",
|
||||
"BroadcastZones",
|
||||
"Repeat",
|
||||
"Language"
|
||||
)
|
||||
for ((colIndex, header) in headers.withIndex()) {
|
||||
val cell = headerRow.createCell(colIndex)
|
||||
cell.setCellValue(header)
|
||||
}
|
||||
var rowIndex = 1
|
||||
while (resultSet?.next() == true) {
|
||||
val row = sheet.createRow(rowIndex++)
|
||||
row.createCell(0).setCellValue(resultSet.getString("index"))
|
||||
row.createCell(1).setCellValue(resultSet.getString("Date_Time"))
|
||||
row.createCell(2).setCellValue(resultSet.getString("Source"))
|
||||
row.createCell(3).setCellValue(resultSet.getString("Type"))
|
||||
row.createCell(4).setCellValue(resultSet.getString("Message"))
|
||||
row.createCell(5).setCellValue(resultSet.getString("SB_TAGS"))
|
||||
row.createCell(6).setCellValue(resultSet.getString("BroadcastZones"))
|
||||
row.createCell(7).setCellValue(resultSet.getString("Repeat"))
|
||||
row.createCell(8).setCellValue(resultSet.getString("Language"))
|
||||
}
|
||||
for (i in headers.indices) {
|
||||
sheet.autoSizeColumn(i)
|
||||
}
|
||||
return workbook
|
||||
} catch (e: Exception) {
|
||||
Logger.error { "Error exporting QueueTable, Msg: ${e.message}" }
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
||||
339
src/database/table/Table_Schedule.kt
Normal file
339
src/database/table/Table_Schedule.kt
Normal file
@@ -0,0 +1,339 @@
|
||||
package database.table
|
||||
|
||||
import codes.Somecodes.Companion.ValidLanguage
|
||||
import codes.Somecodes.Companion.ValidScheduleDay
|
||||
import codes.Somecodes.Companion.ValidScheduleTime
|
||||
import content.ScheduleDay
|
||||
import database.MariaDB.Companion.ValidTime
|
||||
import database.data.ScheduleBank
|
||||
import database.dbFunctions
|
||||
import org.apache.poi.xssf.usermodel.XSSFWorkbook
|
||||
import org.tinylog.Logger
|
||||
import java.sql.Connection
|
||||
import java.util.function.Consumer
|
||||
|
||||
import broadcastDB
|
||||
import codes.Somecodes
|
||||
import messageDB
|
||||
import java.time.LocalDate
|
||||
|
||||
class Table_Schedule(connection: Connection) : dbFunctions<ScheduleBank>("schedulebank", connection, listOf("index", "Description", "Day", "Time", "Soundpath", "Repeat", "Enable", "BroadcastZones", "Language")) {
|
||||
|
||||
/**
|
||||
* A list to hold today's schedule entries.
|
||||
*/
|
||||
val todaySchedule = ArrayList<ScheduleBank>()
|
||||
|
||||
/**
|
||||
* Update today's schedule
|
||||
*/
|
||||
fun UpdateTodaySchedule(){
|
||||
todaySchedule.clear()
|
||||
|
||||
fun Find_Enabled_Schedules() : List<ScheduleBank>{
|
||||
return List
|
||||
.asSequence()
|
||||
.filter{it.Enable} // yang enabled saja
|
||||
.filter{ValidScheduleTime(it.Time)} // yang timenya dalam format HH:MM
|
||||
.filter{ValidLanguage(it.Language)} // yang bahasanya valid
|
||||
.filter{broadcastDB.AllBroadcastZonesValid(it.BroadcastZones, false)} // yang broadcastzonesnya valid
|
||||
// Soundpath ini coding typo, aslinya Messagebank description
|
||||
.filter{messageDB.Messagebank_Exists(it.Soundpath, it.Language)}
|
||||
.toList()
|
||||
}
|
||||
val eligibleSchedule = Find_Enabled_Schedules()
|
||||
|
||||
val tempMap = mutableMapOf<String, ScheduleBank>()
|
||||
|
||||
// prioritas paling rendah adalah everyday
|
||||
eligibleSchedule.filter { it.Day == ScheduleDay.Everyday.day }.forEach { tempMap[it.Time] = it }
|
||||
|
||||
// lebih tinggi adalah weekly, akan replace everyday jika time nya sama
|
||||
val today_DOW = ScheduleDay.from_LocalDate_DOW(LocalDate.now().dayOfWeek)
|
||||
eligibleSchedule.filter { it.Day == today_DOW.day }.forEach { tempMap[it.Time] = it }
|
||||
|
||||
// paling tinggi adalah specific date, akan replace yang lain jika time nya sama
|
||||
val today = Somecodes.Today_to_DateString()
|
||||
eligibleSchedule.filter { it.Day == today }.forEach { tempMap[it.Time] = it }
|
||||
|
||||
// masukin ke todaySchedule yang sudah di sort by Time
|
||||
todaySchedule.addAll(tempMap.values.sortedBy { it.Time })
|
||||
|
||||
}
|
||||
|
||||
override fun Create() {
|
||||
val tabledefinition = "CREATE TABLE IF NOT EXISTS ${super.dbName} (" +
|
||||
"`index` INT AUTO_INCREMENT PRIMARY KEY," +
|
||||
"Description VARCHAR(128) NOT NULL," + // Description of the schedule
|
||||
"Day VARCHAR(255) NOT NULL," + // Day in format DD/MM/YYYY
|
||||
"Time VARCHAR(20) NOT NULL," + // Time in format HH:MM:SS
|
||||
"Soundpath VARCHAR(512) NOT NULL," + // Path to the sound file
|
||||
"`Repeat` TINYINT UNSIGNED NOT NULL," + // Repeat type (0=Once, 1=Daily, 2=Weekly, 3=Monthly, 4=Yearly)
|
||||
"Enable BOOLEAN NOT NULL," + // Enable or disable the schedule
|
||||
"BroadcastZones TEXT NOT NULL," + // Comma-separated list of broadcast zones
|
||||
"Language VARCHAR(45) NOT NULL" + // Language code (e.g., EN, FR)
|
||||
")"
|
||||
super.Create(tabledefinition)
|
||||
}
|
||||
|
||||
override fun Get(cbOK: Consumer<Unit>?, cbFail: Consumer<String>?) {
|
||||
List.clear()
|
||||
try {
|
||||
val statement = connection.createStatement()
|
||||
val resultSet = statement?.executeQuery("SELECT * FROM ${super.dbName}")
|
||||
while (resultSet?.next() == true) {
|
||||
val schedulebank = ScheduleBank(
|
||||
resultSet.getLong("index").toUInt(),
|
||||
resultSet.getString("Description"),
|
||||
resultSet.getString("Day"),
|
||||
resultSet.getString("Time"),
|
||||
resultSet.getString("Soundpath"),
|
||||
resultSet.getInt("Repeat").toUByte(),
|
||||
resultSet.getBoolean("Enable"),
|
||||
resultSet.getString("BroadcastZones"),
|
||||
resultSet.getString("Language")
|
||||
)
|
||||
List.add(schedulebank)
|
||||
}
|
||||
UpdateTodaySchedule()
|
||||
cbOK?.accept(Unit)
|
||||
} catch (e: Exception) {
|
||||
Logger.error("Error fetching ${super.dbName}: ${e.message}" as Any)
|
||||
cbFail?.accept("Error fetching ${super.dbName}: ${e.message}" )
|
||||
}
|
||||
}
|
||||
|
||||
override fun Add(data: ScheduleBank): Boolean {
|
||||
if (!ValidScheduleDay(data.Day)) {
|
||||
Logger.error("Error adding schedulebank entry: Invalid date format ${data.Day}" as Any)
|
||||
return false
|
||||
}
|
||||
if (!ValidTime(data.Time)) {
|
||||
Logger.error("Error adding schedulebank entry: Invalid time format ${data.Time}" as Any)
|
||||
return false
|
||||
}
|
||||
try {
|
||||
val statement =
|
||||
connection.prepareStatement("INSERT INTO ${super.dbName} (Description, Day, Time, Soundpath, `Repeat`, Enable, BroadcastZones, Language) VALUES (?, ?, ?, ?, ?, ?, ?, ?)")
|
||||
statement?.setString(1, data.Description)
|
||||
statement?.setString(2, data.Day)
|
||||
statement?.setString(3, data.Time)
|
||||
statement?.setString(4, data.Soundpath)
|
||||
statement?.setInt(5, data.Repeat.toInt())
|
||||
statement?.setBoolean(6, data.Enable)
|
||||
statement?.setString(7, data.BroadcastZones)
|
||||
statement?.setString(8, data.Language)
|
||||
val rowsAffected = statement?.executeUpdate()
|
||||
if (rowsAffected != null && rowsAffected > 0) {
|
||||
Logger.info("Schedulebank added: ${data.Description}" as Any)
|
||||
return true
|
||||
} else {
|
||||
Logger.warn("No schedulebank entry added for: ${data.Description}" as Any)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Logger.error("Error adding schedulebank entry: ${e.message}" as Any)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
override fun AddAll(data: ArrayList<ScheduleBank>): Boolean {
|
||||
try {
|
||||
connection.autoCommit = false
|
||||
val sql =
|
||||
"INSERT INTO ${super.dbName} (Description, Day, Time, Soundpath, `Repeat`, Enable, BroadcastZones, Language) VALUES (?, ?, ?, ?, ?, ?, ?, ?)"
|
||||
val statement = connection.prepareStatement(sql)
|
||||
for (sb in data) {
|
||||
if (!ValidScheduleDay(sb.Day) || !ValidTime(sb.Time)) {
|
||||
Logger.error("Invalid date or time format for schedulebank: ${sb.Description}" as Any)
|
||||
continue
|
||||
}
|
||||
statement.setString(1, sb.Description)
|
||||
statement.setString(2, sb.Day)
|
||||
statement.setString(3, sb.Time)
|
||||
statement.setString(4, sb.Soundpath)
|
||||
statement.setInt(5, sb.Repeat.toInt())
|
||||
statement.setBoolean(6, sb.Enable)
|
||||
statement.setString(7, sb.BroadcastZones)
|
||||
statement.setString(8, sb.Language)
|
||||
statement.addBatch()
|
||||
}
|
||||
statement.executeBatch()
|
||||
connection.commit()
|
||||
Logger.info("Bulk schedulebank insert successful: ${data.size} entries" as Any)
|
||||
connection.autoCommit = true
|
||||
return true
|
||||
} catch (e: Exception) {
|
||||
Logger.error("Error adding schedulebank entries: ${e.message}" as Any)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
|
||||
override fun UpdateByIndex(index: Int, data: ScheduleBank): Boolean {
|
||||
if (!ValidScheduleDay(data.Day)) {
|
||||
Logger.error("Error updating schedulebank entry: Invalid date format ${data.Day}" as Any)
|
||||
return false
|
||||
}
|
||||
if (!ValidTime(data.Time)) {
|
||||
Logger.error("Error updating schedulebank entry: Invalid time format ${data.Time}" as Any)
|
||||
return false
|
||||
}
|
||||
try {
|
||||
val statement =
|
||||
connection.prepareStatement("UPDATE ${super.dbName} SET Description = ?, Day = ?, Time = ?, Soundpath = ?, `Repeat` = ?, Enable = ?, BroadcastZones = ?, Language = ? WHERE `index` = ?")
|
||||
statement?.setString(1, data.Description)
|
||||
statement?.setString(2, data.Day)
|
||||
statement?.setString(3, data.Time)
|
||||
statement?.setString(4, data.Soundpath)
|
||||
statement?.setInt(5, data.Repeat.toInt())
|
||||
statement?.setBoolean(6, data.Enable)
|
||||
statement?.setString(7, data.BroadcastZones)
|
||||
statement?.setString(8, data.Language)
|
||||
statement?.setLong(9, index.toLong())
|
||||
val rowsAffected = statement?.executeUpdate()
|
||||
if (rowsAffected != null && rowsAffected > 0) {
|
||||
Logger.info("Schedulebank updated at index $index: ${data.Description}" as Any)
|
||||
return true
|
||||
} else {
|
||||
Logger.warn("No schedulebank entry updated at index $index for: ${data.Description}" as Any)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Logger.error("Error updating schedulebank entry at index $index: ${e.message}" as Any)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
override fun Resort(): Boolean {
|
||||
try {
|
||||
val statement = connection.createStatement()
|
||||
val tempdb_name = "temp_${super.dbName}"
|
||||
// use a temporary table to reorder the index
|
||||
statement?.executeUpdate("CREATE TABLE IF NOT EXISTS $tempdb_name LIKE ${super.dbName}")
|
||||
statement?.executeUpdate("TRUNCATE TABLE $tempdb_name")
|
||||
statement?.executeUpdate("INSERT INTO $tempdb_name (Description, Day, Time, Soundpath, `Repeat`, Enable, BroadcastZones, Language) SELECT Description, Day, Time, Soundpath, `Repeat`, Enable, BroadcastZones, Language FROM ${super.dbName} ORDER BY Day , Time ")
|
||||
statement?.executeUpdate("TRUNCATE TABLE ${super.dbName}")
|
||||
statement?.executeUpdate("INSERT INTO ${super.dbName} (Description, Day, Time, Soundpath, `Repeat`, Enable, BroadcastZones, Language) SELECT Description, Day, Time, Soundpath, `Repeat`, Enable, BroadcastZones, Language FROM $tempdb_name")
|
||||
statement?.executeUpdate("DROP TABLE $tempdb_name")
|
||||
Logger.info("${super.dbName} table resorted by Day and Time" as Any)
|
||||
// reload the local list
|
||||
Get()
|
||||
return true
|
||||
} catch (e: Exception) {
|
||||
Logger.error("Error resorting ${super.dbName} table by Day and Time: ${e.message}" as Any)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
override fun Import_XLSX(workbook: XSSFWorkbook): Boolean {
|
||||
try {
|
||||
val sheet =
|
||||
workbook.getSheet("Schedulebank") ?: throw Exception("No sheet named 'Schedulebank' found")
|
||||
val headerRow = sheet.getRow(0) ?: throw Exception("No header row found")
|
||||
val headers = arrayOf(
|
||||
"Index",
|
||||
"Description",
|
||||
"Day",
|
||||
"Time",
|
||||
"Soundpath",
|
||||
"Repeat",
|
||||
"Enable",
|
||||
"BroadcastZones",
|
||||
"Language"
|
||||
)
|
||||
for ((colIndex, header) in headers.withIndex()) {
|
||||
val cell = headerRow.getCell(colIndex) ?: throw Exception("Header '$header' not found")
|
||||
if (cell.stringCellValue != header) throw Exception("Header '$header' not found")
|
||||
}
|
||||
// clear existing schedulebank
|
||||
Clear()
|
||||
// read each row and insert into database
|
||||
val _schedulebankList = ArrayList<ScheduleBank>()
|
||||
//Logger.info{"Sheet last row num: ${sheet.lastRowNum}"}
|
||||
for (rowIndex in 1..sheet.lastRowNum) {
|
||||
val row = sheet.getRow(rowIndex) ?: continue
|
||||
//println(row)
|
||||
val description = row.getCell(1)?.stringCellValue ?: continue
|
||||
//println(description.toString())
|
||||
val day = row.getCell(2)?.stringCellValue ?: continue
|
||||
//println(day.toString())
|
||||
val time = row.getCell(3)?.stringCellValue ?: continue
|
||||
//println(time.toString())
|
||||
val soundpath = row.getCell(4)?.stringCellValue ?: continue
|
||||
//println(soundpath.toString())
|
||||
val repeat = row.getCell(5)?.stringCellValue?.toUByteOrNull() ?: continue
|
||||
// println(repeat.toString())
|
||||
//val enable = row.getCell(6)?.stringCellValue?.toBooleanStrictOrNull() ?: continue
|
||||
val enable = row.getCell(6)?.stringCellValue?.toBoolean() ?: continue
|
||||
//println(enable.toString())
|
||||
val broadcastZones = row.getCell(7)?.stringCellValue ?: continue
|
||||
//println(broadcastZones.toString())
|
||||
val language = row.getCell(8)?.stringCellValue ?: continue
|
||||
//println(language.toString())
|
||||
val schedulebank =
|
||||
ScheduleBank(
|
||||
0u,
|
||||
description,
|
||||
day,
|
||||
time,
|
||||
soundpath,
|
||||
repeat,
|
||||
enable,
|
||||
broadcastZones,
|
||||
language
|
||||
)
|
||||
Logger.info{"SchedulebankList added 1"}
|
||||
|
||||
_schedulebankList.add(schedulebank)
|
||||
}
|
||||
return AddAll(_schedulebankList)
|
||||
} catch (e: Exception) {
|
||||
Logger.error { "Error importing Schedulebank, Msg: ${e.message}" }
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
override fun Export_XLSX(): XSSFWorkbook? {
|
||||
try {
|
||||
val statement = connection.createStatement()
|
||||
val resultSet = statement?.executeQuery("SELECT * FROM ${super.dbName}")
|
||||
val workbook = XSSFWorkbook()
|
||||
val sheet = workbook.createSheet("Schedulebank")
|
||||
val headerRow = sheet.createRow(0)
|
||||
val headers = arrayOf(
|
||||
"Index",
|
||||
"Description",
|
||||
"Day",
|
||||
"Time",
|
||||
"Soundpath",
|
||||
"Repeat",
|
||||
"Enable",
|
||||
"BroadcastZones",
|
||||
"Language"
|
||||
)
|
||||
for ((colIndex, header) in headers.withIndex()) {
|
||||
val cell = headerRow.createCell(colIndex)
|
||||
cell.setCellValue(header)
|
||||
}
|
||||
var rowIndex = 1
|
||||
while (resultSet?.next() == true) {
|
||||
val row = sheet.createRow(rowIndex++)
|
||||
row.createCell(0).setCellValue(resultSet.getString("index"))
|
||||
row.createCell(1).setCellValue(resultSet.getString("Description"))
|
||||
row.createCell(2).setCellValue(resultSet.getString("Day"))
|
||||
row.createCell(3).setCellValue(resultSet.getString("Time"))
|
||||
row.createCell(4).setCellValue(resultSet.getString("Soundpath"))
|
||||
row.createCell(5).setCellValue(resultSet.getString("Repeat"))
|
||||
row.createCell(6).setCellValue(resultSet.getString("Enable"))
|
||||
row.createCell(7).setCellValue(resultSet.getString("BroadcastZones"))
|
||||
row.createCell(8).setCellValue(resultSet.getString("Language"))
|
||||
}
|
||||
for (i in headers.indices) {
|
||||
sheet.autoSizeColumn(i)
|
||||
}
|
||||
return workbook
|
||||
} catch (e: Exception) {
|
||||
Logger.error { "Error exporting Schedulebank, Msg: ${e.message}" }
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
||||
256
src/database/table/Table_SoundChannel.kt
Normal file
256
src/database/table/Table_SoundChannel.kt
Normal file
@@ -0,0 +1,256 @@
|
||||
package database.table
|
||||
|
||||
import database.data.SoundChannel
|
||||
import database.dbFunctions
|
||||
import max_channel
|
||||
import org.apache.poi.xssf.usermodel.XSSFWorkbook
|
||||
import org.tinylog.Logger
|
||||
import java.sql.Connection
|
||||
import java.util.function.Consumer
|
||||
|
||||
class Table_SoundChannel(connection: Connection) : dbFunctions<SoundChannel>("soundchannel", connection, listOf("index", "channel", "ip")) {
|
||||
override fun Create() {
|
||||
val tableDefinition = "CREATE TABLE IF NOT EXISTS ${super.dbName} (" +
|
||||
"`index` INT AUTO_INCREMENT PRIMARY KEY," +
|
||||
"channel VARCHAR(45) NOT NULL," + // Channel 01 to Channel 64
|
||||
"ip VARCHAR(45) NOT NULL" + // IP address or empty string
|
||||
")"
|
||||
|
||||
super.Create(tableDefinition)
|
||||
|
||||
// Check if table is empty, if so, populate with 64 channels
|
||||
try {
|
||||
val statement = connection.createStatement()
|
||||
val countResult = statement?.executeQuery("SELECT COUNT(*) AS count FROM ${super.dbName}")
|
||||
if (countResult?.next() == true) {
|
||||
val count = countResult.getInt("count")
|
||||
if (count < max_channel) {
|
||||
Logger.info("SoundChannel table is empty, populating with default channels" as Any)
|
||||
Clear()
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Logger.error("Error creating SoundChannel table: ${e.message}" as Any)
|
||||
}
|
||||
}
|
||||
|
||||
override fun Get(cbOK: Consumer<Unit>?, cbFail: Consumer<String>?) {
|
||||
List.clear()
|
||||
try {
|
||||
val statement = connection.createStatement()
|
||||
val resultSet = statement?.executeQuery("SELECT * FROM ${super.dbName} ORDER BY `index` ")
|
||||
while (resultSet?.next() == true) {
|
||||
val channel = SoundChannel(
|
||||
resultSet.getLong("index").toUInt(),
|
||||
resultSet.getString("channel"),
|
||||
resultSet.getString("ip")
|
||||
)
|
||||
List.add(channel)
|
||||
}
|
||||
cbOK?.accept(Unit)
|
||||
} catch (e: Exception) {
|
||||
Logger.error("Error fetching sound channels: ${e.message}" as Any)
|
||||
cbFail?.accept("Error fetching sound channels: ${e.message}" )
|
||||
}
|
||||
}
|
||||
|
||||
override fun Add(data: SoundChannel): Boolean {
|
||||
try {
|
||||
val statement = connection.prepareStatement("UPDATE ${super.dbName} SET ip = ? WHERE channel = ?")
|
||||
statement?.setString(1, data.ip)
|
||||
statement?.setString(2, data.channel)
|
||||
val rowsAffected = statement?.executeUpdate()
|
||||
if (rowsAffected != null && rowsAffected > 0) {
|
||||
Logger.info("SoundChannel updated: ${data.channel} -> ${data.ip}" as Any)
|
||||
return true
|
||||
} else {
|
||||
Logger.warn("No SoundChannel entry updated for: ${data.channel} -> ${data.ip}" as Any)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Logger.error("Error updating SoundChannel entry: ${e.message}" as Any)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
override fun AddAll(data: ArrayList<SoundChannel>): Boolean {
|
||||
return try {
|
||||
connection.autoCommit = false
|
||||
val sql = "UPDATE ${super.dbName} SET ip = ? WHERE channel = ?"
|
||||
val statement = connection.prepareStatement(sql)
|
||||
for (sc in data) {
|
||||
statement.setString(1, sc.ip)
|
||||
statement.setString(2, sc.channel)
|
||||
statement.addBatch()
|
||||
}
|
||||
statement.executeBatch()
|
||||
connection.commit()
|
||||
Logger.info("Bulk SoundChannel update successful: ${data.size} entries" as Any)
|
||||
connection.autoCommit = true
|
||||
true
|
||||
} catch (e: Exception) {
|
||||
Logger.error("Error updating SoundChannel entries: ${e.message}" as Any)
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
override fun UpdateByIndex(index: Int, data: SoundChannel): Boolean {
|
||||
try {
|
||||
val statement =
|
||||
connection.prepareStatement("UPDATE ${super.dbName} SET channel = ?, ip = ? WHERE `index` = ?")
|
||||
statement?.setString(1, data.channel)
|
||||
statement?.setString(2, data.ip)
|
||||
statement?.setLong(3, index.toLong())
|
||||
val rowsAffected = statement?.executeUpdate()
|
||||
if (rowsAffected != null && rowsAffected > 0) {
|
||||
Logger.info("SoundChannel updated at index $index: ${data.channel} -> ${data.ip}" as Any)
|
||||
return true
|
||||
} else {
|
||||
Logger.warn("No Sound Channel entry updated at index $index for: ${data.channel} -> ${data.ip}" as Any)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Logger.error("Error updating SoundChannel entry at index $index: ${e.message}" as Any)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
override fun Resort(): Boolean {
|
||||
try {
|
||||
val statement = connection.createStatement()
|
||||
val tempdb_name = "temp_${super.dbName}"
|
||||
// use a temporary table to reorder the index
|
||||
statement?.executeUpdate("CREATE TABLE IF NOT EXISTS $tempdb_name LIKE ${super.dbName}")
|
||||
statement?.executeUpdate("TRUNCATE TABLE $tempdb_name")
|
||||
statement?.executeUpdate("INSERT INTO $tempdb_name (channel, ip) SELECT channel, ip FROM ${super.dbName} ORDER BY `index` ")
|
||||
statement?.executeUpdate("TRUNCATE TABLE ${super.dbName}")
|
||||
statement?.executeUpdate("INSERT INTO ${super.dbName} (channel, ip) SELECT channel, ip FROM $tempdb_name")
|
||||
statement?.executeUpdate("DROP TABLE $tempdb_name")
|
||||
Logger.info("${super.dbName} table resorted by index" as Any)
|
||||
// reload the local list
|
||||
Get()
|
||||
return true
|
||||
} catch (e: Exception) {
|
||||
Logger.error("Error resorting ${super.dbName} table by index: ${e.message}" as Any)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
override fun Clear(): Boolean {
|
||||
try {
|
||||
val statement = connection.createStatement()
|
||||
// use TRUNCATE to reset auto increment index
|
||||
statement?.executeUpdate("TRUNCATE TABLE ${super.dbName}")
|
||||
Logger.info("${super.dbName} table cleared" as Any)
|
||||
List.clear()
|
||||
// create new rows from 1 to 64 with description "Channel 1" to "Channel 64" and empty ip
|
||||
for (i in 1..max_channel) {
|
||||
val channel = String.format("Channel %d", i)
|
||||
val insertStatement =
|
||||
connection.prepareStatement("INSERT INTO ${super.dbName} (channel, ip) VALUES (?, ?)")
|
||||
insertStatement?.setString(1, channel)
|
||||
insertStatement?.setString(2, "")
|
||||
insertStatement?.executeUpdate()
|
||||
}
|
||||
return true
|
||||
} catch (e: Exception) {
|
||||
Logger.error("Error clearing soundchannel table: ${e.message}" as Any)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
override fun Import_XLSX(workbook: XSSFWorkbook): Boolean {
|
||||
try {
|
||||
val sheet =
|
||||
workbook.getSheet("SoundChannel") ?: throw Exception("No sheet named 'SoundChannel' found")
|
||||
val headerRow = sheet.getRow(0) ?: throw Exception("No header row found")
|
||||
val headers = arrayOf("Index", "channel", "ip")
|
||||
for ((colIndex, header) in headers.withIndex()) {
|
||||
val cell = headerRow.getCell(colIndex) ?: throw Exception("Header '$header' not found")
|
||||
if (cell.stringCellValue != header) throw Exception("Header '$header' not found")
|
||||
}
|
||||
// clear existing soundchannel
|
||||
Clear()
|
||||
// read each row and insert into database
|
||||
val _soundChannelList = ArrayList<SoundChannel>()
|
||||
for (rowIndex in 1..sheet.lastRowNum) {
|
||||
val row = sheet.getRow(rowIndex) ?: continue
|
||||
val channel = row.getCell(1)?.stringCellValue ?: continue
|
||||
val ip = row.getCell(2)?.stringCellValue ?: continue
|
||||
val soundChannel = SoundChannel(0u, channel, ip)
|
||||
_soundChannelList.add(soundChannel)
|
||||
}
|
||||
// Bulk update IPs for channels
|
||||
var success = true
|
||||
for (sc in _soundChannelList) {
|
||||
if (!Add(sc)) {
|
||||
success = false
|
||||
}
|
||||
}
|
||||
return success
|
||||
} catch (e: Exception) {
|
||||
Logger.error { "Error importing SoundChannel, Msg: ${e.message}" }
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
override fun Export_XLSX(): XSSFWorkbook? {
|
||||
try {
|
||||
val statement = connection.createStatement()
|
||||
val resultSet = statement?.executeQuery("SELECT * FROM ${super.dbName}")
|
||||
val workbook = XSSFWorkbook()
|
||||
val sheet = workbook.createSheet("SoundChannel")
|
||||
val headerRow = sheet.createRow(0)
|
||||
val headers = arrayOf("Index", "channel", "ip")
|
||||
for ((colIndex, header) in headers.withIndex()) {
|
||||
val cell = headerRow.createCell(colIndex)
|
||||
cell.setCellValue(header)
|
||||
}
|
||||
var rowIndex = 1
|
||||
while (resultSet?.next() == true) {
|
||||
val row = sheet.createRow(rowIndex++)
|
||||
row.createCell(0).setCellValue(resultSet.getString("index"))
|
||||
row.createCell(1).setCellValue(resultSet.getString("channel"))
|
||||
row.createCell(2).setCellValue(resultSet.getString("ip"))
|
||||
}
|
||||
for (i in headers.indices) {
|
||||
sheet.autoSizeColumn(i)
|
||||
}
|
||||
return workbook
|
||||
} catch (e: Exception) {
|
||||
Logger.error { "Error exporting SoundChannel, Msg: ${e.message}" }
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete entry by index, but only clear the IP field
|
||||
* @param index The index of the entry to delete
|
||||
* @return true if successful, false otherwise
|
||||
*/
|
||||
override fun DeleteByIndex(index: Int): Boolean {
|
||||
try {
|
||||
val statement = connection.prepareStatement("UPDATE ${super.dbName} SET ip = '' WHERE `index` = ?")
|
||||
statement?.setLong(1, index.toLong())
|
||||
val rowsAffected = statement?.executeUpdate()
|
||||
if (rowsAffected != null && rowsAffected > 0) {
|
||||
Logger.info("${super.dbName} IP cleared for index $index" as Any)
|
||||
return true
|
||||
} else {
|
||||
Logger.warn("No ${super.dbName} entry cleared for index $index" as Any)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Logger.error("Error clearing ${super.dbName} entry for index $index: ${e.message}" as Any)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all distinct sound channel from soundchannelDB
|
||||
* @return a list of distinct sound channel sorted alphabetically
|
||||
*/
|
||||
fun Get_SoundChannel_List(): List<String> {
|
||||
return List
|
||||
.distinctBy { it.channel }
|
||||
.map { it.channel }
|
||||
.sorted()
|
||||
}
|
||||
}
|
||||
349
src/database/table/Table_Soundbank.kt
Normal file
349
src/database/table/Table_Soundbank.kt
Normal file
@@ -0,0 +1,349 @@
|
||||
package database.table
|
||||
|
||||
import content.Category
|
||||
import database.data.Soundbank
|
||||
import database.dbFunctions
|
||||
import org.apache.poi.xssf.usermodel.XSSFWorkbook
|
||||
import org.tinylog.Logger
|
||||
import java.io.File
|
||||
import java.sql.Connection
|
||||
import java.util.function.Consumer
|
||||
|
||||
class Table_Soundbank(connection: Connection) : dbFunctions<Soundbank>("soundbank", connection, listOf("index", "Description", "TAG", "Category", "Language", "VoiceType", "Path")) {
|
||||
override fun Create() {
|
||||
val tabledefinition = "CREATE TABLE IF NOT EXISTS ${super.dbName} (" +
|
||||
"`index` INT AUTO_INCREMENT PRIMARY KEY," +
|
||||
"Description VARCHAR(1024) NOT NULL," + // Description of the soundbank
|
||||
"TAG VARCHAR(45) NOT NULL," + // TAG of the soundbank
|
||||
"Category VARCHAR(45) NOT NULL," + // Category of the soundbank
|
||||
"Language VARCHAR(45) NOT NULL," + // Language of the soundbank
|
||||
"VoiceType VARCHAR(45) NOT NULL," + // VoiceType of the soundbank
|
||||
"Path VARCHAR(1024) NOT NULL" + // Path to the sound file
|
||||
")"
|
||||
super.Create(tabledefinition)
|
||||
}
|
||||
|
||||
override fun Get(cbOK: Consumer<Unit>?, cbFail: Consumer<String>?) {
|
||||
List.clear()
|
||||
try {
|
||||
val statement = connection.createStatement()
|
||||
val resultSet = statement?.executeQuery("SELECT * FROM ${super.dbName} ORDER BY Category, Language, VoiceType, TAG")
|
||||
while (resultSet?.next() == true) {
|
||||
val soundbank = Soundbank(
|
||||
resultSet.getLong("index").toUInt(),
|
||||
resultSet.getString("Description"),
|
||||
resultSet.getString("TAG"),
|
||||
resultSet.getString("Category"),
|
||||
resultSet.getString("Language"),
|
||||
resultSet.getString("VoiceType"),
|
||||
resultSet.getString("Path")
|
||||
)
|
||||
List.add(soundbank)
|
||||
}
|
||||
cbOK?.accept(Unit)
|
||||
} catch (e: Exception) {
|
||||
Logger.error("Error fetching soundbanks: ${e.message}" as Any)
|
||||
cbFail?.accept("Error fetching ${super.dbName} : ${e.message}")
|
||||
}
|
||||
}
|
||||
|
||||
override fun Add(data: Soundbank): Boolean {
|
||||
try {
|
||||
val statement =
|
||||
connection.prepareStatement("INSERT INTO ${super.dbName} (Description, TAG, Category, Language, VoiceType, Path) VALUES (?, ?, ?, ?, ?, ?)")
|
||||
statement?.setString(1, data.Description)
|
||||
statement?.setString(2, data.TAG)
|
||||
statement?.setString(3, data.Category)
|
||||
statement?.setString(4, data.Language)
|
||||
statement?.setString(5, data.VoiceType)
|
||||
statement?.setString(6, data.Path)
|
||||
val rowsAffected = statement?.executeUpdate()
|
||||
if (rowsAffected != null && rowsAffected > 0) {
|
||||
Logger.info("Soundbank added: ${data.Description}" as Any)
|
||||
return true
|
||||
} else {
|
||||
Logger.warn("No soundbank entry added for: ${data.Description}" as Any)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Logger.error("Error adding soundbank entry: ${e.message}" as Any)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
override fun AddAll(data: ArrayList<Soundbank>): Boolean {
|
||||
// use mysql bulk insert
|
||||
try {
|
||||
connection.autoCommit = false
|
||||
val sql =
|
||||
"INSERT INTO ${super.dbName} (Description, TAG, Category, Language, VoiceType, Path) VALUES (?, ?, ?, ?, ?, ?)"
|
||||
val statement = connection.prepareStatement(sql)
|
||||
for (sb in data) {
|
||||
statement.setString(1, sb.Description)
|
||||
statement.setString(2, sb.TAG)
|
||||
statement.setString(3, sb.Category)
|
||||
statement.setString(4, sb.Language)
|
||||
statement.setString(5, sb.VoiceType)
|
||||
statement.setString(6, sb.Path)
|
||||
statement.addBatch()
|
||||
}
|
||||
statement.executeBatch()
|
||||
connection.commit()
|
||||
Logger.info("Bulk soundbank insert successful: ${data.size} entries" as Any)
|
||||
connection.autoCommit = true
|
||||
return true
|
||||
} catch (e: Exception) {
|
||||
Logger.error("Error adding soundbank entries: ${e.message}" as Any)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
override fun UpdateByIndex(index: Int, data: Soundbank): Boolean {
|
||||
try {
|
||||
val statement =
|
||||
connection.prepareStatement("UPDATE ${super.dbName} SET Description = ?, TAG = ?, Category = ?, Language = ?, VoiceType = ?, Path = ? WHERE `index` = ?")
|
||||
statement?.setString(1, data.Description)
|
||||
statement?.setString(2, data.TAG)
|
||||
statement?.setString(3, data.Category)
|
||||
statement?.setString(4, data.Language)
|
||||
statement?.setString(5, data.VoiceType)
|
||||
statement?.setString(6, data.Path)
|
||||
statement?.setLong(7, index.toLong())
|
||||
val rowsAffected = statement?.executeUpdate()
|
||||
if (rowsAffected != null && rowsAffected > 0) {
|
||||
Logger.info("Soundbank updated at index $index: ${data.Description}" as Any)
|
||||
return true
|
||||
} else {
|
||||
Logger.warn("No soundbank entry updated at index $index for: ${data.Description}" as Any)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Logger.error("Error updating soundbank entry at index $index: ${e.message}" as Any)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
override fun Resort(): Boolean {
|
||||
try {
|
||||
val statement = connection.createStatement()
|
||||
val tempdb_name = "temp_${super.dbName}"
|
||||
|
||||
// use a temporary table to reorder the index
|
||||
statement?.executeUpdate("CREATE TABLE IF NOT EXISTS $tempdb_name LIKE ${super.dbName}")
|
||||
statement?.executeUpdate("TRUNCATE TABLE $tempdb_name")
|
||||
statement?.executeUpdate("INSERT INTO $tempdb_name (Description, TAG, Category, Language, VoiceType, Path) SELECT Description, TAG, Category, Language, VoiceType, Path FROM ${super.dbName} ORDER BY Category, Language, VoiceType, TAG ")
|
||||
statement?.executeUpdate("TRUNCATE TABLE ${super.dbName}")
|
||||
statement?.executeUpdate("INSERT INTO ${super.dbName} (Description, TAG, Category, Language, VoiceType, Path) SELECT Description, TAG, Category, Language, VoiceType, Path FROM $tempdb_name")
|
||||
statement?.executeUpdate("DROP TABLE $tempdb_name")
|
||||
Logger.info("${super.dbName} table resorted by Description" as Any)
|
||||
// reload the local list
|
||||
Get()
|
||||
return true
|
||||
} catch (e: Exception) {
|
||||
Logger.error("Error resorting ${super.dbName} table by Description: ${e.message}" as Any)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
override fun Import_XLSX(workbook: XSSFWorkbook): Boolean {
|
||||
try {
|
||||
// check if there is sheet named "Soundbank"
|
||||
val sheet =
|
||||
workbook.getSheet("Soundbank") ?: throw Exception("No sheet named 'Soundbank' found")
|
||||
// check if the sheet contains header named "index", "Description", "TAG", "Category", "Language", "VoiceType", "Path"
|
||||
val headerRow = sheet.getRow(0) ?: throw Exception("No header row found")
|
||||
val headers =
|
||||
arrayOf("Index", "Description", "TAG", "Category", "Language", "VoiceType", "Path")
|
||||
for ((colIndex, header) in headers.withIndex()) {
|
||||
val cell = headerRow.getCell(colIndex) ?: throw Exception("Header '$header' not found")
|
||||
if (cell.stringCellValue != header) throw Exception("Header '$header' not found")
|
||||
}
|
||||
// clear existing soundbank
|
||||
Clear()
|
||||
// read each row and insert into database
|
||||
val _soundbankList = ArrayList<Soundbank>()
|
||||
for (rowIndex in 1..sheet.lastRowNum) {
|
||||
val row = sheet.getRow(rowIndex) ?: continue
|
||||
val description = row.getCell(1)?.stringCellValue ?: continue
|
||||
val tag = row.getCell(2)?.stringCellValue ?: continue
|
||||
val category = row.getCell(3)?.stringCellValue ?: continue
|
||||
val language = row.getCell(4)?.stringCellValue ?: continue
|
||||
val voiceType = row.getCell(5)?.stringCellValue ?: continue
|
||||
val path = row.getCell(6)?.stringCellValue ?: continue
|
||||
val soundbank = Soundbank(0u, description, tag, category, language, voiceType, path)
|
||||
_soundbankList.add(soundbank)
|
||||
}
|
||||
return AddAll(_soundbankList)
|
||||
} catch (e: Exception) {
|
||||
Logger.error { "Error importing Soundbank, Msg: ${e.message}" }
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
override fun Export_XLSX(): XSSFWorkbook? {
|
||||
try {
|
||||
val statement = connection.createStatement()
|
||||
val resultSet = statement?.executeQuery("SELECT * FROM ${super.dbName}")
|
||||
val workbook = XSSFWorkbook()
|
||||
val sheet = workbook.createSheet("Soundbank")
|
||||
val headerRow = sheet.createRow(0)
|
||||
val headers =
|
||||
arrayOf("Index", "Description", "TAG", "Category", "Language", "VoiceType", "Path")
|
||||
for ((colIndex, header) in headers.withIndex()) {
|
||||
val cell = headerRow.createCell(colIndex)
|
||||
cell.setCellValue(header)
|
||||
}
|
||||
var rowIndex = 1
|
||||
while (resultSet?.next() == true) {
|
||||
val row = sheet.createRow(rowIndex++)
|
||||
row.createCell(0).setCellValue(resultSet.getString("index"))
|
||||
row.createCell(1).setCellValue(resultSet.getString("Description"))
|
||||
row.createCell(2).setCellValue(resultSet.getString("TAG"))
|
||||
row.createCell(3).setCellValue(resultSet.getString("Category"))
|
||||
row.createCell(4).setCellValue(resultSet.getString("Language"))
|
||||
row.createCell(5).setCellValue(resultSet.getString("VoiceType"))
|
||||
row.createCell(6).setCellValue(resultSet.getString("Path"))
|
||||
}
|
||||
for (i in 0 until headers.size) {
|
||||
sheet.autoSizeColumn(i)
|
||||
}
|
||||
return workbook
|
||||
} catch (e: Exception) {
|
||||
Logger.error { "Error exporting Soundbank, Msg: ${e.message}" }
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all distinct airline code tags from soundbank
|
||||
* @return a list of distinct airline code tags sorted alphabetically
|
||||
*/
|
||||
fun Get_AirlineCode_Tags(): List<String> {
|
||||
|
||||
return List
|
||||
.filter { it.Category.equals(Category.Airline_Code.name,true) }
|
||||
.distinctBy { it.TAG }
|
||||
.map { it.TAG }
|
||||
.sorted()
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all distinct city tags from soundbank
|
||||
* @return a list of distinct city tags sorted alphabetically
|
||||
*/
|
||||
fun Get_City_Tags(): List<String> {
|
||||
return List
|
||||
.filter { it.Category.equals(Category.City.name,true) }
|
||||
.distinctBy { it.TAG }
|
||||
.map { it.TAG }
|
||||
.sorted()
|
||||
}
|
||||
|
||||
/**
|
||||
* Find City by TAG
|
||||
* @param tag the city tag to search for
|
||||
* @return a list of Soundbank entries matching the city tag
|
||||
*/
|
||||
fun Find_City_By_TAG(tag: String) : List<Soundbank> {
|
||||
return List
|
||||
.filter {it.Category.equals(Category.City.name,true) }
|
||||
.filter { it.TAG.equals(tag, true) }
|
||||
.distinctBy { it.TAG }
|
||||
}
|
||||
|
||||
/**
|
||||
* Find Airline Name by TAG
|
||||
* @param tag the airline code tag to search for
|
||||
* @return a list of Soundbank entries matching the airline code tag
|
||||
*/
|
||||
fun Find_AirlineName_By_TAG(tag: String) : List<Soundbank> {
|
||||
return List
|
||||
.filter {it.Category.equals(Category.Airplane_Name.name,true) }
|
||||
.filter { it.TAG.equals(tag, true) }
|
||||
.distinctBy { it.TAG }
|
||||
}
|
||||
|
||||
fun Get_Places(): List<Soundbank> {
|
||||
return List
|
||||
.filter { it.Category.equals(Category.Places.name,true) }
|
||||
.distinctBy { it.TAG }
|
||||
.sortedBy { it.TAG }
|
||||
}
|
||||
|
||||
fun Get_Shalat() : List<Soundbank> {
|
||||
return List
|
||||
.filter { it.Category.equals(Category.Shalat.name,true) }
|
||||
.distinctBy { it.TAG }
|
||||
.sortedBy { it.TAG }
|
||||
}
|
||||
|
||||
fun Get_Sequences() : List<Soundbank> {
|
||||
return List
|
||||
.filter { it.Category.equals(Category.Sequence.name,true) }
|
||||
.distinctBy { it.TAG }
|
||||
.sortedBy { it.TAG }
|
||||
}
|
||||
|
||||
fun Get_Reasons() : List<Soundbank> {
|
||||
return List
|
||||
.filter { it.Category.equals(Category.Reason.name,true) }
|
||||
.distinctBy { it.TAG }
|
||||
.sortedBy { it.TAG }
|
||||
}
|
||||
|
||||
fun Get_Gates(): List<Soundbank> {
|
||||
return List
|
||||
.filter { it.Category.equals(Category.Gate.name,true) }
|
||||
.distinctBy { it.TAG }
|
||||
.sortedBy { it.TAG }
|
||||
}
|
||||
|
||||
fun Get_Compensation(): List<Soundbank> {
|
||||
return List
|
||||
.filter { it.Category.equals(Category.Compensation.name,true) }
|
||||
.distinctBy { it.TAG }
|
||||
.sortedBy { it.TAG }
|
||||
}
|
||||
|
||||
fun Get_Greeting() : List<Soundbank> {
|
||||
return List
|
||||
.filter { it.Category.equals(Category.Greeting.name,true) }
|
||||
.distinctBy { it.TAG }
|
||||
.sortedBy { it.TAG }
|
||||
}
|
||||
|
||||
fun Get_Procedures(): List<Soundbank> {
|
||||
return List
|
||||
.filter { it.Category.equals(Category.Procedure.name,true) }
|
||||
.distinctBy { it.TAG }
|
||||
.sortedBy { it.TAG }
|
||||
}
|
||||
|
||||
class FileCheckResult{
|
||||
var validfile = arrayListOf<Soundbank>()
|
||||
var invalidfile = arrayListOf<Soundbank>()
|
||||
}
|
||||
|
||||
/**
|
||||
* Check Soundbank path files existence
|
||||
* @param cb callback with FileCheckResult containing valid and invalid files
|
||||
*/
|
||||
fun FileCheck(cb: Consumer<FileCheckResult>){
|
||||
val result = FileCheckResult()
|
||||
// file wav must at least 10 kb
|
||||
val validfilesize = 10 * 1024; // 10 KB
|
||||
|
||||
for (sb in List){
|
||||
if (sb.Path.isBlank()) {
|
||||
result.invalidfile.add(sb)
|
||||
continue
|
||||
}
|
||||
val file = File(sb.Path)
|
||||
if (file.isFile && file.length() >= validfilesize) {
|
||||
// file size should at leat 10 kb
|
||||
result.validfile.add(sb)
|
||||
} else {
|
||||
result.invalidfile.add(sb)
|
||||
}
|
||||
}
|
||||
cb.accept(result)
|
||||
}
|
||||
}
|
||||
225
src/database/table/Table_Users.kt
Normal file
225
src/database/table/Table_Users.kt
Normal file
@@ -0,0 +1,225 @@
|
||||
package database.table
|
||||
|
||||
import database.data.UserDB
|
||||
import database.dbFunctions
|
||||
import org.apache.poi.xssf.usermodel.XSSFWorkbook
|
||||
import org.tinylog.Logger
|
||||
import java.sql.Connection
|
||||
import java.util.function.Consumer
|
||||
|
||||
class Table_Users(connection : Connection) : dbFunctions<UserDB>("newuser", connection, listOf("index", "username", "password", "location", "airline_tags", "city_tags", "messagebank_ann_id", "broadcastzones")) {
|
||||
override fun Create() {
|
||||
val tableDefinition = "CREATE TABLE IF NOT EXISTS ${super.dbName} (" +
|
||||
"`index` INT AUTO_INCREMENT PRIMARY KEY," +
|
||||
"username VARCHAR(100) NOT NULL," +
|
||||
"password VARCHAR(100) NOT NULL," +
|
||||
"location VARCHAR(100) NOT NULL," +
|
||||
"airline_tags TEXT NOT NULL,"+ // Comma-separated soundbank tags
|
||||
"city_tags TEXT NOT NULL,"+ // Comma-separated soundbank tags
|
||||
"messagebank_ann_id TEXT NOT NULL,"+ // Comma-separated messagebank announcement index
|
||||
"broadcastzones TEXT NOT NULL"+ // Comma-separated broadcast zones
|
||||
")"
|
||||
super.Create(tableDefinition)
|
||||
}
|
||||
|
||||
override fun Get(cbOK: Consumer<Unit>?, cbFail: Consumer<String>?) {
|
||||
List.clear()
|
||||
try {
|
||||
val statement = connection.createStatement()
|
||||
val resultSet = statement?.executeQuery("SELECT * FROM ${super.dbName}")
|
||||
while (resultSet?.next() == true) {
|
||||
val user = UserDB(
|
||||
resultSet.getLong("index").toUInt(),
|
||||
resultSet.getString("username"),
|
||||
resultSet.getString("password"),
|
||||
resultSet.getString("location"),
|
||||
resultSet.getString("airline_tags"),
|
||||
resultSet.getString("city_tags"),
|
||||
resultSet.getString("messagebank_ann_id"),
|
||||
resultSet.getString("broadcastzones")
|
||||
)
|
||||
List.add(user)
|
||||
}
|
||||
cbOK?.accept(Unit)
|
||||
} catch (e: Exception) {
|
||||
Logger.error("Error fetching users: ${e.message}" as Any)
|
||||
cbFail?.accept("Error fetching users: ${e.message}" )
|
||||
}
|
||||
}
|
||||
|
||||
override fun Add(data: UserDB): Boolean {
|
||||
try {
|
||||
val statement = connection.prepareStatement("INSERT INTO ${super.dbName} (username, password, location, airline_tags, city_tags, messagebank_ann_id, broadcastzones) VALUES (?, ?, ?, ?,?, ?, ?)")
|
||||
statement?.setString(1, data.username)
|
||||
statement?.setString(2, data.password)
|
||||
statement?.setString(3, data.location)
|
||||
statement?.setString(4, data.airline_tags)
|
||||
statement?.setString(5, data.city_tags)
|
||||
statement?.setString(6, data.messagebank_ann_id)
|
||||
statement?.setString(7, data.broadcastzones)
|
||||
val rowsAffected = statement?.executeUpdate()
|
||||
if (rowsAffected != null && rowsAffected > 0) {
|
||||
Logger.info("User added: ${data.username}" as Any)
|
||||
return true
|
||||
} else {
|
||||
Logger.warn("No user entry added for: ${data.username}" as Any)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Logger.error("Error adding user entry: ${e.message}" as Any)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
override fun AddAll(data: ArrayList<UserDB>): Boolean {
|
||||
return try {
|
||||
connection.autoCommit = false
|
||||
val sql = "INSERT INTO ${super.dbName} (username, password, location,airline_tags,city_tags, messagebank_ann_id, broadcastzones) VALUES (?, ?, ?,?, ?, ?, ?)"
|
||||
val statement = connection.prepareStatement(sql)
|
||||
for (user in data) {
|
||||
statement.setString(1, user.username)
|
||||
statement.setString(2, user.password)
|
||||
statement.setString(3, user.location)
|
||||
statement.setString(4, user.airline_tags)
|
||||
statement.setString(5, user.city_tags)
|
||||
statement.setString(6, user.messagebank_ann_id)
|
||||
statement.setString(7, user.broadcastzones)
|
||||
statement.addBatch()
|
||||
}
|
||||
statement.executeBatch()
|
||||
connection.commit()
|
||||
Logger.info("Bulk user insert successful: ${data.size} entries" as Any)
|
||||
connection.autoCommit = true
|
||||
true
|
||||
} catch (e: Exception) {
|
||||
Logger.error("Error adding user entries: ${e.message}" as Any)
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
override fun UpdateByIndex(index: Int, data: UserDB): Boolean {
|
||||
try {
|
||||
val statement = connection.prepareStatement("UPDATE ${super.dbName} SET username = ?, password = ?, location = ?, airline_tags = ?,city_tags=?, messagebank_ann_id = ?, broadcastzones = ? WHERE `index` = ?")
|
||||
statement?.setString(1, data.username)
|
||||
statement?.setString(2, data.password)
|
||||
statement?.setString(3, data.location)
|
||||
statement?.setString(4, data.airline_tags)
|
||||
statement?.setString(5, data.city_tags)
|
||||
statement?.setString(6, data.messagebank_ann_id)
|
||||
statement?.setString(7, data.broadcastzones)
|
||||
statement?.setLong(8, index.toLong())
|
||||
val rowsAffected = statement?.executeUpdate()
|
||||
if (rowsAffected != null && rowsAffected > 0) {
|
||||
Logger.info("User updated at index $index: ${data.username}" as Any)
|
||||
return true
|
||||
} else {
|
||||
Logger.warn("No user entry updated at index $index for: ${data.username}" as Any)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Logger.error("Error updating user entry at index $index: ${e.message}" as Any)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
override fun Resort(): Boolean {
|
||||
try {
|
||||
val statement = connection.createStatement()
|
||||
val tempdb_name = "temp_${super.dbName}"
|
||||
// use a temporary table to reorder the index
|
||||
statement?.executeUpdate("CREATE TABLE IF NOT EXISTS $tempdb_name LIKE ${super.dbName}")
|
||||
statement?.executeUpdate("TRUNCATE TABLE $tempdb_name")
|
||||
statement?.executeUpdate("INSERT INTO $tempdb_name (username, password, location, airline_tags, city_tags, messagebank_ann_id, broadcastzones) SELECT username, password, location, airline_tags, city_tags, messagebank_ann_id, broadcastzones FROM ${super.dbName} ORDER BY username ")
|
||||
statement?.executeUpdate("TRUNCATE TABLE ${super.dbName}")
|
||||
statement?.executeUpdate("INSERT INTO ${super.dbName} (username, password, location, airline_tags, city_tags, messagebank_ann_id, broadcastzones) SELECT username, password, location, airline_tags, city_tags, messagebank_ann_id, broadcastzones FROM $tempdb_name")
|
||||
statement?.executeUpdate("DROP TABLE $tempdb_name")
|
||||
Logger.info("${super.dbName} table resorted by index" as Any)
|
||||
// reload the local list
|
||||
Get()
|
||||
return true
|
||||
} catch (e: Exception) {
|
||||
Logger.error("Error resorting ${super.dbName} table by index: ${e.message}" as Any)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
override fun Import_XLSX(workbook: XSSFWorkbook): Boolean {
|
||||
try {
|
||||
val sheet = workbook.getSheet("User") ?: throw Exception("No sheet named 'User' found")
|
||||
val headerRow = sheet.getRow(0) ?: throw Exception("No header row found")
|
||||
val headers = arrayOf("Index", "username", "password", "location", "airline_tags", "city_tags", "messagebank_ann_id", "broadcastzones")
|
||||
for ((colIndex, header) in headers.withIndex()) {
|
||||
val cell = headerRow.getCell(colIndex) ?: throw Exception("Header '$header' not found")
|
||||
if (cell.stringCellValue != header) throw Exception("Header '$header' not found")
|
||||
}
|
||||
// clear existing users
|
||||
Clear()
|
||||
// read each row and insert into database
|
||||
val _userList = ArrayList<UserDB>()
|
||||
for (rowIndex in 1..sheet.lastRowNum) {
|
||||
val row = sheet.getRow(rowIndex) ?: continue
|
||||
val username = row.getCell(1)?.stringCellValue ?: continue
|
||||
val password = row.getCell(2)?.stringCellValue ?: continue
|
||||
val location = row.getCell(3)?.stringCellValue ?: continue
|
||||
val airline_tags = row.getCell(4)?.stringCellValue ?: continue
|
||||
val city_tags = row.getCell(5)?.stringCellValue ?: continue
|
||||
val messagebank_ann_id = row.getCell(6)?.stringCellValue ?: continue
|
||||
val broadcastzones = row.getCell(7)?.stringCellValue ?: continue
|
||||
val user = UserDB(
|
||||
0u,
|
||||
username,
|
||||
password,
|
||||
location,
|
||||
airline_tags,
|
||||
city_tags,
|
||||
messagebank_ann_id,
|
||||
broadcastzones
|
||||
)
|
||||
_userList.add(user)
|
||||
}
|
||||
return AddAll(_userList)
|
||||
} catch (e: Exception) {
|
||||
Logger.error { "Error importing User, Msg: ${e.message}" }
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
override fun Export_XLSX(): XSSFWorkbook? {
|
||||
try {
|
||||
val statement = connection.createStatement()
|
||||
val resultSet = statement?.executeQuery("SELECT * FROM ${super.dbName}")
|
||||
val workbook = XSSFWorkbook()
|
||||
val sheet = workbook.createSheet("User")
|
||||
val headerRow = sheet.createRow(0)
|
||||
val headers = arrayOf("Index", "username", "password", "location", "airline_tags","city_tags", "messagebank_ann_id", "broadcastzones")
|
||||
for ((colIndex, header) in headers.withIndex()) {
|
||||
val cell = headerRow.createCell(colIndex)
|
||||
cell.setCellValue(header)
|
||||
}
|
||||
var rowIndex = 1
|
||||
while (resultSet?.next() == true) {
|
||||
val row = sheet.createRow(rowIndex++)
|
||||
row.createCell(0).setCellValue(resultSet.getString("index"))
|
||||
row.createCell(1).setCellValue(resultSet.getString("username"))
|
||||
row.createCell(2).setCellValue(resultSet.getString("password"))
|
||||
row.createCell(3).setCellValue(resultSet.getString("location"))
|
||||
row.createCell(4).setCellValue(resultSet.getString("airline_tags"))
|
||||
row.createCell(5).setCellValue(resultSet.getString("city_tags"))
|
||||
row.createCell(6).setCellValue(resultSet.getString("messagebank_ann_id"))
|
||||
row.createCell(7).setCellValue(resultSet.getString("broadcastzones"))
|
||||
}
|
||||
for (i in headers.indices) {
|
||||
sheet.autoSizeColumn(i)
|
||||
}
|
||||
return workbook
|
||||
} catch (e: Exception) {
|
||||
Logger.error { "Error exporting User, Msg: ${e.message}" }
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a username already exists in the userDB (case-insensitive)
|
||||
*/
|
||||
fun Username_exists(username: String): Boolean {
|
||||
return List.any { it.username.equals(username, ignoreCase = true) }
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,7 @@ import com.google.cloud.texttospeech.v1.VoiceSelectionParams
|
||||
import content.Category
|
||||
import content.Language
|
||||
import content.VoiceType
|
||||
import database.Soundbank
|
||||
import database.data.Soundbank
|
||||
import org.tinylog.Logger
|
||||
import java.nio.file.Files
|
||||
import java.util.function.BiConsumer
|
||||
|
||||
37
src/ourAirport/AirportData.kt
Normal file
37
src/ourAirport/AirportData.kt
Normal file
@@ -0,0 +1,37 @@
|
||||
package ourAirport
|
||||
|
||||
import codes.Somecodes
|
||||
|
||||
data class AirportData(val description: String, val latitude: Double, val longitude: Double, val country: String, val IATA: String, val ICAO: String)
|
||||
{
|
||||
companion object{
|
||||
/**
|
||||
* create AirportData from CSV line
|
||||
* CSV format : id,ident,type,name,latitude_deg,longitude_deg,elevation_ft,continent,country_name,iso_country,region_name,iso_region,local_region,municipality,scheduled_service,gps_code,icao_code,iata_code,local_code,home_link,wikipedia_link,keywords,score,last_updated
|
||||
* @param line CSV line
|
||||
* @return AirportData or null if failed
|
||||
*/
|
||||
fun fromString(line: String) : AirportData? {
|
||||
if (Somecodes.Companion.ValidString(line)){
|
||||
try{
|
||||
val values = line.split(",")
|
||||
// description on index 3
|
||||
val description = values[3].trim()
|
||||
// latitude on index 4
|
||||
val latitude = values[4].toDoubleOrNull() ?: return null
|
||||
// longitude on index 5
|
||||
val longitude = values[5].toDoubleOrNull() ?: return null
|
||||
// country on index 8
|
||||
val country = values[8].trim()
|
||||
// ICAO on index 16
|
||||
val ICAO = values[16].trim()
|
||||
// IATA on index 17
|
||||
val IATA = values[17].trim()
|
||||
return AirportData(description, latitude, longitude, country, IATA, ICAO)
|
||||
} catch (_ : Exception){
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
||||
}
|
||||
47
src/ourAirport/OurAirport.kt
Normal file
47
src/ourAirport/OurAirport.kt
Normal file
@@ -0,0 +1,47 @@
|
||||
package ourAirport
|
||||
|
||||
import codes.Somecodes
|
||||
import org.tinylog.Logger
|
||||
import java.nio.file.Files
|
||||
import kotlin.io.path.Path
|
||||
|
||||
/**
|
||||
* this class read CSV from world-airports.csv included in resources folder
|
||||
* and filter only the airports in the InterestedCountries list
|
||||
* @param InterestedCountries vararg list of country names to filter airports. Default is "Indonesia"
|
||||
*/
|
||||
@Suppress("unused")
|
||||
class OurAirport(vararg InterestedCountries: String = arrayOf("Indonesia")) {
|
||||
val List: MutableList<AirportData> = mutableListOf()
|
||||
init{
|
||||
// extract world-airports.csv from resources to current folder
|
||||
try{
|
||||
val current = Path(Somecodes.current_directory)
|
||||
if (!Files.exists(current.resolve("world-airports.csv"))){
|
||||
val inputStream = this::class.java.getResourceAsStream("/world-airports.csv")
|
||||
if (inputStream != null) {
|
||||
Files.copy(inputStream, current)
|
||||
Logger.info { "Extracted world-airports.csv to ${Somecodes.current_directory}" }
|
||||
} else throw Exception("Resource world-airports.csv not found")
|
||||
}
|
||||
val lines = Files.readAllLines(current.resolve("world-airports.csv"))
|
||||
for (line in lines.drop(1)) { // skip header
|
||||
AirportData.fromString(line)?.let { ad ->
|
||||
if (InterestedCountries.contains(ad.country)) {
|
||||
List.add(ad)
|
||||
}
|
||||
}
|
||||
}
|
||||
}catch(ex: Exception){
|
||||
Logger.error { "Failed to copy world-airports.csv: ${ex.message}" }
|
||||
}
|
||||
}
|
||||
|
||||
fun GetFromIATA(iata: String): AirportData? {
|
||||
return List.find { it.IATA.equals(iata, ignoreCase = true) }
|
||||
}
|
||||
|
||||
fun GetFromICAO(icao: String): AirportData? {
|
||||
return List.find { it.ICAO.equals(icao, ignoreCase = true) }
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
package web
|
||||
|
||||
class StreamerOutputData(val index: UInt, val channel: String, val ipaddress: String, val vu: Int, val bufferRemain: Int, var isPlaying: Boolean) {
|
||||
data class StreamerOutputData(val index: UInt, val channel: String, val ipaddress: String, val vu: Int, val bufferRemain: Int, val isPlaying: Boolean, val filename: String, val duration: String, val elapsed: String, val broadcastzones: String) {
|
||||
companion object{
|
||||
fun fromBarixConnection(bc: barix.BarixConnection): StreamerOutputData {
|
||||
return StreamerOutputData(bc.index, bc.channel, bc.ipaddress, bc.vu, bc.bufferRemain, bc.isPlaying())
|
||||
return StreamerOutputData(bc.index, bc.channel, bc.ipaddress, bc.vu, bc.bufferRemain, bc.isPlaying(), bc.GetAudioFileInfo()?.fileName ?: "", bc.GetAudioFileInfo()?.DurationToString() ?: "", bc.GetElapsed(), bc.GetUsedByBroadcastZones())
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user