Compare commits
9 Commits
5e128ab36a
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 7b4420ddbd | |||
| c797c6e7fe | |||
| 546f2e27af | |||
| e18976ace3 | |||
| eed96ca8c0 | |||
| 1790852242 | |||
| c8f7f35c79 | |||
| afd896161e | |||
| 4a6df6bef4 |
1
.idea/inspectionProfiles/Project_Default.xml
generated
1
.idea/inspectionProfiles/Project_Default.xml
generated
@@ -17,5 +17,6 @@
|
||||
<option name="processComments" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="SqlDialectInspection" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="SqlSourceToSinkFlow" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
</profile>
|
||||
</component>
|
||||
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
@@ -16,16 +16,20 @@ function load_log_data(datevalue){
|
||||
data.forEach(function(item, index){
|
||||
tblog.row.add({
|
||||
index: index + 1,
|
||||
date: item.datenya,
|
||||
time: item.timenya,
|
||||
source: item.machine,
|
||||
message: item.description
|
||||
})
|
||||
date: item.date,
|
||||
time: item.time,
|
||||
source: item.source,
|
||||
description: item.description
|
||||
});
|
||||
});
|
||||
tblog.draw();
|
||||
} else {
|
||||
console.log("No log data found for date:", datevalue);
|
||||
alert("No log data found for the selected date.");
|
||||
}
|
||||
},function(error){
|
||||
console.error("Error fetching log data:", error);
|
||||
alert("Error fetching log data : " + error.message);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -37,7 +41,7 @@ $(document).ready(function () {
|
||||
{title: 'Date', data: 'date'},
|
||||
{title: 'Time', data: 'time'},
|
||||
{title: 'Source', data: 'source'},
|
||||
{title: 'Message', data: 'message'}
|
||||
{title: 'Description', data: 'description'}
|
||||
],
|
||||
pageLength: 25,
|
||||
data: [],
|
||||
|
||||
@@ -65,29 +65,7 @@
|
||||
<th class="class50 wrap" data-sortable="true">Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="w-100 table-fixed" id="tbodylog">
|
||||
<tr>
|
||||
<td>999999</td>
|
||||
<td>13-OKT-25</td>
|
||||
<td>24:10:10</td>
|
||||
<td><strong>1.Lorem Ipsum</strong> is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. <br><br></td>
|
||||
<td><strong><span style="color: rgb(0, 0, 0);">2.Lorem Ipsum</span></strong><span style="color: rgb(0, 0, 0);"> is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. </span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>2222</td>
|
||||
<td>13-OKT-25<br><strong>Lorem Ipsum</strong> is simply dummy text of the printing and typesetting industry.<br><br><br></td>
|
||||
<td>24:10:10</td>
|
||||
<td><strong>2.Lorem Ipsum</strong> is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. </td>
|
||||
<td><strong>2.Lorem Ipsum</strong> is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>33333</td>
|
||||
<td>13-OKT-25<br><strong>Lorem Ipsum</strong> is simply dummy text of the printing and typesetting industry.<br><br></td>
|
||||
<td>Text</td>
|
||||
<td><strong>3.Lorem Ipsum</strong> is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. </td>
|
||||
<td><strong>3. Lorem Ipsum</strong> is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. </td>
|
||||
</tr>
|
||||
</tbody>
|
||||
<tbody class="w-100 table-fixed" id="tbodylog"></tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
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
@@ -210,6 +210,7 @@ function get_pagingresult_files() {
|
||||
player.pause();
|
||||
window.URL.revokeObjectURL(player.src);
|
||||
}
|
||||
$icon.removeClass("fa-stop").addClass("fa-play");
|
||||
} else {
|
||||
// start playback
|
||||
play("PlayPagingResultFile", filename, () => {
|
||||
@@ -221,8 +222,9 @@ function get_pagingresult_files() {
|
||||
}
|
||||
window.lastplaybutton = $(this);
|
||||
});
|
||||
$icon.removeClass("fa-play").addClass("fa-stop");
|
||||
}
|
||||
$icon.toggleClass("fa-play fa-stop");
|
||||
//$icon.toggleClass("fa-play fa-stop");
|
||||
});
|
||||
|
||||
let $tdbutton = $("<td></td>").append($btndownload).append($btnplay).addClass("text-center");
|
||||
@@ -262,6 +264,7 @@ function get_soundbankresult_files() {
|
||||
player.pause();
|
||||
window.URL.revokeObjectURL(player.src);
|
||||
}
|
||||
$icon.removeClass("fa-stop").addClass("fa-play");
|
||||
} else {
|
||||
// start playback
|
||||
play("PlaySoundbankResultFile", filename, () => {
|
||||
@@ -273,8 +276,9 @@ function get_soundbankresult_files() {
|
||||
}
|
||||
window.lastplaybutton = $(this);
|
||||
});
|
||||
$icon.removeClass("fa-play").addClass("fa-stop");
|
||||
}
|
||||
$icon.toggleClass("fa-play fa-stop");
|
||||
//$icon.toggleClass("fa-play fa-stop");
|
||||
});
|
||||
$tr.append($tdtitle);
|
||||
$tr.append($tdbutton);
|
||||
|
||||
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}" }
|
||||
@@ -280,14 +334,18 @@ fun main(args: Array<String>) {
|
||||
}
|
||||
}
|
||||
|
||||
db.Add_Log("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.Add_Log("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')"
|
||||
}
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
package database
|
||||
|
||||
@Suppress("unused")
|
||||
data class Log(
|
||||
val index: ULong,
|
||||
val datenya: String,
|
||||
val timenya: String,
|
||||
val machine: String,
|
||||
val description : String
|
||||
){
|
||||
companion object{
|
||||
|
||||
fun NewLog(
|
||||
machine: String,
|
||||
description: String
|
||||
) : Log {
|
||||
val current = java.time.LocalDateTime.now()
|
||||
val date = current.toLocalDate().toString() // format YYYY-MM-DD
|
||||
val time = current.toLocalTime().withNano(0).toString() // format HH:MM:SS
|
||||
return Log(0u, date, time, machine, description)
|
||||
}
|
||||
}
|
||||
|
||||
override fun toString() : String {
|
||||
return "$datenya $timenya [$machine] $description"
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
package database
|
||||
|
||||
data class LogSemiauto(val index: UInt, val date: String, val time: String, val source: String, val description: String){
|
||||
|
||||
|
||||
override fun toString(): String {
|
||||
return "$date $time [$source] $description"
|
||||
}
|
||||
}
|
||||
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')"
|
||||
}
|
||||
}
|
||||
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){
|
||||
|
||||
33
src/database/data/Log.kt
Normal file
33
src/database/data/Log.kt
Normal file
@@ -0,0 +1,33 @@
|
||||
package database.data
|
||||
|
||||
import java.time.LocalDate
|
||||
import java.time.LocalTime
|
||||
import java.time.format.DateTimeFormatter
|
||||
|
||||
@Suppress("unused")
|
||||
data class Log(
|
||||
val index: ULong,
|
||||
val datenya: String,
|
||||
val timenya: String,
|
||||
val machine: String,
|
||||
val description : String
|
||||
){
|
||||
companion object{
|
||||
|
||||
fun NewLog(
|
||||
machine: String,
|
||||
description: String
|
||||
) : Log {
|
||||
//val current = java.time.LocalDateTime.now()
|
||||
//val date = current.toLocalDate().toString() // format YYYY-MM-DD
|
||||
//val time = current.toLocalTime().withNano(0).toString() // format HH:MM:SS
|
||||
val date = LocalDate.now().format(DateTimeFormatter.ofPattern("dd/MM/yyyy"))
|
||||
val time = LocalTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss"))
|
||||
return Log(0u, date, time, machine, description)
|
||||
}
|
||||
}
|
||||
|
||||
override fun toString() : String {
|
||||
return "$datenya $timenya [$machine] $description"
|
||||
}
|
||||
}
|
||||
18
src/database/data/LogSemiauto.kt
Normal file
18
src/database/data/LogSemiauto.kt
Normal file
@@ -0,0 +1,18 @@
|
||||
package database.data
|
||||
|
||||
import java.time.LocalDate
|
||||
import java.time.LocalTime
|
||||
import java.time.format.DateTimeFormatter
|
||||
|
||||
data class LogSemiauto(val index: ULong, val date: String, val time: String, val source: String, val description: String){
|
||||
override fun toString(): String {
|
||||
return "$date $time [$source] $description"
|
||||
}
|
||||
companion object{
|
||||
fun NewLog(source: String, description: String): LogSemiauto {
|
||||
val date = LocalDate.now().format(DateTimeFormatter.ofPattern("dd/MM/yyyy"))
|
||||
val time = LocalTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss"))
|
||||
return LogSemiauto(0u, date, time, source, description)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
248
src/database/table/Table_LogSemiAuto.kt
Normal file
248
src/database/table/Table_LogSemiAuto.kt
Normal file
@@ -0,0 +1,248 @@
|
||||
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
|
||||
|
||||
class Table_LogSemiAuto(connection: Connection) : dbFunctions<LogSemiauto>("logsemiauto", connection, listOf("index", "date", "time", "source", "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," +
|
||||
"date VARCHAR(20) NOT NULL," + // format DD/MM/YYYY
|
||||
"time VARCHAR(20) NOT NULL," + // format HH:MM:SS
|
||||
"source VARCHAR(45) NOT NULL," +
|
||||
"description TEXT NOT NULL" +
|
||||
")"
|
||||
|
||||
super.Create(tabledefinition)
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
override fun Get(cbOK: Consumer<Unit>?, cbFail: Consumer<String>?) {
|
||||
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){
|
||||
val log = LogSemiauto.NewLog(source, description)
|
||||
Add(log)
|
||||
}
|
||||
|
||||
override fun Add(data: LogSemiauto): Boolean {
|
||||
try{
|
||||
val statement = connection.prepareStatement("INSERT INTO ${super.dbName} (date, time, source, description) VALUES (?, ?, ?, ?)")
|
||||
statement?.setString(1, data.date)
|
||||
statement?.setString(2, data.time)
|
||||
statement?.setString(3, data.source)
|
||||
statement?.setString(4, data.description)
|
||||
val rowsAffected = statement?.executeUpdate()
|
||||
if (rowsAffected != null && rowsAffected > 0) {
|
||||
return true
|
||||
} else throw Exception("Failed to add logsemiauto entry: $data")
|
||||
} catch (e : Exception){
|
||||
Logger.error { "Error adding logsemiauto entry: ${e.message}" }
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
fun GetLogSemiAutoForHtml(date: String, filter: String?, cbOK: Consumer<ArrayList<LogSemiauto>>?, cbFail: Consumer<String>?){
|
||||
try{
|
||||
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){
|
||||
val statement = if (filter.isNullOrEmpty()){
|
||||
connection.prepareStatement("SELECT * FROM ${super.dbName} WHERE COALESCE(STR_TO_DATE(date,'%d/%m/%Y'), STR_TO_DATE(date,'%d-%m-%Y'), STR_TO_DATE(date,'%Y/%m/%d'), STR_TO_DATE(date,'%Y-%m-%d')) = ?")
|
||||
} else {
|
||||
connection.prepareStatement("SELECT * FROM ${super.dbName} WHERE COALESCE(STR_TO_DATE(date,'%d/%m/%Y'), STR_TO_DATE(date,'%d-%m-%Y'), STR_TO_DATE(date,'%Y/%m/%d'), STR_TO_DATE(date,'%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<LogSemiauto>()
|
||||
while (resultSet?.next() == true) {
|
||||
val log = LogSemiauto(
|
||||
resultSet.getLong("index").toULong(),
|
||||
resultSet.getString("date"),
|
||||
resultSet.getString("time"),
|
||||
resultSet.getString("source"),
|
||||
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 AddAll(data: ArrayList<LogSemiauto>): Boolean {
|
||||
try{
|
||||
val statement = connection.prepareStatement("INSERT INTO ${super.dbName} (date, time, source, description) VALUES (?, ?, ?, ?)")
|
||||
for (log in data) {
|
||||
statement.setString(1, log.date)
|
||||
statement.setString(2, log.time)
|
||||
statement.setString(3, log.source)
|
||||
statement.setString(4, log.description)
|
||||
statement.addBatch()
|
||||
}
|
||||
val rowsAffected = statement.executeBatch()
|
||||
if (rowsAffected.isNotEmpty()) {
|
||||
return true
|
||||
} else throw Exception("Failed to add logsemiauto entries: $data")
|
||||
} catch (e : Exception){
|
||||
Logger.error { "Error adding logsemiauto entries: ${e.message}" }
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
override fun UpdateByIndex(index: Int, data: LogSemiauto): Boolean {
|
||||
try{
|
||||
val statement = connection.prepareStatement("UPDATE ${super.dbName} SET date = ?, time = ?, source = ?, description = ? WHERE `index` = ?")
|
||||
statement.setString(1, data.date)
|
||||
statement.setString(2, data.time)
|
||||
statement.setString(3, data.source)
|
||||
statement.setString(4, data.description)
|
||||
statement.setInt(5, index)
|
||||
val rowsAffected = statement.executeUpdate()
|
||||
if (rowsAffected > 0) {
|
||||
return true
|
||||
} else throw Exception("Failed to update logsemiauto entry at index $index: $data")
|
||||
} catch (e : Exception){
|
||||
Logger.error { "Error updating logsemiauto entry at index $index: ${e.message}" }
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
override fun Resort(): Boolean {
|
||||
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 {
|
||||
throw Exception("Importing LogSemiauto 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("LogSemiauto")
|
||||
val headerRow = sheet.createRow(0)
|
||||
val headers = arrayOf("Index", "date", "time", "source", "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("date"))
|
||||
row.createCell(2).setCellValue(resultSet.getString("time"))
|
||||
row.createCell(3).setCellValue(resultSet.getString("source"))
|
||||
row.createCell(4).setCellValue(resultSet.getString("description"))
|
||||
}
|
||||
for (i in headers.indices) {
|
||||
sheet.autoSizeColumn(i)
|
||||
}
|
||||
return workbook
|
||||
} catch (e : Exception){
|
||||
Logger.error { "Error exporting LogSemiauto, Msg: ${e.message}" }
|
||||
return null
|
||||
}
|
||||
}
|
||||
}
|
||||
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