2
0
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:
2023-10-12 09:31:35 +03:00
parent 7b200a750b
commit 5d7e42ab90
32 changed files with 20862 additions and 21646 deletions
+137
View File
@@ -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>
+151
View File
@@ -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,
}
}
+63
View File
@@ -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]
}
+154
View File
@@ -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])
},
},
})
+18
View File
@@ -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