From ccb30b6bdcb2c43a2482cf0070d794314284f72f Mon Sep 17 00:00:00 2001 From: Anantha Kumaran Date: Sun, 1 Oct 2023 22:46:03 +0530 Subject: [PATCH] add schedule widget --- src/app.scss | 4 ++ src/lib/editor.ts | 4 +- src/lib/transaction_sequence.ts | 9 +++- src/lib/transaction_tag.ts | 76 +++++++++++++++++++++++++++++++++ src/lib/utils.ts | 7 +++ 5 files changed, 97 insertions(+), 3 deletions(-) create mode 100644 src/lib/transaction_tag.ts diff --git a/src/app.scss b/src/app.scss index 933add92..02f1630b 100644 --- a/src/app.scss +++ b/src/app.scss @@ -623,6 +623,10 @@ nav.level.grid-2 { font-family: $family-monospace; } +.cm-tag { + height: 1.5em !important; +} + .svelte-select { --chevron-color: hsl(229deg, 53%, 53%); --font-size: 14px; diff --git a/src/lib/editor.ts b/src/lib/editor.ts index c30a0cd3..5dbb5187 100644 --- a/src/lib/editor.ts +++ b/src/lib/editor.ts @@ -11,6 +11,7 @@ import _ from "lodash"; import { editorState, initialEditorState } from "../store"; import { autocompletion, completeFromList, ifIn } from "@codemirror/autocomplete"; import { MergeView } from "@codemirror/merge"; +import { schedulePlugin } from "./transaction_tag"; export { editorState } from "../store"; @@ -87,7 +88,8 @@ export function createEditor( redoDepth: redoDepth(viewUpdate.state) }) ); - }) + }), + schedulePlugin ], doc: content, parent: dom diff --git a/src/lib/transaction_sequence.ts b/src/lib/transaction_sequence.ts index fafd3ae6..3c4cde0e 100644 --- a/src/lib/transaction_sequence.ts +++ b/src/lib/transaction_sequence.ts @@ -1,5 +1,10 @@ import _ from "lodash"; -import type { Transaction, TransactionSchedule, TransactionSequence } from "./utils"; +import { + prefixMinutesSeconds, + type Transaction, + type TransactionSchedule, + type TransactionSequence +} from "./utils"; import dayjs from "dayjs"; import { parse, type CronExprs } from "@datasert/cronjs-parser"; import { getFutureMatches } from "@datasert/cronjs-matcher"; @@ -83,7 +88,7 @@ function enrich(ts: TransactionSequence) { let cron: CronExprs; try { if (ts.period != "") { - cron = parse("0 0 " + ts.period, { hasSeconds: false }); + cron = parse(prefixMinutesSeconds(ts.period), { hasSeconds: false }); periodAvailable = true; } else { periodAvailable = false; diff --git a/src/lib/transaction_tag.ts b/src/lib/transaction_tag.ts new file mode 100644 index 00000000..091b4b8f --- /dev/null +++ b/src/lib/transaction_tag.ts @@ -0,0 +1,76 @@ +import dayjs from "dayjs"; +import { parse } from "@datasert/cronjs-parser"; +import { getFutureMatches } from "@datasert/cronjs-matcher"; +import { WidgetType, MatchDecorator } from "@codemirror/view"; +import { + type DecorationSet, + ViewUpdate, + ViewPlugin, + EditorView, + Decoration +} from "@codemirror/view"; +import _ from "lodash"; +import { prefixMinutesSeconds } from "./utils"; + +class SchedulePreview extends WidgetType { + constructor(readonly period: string) { + super(); + } + + eq(other: SchedulePreview) { + return this.period === other.period; + } + + toDOM() { + let text = ""; + try { + const cron = parse(prefixMinutesSeconds(this.period), { hasSeconds: false }); + const schedules = getFutureMatches(cron, { + matchCount: 3, + timezone: dayjs.tz.guess() + }); + text = _.chain(schedules) + .map((schedule) => dayjs(schedule).format("DD MMM YYYY")) + .join(", ") + .value(); + + if (_.isEmpty(schedules)) { + text = "Invalid"; + } + } catch (e) { + text = "Invalid"; + } + const wrapper = document.createElement("span"); + wrapper.innerHTML = text; + wrapper.className = "cm-tag tag ml-2"; + return wrapper; + } +} + +const periodDecorator = new MatchDecorator({ + regexp: /;\s*Period: (([^ ]+ ){2,}[^ ]+)$/gi, + decorate: (add, from, to, match) => { + const period = match[1]; + const start = to, + end = to; + const preview = new SchedulePreview(period); + add(start, end, Decoration.widget({ widget: preview, side: 1 })); + } +}); + +export const schedulePlugin = ViewPlugin.fromClass( + class ScheduleView { + decorator: MatchDecorator; + decorations: DecorationSet; + constructor(view: EditorView) { + this.decorator = periodDecorator; + this.decorations = this.decorator.createDeco(view); + } + update(update: ViewUpdate) { + if (update.docChanged || update.viewportChanged) { + this.decorations = this.decorator.updateDeco(update, this.decorations); + } + } + }, + { decorations: (v) => v.decorations } +); diff --git a/src/lib/utils.ts b/src/lib/utils.ts index 21e254fd..9055ebc9 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -919,3 +919,10 @@ export function monthDays(month: string) { } return { days, monthStart, monthEnd }; } + +export function prefixMinutesSeconds(cronExpression: string) { + return cronExpression + .split("|") + .map((cron) => "0 0 " + cron) + .join("|"); +}