mirror of https://github.com/4xmen/xshop.git
You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1220 lines
39 KiB
Vue
1220 lines
39 KiB
Vue
3 months ago
|
<template>
|
||
|
<div id="vue-datepicker">
|
||
|
|
||
|
<div id="dp-modal" @click.self="hideModal" @mousedown.self="canCloseModal = true" v-if="modalShow">
|
||
|
<div id="picker">
|
||
|
<div class="equal-width" id="vuejs-tabs">
|
||
|
<div :class="tabIndex == 0?'active-tab':''" @click="tabIndex = 0;">
|
||
|
{{ pTitle }}
|
||
|
</div>
|
||
|
<div :class="tabIndex == 1?'active-tab':''" @click="tabIndex = 1;">
|
||
|
{{ gTitle }}
|
||
|
</div>
|
||
|
<div v-if="timepicker" :class="tabIndex == 2?'active-tab':''" @click="tabIndex = 2;">
|
||
|
{{ tTitle }}
|
||
|
</div>
|
||
|
</div>
|
||
|
<div class="equal-width" v-if="pDate !== null && tabIndex < 2">
|
||
|
<div @click="next" class="vuejsbtn">
|
||
|
<i class="ri-arrow-left-s-line"></i>
|
||
|
</div>
|
||
|
<div @click="monthPick">
|
||
|
<span v-if="tabIndex == 0">
|
||
|
{{ pMonths[parseInt(peDate[1]) - 1] }}
|
||
|
</span>
|
||
|
<span v-if="tabIndex == 1">
|
||
|
{{ gMonths[geDate[1]] }}
|
||
|
</span>
|
||
|
</div>
|
||
|
<div @click="yearPick">
|
||
|
<span v-if="tabIndex == 0">
|
||
|
{{ pDate.parseHindi(peDate[0]) }}
|
||
|
</span>
|
||
|
<span v-if="tabIndex == 1">
|
||
|
{{ geDate[0] }}
|
||
|
</span>
|
||
|
</div>
|
||
|
<div @click="previous" class="vuejsbtn">
|
||
|
<i class="ri-arrow-right-s-line"></i>
|
||
|
</div>
|
||
|
</div>
|
||
|
<div style="overflow: hidden"
|
||
|
@mousedown="startSwipe($event)"
|
||
|
@touchstart="startSwipe($event, $event.touches[0])"
|
||
|
@mousemove="handleSwipe($event)"
|
||
|
@touchmove="handleSwipe($event, $event.touches[0])"
|
||
|
@mouseup="endSwipe($event)"
|
||
|
@touchend="endSwipe($event)"
|
||
|
@mouseleave="endSwipe($event)"
|
||
|
>
|
||
|
<div id="calendar-container" :style="`top:${calContainerTop}px;left:${calContainerLeft}px;`">
|
||
|
<div class="sub-picker" v-if="yPicker" dir="rtl">
|
||
|
<div class="equal-width month-list" v-for="j in 5">
|
||
|
<template v-for="i in 5">
|
||
|
<template v-if="i == 1 && j == 1">
|
||
|
<div @click="startYear -= 23">
|
||
|
<i class="ri-arrow-right-s-line"></i>
|
||
|
</div>
|
||
|
</template>
|
||
|
<template v-else-if="i == 5 && j == 5">
|
||
|
<div @click="startYear += 23">
|
||
|
<i class="ri-arrow-left-s-line"></i>
|
||
|
</div>
|
||
|
</template>
|
||
|
<div v-else class="year"
|
||
|
@click="yearPicking( (startYear - 13) + ( i + ((j - 1)*5)) )">
|
||
|
<span v-if="tabIndex == 0">
|
||
|
{{ pDate.parseHindi((startYear - 13) + (i + ((j - 1) * 5))) }}
|
||
|
</span>
|
||
|
<span v-else>
|
||
|
{{ (startYear - 13) + (i + ((j - 1) * 5)) }}
|
||
|
</span>
|
||
|
</div>
|
||
|
</template>
|
||
|
</div>
|
||
|
</div>
|
||
|
<div class="sub-picker" v-if="pmPicker" dir="rtl">
|
||
|
<div class="equal-width month-list" v-for="(ms,j) in chunkArray(pMonths,3)">
|
||
|
<div v-for="(m,i) in ms" class="month" @click="pMonthPicking((i + (j * 3)))">
|
||
|
{{ m }}
|
||
|
</div>
|
||
|
</div>
|
||
|
</div>
|
||
|
<div class="sub-picker" v-if="gmPicker">
|
||
|
<div class="equal-width month-list" v-for="(ms,j) in chunkArray(gMonths,3)">
|
||
|
<div v-for="(m,i) in ms" class="month" @click="gMonthPicking((i + (j * 3)))">
|
||
|
{{ m }}
|
||
|
</div>
|
||
|
</div>
|
||
|
</div>
|
||
|
<div v-if="tabIndex == 0 || _debug" dir="rtl">
|
||
|
<table>
|
||
|
<thead>
|
||
|
<tr>
|
||
|
<th v-for="day in pWeekDays">
|
||
|
{{ day }}
|
||
|
</th>
|
||
|
</tr>
|
||
|
</thead>
|
||
|
<tbody v-if="pDate !== null">
|
||
|
<tr v-for="week in pArray">
|
||
|
<td v-for="d in week" :class="d.class +' '+ isActive(d)" :title="d.date"
|
||
|
@click="select(d)">
|
||
|
{{ pDate.parseHindi(d.pDay) }}
|
||
|
</td>
|
||
|
</tr>
|
||
|
</tbody>
|
||
|
</table>
|
||
|
</div>
|
||
|
<div v-if="tabIndex == 1 || _debug">
|
||
|
<table>
|
||
|
<thead>
|
||
|
<tr>
|
||
|
<th v-for="day in gWeekDays">
|
||
|
{{ day }}
|
||
|
</th>
|
||
|
</tr>
|
||
|
</thead>
|
||
|
<tbody v-if="pDate !== null">
|
||
|
<tr v-for="week in gArray">
|
||
|
<td v-for="d in week" :class="d.class +' '+ isActive(d) " :title="d.pdate"
|
||
|
@click="select(d)">
|
||
|
{{ d.day }}
|
||
|
</td>
|
||
|
</tr>
|
||
|
</tbody>
|
||
|
</table>
|
||
|
</div>
|
||
|
<div v-if="tabIndex == 2">
|
||
|
<div style="padding-top: 10px">
|
||
|
|
||
|
<div id="clock"
|
||
|
@mousedown="startDrag"
|
||
|
@mousemove="pickMinutes"
|
||
|
@mouseup="endDrag"
|
||
|
@touchstart="startDrag"
|
||
|
@touchmove.prevent="pickMinutes"
|
||
|
@touchend="endDrag"
|
||
|
>
|
||
|
<div id="modes">
|
||
|
<div :class="`vuejs-btn ${mode == 'AM'?'active-selected':''}`"
|
||
|
@click="changeMode('AM')"
|
||
|
@touchend="changeMode('AM')"
|
||
|
>
|
||
|
AM
|
||
|
</div>
|
||
|
<div :class="`vuejs-btn ${mode == 'PM'?'active-selected':''}`"
|
||
|
@click="changeMode('PM')"
|
||
|
@touchend="changeMode('PM')"
|
||
|
>
|
||
|
PM
|
||
|
</div>
|
||
|
</div>
|
||
|
<div id="time">
|
||
|
{{ pDate.make2number(cTime[0]) }} :
|
||
|
{{ pDate.make2number(cTime[1]) }}
|
||
|
</div>
|
||
|
<div id="clock-container">
|
||
|
<div class="wrapper">
|
||
|
<div id="circle"></div>
|
||
|
<div class="bar-seconds">
|
||
|
<span v-for="i in 60" :key="i" :style="{ '--index': i }">
|
||
|
<i :class="{ 'thick-bar': i % 5 === 0 }"></i>
|
||
|
</span>
|
||
|
</div>
|
||
|
<div class="number-hours">
|
||
|
<span v-for="i in 12" :key="i" :style="{ '--index': i }">
|
||
|
<i>
|
||
|
<b @touchend.self="pickHour(i)" @click="pickHour(i)"
|
||
|
:class="((cTime[0] % 12) == i?'active-selected':'')">
|
||
|
{{ i }}
|
||
|
</b>
|
||
|
</i>
|
||
|
</span>
|
||
|
</div>
|
||
|
<div class="hands-box">
|
||
|
<div class="hand minutes"
|
||
|
:style="`transform: rotate(${cTime[1] * 6}deg)`">
|
||
|
<i></i></div>
|
||
|
<div class="hand hours"
|
||
|
:style="`transform: rotate(${cTime[0] * 30 + cTime[1] / 2}deg)`">
|
||
|
<i></i></div>
|
||
|
</div>
|
||
|
</div>
|
||
|
</div>
|
||
|
</div>
|
||
|
</div>
|
||
|
</div>
|
||
|
|
||
|
</div>
|
||
|
</div>
|
||
|
<div id="bottom-bar">
|
||
|
<div>
|
||
|
<div class="vuejs-btn" title="Clear" @click="clear">
|
||
|
<i class="ri-eraser-line"></i>
|
||
|
</div>
|
||
|
</div>
|
||
|
<div class="centered">
|
||
|
<span v-if="pDate != null && '1970-0-1' != vgeDate.join('-')">
|
||
|
[{{ pDate.parseHindi(vpeDate.join('/')) }}] [{{ vgeDate.join('-') }}]
|
||
|
</span>
|
||
|
</div>
|
||
|
<div>
|
||
|
<div class="vuejs-btn" title="Now" @click="nowSelect">
|
||
|
<i class="ri-time-line"></i>
|
||
|
</div>
|
||
|
</div>
|
||
|
</div>
|
||
|
</div>
|
||
|
</div>
|
||
|
<div id="datepicker">
|
||
|
<input @focus="modalShow = true" :id="xid" :placeholder="xtitle"
|
||
|
:class="getClass" type="text"
|
||
|
:value="(val == null || val == ''?'':selectedDateTime)">
|
||
|
<input type="hidden" :name="xname" :value="val">
|
||
|
</div>
|
||
|
</div>
|
||
|
</template>
|
||
|
|
||
|
<script>
|
||
|
|
||
|
import persianDate from './../components/libs/persian-date.js';
|
||
|
|
||
|
const ONE_DAY = 86400;
|
||
|
const ONE_YEAR = ONE_DAY * 365;
|
||
|
|
||
|
function chunkArray(arr, count) {
|
||
|
const result = [];
|
||
|
|
||
|
for (let i = 0; i < arr.length; i += count) {
|
||
|
result.push(arr.slice(i, i + count));
|
||
|
}
|
||
|
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
export default {
|
||
|
name: "vue-datetimepicker",
|
||
|
components: {},
|
||
|
data: () => {
|
||
|
return {
|
||
|
_debug: false, // debug all tabs
|
||
|
|
||
|
/**
|
||
|
* to handling swiping calendar
|
||
|
*/
|
||
|
startX: 0,
|
||
|
startY: 0,
|
||
|
swipeDirection: null,
|
||
|
swipeDistance: 0,
|
||
|
tableRect: null,
|
||
|
calContainerTop: 0,
|
||
|
calContainerLeft: 0,
|
||
|
isSwiping: false,
|
||
|
canCloseModal: false,
|
||
|
|
||
|
/**
|
||
|
* to handling dragging clock
|
||
|
*/
|
||
|
isDragging: false,
|
||
|
fullData: {}, // full data of this date
|
||
|
modalShow: false, // modal handle
|
||
|
pDate: null, // persian date update
|
||
|
startYear: 1970, // start year for year picking
|
||
|
tabIndex: 0, // active tab index
|
||
|
current: null, // current is not selected value just for show calendar
|
||
|
gmPicker: false, // handle gr month picker modal
|
||
|
pmPicker: false,// handle persian month picker modal
|
||
|
yPicker: false, // handle year picker modal
|
||
|
val: null, // selected value
|
||
|
pWeekDays: ['ش', 'ی', 'د', 'س', 'چ', 'پ', 'آ'],
|
||
|
gWeekDays: ['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa'],
|
||
|
pMonths: ['فروردین', 'اردیبهشت', 'خرداد', 'تیر', 'مرداد', 'شهریور', 'مهر', 'آبان', 'آذر', 'دی', 'بهمن', 'اسفند'],
|
||
|
gMonths: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'],
|
||
|
}
|
||
|
},
|
||
|
emits: ['update:modelValue'],
|
||
|
props: {
|
||
|
modelValue: {
|
||
|
default: NaN,
|
||
|
},
|
||
|
xvalue: {
|
||
|
default: null,
|
||
|
type: Number,
|
||
|
},
|
||
|
xmax: {
|
||
|
default: null,
|
||
|
type: Number,
|
||
|
},
|
||
|
xmin: {
|
||
|
default: null,
|
||
|
type: Number,
|
||
|
},
|
||
|
xshow: {
|
||
|
default: 'pdate', // show value
|
||
|
type: String,
|
||
|
},
|
||
|
onSelect: {
|
||
|
default: function (date) {
|
||
|
|
||
|
},
|
||
|
type: Function,
|
||
|
},
|
||
|
pTitle: {
|
||
|
default: 'Persian',
|
||
|
type: String,
|
||
|
},
|
||
|
gTitle: {
|
||
|
default: 'Gregorian',
|
||
|
type: String,
|
||
|
},
|
||
|
tTitle: {
|
||
|
default: 'Time',
|
||
|
type: String,
|
||
|
},
|
||
|
defTab: {
|
||
|
default: 0,
|
||
|
type: Number,
|
||
|
},
|
||
|
xname: {
|
||
|
default: "",
|
||
|
type: String,
|
||
|
},
|
||
|
xtitle: {
|
||
|
default: "",
|
||
|
type: String,
|
||
|
},
|
||
|
xid: {
|
||
|
default: "",
|
||
|
type: String,
|
||
|
},
|
||
|
customClass: {
|
||
|
default: "",
|
||
|
type: String,
|
||
|
},
|
||
|
err: {
|
||
|
default: false,
|
||
|
type: Boolean,
|
||
|
},
|
||
|
timepicker: {
|
||
|
default: false,
|
||
|
type: Boolean,
|
||
|
},
|
||
|
closeOnSelect: {
|
||
|
default: false,
|
||
|
type: Boolean,
|
||
|
},
|
||
|
},
|
||
|
mounted() {
|
||
|
this.pDate = new persianDate();
|
||
|
let dt;
|
||
|
// check value changed by user or not, then ignore xvalue
|
||
|
if (this.val == null) {
|
||
|
|
||
|
|
||
|
if (!isNaN(this.modelValue)) {
|
||
|
dt = new Date(parseInt(this.modelValue) * 1000);
|
||
|
if (this.modelValue == null || this.modelValue == '' || this.modelValue == 'null') {
|
||
|
dt = new Date();
|
||
|
this.val = null;
|
||
|
this.current = Math.floor(new Date() / 1000);
|
||
|
} else {
|
||
|
this.current = new Date(parseInt(this.modelValue));
|
||
|
this.val = this.modelValue;
|
||
|
}
|
||
|
} else {
|
||
|
dt = new Date(parseInt(this.xvalue) * 1000);
|
||
|
if (this.xvalue == null || this.xvalue == '' || this.xvalue == 'null') {
|
||
|
dt = new Date();
|
||
|
this.val = null;
|
||
|
this.current = Math.floor(new Date() / 1000);
|
||
|
} else {
|
||
|
this.current = new Date(parseInt(this.xvalue));
|
||
|
this.val = this.xvalue;
|
||
|
}
|
||
|
}
|
||
|
// tab fix
|
||
|
this.tabIndex = parseInt(this.defTab);
|
||
|
|
||
|
|
||
|
} else {
|
||
|
this.current = this.val;
|
||
|
}
|
||
|
this.fullData = this.makeDateObject(dt)
|
||
|
|
||
|
// if (this.xvalue != this.val){
|
||
|
//
|
||
|
// }
|
||
|
},
|
||
|
computed: {
|
||
|
selectedDateTime() {
|
||
|
// fullData[xshow]
|
||
|
const dt = new Date(this.val * 1000);
|
||
|
return this.makeDateObject(dt)[this.xshow];
|
||
|
},
|
||
|
// get input class
|
||
|
getClass: function () {
|
||
|
if (this.err == true || (typeof this.err == 'String' && this.err.trim() == '1')) {
|
||
|
return 'form-control is-invalid ' + this.customClass;
|
||
|
}
|
||
|
return 'form-control ' + this.customClass;
|
||
|
},
|
||
|
/*
|
||
|
* make array of this month days [gregorian]
|
||
|
*/
|
||
|
gArray: function () {
|
||
|
let result = [];
|
||
|
const baseDate = this.current * 1000;
|
||
|
let d = new Date(baseDate);
|
||
|
const currentMonth = d.getMonth(baseDate);
|
||
|
// find days of last month end by dayweek
|
||
|
for (let i = 0; i >= -7; i--) {
|
||
|
d = new Date(baseDate);
|
||
|
d.setDate(i);
|
||
|
result.push(this.makeDateObject(d, 'previous'));
|
||
|
if (d.getDay() == 0) {
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
result = result.reverse(); // fix sort
|
||
|
let nextCount = 0;
|
||
|
// find days of this month and start of next end by dayweek
|
||
|
for (let i = 1; i <= 45; i++) {
|
||
|
d = new Date(baseDate);
|
||
|
d.setDate(i);
|
||
|
if (d.getMonth() == currentMonth) {
|
||
|
result.push(this.makeDateObject(d));
|
||
|
} else {
|
||
|
if (d.getDay() == 0 && nextCount > 0) {
|
||
|
break;
|
||
|
}
|
||
|
result.push(this.makeDateObject(d, 'next'));
|
||
|
nextCount++;
|
||
|
}
|
||
|
}
|
||
|
return chunkArray(result, 7);
|
||
|
},
|
||
|
/*
|
||
|
* make array of this month days [persian]
|
||
|
*/
|
||
|
pArray: function () {
|
||
|
let result = [];
|
||
|
const baseDate = this.current * 1000;
|
||
|
let d = this.pDate.convertDate2Persian(new Date(baseDate));
|
||
|
const currentMonth = d[1];
|
||
|
// get current date to prev until last month end by day week
|
||
|
for (let i = 0; i > -40; i--) {
|
||
|
let dt = new Date(baseDate + (i * ONE_DAY * 1000));
|
||
|
let pdt = this.pDate.convertDate2Persian(dt);
|
||
|
if (pdt[1] == currentMonth) {
|
||
|
result.push(this.makeDateObject(dt));
|
||
|
} else {
|
||
|
result.push(this.makeDateObject(dt, 'previous'));
|
||
|
if (this.makePWeek(dt) == 0) {
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
// fix sort
|
||
|
result = result.reverse();
|
||
|
// get current date to next until next month end by day week
|
||
|
for (let i = 1; i < 40; i++) {
|
||
|
let dt = new Date(baseDate + (i * ONE_DAY * 1000));
|
||
|
let pdt = this.pDate.convertDate2Persian(dt);
|
||
|
if (pdt[1] == currentMonth) {
|
||
|
result.push(this.makeDateObject(dt));
|
||
|
} else {
|
||
|
result.push(this.makeDateObject(dt, 'next'));
|
||
|
if (this.makePWeek(dt) == 6) {
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
return chunkArray(result, 7);
|
||
|
},
|
||
|
// gregorian date
|
||
|
geDate() {
|
||
|
const baseDate = this.current * 1000;
|
||
|
let d = new Date(baseDate);
|
||
|
return [d.getFullYear(), d.getMonth(), d.getDate()];
|
||
|
},
|
||
|
// persian date
|
||
|
peDate() {
|
||
|
const baseDate = this.current * 1000;
|
||
|
let d = new Date(baseDate);
|
||
|
return this.pDate.convertDate2Persian(d);
|
||
|
},
|
||
|
// gregorian date by value
|
||
|
vgeDate() {
|
||
|
const baseDate = this.val * 1000;
|
||
|
let d = new Date(baseDate);
|
||
|
return [d.getFullYear(), d.getMonth(), d.getDate()];
|
||
|
},
|
||
|
// persian date by value
|
||
|
vpeDate() {
|
||
|
const baseDate = this.val * 1000;
|
||
|
let d = new Date(baseDate);
|
||
|
return this.pDate.convertDate2Persian(d);
|
||
|
},
|
||
|
// current time
|
||
|
cTime() {
|
||
|
const baseDate = this.val * 1000;
|
||
|
let d = new Date(baseDate);
|
||
|
return [d.getHours(), d.getMinutes()];
|
||
|
},
|
||
|
mode() {
|
||
|
const date = new Date(this.val * 1000); // Convert Unix timestamp to milliseconds
|
||
|
const hours = date.getHours();
|
||
|
|
||
|
if (hours >= 12) {
|
||
|
return 'PM';
|
||
|
} else {
|
||
|
return 'AM';
|
||
|
}
|
||
|
},
|
||
|
},
|
||
|
methods: {
|
||
|
// clear input
|
||
|
clear() {
|
||
|
this.hideModal();
|
||
|
this.val = null;
|
||
|
this.fullData[this.xshow] = '';
|
||
|
},
|
||
|
// select now time
|
||
|
nowSelect() {
|
||
|
this.val = Math.floor(new Date() / 1000);
|
||
|
this.select(this.makeDateObject(new Date()));
|
||
|
},
|
||
|
// handle select
|
||
|
select(obj) {
|
||
|
|
||
|
if (this.xmax != null && obj.unix > this.xmax) {
|
||
|
return;
|
||
|
}
|
||
|
if (this.xmin != null && obj.unix < this.xmin) {
|
||
|
return;
|
||
|
}
|
||
|
if (this.isSwiping) {
|
||
|
return false;
|
||
|
}
|
||
|
if (obj.class == 'next') {
|
||
|
this.next();
|
||
|
return false;
|
||
|
}
|
||
|
if (obj.class == 'previous') {
|
||
|
this.previous();
|
||
|
return false;
|
||
|
}
|
||
|
// reset values
|
||
|
this.onSelect(obj);
|
||
|
this.val = obj.unix;
|
||
|
this.fullData = obj;
|
||
|
this.current = this.val = obj.unix;
|
||
|
if (this.closeOnSelect) {
|
||
|
this.canCloseModal = true;
|
||
|
this.hideModal();
|
||
|
}
|
||
|
return true;
|
||
|
|
||
|
},
|
||
|
// select year
|
||
|
yearPicking(i) {
|
||
|
let dt = this.current * 1000;
|
||
|
dt = new Date(dt);
|
||
|
// for gregorian
|
||
|
if (this.tabIndex == 1) {
|
||
|
dt.setFullYear(i);
|
||
|
this.current = Math.floor(dt / 1000);
|
||
|
} else {
|
||
|
// for persian
|
||
|
let cYear = parseInt(this.peDate[0]);
|
||
|
let diff = ONE_YEAR * (i - cYear);
|
||
|
this.current = Math.floor((dt / 1000) + diff);
|
||
|
}
|
||
|
|
||
|
this.yPicker = false;
|
||
|
},
|
||
|
// change gregorian current month
|
||
|
gMonthPicking(i) {
|
||
|
let dt = this.current * 1000;
|
||
|
dt = new Date(dt);
|
||
|
dt.setMonth(parseInt(i));
|
||
|
this.current = Math.floor(dt / 1000);
|
||
|
this.gmPicker = false;
|
||
|
},
|
||
|
// change persian current month
|
||
|
pMonthPicking(i) {
|
||
|
let dt = this.current * 1000;
|
||
|
dt = new Date(dt);
|
||
|
let x = 10;
|
||
|
if ((i + 1) != parseInt(this.peDate[1])) {
|
||
|
if ((i + 1) < parseInt(this.peDate[1])) {
|
||
|
x = -10;
|
||
|
}
|
||
|
// for persian find by loop for dec tolerance
|
||
|
do {
|
||
|
dt.setDate(dt.getDate() + x);
|
||
|
} while ((i + 1) != this.pDate.convertDate2Persian(dt)[1]);
|
||
|
this.current = Math.floor(dt / 1000);
|
||
|
}
|
||
|
|
||
|
this.pmPicker = false;
|
||
|
},
|
||
|
|
||
|
// next month
|
||
|
next() {
|
||
|
let dt = this.current * 1000;
|
||
|
dt = new Date(dt);
|
||
|
// for gregorian
|
||
|
if (this.tabIndex == 1) {
|
||
|
dt.setMonth(dt.getMonth() + 1);
|
||
|
} else {
|
||
|
let currentMonth = this.pDate.convertDate2Persian(new Date(dt))[1];
|
||
|
// for persian find by loop for dec tolerance
|
||
|
do {
|
||
|
dt.setDate(dt.getDate() + 10);
|
||
|
} while (currentMonth == this.pDate.convertDate2Persian(dt)[1]);
|
||
|
}
|
||
|
this.current = Math.floor(dt / 1000);
|
||
|
},
|
||
|
// previous month
|
||
|
previous() {
|
||
|
let dt = this.current * 1000;
|
||
|
dt = new Date(dt);
|
||
|
// for gregorian
|
||
|
if (this.tabIndex == 1) {
|
||
|
dt.setMonth(dt.getMonth() - 1);
|
||
|
} else {
|
||
|
|
||
|
// persian
|
||
|
let currentMonth = this.pDate.convertDate2Persian(new Date(dt))[1];
|
||
|
// for persian find by loop for dec tolerance
|
||
|
do {
|
||
|
dt.setDate(dt.getDate() - 10);
|
||
|
} while (currentMonth == this.pDate.convertDate2Persian(dt)[1]);
|
||
|
}
|
||
|
this.current = Math.floor(dt / 1000);
|
||
|
},
|
||
|
makeDateObject(dt, cls) {
|
||
|
dt.setHours(this.cTime[0], this.cTime[1]);
|
||
|
return {
|
||
|
day: this.pDate.make2number(dt.getDate()), // day
|
||
|
pDay: this.pDate.convertDate2Persian(dt)[2], // persian date
|
||
|
date: dt.getFullYear() + '-' + dt.getMonth() + '-' + dt.getDate(), // gregorian date
|
||
|
datetime: dt.getFullYear() + '-' + dt.getMonth() + '-' + dt.getDate() + ' ' + this.pDate.make2number(this.cTime[0]) + ':' + this.pDate.make2number(this.cTime[1]), // gregorian datetime
|
||
|
pdatetime: this.pDate.convertDate2Persian(dt).join('/') + ' ' + this.pDate.make2number(this.cTime[0]) + ':' + this.pDate.make2number(this.cTime[1]), // persian date
|
||
|
pdate: this.pDate.convertDate2Persian(dt).join('/'), // persian date
|
||
|
hpdatetime: this.pDate.parseHindi(this.pDate.convertDate2Persian(dt).join('/') + ' ' + this.pDate.make2number(this.cTime[0]) + ':' + this.pDate.make2number(this.cTime[1])), // persian date hindi number
|
||
|
hpdate: this.pDate.parseHindi(this.pDate.convertDate2Persian(dt).join('/')), // persian date hindi number
|
||
|
weekDay: dt.getDay(), // week day
|
||
|
class: cls, // class of d
|
||
|
unix: Math.floor(dt / 1000) // unix time stamp
|
||
|
};
|
||
|
},
|
||
|
// make persian week day
|
||
|
makePWeek(dt) {
|
||
|
let t = dt.getDay() + 1 % 7;
|
||
|
if (t == 7) {
|
||
|
return 0
|
||
|
}
|
||
|
return t;
|
||
|
},
|
||
|
// show month pick modal
|
||
|
monthPick() {
|
||
|
// gregorian
|
||
|
if (this.tabIndex == 1) {
|
||
|
this.gmPicker = !this.gmPicker;
|
||
|
this.pmPicker = false;
|
||
|
} else {
|
||
|
// persian
|
||
|
this.pmPicker = !this.pmPicker;
|
||
|
this.gmPicker = false;
|
||
|
}
|
||
|
},
|
||
|
|
||
|
// show year pick modal
|
||
|
yearPick() {
|
||
|
// gregorian
|
||
|
if (this.tabIndex == 1) {
|
||
|
this.startYear = parseInt(this.geDate[0]);
|
||
|
} else {
|
||
|
// persian
|
||
|
this.startYear = parseInt(this.peDate[0]);
|
||
|
}
|
||
|
this.yPicker = !this.yPicker;
|
||
|
},
|
||
|
// is selected this td
|
||
|
isActive(obj) {
|
||
|
let dt = new Date(this.val * 1000);
|
||
|
let r = '';
|
||
|
if (dt.getFullYear() + '-' + dt.getMonth() + '-' + dt.getDate() == obj.date) {
|
||
|
r = 'active-selected';
|
||
|
}
|
||
|
if (this.xmax != null && obj.unix > this.xmax) {
|
||
|
r += ' disabled-date';
|
||
|
}
|
||
|
if (this.xmin != null && obj.unix < this.xmin) {
|
||
|
r += ' disabled-date';
|
||
|
}
|
||
|
return r;
|
||
|
},
|
||
|
// select hour
|
||
|
pickHour(i, ignore = false) {
|
||
|
let dt = new Date(this.val * 1000);
|
||
|
if (ignore) {
|
||
|
dt.setHours(i);
|
||
|
} else {
|
||
|
dt.setHours((this.mode == 'AM' ? i : (i + 12)));
|
||
|
}
|
||
|
dt.setMinutes(this.cTime[1]);
|
||
|
this.val = Math.floor(dt.getTime() / 1000);
|
||
|
},
|
||
|
|
||
|
|
||
|
/**
|
||
|
* drag handling for clock select
|
||
|
*/
|
||
|
startDrag(e) {
|
||
|
if (e.target.tagName != 'B') {
|
||
|
this.isDragging = true;
|
||
|
}
|
||
|
this.dragHandle(e);
|
||
|
},
|
||
|
pickMinutes(e) {
|
||
|
if (!this.isDragging) return;
|
||
|
this.dragHandle(e);
|
||
|
},
|
||
|
endDrag(e) {
|
||
|
this.isDragging = false;
|
||
|
this.dragHandle(e);
|
||
|
},
|
||
|
dragHandle(e) {
|
||
|
|
||
|
e.preventDefault();
|
||
|
if (!this.isDragging) {
|
||
|
return;
|
||
|
}
|
||
|
// calc polar system delta
|
||
|
const touch = e.touches ? e.touches[0] : null;
|
||
|
const eventX = touch ? touch.clientX : e.clientX;
|
||
|
const eventY = touch ? touch.clientY : e.clientY;
|
||
|
|
||
|
const rect = e.currentTarget.getBoundingClientRect();
|
||
|
const centerX = rect.left + rect.width / 2;
|
||
|
const centerY = rect.top + rect.height / 2;
|
||
|
|
||
|
const deltaX = eventX - centerX;
|
||
|
const deltaY = eventY - centerY;
|
||
|
|
||
|
const r = Math.sqrt(deltaX ** 2 + deltaY ** 2);
|
||
|
|
||
|
let theta = Math.atan2(deltaY, deltaX);
|
||
|
theta = ((theta * 180 / Math.PI) + 450) % 360;
|
||
|
|
||
|
// console.log('r:', r);
|
||
|
// console.log('theta:', theta);
|
||
|
if (r > 90 && r < 160) {
|
||
|
const minutes = Math.floor((theta / 360) * 60);
|
||
|
let dt = new Date(this.val * 1000);
|
||
|
dt.setHours(dt.getHours());
|
||
|
dt.setMinutes(minutes);
|
||
|
this.val = Math.floor(dt / 1000);
|
||
|
}
|
||
|
},
|
||
|
|
||
|
|
||
|
/**
|
||
|
* swipe calendar next / perv ( month / year)
|
||
|
*/
|
||
|
|
||
|
startSwipe(e, touch) {
|
||
|
this.isSwiping = true;
|
||
|
this.canCloseModal = false;
|
||
|
this.contentRect = e.currentTarget.getBoundingClientRect();
|
||
|
this.startX = touch ? touch.clientX : e.clientX;
|
||
|
this.startY = touch ? touch.clientY : e.clientY;
|
||
|
this.swipeDirection = null;
|
||
|
this.swipeDistance = 0;
|
||
|
this.hasSwipedOnce = false; // Reset the swipe flag
|
||
|
},
|
||
|
handleSwipe(e, touch) {
|
||
|
e.preventDefault();
|
||
|
if (this.tabIndex == 2) {
|
||
|
return false;
|
||
|
}
|
||
|
if (!this.startX || !this.startY) return;
|
||
|
|
||
|
const currentX = touch ? touch.clientX : e.clientX;
|
||
|
const currentY = touch ? touch.clientY : e.clientY;
|
||
|
|
||
|
const deltaX = currentX - this.startX;
|
||
|
const deltaY = currentY - this.startY;
|
||
|
|
||
|
|
||
|
if (Math.abs(deltaY) > Math.abs(deltaX)) {
|
||
|
this.calContainerTop = deltaY * .5;
|
||
|
this.calContainerLeft = 0;
|
||
|
}
|
||
|
|
||
|
if (Math.abs(deltaY) < Math.abs(deltaX)) {
|
||
|
this.calContainerTop = 0;
|
||
|
this.calContainerLeft = deltaX * .5;
|
||
|
}
|
||
|
|
||
|
|
||
|
this.swipeDirection = Math.abs(deltaX) > Math.abs(deltaY)
|
||
|
? deltaX > 0 ? 'right' : 'left'
|
||
|
: deltaY > 0 ? 'down' : 'up';
|
||
|
|
||
|
this.swipeDistance = this.swipeDirection === 'right' || this.swipeDirection === 'left'
|
||
|
? Math.abs(deltaX)
|
||
|
: Math.abs(deltaY);
|
||
|
|
||
|
// Update content padding based on swipe distance and direction (handle 40%)
|
||
|
if (this.swipeDistance > this.contentRect.width * 0.4 && !this.hasSwipedOnce) {
|
||
|
this.triggerSwipe(this.swipeDirection);
|
||
|
this.hasSwipedOnce = true; // Set the swipe flag to true
|
||
|
}
|
||
|
},
|
||
|
endSwipe() {
|
||
|
this.startX = 0;
|
||
|
this.startY = 0;
|
||
|
this.swipeDirection = null;
|
||
|
this.swipeDistance = 0;
|
||
|
this.calContainerTop = 0;
|
||
|
this.calContainerLeft = 0;
|
||
|
this.isSwiping = false;
|
||
|
},
|
||
|
triggerSwipe(direction) {
|
||
|
// Update content padding based on swipe direction
|
||
|
let y = parseInt(this.peDate[0]);
|
||
|
if (this.tabIndex == 1) {
|
||
|
y = parseInt(this.geDate[1]);
|
||
|
}
|
||
|
switch (direction) {
|
||
|
case 'right':
|
||
|
this.previous();
|
||
|
break;
|
||
|
case 'left':
|
||
|
this.next();
|
||
|
break;
|
||
|
case 'up':
|
||
|
this.yearPicking(y + 1);
|
||
|
break;
|
||
|
case 'down':
|
||
|
this.yearPicking(y - 1);
|
||
|
break;
|
||
|
}
|
||
|
this.endSwipe();
|
||
|
},
|
||
|
|
||
|
|
||
|
// change mode am/pm
|
||
|
changeMode(mode) {
|
||
|
// ignore AM while AM
|
||
|
if (this.mode == 'AM' && mode == 'AM') {
|
||
|
return;
|
||
|
}
|
||
|
// ignore PM while PM
|
||
|
if (this.mode == 'PM' && mode == 'PM') {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if (mode == 'AM') {
|
||
|
|
||
|
if (this.cTime[0] == 12) {
|
||
|
this.pickHour(12);
|
||
|
} else {
|
||
|
this.pickHour(this.cTime[0] - 12, true);
|
||
|
}
|
||
|
} else {
|
||
|
this.pickHour(this.cTime[0] + 12, true);
|
||
|
}
|
||
|
},
|
||
|
|
||
|
selfUpdate() {
|
||
|
let dt;
|
||
|
// check value changed by user or not, then ignore xvalue
|
||
|
if (this.val == null) {
|
||
|
dt = new Date(parseInt(this.xvalue) * 1000);
|
||
|
if (this.xvalue == null || this.xvalue == '' || this.xvalue == 'null') {
|
||
|
dt = new Date();
|
||
|
this.val = null;
|
||
|
this.current = Math.floor(new Date() / 1000);
|
||
|
} else {
|
||
|
this.current = new Date(parseInt(this.xvalue));
|
||
|
this.val = this.xvalue;
|
||
|
}
|
||
|
} else {
|
||
|
this.current = this.val;
|
||
|
}
|
||
|
// this.fullData = this.makeDateObject(dt);
|
||
|
},
|
||
|
// hide modal
|
||
|
hideModal() {
|
||
|
if (this.canCloseModal) {
|
||
|
this.modalShow = false;
|
||
|
}
|
||
|
},
|
||
|
chunkArray: chunkArray,
|
||
|
},
|
||
|
watch: {
|
||
|
val(newValue) {
|
||
|
if (!isNaN(this.modelValue)) {
|
||
|
this.$emit('update:modelValue', newValue);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
</script>
|
||
|
|
||
|
<style scoped>
|
||
|
|
||
|
|
||
|
#vue-datepicker {
|
||
|
font-size: 12pt;
|
||
|
direction: ltr;
|
||
|
}
|
||
|
|
||
|
#dp-modal {
|
||
|
position: fixed;
|
||
|
//display: none; left: 0; right: 0; top: 0; bottom: 0; z-index: 999; background: #00000033; backdrop-filter: blur(4px);
|
||
|
}
|
||
|
|
||
|
#picker {
|
||
|
max-width: 400px;
|
||
|
min-height: 450px;
|
||
|
margin: calc(50vh - 225px) auto;
|
||
|
background: #ffffffdd;
|
||
|
backdrop-filter: blur(4px);
|
||
|
user-select: none;
|
||
|
color: black;
|
||
|
padding: 5px;
|
||
|
font-family: 'Vazir', 'Vazirmatn', sans-serif;
|
||
|
}
|
||
|
|
||
|
#picker table {
|
||
|
border: 1px solid black;
|
||
|
width: 100%;
|
||
|
margin-top: 5px;
|
||
|
}
|
||
|
|
||
|
#picker table td, #picker table th {
|
||
|
border: 1px solid silver;
|
||
|
width: calc(100% / 7);
|
||
|
text-align: center;
|
||
|
padding: 7px;
|
||
|
transition: 500ms;
|
||
|
}
|
||
|
|
||
|
#picker table td:hover {
|
||
|
background: deepskyblue;
|
||
|
cursor: pointer;
|
||
|
}
|
||
|
|
||
|
#picker .next, #picker .previous {
|
||
|
color: gray;
|
||
|
}
|
||
|
|
||
|
.equal-width {
|
||
|
display: grid;
|
||
|
grid-auto-columns: minmax(0, 1fr);
|
||
|
grid-auto-flow: column;
|
||
|
text-align: center;
|
||
|
cursor: pointer;
|
||
|
}
|
||
|
|
||
|
.equal-width div {
|
||
|
padding: .5rem 5px;
|
||
|
font-weight: 800;
|
||
|
}
|
||
|
|
||
|
.equal-width div i {
|
||
|
font-size: 25px;
|
||
|
}
|
||
|
|
||
|
#bottom-bar {
|
||
|
display: grid;
|
||
|
grid-template-columns: 1fr 2fr 1fr;
|
||
|
text-align: center;
|
||
|
}
|
||
|
|
||
|
#bottom-bar > div {
|
||
|
padding: 7px 4px;
|
||
|
}
|
||
|
|
||
|
#vuejs-tabs {
|
||
|
border: 1px solid gray;
|
||
|
margin-bottom: .5rem;
|
||
|
}
|
||
|
|
||
|
.equal-width div:hover {
|
||
|
background: teal;
|
||
|
color: white;;
|
||
|
}
|
||
|
|
||
|
.active-tab {
|
||
|
background: deepskyblue;
|
||
|
color: white;
|
||
|
}
|
||
|
|
||
|
.vuejsbtn {
|
||
|
padding: 1px !important;
|
||
|
}
|
||
|
|
||
|
#calendar-container {
|
||
|
position: relative;
|
||
|
min-height: 285px;
|
||
|
}
|
||
|
|
||
|
.sub-picker {
|
||
|
position: absolute;
|
||
|
left: 0;
|
||
|
top: 0;
|
||
|
bottom: 0;
|
||
|
right: 0;
|
||
|
background: white;
|
||
|
}
|
||
|
|
||
|
.month {
|
||
|
padding: 1.4rem 0 !important;
|
||
|
}
|
||
|
|
||
|
.year {
|
||
|
padding: .97rem 0 !important;
|
||
|
}
|
||
|
|
||
|
.vuejs-btn {
|
||
|
padding: 3px;
|
||
|
background: silver;
|
||
|
display: block;
|
||
|
cursor: pointer;
|
||
|
}
|
||
|
|
||
|
.vuejs-btn:hover {
|
||
|
background: deepskyblue;
|
||
|
color: white;
|
||
|
}
|
||
|
|
||
|
.vuejs-btn i {
|
||
|
font-size: 20px;
|
||
|
}
|
||
|
|
||
|
.centered {
|
||
|
display: flex;
|
||
|
align-items: center;
|
||
|
justify-content: center;
|
||
|
}
|
||
|
|
||
|
.centered span {
|
||
|
margin-top: 6px;
|
||
|
display: inline-block;
|
||
|
}
|
||
|
|
||
|
.active-selected {
|
||
|
background: teal;
|
||
|
color: yellow !important;
|
||
|
}
|
||
|
|
||
|
#clock {
|
||
|
padding: 33.5px 65px;
|
||
|
}
|
||
|
|
||
|
#circle {
|
||
|
position: absolute;
|
||
|
background: deepskyblue;
|
||
|
width: 6px;
|
||
|
height: 6px;
|
||
|
left: calc(50% - 3px);
|
||
|
top: calc(50% - 3px);
|
||
|
border-radius: 50%;
|
||
|
z-index: 5;
|
||
|
}
|
||
|
|
||
|
.wrapper {
|
||
|
position: relative;
|
||
|
width: 255px;
|
||
|
height: 255px;
|
||
|
border-radius: 50%;
|
||
|
display: flex;
|
||
|
justify-content: center;
|
||
|
align-items: center;
|
||
|
}
|
||
|
|
||
|
.bar-seconds,
|
||
|
.number-hours {
|
||
|
position: absolute;
|
||
|
width: 100%;
|
||
|
height: 100%;
|
||
|
border-radius: 50%;
|
||
|
}
|
||
|
|
||
|
.bar-seconds span {
|
||
|
position: absolute;
|
||
|
transform: rotate(calc(var(--index) * 6deg));
|
||
|
inset: -20px;
|
||
|
text-align: center;
|
||
|
pointer-events: none;
|
||
|
}
|
||
|
|
||
|
.bar-seconds span i {
|
||
|
display: inline-block;
|
||
|
width: 2px;
|
||
|
height: 12px;
|
||
|
background: deepskyblue;
|
||
|
border-radius: 2px;
|
||
|
box-shadow: 0 0 10px deepskyblue;
|
||
|
pointer-events: none;
|
||
|
font-style: normal;
|
||
|
}
|
||
|
|
||
|
.bar-seconds span:nth-child(5n) i { /* 5n pour dire tout les mutliples de 5 */
|
||
|
width: 6px;
|
||
|
height: 18px;
|
||
|
transform: translateY(1px);
|
||
|
}
|
||
|
|
||
|
.number-hours span {
|
||
|
position: absolute;
|
||
|
transform: rotate(calc(var(--index) * 30deg));
|
||
|
inset: 6px;
|
||
|
text-align: center;
|
||
|
pointer-events: none;
|
||
|
}
|
||
|
|
||
|
.number-hours span i {
|
||
|
font-size: 25px;
|
||
|
color: deepskyblue;
|
||
|
transform: rotate(calc(var(--index) * -30deg));
|
||
|
pointer-events: none;
|
||
|
font-style: normal;
|
||
|
}
|
||
|
|
||
|
.number-hours span i b {
|
||
|
text-decoration: none;
|
||
|
color: deepskyblue;
|
||
|
pointer-events: all;
|
||
|
display: inline-block;
|
||
|
width: 40px;
|
||
|
border-radius: 50%;
|
||
|
height: 40px;
|
||
|
box-sizing: border-box;
|
||
|
padding: 2px;
|
||
|
font-weight: 400;
|
||
|
}
|
||
|
|
||
|
.number-hours span i b:hover {
|
||
|
background: teal;
|
||
|
color: white;
|
||
|
}
|
||
|
|
||
|
.hands-box {
|
||
|
position: relative;
|
||
|
display: flex;
|
||
|
justify-content: center;
|
||
|
align-items: center;
|
||
|
pointer-events: none;
|
||
|
}
|
||
|
|
||
|
.hands-box .hand {
|
||
|
position: absolute;
|
||
|
border-radius: 50%;
|
||
|
display: flex;
|
||
|
justify-content: center;
|
||
|
}
|
||
|
|
||
|
.hands-box .hand i {
|
||
|
display: inline-block;
|
||
|
transform-origin: bottom;
|
||
|
border-radius: 50%;
|
||
|
box-shadow: 0 0 10px deepskyblue;
|
||
|
}
|
||
|
|
||
|
.hands-box .hours {
|
||
|
width: 180px;
|
||
|
height: 180px;
|
||
|
}
|
||
|
|
||
|
.hands-box .hours i {
|
||
|
width: 8px;
|
||
|
height: 90px;
|
||
|
background: deepskyblue;
|
||
|
}
|
||
|
|
||
|
.hands-box .minutes {
|
||
|
width: 275px;
|
||
|
height: 275px;
|
||
|
}
|
||
|
|
||
|
.hands-box .minutes i {
|
||
|
width: 6px;
|
||
|
height: 140px;
|
||
|
background: dodgerblue;
|
||
|
border-radius: 2px;
|
||
|
}
|
||
|
|
||
|
#modes {
|
||
|
position: absolute;
|
||
|
left: 5px;
|
||
|
top: 5px;
|
||
|
}
|
||
|
|
||
|
#modes .vuejs-btn {
|
||
|
padding: 5px;
|
||
|
width: 40px;
|
||
|
text-align: center;
|
||
|
padding-top: 10px;
|
||
|
}
|
||
|
|
||
|
#time {
|
||
|
position: absolute;
|
||
|
right: 5px;
|
||
|
top: 5px;
|
||
|
font-size: 25px;
|
||
|
}
|
||
|
|
||
|
.disabled-date {
|
||
|
background: silver;
|
||
|
}
|
||
|
</style>
|