mirror of
https://github.com/tenrok/vue-cron-editor-bootstrap.git
synced 2026-06-20 20:10:34 +03:00
refactoring
This commit is contained in:
@@ -0,0 +1,137 @@
|
||||
<template>
|
||||
<b-tabs v-model="activeTabIndex" content-class="p-2" @input="reset">
|
||||
<b-tab :value="0" :title="translate('minutes')" class="minutes-tab">
|
||||
<b-row>
|
||||
<b-col class="d-flex align-items-center">
|
||||
<span class="mr-1">{{ translate('every') }}</span>
|
||||
<b-form-input v-model="editorData.minuteInterval" type="number" min="1" max="59" class="mr-1" style="width: 80px" />
|
||||
<span>{{ translate('mminutes') }}</span>
|
||||
</b-col>
|
||||
</b-row>
|
||||
</b-tab>
|
||||
<b-tab :value="1" :title="translate('hourly')" class="hourly-tab">
|
||||
<b-row>
|
||||
<b-col class="d-flex align-items-center">
|
||||
<span class="mr-1">{{ translate('every') }}</span>
|
||||
<b-form-input v-model="editorData.hourInterval" type="number" min="1" max="24" class="mr-1" style="width: 80px" />
|
||||
<span class="mr-1">{{ translate('hoursOnMinute') }}</span>
|
||||
<b-form-input v-model="editorData.minutes" type="number" min="0" max="59" style="width: 80px" />
|
||||
</b-col>
|
||||
</b-row>
|
||||
</b-tab>
|
||||
<b-tab :value="2" :title="translate('daily')" class="daily-tab">
|
||||
<b-row>
|
||||
<b-col class="d-flex align-items-center">
|
||||
<span class="mr-1">{{ translate('every') }}</span>
|
||||
<b-form-input v-model="editorData.dayInterval" type="number" class="mr-1" style="width: 80px" />
|
||||
<span class="mr-1">{{ translate('daysAt') }}</span>
|
||||
<b-form-timepicker :value="dateTime" :hour12="false" now-button style="width: auto" @input="setDateTime" />
|
||||
</b-col>
|
||||
</b-row>
|
||||
</b-tab>
|
||||
<b-tab :value="3" :title="translate('weekly')" class="weekly-tab">
|
||||
<b-row>
|
||||
<b-col class="d-flex align-items-center">
|
||||
<span class="mr-3">{{ translate('onlyOn') }}</span>
|
||||
<b-form-checkbox v-model="editorData.days" value="1" class="mr-3">{{ translate('mon') }}</b-form-checkbox>
|
||||
<b-form-checkbox v-model="editorData.days" value="2" class="mr-3">{{ translate('tue') }}</b-form-checkbox>
|
||||
<b-form-checkbox v-model="editorData.days" value="3" class="mr-3">{{ translate('wed') }}</b-form-checkbox>
|
||||
<b-form-checkbox v-model="editorData.days" value="4" class="mr-3">{{ translate('thu') }}</b-form-checkbox>
|
||||
<b-form-checkbox v-model="editorData.days" value="5" class="mr-3">{{ translate('fri') }}</b-form-checkbox>
|
||||
<b-form-checkbox v-model="editorData.days" value="6" class="mr-3">{{ translate('sat') }}</b-form-checkbox>
|
||||
<b-form-checkbox v-model="editorData.days" value="0" class="mr-3">{{ translate('sun') }}</b-form-checkbox>
|
||||
<span class="mr-1">{{ translate('at') }}</span>
|
||||
<b-form-timepicker :value="dateTime" :hour12="false" now-button style="width: auto" @input="setDateTime" />
|
||||
</b-col>
|
||||
</b-row>
|
||||
</b-tab>
|
||||
<b-tab :value="4" :title="translate('monthly')" class="monthly-tab">
|
||||
<b-row>
|
||||
<b-col class="d-flex align-items-center">
|
||||
<span class="mr-1">{{ translate('onThe') }}</span>
|
||||
<b-form-input v-model="editorData.day" type="number" min="1" max="31" class="mr-1" style="width: 80px" />
|
||||
<span class="mr-1">{{ translate('dayOfEvery') }}</span>
|
||||
<b-form-input v-model="editorData.monthInterval" type="number" min="1" class="mr-1" style="width: 80px" />
|
||||
<span class="mr-1">{{ translate('monthsAt') }}</span>
|
||||
<b-form-timepicker :value="dateTime" :hour12="false" now-button style="width: auto" @input="setDateTime" />
|
||||
</b-col>
|
||||
</b-row>
|
||||
</b-tab>
|
||||
<b-tab :value="5" :title="translate('advanced')" class="advanced-tab">
|
||||
<b-row>
|
||||
<b-col class="d-flex" style="align-items: center">
|
||||
<span class="mr-2">{{ translate('cronExpression') }}</span>
|
||||
<b-form-input v-model="editorData.cronExpression" class="mr-2" style="width: auto" />
|
||||
<span>{{ explanation }}</span>
|
||||
</b-col>
|
||||
</b-row>
|
||||
</b-tab>
|
||||
</b-tabs>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import vueCronEditorMixin from '../core/vueCronEditorMixin'
|
||||
import { BTabs, BTab, BRow, BCol, BFormCheckbox, BFormInput, BFormTimepicker } from '@tenrok/bootstrap-vue'
|
||||
|
||||
export default {
|
||||
name: 'VueCronEditorBootstrap',
|
||||
|
||||
components: {
|
||||
BTabs,
|
||||
BTab,
|
||||
BRow,
|
||||
BCol,
|
||||
BFormCheckbox,
|
||||
BFormInput,
|
||||
BFormTimepicker,
|
||||
},
|
||||
|
||||
mixins: [vueCronEditorMixin],
|
||||
|
||||
data: () => ({
|
||||
activeTabIndex: null,
|
||||
tabs: [
|
||||
{ idx: 0, key: 'minutes' },
|
||||
{ idx: 1, key: 'hourly' },
|
||||
{ idx: 2, key: 'daily' },
|
||||
{ idx: 3, key: 'weekly' },
|
||||
{ idx: 4, key: 'monthly' },
|
||||
{ idx: 5, key: 'advanced' },
|
||||
],
|
||||
}),
|
||||
|
||||
computed: {
|
||||
dateTime() {
|
||||
return `${this.editorData.hours}:${this.editorData.minutes}:00`
|
||||
},
|
||||
},
|
||||
|
||||
watch: {
|
||||
currentTab() {
|
||||
this.activeTabIndex = this.tabs.find(t => t.key === this.currentTab).idx
|
||||
},
|
||||
},
|
||||
|
||||
created() {
|
||||
this.activeTabIndex = this.tabs.find(t => t.key === this.currentTab).idx
|
||||
},
|
||||
|
||||
methods: {
|
||||
reset(tabIndex) {
|
||||
const tab = this.tabs.find(t => t.idx === tabIndex)
|
||||
if (tab) {
|
||||
this.resetToTab(tab.key)
|
||||
}
|
||||
},
|
||||
|
||||
setDateTime(time) {
|
||||
if (time == null) {
|
||||
return
|
||||
}
|
||||
const splittedTime = time.split(':')
|
||||
this.editorData.hours = splittedTime[0]
|
||||
this.editorData.minutes = splittedTime[1]
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,151 @@
|
||||
interface MinutesTabUpdatedEvent {
|
||||
type: 'minutes'
|
||||
minuteInterval: number
|
||||
}
|
||||
|
||||
interface HourlyTabUpdatedEvent {
|
||||
type: 'hourly'
|
||||
minutes: number
|
||||
hourInterval: number
|
||||
}
|
||||
|
||||
interface DailyTabUpdatedEvent {
|
||||
type: 'daily'
|
||||
minutes: number
|
||||
hours: number
|
||||
dayInterval: number
|
||||
}
|
||||
|
||||
interface WeeklyTabUpdatedEvent {
|
||||
type: 'weekly'
|
||||
minutes: number
|
||||
hours: number
|
||||
days: string[]
|
||||
}
|
||||
|
||||
interface MonthlyTabUpdatedEvent {
|
||||
type: 'monthly'
|
||||
minutes: number
|
||||
hours: number
|
||||
day: number
|
||||
monthInterval: number
|
||||
}
|
||||
|
||||
interface AdvancedTabUpdatedEvent {
|
||||
type: 'advanced'
|
||||
cronExpression: string
|
||||
}
|
||||
|
||||
export type TabUpdatedEvent =
|
||||
| MinutesTabUpdatedEvent
|
||||
| HourlyTabUpdatedEvent
|
||||
| DailyTabUpdatedEvent
|
||||
| WeeklyTabUpdatedEvent
|
||||
| MonthlyTabUpdatedEvent
|
||||
| AdvancedTabUpdatedEvent
|
||||
|
||||
export type TabKey = TabUpdatedEvent[keyof TabUpdatedEvent]
|
||||
|
||||
export const buildExpression = (event: TabUpdatedEvent): string => {
|
||||
if (event.type === 'minutes') {
|
||||
return `*/${event.minuteInterval} * * * *`
|
||||
}
|
||||
|
||||
if (event.type === 'hourly') {
|
||||
return `${event.minutes} */${event.hourInterval} * * *`
|
||||
}
|
||||
|
||||
if (event.type === 'daily') {
|
||||
return `${event.minutes} ${event.hours} */${event.dayInterval} * *`
|
||||
}
|
||||
|
||||
if (event.type === 'weekly') {
|
||||
if ([0, 7].includes(event.days.length)) {
|
||||
return `${event.minutes} ${event.hours} * * *`
|
||||
} else {
|
||||
return (
|
||||
`${event.minutes} ${event.hours} * * ` +
|
||||
`${event.days
|
||||
.filter(d => d)
|
||||
.sort()
|
||||
.join()}`
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (event.type === 'monthly') {
|
||||
return `${event.minutes} ${event.hours} ${event.day} */${event.monthInterval} *`
|
||||
}
|
||||
|
||||
if (event.type === 'advanced') {
|
||||
return event.cronExpression
|
||||
}
|
||||
|
||||
throw `unknown event type: ${event}`
|
||||
}
|
||||
|
||||
export const parseExpression = (expression: string): TabUpdatedEvent => {
|
||||
let groups = null
|
||||
|
||||
if (expression!.split(' ').length != 5) {
|
||||
return {
|
||||
type: 'advanced',
|
||||
cronExpression: expression,
|
||||
}
|
||||
}
|
||||
|
||||
if ((groups = expression.match(/^\*\/(\d+) \* \* \* \*$/))) {
|
||||
return {
|
||||
type: 'minutes',
|
||||
minuteInterval: Number(groups[1]),
|
||||
}
|
||||
}
|
||||
|
||||
if ((groups = expression.match(/^(\d+) \*\/(\d+) \* \* \*$/))) {
|
||||
return {
|
||||
type: 'hourly',
|
||||
minutes: Number(groups[1]),
|
||||
hourInterval: Number(groups[2]),
|
||||
}
|
||||
}
|
||||
|
||||
if ((groups = expression.match(/^(\d+) (\d+) \*\/(\d+) \* \*$/))) {
|
||||
return {
|
||||
type: 'daily',
|
||||
minutes: Number(groups[1]),
|
||||
hours: Number(groups[2]),
|
||||
dayInterval: Number(groups[3]),
|
||||
}
|
||||
}
|
||||
|
||||
if ((groups = expression.match(/^(\d+) (\d+) \* \* (\d)(,\d)?(,\d)?(,\d)?(,\d)?(,\d)?(,\d)?$/))) {
|
||||
const optionalDaysBeginIndex = 4
|
||||
const matchesEndIndex = 10
|
||||
return {
|
||||
type: 'weekly',
|
||||
minutes: Number(groups[1]),
|
||||
hours: Number(groups[2]),
|
||||
days: [groups[3]].concat(
|
||||
groups
|
||||
.slice(optionalDaysBeginIndex, matchesEndIndex)
|
||||
.map(d => d && d.replace(/,/, ''))
|
||||
.filter(d => d)
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
if ((groups = expression.match(/^(\d+) (\d+) (\d+) \*\/(\d+) \*$/))) {
|
||||
return {
|
||||
type: 'monthly',
|
||||
minutes: Number(groups[1]),
|
||||
hours: Number(groups[2]),
|
||||
day: Number(groups[3]),
|
||||
monthInterval: Number(groups[4]),
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
type: 'advanced',
|
||||
cronExpression: expression,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
export const defaultLocales: Record<string, Record<string, string>> = {
|
||||
en: {
|
||||
every: 'Every',
|
||||
mminutes: 'minute(s)',
|
||||
hoursOnMinute: 'hour(s) on minute',
|
||||
daysAt: 'day(s) at',
|
||||
at: 'at',
|
||||
onThe: 'On the',
|
||||
dayOfEvery: 'day, of every',
|
||||
monthsAt: 'month(s), at',
|
||||
// everyDay: 'Every',
|
||||
mon: 'Mon',
|
||||
tue: 'Tue',
|
||||
wed: 'Wed',
|
||||
thu: 'Thu',
|
||||
fri: 'Fri',
|
||||
sat: 'Sat',
|
||||
sun: 'Sun',
|
||||
// hasToBeBetween: 'Has to be between',
|
||||
// and: 'and',
|
||||
minutes: 'MINUTES',
|
||||
hourly: 'HOURLY',
|
||||
daily: 'DAILY',
|
||||
weekly: 'WEEKLY',
|
||||
monthly: 'MONTHLY',
|
||||
advanced: 'ADVANCED',
|
||||
cronExpression: 'cron expression:',
|
||||
onlyOn: 'Only on',
|
||||
},
|
||||
pl: {
|
||||
every: 'Co',
|
||||
mminutes: 'minut',
|
||||
hoursOnMinute: 'godzin w minucie',
|
||||
daysAt: 'dni o',
|
||||
at: 'o',
|
||||
onThe: '',
|
||||
dayOfEvery: 'dzień miesiąca, co',
|
||||
monthsAt: 'miesięcy, o godzinie',
|
||||
// everyDay: 'W każdy',
|
||||
mon: 'Pon',
|
||||
tue: 'Wt',
|
||||
wed: 'Śr',
|
||||
thu: 'Czw',
|
||||
fri: 'Pt',
|
||||
sat: 'So',
|
||||
sun: 'Nie',
|
||||
// hasToBeBetween: 'Wymagana wartość pomiędzy',
|
||||
// and: 'i',
|
||||
minutes: 'Minuty',
|
||||
hourly: 'Godziny',
|
||||
daily: 'Dni',
|
||||
weekly: 'Tygodnie',
|
||||
monthly: 'Miesiące',
|
||||
advanced: 'Zaawansowane',
|
||||
cronExpression: 'Wyrażenie cron:',
|
||||
onlyOn: 'Co',
|
||||
},
|
||||
}
|
||||
|
||||
export function createI18n(customLocales: Record<string, Record<string, string>>, locale: string): Record<string, string> {
|
||||
const allLocales = { ...defaultLocales, ...customLocales }
|
||||
return allLocales[locale]
|
||||
}
|
||||
@@ -0,0 +1,154 @@
|
||||
/**
|
||||
* vueCronEditorMixin
|
||||
* Core logic of a component.
|
||||
* Functionality dependent on UI frameworks should be implemented in derived components
|
||||
*/
|
||||
|
||||
import { buildExpression, parseExpression, TabUpdatedEvent, TabKey } from './cronExpressions'
|
||||
import * as cronValidator from 'cron-validator'
|
||||
import * as cronstrue from 'cronstrue/i18n'
|
||||
import { createI18n } from './i18n'
|
||||
|
||||
import Vue from 'vue'
|
||||
|
||||
const initialData: Record<TabKey, TabUpdatedEvent> = {
|
||||
minutes: {
|
||||
type: 'minutes',
|
||||
minuteInterval: 1,
|
||||
},
|
||||
hourly: {
|
||||
type: 'hourly',
|
||||
minutes: 0,
|
||||
hourInterval: 1,
|
||||
},
|
||||
daily: {
|
||||
type: 'daily',
|
||||
minutes: 0,
|
||||
hours: 0,
|
||||
dayInterval: 1,
|
||||
},
|
||||
weekly: {
|
||||
type: 'weekly',
|
||||
minutes: 0,
|
||||
hours: 0,
|
||||
days: ['1'],
|
||||
},
|
||||
monthly: {
|
||||
type: 'monthly',
|
||||
hours: 0,
|
||||
minutes: 0,
|
||||
day: 1,
|
||||
monthInterval: 1,
|
||||
},
|
||||
advanced: {
|
||||
type: 'advanced',
|
||||
cronExpression: '',
|
||||
},
|
||||
}
|
||||
|
||||
interface ComponentData {
|
||||
editorData: any
|
||||
currentTab: TabKey
|
||||
innerValue: string | null
|
||||
i18n: Record<string, string> | null
|
||||
}
|
||||
|
||||
export default Vue.extend({
|
||||
props: {
|
||||
value: { type: String, default: '*/1 * * * *' },
|
||||
isAdvancedTabVisible: { type: Boolean, default: true },
|
||||
preserveStateOnSwitchToAdvanced: { type: Boolean, default: false },
|
||||
locale: { type: String, default: 'en' },
|
||||
customLocales: { type: Object, default: null },
|
||||
},
|
||||
|
||||
data() {
|
||||
return <ComponentData>{
|
||||
innerValue: '*/1 * * * *',
|
||||
editorData: Object.assign({}, initialData.minutes),
|
||||
currentTab: 'minutes',
|
||||
i18n: null,
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
explanation(): string {
|
||||
if (!this.innerValue) {
|
||||
return ''
|
||||
}
|
||||
|
||||
return (cronstrue as any).toString(this.innerValue, {
|
||||
locale: this.locale,
|
||||
})
|
||||
},
|
||||
},
|
||||
|
||||
watch: {
|
||||
value: {
|
||||
handler() {
|
||||
if (this.value == this.innerValue) {
|
||||
return
|
||||
}
|
||||
this.loadDataFromExpression()
|
||||
},
|
||||
},
|
||||
|
||||
editorData: {
|
||||
deep: true,
|
||||
handler(changedData) {
|
||||
this.updateCronExpression(changedData)
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
created() {
|
||||
this.i18n = createI18n(this.customLocales, this.locale)
|
||||
this.innerValue = this.value
|
||||
this.loadDataFromExpression()
|
||||
},
|
||||
|
||||
methods: {
|
||||
translate(key: string) {
|
||||
return this.i18n![key]
|
||||
},
|
||||
|
||||
loadDataFromExpression() {
|
||||
const tabData = parseExpression(this.value)
|
||||
this.editorData = { ...tabData }
|
||||
this.currentTab = tabData.type
|
||||
},
|
||||
|
||||
updateCronExpression(event: TabUpdatedEvent) {
|
||||
const cronExpression = buildExpression({ ...event })
|
||||
|
||||
if (cronValidator.isValidCron(cronExpression)) {
|
||||
this.innerValue = cronExpression
|
||||
this.$emit('input', cronExpression)
|
||||
} else {
|
||||
this.innerValue = null
|
||||
this.$emit('input', null)
|
||||
}
|
||||
},
|
||||
|
||||
resetToTab(tabKey: TabKey) {
|
||||
this.currentTab = tabKey
|
||||
|
||||
if (this.preserveStateOnSwitchToAdvanced && tabKey === 'advanced') {
|
||||
this.editorData = {
|
||||
type: 'advanced',
|
||||
cronExpression: this.innerValue,
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
const tabData = parseExpression(this.value)
|
||||
if (tabKey == tabData.type) {
|
||||
this.editorData = Object.assign({}, tabData)
|
||||
return
|
||||
}
|
||||
|
||||
this.editorData = Object.assign({}, initialData[tabKey])
|
||||
this.updateCronExpression(initialData[tabKey])
|
||||
},
|
||||
},
|
||||
})
|
||||
@@ -0,0 +1,18 @@
|
||||
import _Vue from 'vue'
|
||||
import VueCronEditorBootstrap from './components/VueCronEditorBootstrap.vue'
|
||||
|
||||
const components = [VueCronEditorBootstrap]
|
||||
|
||||
class CronEditorPluginOptions {}
|
||||
|
||||
type CronEditorPlugin = {
|
||||
install(vue: typeof _Vue, options?: CronEditorPluginOptions): void
|
||||
}
|
||||
|
||||
const instance: CronEditorPlugin = {
|
||||
install(vue) {
|
||||
components.forEach(component => vue.component(component.name, component))
|
||||
},
|
||||
}
|
||||
|
||||
export default instance
|
||||
Reference in New Issue
Block a user