2
0
mirror of https://github.com/tenrok/vue-cron-editor-bootstrap.git synced 2026-06-17 19:30: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
+1 -1
View File
@@ -1,3 +1,3 @@
module.exports = {
presets: ['@vue/cli-plugin-babel/preset']
presets: ['@vue/cli-plugin-babel/preset'],
}
+1 -12
View File
@@ -1,19 +1,8 @@
# EditorConfig is awesome: https://EditorConfig.org
# top-most EditorConfig file
root = true
# Unix-style newlines with a newline ending every file
[*]
end_of_line = lf
insert_final_newline = true
# Matches multiple files with brace expansion notation
# Set default charset
[*]
charset = utf-8
# Space indentation
[*]
indent_style = space
indent_style = tab
indent_size = 2
+19 -22
View File
@@ -1,25 +1,22 @@
const isProd = process.env.NODE_ENV === 'production'
const isProduction = process.env.NODE_ENV === 'production'
module.exports = {
root: true,
env: {
node: true,
},
parser: 'vue-eslint-parser',
parserOptions: {
parser: '@typescript-eslint/parser',
},
extends: [
'plugin:vue/recommended',
'plugin:@typescript-eslint/recommended', // Uses the recommended rules from the @typescript-eslint/eslint-plugin
],
rules: {
curly: 'warn',
'no-console': isProd ? 'error' : 'off',
'no-debugger': isProd ? 'error' : 'off',
'vue/max-attributes-per-line': 'off',
'vue/singleline-html-element-content-newline': 'off',
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/no-non-null-assertion': 'off',
},
root: true,
env: {
node: true,
},
parserOptions: {
parser: '@typescript-eslint/parser',
ecmaVersion: 2020,
},
extends: ['plugin:vue/recommended', 'eslint:recommended', '@vue/typescript/recommended', 'plugin:prettier/recommended'],
rules: {
curly: 'warn',
'no-console': isProduction ? 'error' : 'off',
'no-debugger': isProduction ? 'error' : 'off',
'vue/max-attributes-per-line': 'off',
'vue/singleline-html-element-content-newline': 'off',
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/no-non-null-assertion': 'off',
},
}
+1
View File
@@ -0,0 +1 @@
* text=auto eol=lf
+31
View File
@@ -0,0 +1,31 @@
name: Deploy to GH Pages
on:
push:
branches:
- main
jobs:
build-deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout 🛎️
uses: actions/checkout@master
with:
persist-credentials: false
- name: Install 🔧
run: npm install
- name: Build 🏗️
run: npm run demo:build
env:
NODE_ENV: production
- name: Deploy to GH Pages 🚀
if: ${{ github.event_name != 'pull_request' }}
uses: peaceiris/actions-gh-pages@v3
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: demo
-29
View File
@@ -1,29 +0,0 @@
name: Node.js Package
on:
release:
types: [created]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v1
with:
node-version: 12
- run: npm ci
publish-npm:
needs: build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v1
with:
node-version: 12
registry-url: https://registry.npmjs.org/
- run: npm ci
- run: npm publish
env:
NODE_AUTH_TOKEN: ${{secrets.npm_token}}
+11 -20
View File
@@ -1,26 +1,17 @@
.DS_Store
### VisualStudioCode ###
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
# local env files
.env.local
.env.*.local
# Log files
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Editor directories and files
.idea
.vscode
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
### VisualStudioCode Patch ###
# Ignore all local history of files
**/.history
# npm
node_modules
# Output directories
/demo
/dist
/demo/
/dist/
+2
View File
@@ -0,0 +1,2 @@
registry=https://registry.npmjs.org/
@tenrok:registry=https://registry.npmjs.org/
+5 -4
View File
@@ -1,8 +1,9 @@
// https://prettier.io/docs/en/options.html
module.exports = {
arrowParens: 'avoid', // Include parentheses around a sole arrow function parameter
printWidth: 120, // Specify the line length that the printer will wrap on
semi: false, // Print semicolons at the ends of statements
singleQuote: true // Use single quotes instead of double quotes
arrowParens: 'avoid',
printWidth: 140,
semi: false,
singleQuote: true,
useTabs: true,
}
+3
View File
@@ -0,0 +1,3 @@
{
"recommendations": ["editorconfig.editorconfig", "esbenp.prettier-vscode"]
}
+9
View File
@@ -0,0 +1,9 @@
{
"editor.fontFamily": "'Fira Code', Consolas, 'Courier New', monospace",
"editor.fontLigatures": true,
"editor.fontWeight": "400",
"editor.foldingStrategy": "indentation",
"editor.formatOnSave": true,
"editor.rulers": [140],
"editor.defaultFormatter": "esbenp.prettier-vscode"
}
-63
View File
@@ -1,63 +0,0 @@
# Changelog
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
### [0.2.8](https://github.com/karoletrych/vue-cron-editor/compare/v0.2.7...v0.2.8) (2020-04-22)
### Features
* add explaining of advanced expressions, move i18n to mixin ([0560d1f](https://github.com/karoletrych/vue-cron-editor/commit/0560d1f6dd391d088262626f554108c53a78e60a))
### [0.2.7](https://github.com/karoletrych/vue-cron-editor/compare/v0.2.6...v0.2.7) (2020-04-14)
### Bug Fixes
* preserve state on first switch of tab ([8b0371d](https://github.com/karoletrych/vue-cron-editor/commit/8b0371d692cf80ccea4e61b2c448b2fd01b8fb28))
### [0.2.6](https://github.com/karoletrych/vue-cron-editor/compare/v0.2.4...v0.2.6) (2020-04-14)
### Features
* preserve state on switch to advanced ([755edeb](https://github.com/karoletrych/vue-cron-editor/commit/755edebb715708ebfe41156443c5ff208f740ae0))
### Bug Fixes
* error on all days checked ([#33](https://github.com/karoletrych/vue-cron-editor/issues/33)) ([e13deeb](https://github.com/karoletrych/vue-cron-editor/commit/e13deebb2485df3cc1d64031398e6fb6593b53ae))
### [0.2.5](https://github.com/karoletrych/vue-cron-editor/compare/v0.2.4...v0.2.5) (2020-04-12)
### [0.2.4](https://github.com/karoletrych/vue-cron-editor/compare/v0.2.2...v0.2.4) (2020-04-12)
### Bug Fixes
* reopening expressions ([#32](https://github.com/karoletrych/vue-cron-editor/issues/32)) ([bdf7c61](https://github.com/karoletrych/vue-cron-editor/commit/bdf7c61fac2b94ee610057f2dd8fa922d3f6fa28))
### [0.2.3](https://github.com/karoletrych/vue-cron-editor/compare/v0.2.2...v0.2.3) (2020-04-12)
### Bug Fixes
* reopening expressions ([#32](https://github.com/karoletrych/vue-cron-editor/issues/32)) ([bdf7c61](https://github.com/karoletrych/vue-cron-editor/commit/bdf7c61fac2b94ee610057f2dd8fa922d3f6fa28))
### [0.2.2](https://github.com/karoletrych/vue-cron-editor/compare/v0.2.0...v0.2.2) (2020-04-12)
### [0.2.1](https://github.com/karoletrych/vue-cron-editor/compare/v0.2.0...v0.2.1) (2020-04-12)
## [0.2.0](https://github.com/karoletrych/vue-cron-editor/compare/v0.1.15...v0.2.0) (2020-04-12)
### [0.1.16](https://github.com/karoletrych/vue-cron-editor/compare/v0.1.15...v0.1.16) (2020-04-12)
### [0.1.15](https://github.com/karoletrych/vue-cron-editor/compare/v0.1.14...v0.1.15) (2020-04-02)
### [0.1.14](https://github.com/karoletrych/vue-cron-editor/compare/v0.1.13...v0.1.14) (2020-04-02)
### [0.1.13](https://github.com/karoletrych/vue-cron-editor/compare/v0.1.11...v0.1.13) (2020-03-16)
### [0.1.2](https://github.com/karoletrych/vue-cron-editor/compare/v0.1.11...v0.1.2) (2020-03-16)
### [0.1.1](https://github.com/karoletrych/vue-cron-editor/compare/v0.1.11...v0.1.1) (2020-01-22)
+1 -1
View File
@@ -1,6 +1,6 @@
MIT License
Copyright (c) 2020 Karol Etrych
Copyright (c) 2023 Sergey Solodyagin
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
+8 -6
View File
@@ -1,20 +1,22 @@
# vue-cron-editor-bootstrap
[![Version](https://img.shields.io/npm/v/@tenrok/vue-cron-editor-bootstrap.svg)](https://www.npmjs.com/package/@tenrok/vue-cron-editor-bootstrap)
[![License](https://img.shields.io/npm/l/@tenrok/vue-cron-editor-bootstrap.svg)](https://github.com/tenrok/vue-cron-editor-bootstrap/blob/main/LICENSE)
[![Vue.js](https://img.shields.io/badge/vue-2.7.14-brightgreen.svg?logo=vue.js)](https://github.com/vuejs/vue)
[![GitHub last commit](https://img.shields.io/github/last-commit/tenrok/vue-cron-editor-bootstrap.svg)](https://github.com/tenrok/vue-cron-editor-bootstrap)
[![Downloads](https://img.shields.io/npm/dm/@tenrok/vue-cron-editor-bootstrap.svg)](https://npmcharts.com/compare/@tenrok/vue-cron-editor-bootstrap?minimal=true)
[![Demo](https://img.shields.io/badge/demo-demo-blue.svg)](https://tenrok.github.io/vue-cron-editor-bootstrap/)
VueCronEditor is a component library built with Vue and Bootstrap-Vue allowing for easier editing of cron expressions.
Inspired by https://github.com/karoletrych/vue-cron-editor
# Demo
[vue-cron-editor-bootstrap.vercel.app](https://vue-cron-editor-bootstrap.vercel.app/)
# Requirements
- Vue ^2.0
- Bootstrap-Vue ^2.14.0
- @tenrok/bootstrap-vue ^2.23.2
# Installation
- vue-cron-editor-bootstrap
```
npm install @tenrok/vue-cron-editor-bootstrap --save
```
+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
+20127 -20841
View File
File diff suppressed because it is too large Load Diff
+57 -48
View File
@@ -1,50 +1,59 @@
{
"name": "@tenrok/vue-cron-editor-bootstrap",
"version": "0.2.1",
"private": false,
"repository": {
"type": "git",
"url": "git+https://github.com/alexmfreitas/vue-cron-editor-bootstrap.git"
},
"license": "MIT",
"author": "alexmfreitas",
"main": "dist/vue-cron-editor-bootstrap.umd.js",
"unpkg": "dist/vue-cron-editor-bootstrap.min.js",
"module": "dist/vue-cron-editor-bootstrap.esm.js",
"browser": {
"./sfc": "src/VueCronEditorBootstrap.vue"
},
"files": [
"dist/vue-cron-editor-bootstrap.*"
],
"scripts": {
"build": "vue-cli-service build --target lib src/VueCronEditorBootstrap.vue",
"build:demo": "vue-cli-service build",
"lint": "vue-cli-service lint --fix",
"serve": "vue-cli-service serve --open"
},
"dependencies": {
"core-js": "^3.26.0",
"cron-validator": "^1.3.1",
"cronstrue": "^2.14.0",
"vue": "^2.7.14"
},
"devDependencies": {
"@typescript-eslint/eslint-plugin": "^5.42.1",
"@typescript-eslint/parser": "^5.42.1",
"@vue/cli-plugin-babel": "^5.0.8",
"@vue/cli-plugin-eslint": "^5.0.8",
"@vue/cli-plugin-typescript": "^5.0.8",
"@vue/cli-service": "^5.0.8",
"@vue/eslint-config-typescript": "^11.0.2",
"eslint": "^7.32.0",
"eslint-plugin-prettier": "^4.2.1",
"eslint-plugin-vue": "^9.6.0",
"prettier": "^2.7.1",
"typescript": "^4.8.4",
"vue-template-compiler": "^2.7.14"
},
"peerDependencies": {
"bootstrap-vue": "^2.23.1"
}
"name": "@tenrok/vue-cron-editor-bootstrap",
"version": "0.2.2",
"homepage": "https://github.com/tenrok/vue-cron-editor-bootstrap",
"bugs": {
"url": "https://github.com/tenrok/vue-cron-editor-bootstrap/issues"
},
"repository": {
"type": "git",
"url": "git+https://github.com/tenrok/vue-cron-editor-bootstrap.git"
},
"license": "MIT",
"author": {
"name": "alexmfreitas",
"url": "https://github.com/alexmfreitas"
},
"contributors": [
{
"name": "Sergey Solodyagin",
"url": "https://github.com/solodyagin"
}
],
"main": "dist/vue-cron-editor-bootstrap.common.js",
"files": [
"dist/vue-cron-editor-bootstrap.*"
],
"scripts": {
"build": "vue-cli-service build --target lib --name vue-cron-editor-bootstrap lib/index.ts",
"demo:build": "vue-cli-service build",
"lint": "vue-cli-service lint --fix",
"serve": "vue-cli-service serve --open"
},
"dependencies": {
"bootstrap": "^4.6.0",
"core-js": "^3.29.0",
"cron-validator": "^1.3.1",
"cronstrue": "^2.14.0",
"vue": "^2.7.14"
},
"devDependencies": {
"@typescript-eslint/eslint-plugin": "^5.50.0",
"@typescript-eslint/parser": "^5.50.0",
"@vue/cli-plugin-babel": "^5.0.8",
"@vue/cli-plugin-eslint": "^5.0.8",
"@vue/cli-plugin-typescript": "^5.0.8",
"@vue/cli-service": "^5.0.8",
"@vue/eslint-config-typescript": "^11.0.2",
"eslint": "^8.33.0",
"eslint-config-prettier": "^8.6.0",
"eslint-plugin-prettier": "^4.2.1",
"eslint-plugin-vue": "^9.17.0",
"prettier": "^2.8.8",
"typescript": "^4.9.5",
"vue-template-compiler": "^2.7.14"
},
"peerDependencies": {
"@tenrok/bootstrap-vue": "^2.23.2"
}
}
+21 -17
View File
@@ -1,19 +1,23 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<title>vue-cron-editor</title>
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@mdi/font@latest/css/materialdesignicons.min.css">
</head>
<body>
<noscript>
<strong>We're sorry but vue-cron-editor doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<title>
<%= htmlWebpackPlugin.options.title %>
</title>
</head>
<body>
<noscript>
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled.
Please enable it to continue.</strong>
</noscript>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>
+10 -20
View File
@@ -1,28 +1,18 @@
<template>
<div id="app">
<div class="p-3">
<VueCronEditorBootstrap v-model="sample1CronExpression" preserve-state-on-switch-to-advanced />
<b-form-input v-model="sample1CronExpression" readonly />
</div>
</div>
<div id="app">
<div class="p-3">
<vue-cron-editor-bootstrap v-model="sample1CronExpression" preserve-state-on-switch-to-advanced />
<b-form-input v-model="sample1CronExpression" readonly />
</div>
</div>
</template>
<script>
import VueCronEditorBootstrap from './VueCronEditorBootstrap.vue'
import { BFormInput } from 'bootstrap-vue'
import 'bootstrap/dist/css/bootstrap.css'
import 'bootstrap-vue/dist/bootstrap-vue.css'
export default {
name: 'App',
name: 'App',
components: {
VueCronEditorBootstrap,
BFormInput,
},
data: () => ({
sample1CronExpression: '4 4 * * 0,2,3,5',
}),
data: () => ({
sample1CronExpression: '4 4 * * 0,2,3,5',
}),
}
</script>
-151
View File
@@ -1,151 +0,0 @@
<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 '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
@@ -1,151 +0,0 @@
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,
}
}
-66
View File
@@ -1,66 +0,0 @@
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
@@ -1,154 +0,0 @@
/**
* 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])
}
}
})
+9 -1
View File
@@ -1,8 +1,16 @@
import Vue from 'vue'
import App from './App.vue'
import VueCronEditorBootstrap from '../lib'
Vue.use(VueCronEditorBootstrap)
import { BFormInput } from '@tenrok/bootstrap-vue'
import 'bootstrap/dist/css/bootstrap.css'
import '@tenrok/bootstrap-vue/dist/bootstrap-vue.css'
Vue.component('BFormInput', BFormInput)
Vue.config.productionTip = false
new Vue({
render: h => h(App)
render: h => h(App),
}).$mount('#app')
-15
View File
@@ -1,15 +0,0 @@
import Vue, { VNode } from 'vue'
declare global {
namespace JSX {
// tslint:disable no-empty-interface
type Element = VNode
// tslint:disable no-empty-interface
type ElementClass = Vue
interface IntrinsicElements {
[elem: string]: any
}
}
}
+2 -2
View File
@@ -1,4 +1,4 @@
declare module '*.vue' {
import Vue from 'vue'
export default Vue
import Vue from 'vue'
export default Vue
}
+14 -19
View File
@@ -1,21 +1,16 @@
{
"compilerOptions": {
"target": "esnext",
"module": "esnext",
"strict": true,
"jsx": "preserve",
"importHelpers": true,
"moduleResolution": "node",
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"sourceMap": true,
"baseUrl": ".",
"types": ["webpack-env", "jest"],
"paths": {
"@/*": ["src/*"]
},
"lib": ["esnext", "dom", "dom.iterable", "scripthost"]
},
"include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue", "tests/**/*.ts", "tests/**/*.tsx"],
"exclude": ["node_modules"]
"compilerOptions": {
"allowSyntheticDefaultImports": true,
"baseUrl": ".",
"moduleResolution": "node",
"outDir": "dist",
"removeComments": true,
"sourceMap": true,
"target": "esnext",
"paths": {
"@/*": ["src/*"]
}
},
"include": ["lib/**/*.ts", "src/**/*.ts", "src/**/*.vue"],
"exclude": ["node_modules"]
}
+7 -3
View File
@@ -1,7 +1,11 @@
const isProduction = process.env.NODE_ENV === 'production'
const isBuildLib = (process.env.npm_lifecycle_script || '').indexOf('--target lib') > 0
module.exports = {
publicPath: '',
outputDir: isBuildLib ? 'dist' : 'demo',
css: { extract: false },
publicPath: isProduction ? '/vue-cron-editor-bootstrap/' : '',
outputDir: isBuildLib ? 'dist' : 'demo',
css: {
extract: true,
},
productionSourceMap: false,
}