commit 12/02/2026

This commit is contained in:
2026-02-12 17:08:20 +07:00
parent 546f2e27af
commit c797c6e7fe
47 changed files with 89103 additions and 214 deletions

10
.idea/libraries/batoulapps_adhan.xml generated Normal file
View 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>

View 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>

View 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>

View File

@@ -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

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View 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);
}
}

View 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);
}
}

View 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);
}
}

View 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;
}

View 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);

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,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);
}
}
});
};
}());

View File

@@ -139,6 +139,8 @@ function reloadTodaySchedule(APIURL = "ScheduleBank/") {
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");
@@ -154,6 +156,8 @@ $(document).ready(function () {
$btnRemove.prop('disabled', true);
let APIURL = "ScheduleBank/";
if (dtScheduleBank === null) {
dtScheduleBank = new DataTable('#schedulebanktable', {
dom: 'Bfrtip',
@@ -239,15 +243,24 @@ $(document).ready(function () {
})
let $schedulemodal = $('#schedulemodal');
$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
@@ -265,6 +278,7 @@ $(document).ready(function () {
// radio button for specific date
let $schedulespecialdate = $schedulemodal.find('#schedulespecialdate');
scheduledate = new Litepicker({
element: document.getElementById('scheduledate'),
format: 'DD/MM/YYYY',
@@ -276,6 +290,8 @@ $(document).ready(function () {
console.log("Selected special date: " + date.format('DD/MM/YYYY'));
}
})
// date input
//let $scheduledate = $schedulemodal.find('#scheduledate');
// select2 for language
@@ -294,8 +310,8 @@ $(document).ready(function () {
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);
@@ -403,9 +419,9 @@ $(document).ready(function () {
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) {
@@ -488,8 +504,9 @@ $(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);
@@ -562,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) {

View File

@@ -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,10 +404,13 @@ $(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 () {
Set_WebAccessSetting();
$('#webaccesssave').click(function () {
Set_WebAccessSetting();
});
$('#adzansave').click(function () {
Set_AdzanSetting();
});

View File

@@ -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>
@@ -292,7 +291,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/soundchannel.js"></script>
<script src="assets/js/broadcastzones.js"></script>
</body>

View File

@@ -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>

View File

@@ -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>&nbsp;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>&nbsp;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>&nbsp;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>&nbsp;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>&nbsp;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>&nbsp; &nbsp;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>&nbsp;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>&nbsp;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>&nbsp;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>&nbsp;Logout</a></li>
@@ -160,9 +162,11 @@
<script src="assets/js/bs-init.js"></script>
<script src="assets/js/jquery-3.7.1.min.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>

View File

@@ -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>&nbsp;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>&nbsp; &nbsp;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>&nbsp;Logout</a></li>
@@ -120,9 +122,11 @@
<script src="assets/js/bs-init.js"></script>
<script src="assets/js/jquery-3.7.1.min.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>

View File

@@ -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>
@@ -99,7 +98,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/languagelink.js"></script>
</body>

View File

@@ -14,8 +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/litepicker.css">
<link rel="stylesheet" href="assets/css/Login-Form-Basic-icons.css">
<link rel="stylesheet" href="assets/css/styles.css">
</head>
@@ -71,8 +69,6 @@
</div>
<script src="assets/bootstrap/js/bootstrap.min.js"></script>
<script src="assets/js/bs-init.js"></script>
<script src="assets/js/litepicker.js"></script>
<script src="assets/js/datatables.js"></script>
<script src="assets/js/log.js"></script>
</body>

View File

@@ -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>

View File

@@ -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>
@@ -136,7 +132,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/messagebank.js"></script>
</body>

View File

@@ -15,7 +15,6 @@
<link rel="stylesheet" href="assets/css/FontAwesome.css">
<link rel="stylesheet" href="assets/fonts/font-awesome.min.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>
@@ -4684,7 +4683,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/overview.js"></script>
</body>

View File

@@ -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>

View File

@@ -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>
@@ -120,7 +119,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/soundbank.js"></script>
</body>

View File

@@ -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>
@@ -91,7 +90,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>
</body>
</html>

View File

@@ -14,8 +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/litepicker.css">
<link rel="stylesheet" href="assets/css/Login-Form-Basic-icons.css">
<link rel="stylesheet" href="assets/css/styles.css">
</head>
@@ -80,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>
@@ -213,8 +206,6 @@
</div>
<script src="assets/bootstrap/js/bootstrap.min.js"></script>
<script src="assets/js/bs-init.js"></script>
<script src="assets/js/litepicker.js"></script>
<script src="assets/js/datatables.js"></script>
<script src="assets/js/schedulebank.js"></script>
</body>

View File

@@ -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>

View File

@@ -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>

View File

@@ -16,6 +16,7 @@ import content.Language
import content.VoiceType
import database.data.Log
import database.MariaDB
import database.table.Table_Adzan
import database.table.Table_BroadcastZones
import database.table.Table_Logs
import database.table.Table_Messagebank
@@ -33,6 +34,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
@@ -43,7 +45,7 @@ lateinit var audioPlayer: AudioPlayer
val StreamerOutputs: MutableMap<String, BarixConnection> = HashMap()
lateinit var udpreceiver: UDPReceiver
lateinit var tcpreceiver: TCPReceiver
const val version = "0.0.29 (09/02/2026)"
const val version = "0.0.30 (12/02/2026)"
// AAS 64 channels
const val max_channel = 64
@@ -55,7 +57,7 @@ lateinit var soundchannelDB: Table_SoundChannel
lateinit var messageDB: Table_Messagebank
lateinit var logDB: Table_Logs
lateinit var adzanTable : Table_Adzan
val contentCache = ContentCache()
/**
* Create necessary folders if not exist
@@ -176,6 +178,11 @@ 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))
)
val subcode01 = MainExtension01()
@@ -305,8 +312,12 @@ fun main(args: Array<String>) {
}
logDB.Add("AAS"," Application started")
adzanTable = Table_Adzan(latitude = config.Get(configKeys.LATITUDE.key).toDouble(),
longitude = config.Get(configKeys.LONGITUDE.key).toDouble(),
timezone = TimeZone.getTimeZone(config.Get(configKeys.TIMEZONE.key))
)
logDB.Add("AAS"," Application started")
// shutdown hook
Runtime.getRuntime().addShutdownHook(Thread ({

View File

@@ -39,8 +39,8 @@ 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) {
@@ -50,6 +50,7 @@ class TCP_Barix_Command_Server {
Logger.info { "Connection closed by Streamer Output with IP $key" }
break
}
if (readbytes == 0) continue
var stringlength = 0
try{

View File

@@ -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")
@@ -352,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
@@ -707,6 +710,8 @@ class Somecodes {
return false
}
/**
* Find a schedule day by its name.
* @param value The name of the schedule day to find.
@@ -752,6 +757,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

View File

@@ -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()
}
}

View File

@@ -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")
}

View 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)

View 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
}
}
}

View 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"
}
}
}

View File

@@ -0,0 +1,155 @@
package database.table
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 java.text.SimpleDateFormat
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".
*/
@Suppress("unused")
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_sound = ""
var dzuhur_sound = ""
var ashar_sound = ""
var maghrib_sound = ""
var isya_sound = ""
init{
// sumber chatgpt Kemenag
params.fajrAngle = 20.0
params.ishaAngle = 18.0
params.madhab = Madhab.SHAFI
timeformatter.timeZone = timezone
}
/**
* 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 (e: 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.
* @return An AdzanPrayerTime object containing the prayer times for today.
*/
fun GetTodayPrayerTimes() : AdzanPrayerTime{
return GetPrayerTimes(Date())
}
/**
* 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
}
}

View File

@@ -47,26 +47,27 @@ class Table_LogSemiAuto(connection: Connection) : dbFunctions<LogSemiauto>("logs
override fun Get(cbOK: Consumer<Unit>?, cbFail: Consumer<String>?) {
List.clear()
try {
val statement = connection.createStatement()
val resultSet = statement?.executeQuery("SELECT * FROM ${super.dbName}")
while (resultSet?.next() == true) {
val log = LogSemiauto(
resultSet.getLong("index").toULong(),
resultSet.getString("date"),
resultSet.getString("time"),
resultSet.getString("source"),
resultSet.getString("description")
)
List.add(log)
}
cbOK?.accept(Unit)
} catch (e: Exception) {
cbFail?.accept("Error fetching ${super.dbName}: ${e.message}")
Logger.error("Error fetching ${super.dbName}: ${e.message}" as Any)
}
throw Exception("Getting all LogSemiauto entries is not supported")
// List.clear()
// try {
// val statement = connection.createStatement()
// val resultSet = statement?.executeQuery("SELECT * FROM ${super.dbName}")
// while (resultSet?.next() == true) {
// val log = LogSemiauto(
// resultSet.getLong("index").toULong(),
// resultSet.getString("date"),
// resultSet.getString("time"),
// resultSet.getString("source"),
// resultSet.getString("description")
// )
// List.add(log)
// }
// cbOK?.accept(Unit)
// } catch (e: Exception) {
// cbFail?.accept("Error fetching ${super.dbName}: ${e.message}")
// Logger.error("Error fetching ${super.dbName}: ${e.message}" as Any)
//
// }
}
fun Add(source: String, description: String){
@@ -182,31 +183,32 @@ class Table_LogSemiAuto(connection: Connection) : dbFunctions<LogSemiauto>("logs
}
override fun Resort(): Boolean {
try{
val statement = connection.createStatement()
val tempdb_name = "temp_${super.dbName}"
// use a temporary table to reorder the index
statement?.executeUpdate("CREATE TABLE IF NOT EXISTS $tempdb_name LIKE ${super.dbName}")
statement?.executeUpdate("TRUNCATE TABLE $tempdb_name")
statement?.executeUpdate(
"INSERT INTO $tempdb_name (date, time, source, description) " +
"SELECT date, time, source, description FROM ${super.dbName} " +
"ORDER BY date , time , source "
)
statement?.executeUpdate("TRUNCATE TABLE ${super.dbName}")
statement?.executeUpdate(
"INSERT INTO ${super.dbName} (date, time, source, description) " +
"SELECT date, time, source, description FROM $tempdb_name"
)
statement?.executeUpdate("DROP TABLE $tempdb_name")
Logger.info("${super.dbName} table resorted by date, time, source" as Any)
// reload the local list
Get()
return true
} catch (e : Exception){
Logger.error { "Error resorting logsemiauto table: ${e.message}" }
return false
}
throw Exception("Resorting LogSemiauto table is not supported")
// try{
// val statement = connection.createStatement()
// val tempdb_name = "temp_${super.dbName}"
// // use a temporary table to reorder the index
// statement?.executeUpdate("CREATE TABLE IF NOT EXISTS $tempdb_name LIKE ${super.dbName}")
// statement?.executeUpdate("TRUNCATE TABLE $tempdb_name")
// statement?.executeUpdate(
// "INSERT INTO $tempdb_name (date, time, source, description) " +
// "SELECT date, time, source, description FROM ${super.dbName} " +
// "ORDER BY date , time , source "
// )
// statement?.executeUpdate("TRUNCATE TABLE ${super.dbName}")
// statement?.executeUpdate(
// "INSERT INTO ${super.dbName} (date, time, source, description) " +
// "SELECT date, time, source, description FROM $tempdb_name"
// )
// statement?.executeUpdate("DROP TABLE $tempdb_name")
// Logger.info("${super.dbName} table resorted by date, time, source" as Any)
// // reload the local list
// Get()
// return true
// } catch (e : Exception){
// Logger.error { "Error resorting logsemiauto table: ${e.message}" }
// return false
// }
}
override fun Import_XLSX(workbook: XSSFWorkbook): Boolean {

View File

@@ -2,9 +2,6 @@ package database.table
import database.data.Log
import database.dbFunctions
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import org.apache.poi.xssf.usermodel.XSSFWorkbook
import org.tinylog.Logger
import java.sql.Connection
@@ -116,28 +113,29 @@ class Table_Logs(connection: Connection) : dbFunctions<Log>("logs", connection,l
* @param cbFail Optional callback invoked upon failure with an error message.
*/
override fun Get(cbOK: Consumer<Unit>?, cbFail: Consumer<String>?) {
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)
}
}
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)
//
// }
// }
}
@@ -196,31 +194,32 @@ class Table_Logs(connection: Connection) : dbFunctions<Log>("logs", connection,l
}
override fun Resort(): Boolean {
try {
val statement = connection.createStatement()
val tempdb_name = "temp_${super.dbName}"
// use a temporary table to reorder the index
statement?.executeUpdate("CREATE TABLE IF NOT EXISTS $tempdb_name LIKE ${super.dbName}")
statement?.executeUpdate("TRUNCATE TABLE $tempdb_name")
statement?.executeUpdate(
"INSERT INTO $tempdb_name (datenya, timenya, machine, description) " +
"SELECT datenya, timenya, machine, description FROM ${super.dbName} " +
"ORDER BY datenya , timenya , machine "
)
statement?.executeUpdate("TRUNCATE TABLE ${super.dbName}")
statement?.executeUpdate(
"INSERT INTO ${super.dbName} (datenya, timenya, machine, description) " +
"SELECT datenya, timenya, machine, description FROM $tempdb_name"
)
statement?.executeUpdate("DROP TABLE $tempdb_name")
Logger.info("${super.dbName} table resorted by datenya, timenya, machine" as Any)
// reload the local list
Get()
return true
} catch (e: Exception) {
Logger.error("Error resorting ${super.dbName} table by datenya, timenya, machine: ${e.message}" as Any)
}
return false
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 {

View File

@@ -32,12 +32,14 @@ class Table_Schedule(connection: Connection) : dbFunctions<ScheduleBank>("schedu
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()
@@ -57,7 +59,6 @@ class Table_Schedule(connection: Connection) : dbFunctions<ScheduleBank>("schedu
// masukin ke todaySchedule yang sudah di sort by Time
todaySchedule.addAll(tempMap.values.sortedBy { it.Time })
println("Todays schedule : $todaySchedule")
}
override fun Create() {

View 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
}
}
}

View 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) }
}
}

View File

@@ -1,6 +1,7 @@
package web
import StreamerOutputs
import adzanTable
import barix.BarixConnection
import broadcastDB
import codes.Somecodes
@@ -46,6 +47,7 @@ import codes.configKeys
import config
import database.data.LogSemiauto
import database.data.QueueTable
import database.table.AdzanSetting
import google.GoogleTTS
import google.autoadd
import google.fileoperation
@@ -58,6 +60,7 @@ import version
import java.io.File
import java.nio.ByteBuffer
import java.nio.file.Path
import java.util.TimeZone
import java.util.UUID
@@ -2467,6 +2470,76 @@ class WebApp(val listenPort: Int, var userlist: List<Pair<String, String>>, val
}
}
path("Settings") {
path("AdzanSetting"){
get{
val todayadzan = adzanTable.GetTodayPrayerTimes()
val value = AdzanSetting(
latitude = _config.Get(configKeys.LATITUDE.key).toDoubleOrNull() ?: 0.0,
longitude = _config.Get(configKeys.LONGITUDE.key).toDoubleOrNull() ?: 0.0,
timezone = _config.Get(configKeys.TIMEZONE.key),
fajar_sound = _config.Get(configKeys.ADZAN_FAJR_SOUND.key),
fajar_enable = _config.Get(configKeys.ADZAN_FAJR_ENABLED.key).toBoolean(),
fajar_time = todayadzan.fajr,
dzuhur_sound = _config.Get(configKeys.ADZAN_DHUHR_SOUND.key),
dzuhur_enable = _config.Get(configKeys.ADZAN_DHUHR_ENABLED.key).toBoolean(),
dzuhur_time = todayadzan.dhuhr,
ashar_sound = _config.Get(configKeys.ADZAN_ASR_SOUND.key),
ashar_enable = _config.Get(configKeys.ADZAN_FAJR_ENABLED.key).toBoolean(),
ashar_time = todayadzan.asr,
maghrib_sound = _config.Get(configKeys.ADZAN_MAGHRIB_SOUND.key),
maghrib_enable = _config.Get(configKeys.ADZAN_MAGHRIB_ENABLED.key).toBoolean(),
maghrib_time = todayadzan.maghrib,
isya_sound = _config.Get(configKeys.ADZAN_ISHA_SOUND.key),
isya_enable = _config.Get(configKeys.ADZAN_ISHA_ENABLED.key).toBoolean(),
isya_time = todayadzan.isha
)
it.json(value)
}
post{
val json: JsonNode = objectmapper.readTree(it.body())
try{
val newsetting = AdzanSetting.FromJsonNode(json)
_config.CompareWithAdzanSetting(newsetting).let { changes ->
if (changes.isNotEmpty()){
// something changes
changes.forEach { change ->
_config.Set(change.key, change.newValue)
Logger.info{"AdzanSetting change: ${change.key} from ${change.oldValue} to ${change.newValue}"}
when(change.key){
configKeys.LATITUDE.key -> adzanTable.ChangeLatitude(change.newValue.toDouble())
configKeys.LONGITUDE.key -> adzanTable.ChangeLongitude(change.newValue.toDouble())
configKeys.TIMEZONE.key -> adzanTable.ChangeTimeZone(change.newValue)
configKeys.ADZAN_FAJR_ENABLED.key -> adzanTable.fajar_enable = change.newValue.toBoolean()
configKeys.ADZAN_DHUHR_ENABLED.key -> adzanTable.dzuhur_enable = change.newValue.toBoolean()
configKeys.ADZAN_ASR_ENABLED.key -> adzanTable.ashar_enable = change.newValue.toBoolean()
configKeys.ADZAN_MAGHRIB_ENABLED.key -> adzanTable.maghrib_enable = change.newValue.toBoolean()
configKeys.ADZAN_ISHA_ENABLED.key -> adzanTable.isya_enable = change.newValue.toBoolean()
configKeys.ADZAN_FAJR_SOUND.key -> adzanTable.fajar_sound = change.newValue
configKeys.ADZAN_DHUHR_SOUND.key -> adzanTable.dzuhur_sound = change.newValue
configKeys.ADZAN_ASR_SOUND.key -> adzanTable.ashar_sound = change.newValue
configKeys.ADZAN_MAGHRIB_SOUND.key -> adzanTable.maghrib_sound = change.newValue
configKeys.ADZAN_ISHA_SOUND.key -> adzanTable.isya_sound = change.newValue
else -> {
// nothing to do
}
}
}
_config.Save()
Logger.info { "Updated AdzanSetting configuration" }
} else {
Logger.info { "No changes detected in AdzanSetting"}
}
}
it.result(objectmapper.writeValueAsString(resultMessage("OK")))
} catch (e : Exception){
it.status(400)
.result(objectmapper.writeValueAsString(resultMessage("Incomplete AdzanSetting data: ${e.message}")))
}
}
}
path("OldResultDays") {
get {
it.result(objectmapper.writeValueAsString(resultMessage(_config.Get(configKeys.AUTO_DELETE_RESULT_DAYS.key))))