refactor: migrate frontend event page
This commit is contained in:
@@ -81,7 +81,7 @@ const windowRange = computed<WindowRange>(() => {
|
|||||||
return { startTs, endTs, year, month1 }
|
return { startTs, endTs, year, month1 }
|
||||||
})
|
})
|
||||||
|
|
||||||
// Analyse phase: build the 42 cells and expand every event (loop expansion +
|
// Analyse phase: build the 6 * 7 cells and expand every event (loop expansion +
|
||||||
// multi-day splitting). Deliberately does NOT read the visibility maps so it
|
// multi-day splitting). Deliberately does NOT read the visibility maps so it
|
||||||
// stays cached across visibility toggles.
|
// stays cached across visibility toggles.
|
||||||
const expandedCells = computed<CalendarCell[]>(() => {
|
const expandedCells = computed<CalendarCell[]>(() => {
|
||||||
@@ -89,10 +89,10 @@ const expandedCells = computed<CalendarCell[]>(() => {
|
|||||||
const owned = ownedUuids.value
|
const owned = ownedUuids.value
|
||||||
const myTz = myTimezone.value
|
const myTz = myTimezone.value
|
||||||
|
|
||||||
// build the 42 day cells
|
// build the 6 * 7 day cells
|
||||||
const cells: CalendarCell[] = []
|
const cells: CalendarCell[] = []
|
||||||
const iter = new Date(startTs * 60000)
|
const iter = new Date(startTs * 60000)
|
||||||
for (let i = 0; i < 42; i++) {
|
for (let i = 0; i < 6 * 7; i++) {
|
||||||
cells.push({
|
cells.push({
|
||||||
month: iter.getMonth() + 1,
|
month: iter.getMonth() + 1,
|
||||||
day: iter.getDate(),
|
day: iter.getDate(),
|
||||||
@@ -119,17 +119,19 @@ const expandedCells = computed<CalendarCell[]>(() => {
|
|||||||
if (typeof result === 'undefined') continue
|
if (typeof result === 'undefined') continue
|
||||||
const loopText = resolveLoopRules4Text(item[8], item[5], item[7])
|
const loopText = resolveLoopRules4Text(item[8], item[5], item[7])
|
||||||
const tzWarn = myTz !== item[7]
|
const tzWarn = myTz !== item[7]
|
||||||
// lock icon = event is in one of MY collections (belongTo). (Legacy
|
// lock icon = event is NOT in one of MY collections (belongTo). (Legacy
|
||||||
// mistakenly keyed this by the event uuid; fixed per migration decision.)
|
// mistakenly keyed this by the event uuid; fixed per migration decision.)
|
||||||
const isLocked = owned.has(item[1])
|
const isLocked = !owned.has(item[1])
|
||||||
|
|
||||||
for (const range of result) {
|
for (const range of result) {
|
||||||
const it0 = range[0]
|
const it0 = range[0]
|
||||||
const it1 = range[1]
|
const it1 = range[1]
|
||||||
|
// try get event belong to which cell
|
||||||
const eventDateTime = new Date(it0 * 60000)
|
const eventDateTime = new Date(it0 * 60000)
|
||||||
let count = Math.floor((it0 - startTs) / DAY1_SPAN)
|
let count = Math.floor((it0 - startTs) / DAY1_SPAN)
|
||||||
let exitFlag = false
|
let exitFlag = false
|
||||||
while (count < 42) {
|
// then split event
|
||||||
|
while (count < 6 * 7) {
|
||||||
const eventItem: ScheduleEventItem = {
|
const eventItem: ScheduleEventItem = {
|
||||||
uuid: item[0],
|
uuid: item[0],
|
||||||
belongTo: item[1],
|
belongTo: item[1],
|
||||||
|
|||||||
@@ -1,8 +1,673 @@
|
|||||||
<script setup lang="ts"></script>
|
<script setup lang="ts">
|
||||||
|
import { computed, onMounted, ref } from 'vue';
|
||||||
|
import { useRoute } from 'vue-router';
|
||||||
|
import { useTokenStore } from '@/stores/token';
|
||||||
|
import MessageBox from '@/components/MessageBox.vue';
|
||||||
|
import DateTimePicker, { TabType } from '@/components/DateTimePicker.vue';
|
||||||
|
import { goToCalendar } from '@/router';
|
||||||
|
import {
|
||||||
|
getDetail as apiCalendarGetDetail,
|
||||||
|
add as apiCalendarAdd,
|
||||||
|
update as apiCalendarUpdate,
|
||||||
|
deserializeDescription,
|
||||||
|
serializeDescription,
|
||||||
|
type CalendarRow,
|
||||||
|
} from '@/api/calendar';
|
||||||
|
import { getFullOwn as apiCollectionGetFullOwn, type CollectionRow } from '@/api/collection';
|
||||||
|
import { resolveLoopRules4UI, getDayInMonth } from '@/utils/datetime';
|
||||||
|
import { WEEK_NAMES } from '@/utils/i18n';
|
||||||
|
import { format, getWeekday, DEFAULT_COLOR } from '@/utils/utils';
|
||||||
|
|
||||||
|
const token = useTokenStore();
|
||||||
|
const route = useRoute();
|
||||||
|
const messagebox = ref<InstanceType<typeof MessageBox>>();
|
||||||
|
const picker = ref<InstanceType<typeof DateTimePicker>>();
|
||||||
|
|
||||||
|
// the event being edited (undefined = add mode)
|
||||||
|
const editingEvent = ref<CalendarRow | undefined>(undefined);
|
||||||
|
|
||||||
|
// basic fields
|
||||||
|
const title = ref<string>('');
|
||||||
|
const description = ref<string>('');
|
||||||
|
const color = ref<string>(DEFAULT_COLOR);
|
||||||
|
const belongTo = ref<string>('');
|
||||||
|
const collections = ref<CollectionRow[]>([]);
|
||||||
|
|
||||||
|
// datetime (3 slots share one picker)
|
||||||
|
const startDt = ref<Date>(new Date());
|
||||||
|
const endDt = ref<Date>(new Date());
|
||||||
|
const loopStopDt = ref<Date>(new Date());
|
||||||
|
const editingSlot = ref<'start' | 'end' | 'loopstop'>('start');
|
||||||
|
|
||||||
|
// loop method
|
||||||
|
const loopMethod = ref<'never' | 'day' | 'week' | 'month' | 'year'>('never');
|
||||||
|
const loopDaySpan = ref<string>('1');
|
||||||
|
const loopWeekSpan = ref<string>('1');
|
||||||
|
const loopWeekChecks = ref<boolean[]>([false, false, false, false, false, false, false]);
|
||||||
|
const loopMonthSpan = ref<string>('1');
|
||||||
|
const loopMonthMode = ref<'A' | 'B' | 'C' | 'D'>('A');
|
||||||
|
const loopYearSpan = ref<string>('1');
|
||||||
|
|
||||||
|
// loop stop
|
||||||
|
const loopStop = ref<'forever' | 'datetime' | 'times'>('forever');
|
||||||
|
const loopStopTimes = ref<string>('1');
|
||||||
|
|
||||||
|
// strict mode + timezone
|
||||||
|
const strictMode = ref<'strict' | 'rough'>('strict');
|
||||||
|
const timezoneOption = ref<'keep' | 'replace'>('keep');
|
||||||
|
const showTimezoneBox = ref<boolean>(false);
|
||||||
|
|
||||||
|
// region: v-show conditions (legacy RefreshRadioDiaplay)
|
||||||
|
|
||||||
|
const showBoxLoopStop = computed<boolean>(() => loopMethod.value !== 'never');
|
||||||
|
const showBoxLoopDay = computed<boolean>(() => loopMethod.value === 'day');
|
||||||
|
const showBoxLoopWeek = computed<boolean>(() => loopMethod.value === 'week');
|
||||||
|
const showBoxLoopMonth = computed<boolean>(() => loopMethod.value === 'month');
|
||||||
|
const showBoxLoopYear = computed<boolean>(() => loopMethod.value === 'year');
|
||||||
|
const showBoxStrictMode = computed<boolean>(() => loopMethod.value === 'month' || loopMethod.value === 'year');
|
||||||
|
const showBoxLoopStopDateTime = computed<boolean>(() => loopStop.value === 'datetime');
|
||||||
|
const showBoxLoopStopTimes = computed<boolean>(() => loopStop.value === 'times');
|
||||||
|
|
||||||
|
// endregion
|
||||||
|
|
||||||
|
// region: month-loop option labels (legacy RefreshLoopMonthType)
|
||||||
|
|
||||||
|
const monthOptionLabels = computed(() => {
|
||||||
|
const d = getDayInMonth(startDt.value.getFullYear(), startDt.value.getMonth() + 1, startDt.value.getDate());
|
||||||
|
return {
|
||||||
|
a: format('Day {0} in month', d[0]),
|
||||||
|
b: format('Day {0} from the end of the month', d[1]),
|
||||||
|
c: format('Day {1} in week {0}', d[2], d[3] + 1),
|
||||||
|
d: format('Day {1} in week {0} from the end of the month', d[4], d[5] + 1),
|
||||||
|
};
|
||||||
|
})
|
||||||
|
|
||||||
|
// endregion
|
||||||
|
|
||||||
|
// region: Helpers
|
||||||
|
|
||||||
|
// Given a Date whose UTC components represent a wall-clock in the event's
|
||||||
|
// timezone, return a Date whose LOCAL components equal that wall-clock. The
|
||||||
|
// DateTimePicker edits via local accessors, so this lets us display/edit the
|
||||||
|
// event's wall-clock directly regardless of the browser timezone.
|
||||||
|
const wallClockToLocal = (utcWall: Date): Date => {
|
||||||
|
return new Date(
|
||||||
|
utcWall.getUTCFullYear(),
|
||||||
|
utcWall.getUTCMonth(),
|
||||||
|
utcWall.getUTCDate(),
|
||||||
|
utcWall.getUTCHours(),
|
||||||
|
utcWall.getUTCMinutes(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// endregion
|
||||||
|
|
||||||
|
// region: Init
|
||||||
|
|
||||||
|
const init = async () => {
|
||||||
|
const uuid = route.params.uuid as string | undefined;
|
||||||
|
let isAdd = true;
|
||||||
|
if (typeof uuid === 'string' && uuid !== '') {
|
||||||
|
const ev = await apiCalendarGetDetail(token.currentToken, uuid);
|
||||||
|
if (typeof ev !== 'undefined') {
|
||||||
|
editingEvent.value = ev;
|
||||||
|
isAdd = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const ev = editingEvent.value;
|
||||||
|
const desc = isAdd ? undefined : deserializeDescription(ev![3]);
|
||||||
|
|
||||||
|
// title / description / color
|
||||||
|
title.value = isAdd ? '' : ev![2];
|
||||||
|
description.value = isAdd ? '' : desc!.description;
|
||||||
|
color.value = isAdd ? DEFAULT_COLOR : desc!.color;
|
||||||
|
|
||||||
|
// collection select
|
||||||
|
const collectionsRv = await apiCollectionGetFullOwn(token.currentToken);
|
||||||
|
collections.value = collectionsRv ?? [];
|
||||||
|
belongTo.value = isAdd ? '' : ev![1];
|
||||||
|
|
||||||
|
// start / end datetime
|
||||||
|
if (isAdd) {
|
||||||
|
const cur = new Date();
|
||||||
|
cur.setMilliseconds(0);
|
||||||
|
cur.setSeconds(0);
|
||||||
|
cur.setMinutes(0);
|
||||||
|
startDt.value = new Date(cur.getTime());
|
||||||
|
cur.setHours(cur.getHours() + 2);
|
||||||
|
endDt.value = new Date(cur.getTime());
|
||||||
|
} else {
|
||||||
|
// (eventDateTime + tzOffset) * 60000 yields a Date whose UTC fields are the
|
||||||
|
// event's wall-clock in its own timezone; relabel to local for the picker.
|
||||||
|
startDt.value = wallClockToLocal(new Date((ev![5] + ev![7]) * 60000));
|
||||||
|
endDt.value = wallClockToLocal(new Date((ev![6] + ev![7]) * 60000));
|
||||||
|
}
|
||||||
|
|
||||||
|
// timezone box (only when editing AND event tz != browser tz)
|
||||||
|
timezoneOption.value = 'keep';
|
||||||
|
const nowtime = new Date();
|
||||||
|
showTimezoneBox.value = (!isAdd) && (-nowtime.getTimezoneOffset()) !== ev![7];
|
||||||
|
|
||||||
|
// loop rules
|
||||||
|
let loopIsAdd = isAdd;
|
||||||
|
let data: ReturnType<typeof resolveLoopRules4UI> = undefined;
|
||||||
|
if (!isAdd) {
|
||||||
|
data = resolveLoopRules4UI(ev![8]);
|
||||||
|
if (typeof data === 'undefined') loopIsAdd = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// defaults (applied regardless of mode)
|
||||||
|
loopMonthMode.value = 'A';
|
||||||
|
loopWeekChecks.value = [false, false, false, false, false, false, false];
|
||||||
|
loopWeekChecks.value[getWeekday(nowtime)] = true;
|
||||||
|
strictMode.value = 'strict';
|
||||||
|
|
||||||
|
if (loopIsAdd) {
|
||||||
|
loopMethod.value = 'never';
|
||||||
|
} else {
|
||||||
|
const d = data!;
|
||||||
|
switch (d[0][0]) {
|
||||||
|
case 0: {
|
||||||
|
// year: [0, isStrict, yearSpan]
|
||||||
|
const r = d[0] as [number, boolean, number];
|
||||||
|
loopMethod.value = 'year';
|
||||||
|
loopYearSpan.value = String(r[2]);
|
||||||
|
strictMode.value = r[1] ? 'strict' : 'rough';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 1: {
|
||||||
|
// month: [1, isStrict, mode, monthSpan]
|
||||||
|
const r = d[0] as [number, boolean, 'A' | 'B' | 'C' | 'D', number];
|
||||||
|
loopMethod.value = 'month';
|
||||||
|
loopMonthSpan.value = String(r[3]);
|
||||||
|
loopMonthMode.value = r[2];
|
||||||
|
strictMode.value = r[1] ? 'strict' : 'rough';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 2: {
|
||||||
|
// week: [2, 7 booleans, weekSpan]
|
||||||
|
const r = d[0] as [number, boolean, boolean, boolean, boolean, boolean, boolean, boolean, number];
|
||||||
|
loopMethod.value = 'week';
|
||||||
|
loopWeekSpan.value = String(r[8]);
|
||||||
|
loopWeekChecks.value = [r[1], r[2], r[3], r[4], r[5], r[6], r[7]];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 3: {
|
||||||
|
// day: [3, daySpan]
|
||||||
|
const r = d[0] as [number, number];
|
||||||
|
loopMethod.value = 'day';
|
||||||
|
loopDaySpan.value = String(r[1]);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// loopStopDt default now
|
||||||
|
loopStopDt.value = new Date();
|
||||||
|
|
||||||
|
if (loopIsAdd) {
|
||||||
|
loopStop.value = 'forever';
|
||||||
|
} else {
|
||||||
|
const d = data!;
|
||||||
|
switch (d[1][0]) {
|
||||||
|
case 0:
|
||||||
|
loopStop.value = 'forever';
|
||||||
|
break;
|
||||||
|
case 1: {
|
||||||
|
const r = d[1] as [number, number];
|
||||||
|
loopStop.value = 'datetime';
|
||||||
|
loopStopDt.value = wallClockToLocal(new Date((r[1] + ev![7]) * 60000));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 2: {
|
||||||
|
const r = d[1] as [number, number];
|
||||||
|
loopStop.value = 'times';
|
||||||
|
loopStopTimes.value = String(r[1]);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// endregion
|
||||||
|
|
||||||
|
// region: Picker
|
||||||
|
|
||||||
|
const openStartPicker = () => {
|
||||||
|
editingSlot.value = 'start';
|
||||||
|
picker.value?.modal(startDt.value, TabType.Minute);
|
||||||
|
}
|
||||||
|
|
||||||
|
const openEndPicker = () => {
|
||||||
|
editingSlot.value = 'end';
|
||||||
|
picker.value?.modal(endDt.value, TabType.Minute);
|
||||||
|
}
|
||||||
|
|
||||||
|
const openLoopStopPicker = () => {
|
||||||
|
editingSlot.value = 'loopstop';
|
||||||
|
picker.value?.modal(loopStopDt.value, TabType.Day);
|
||||||
|
}
|
||||||
|
|
||||||
|
const onPickerConfirm = (d: Date) => {
|
||||||
|
if (editingSlot.value === 'start') startDt.value = d;
|
||||||
|
else if (editingSlot.value === 'end') endDt.value = d;
|
||||||
|
else loopStopDt.value = d;
|
||||||
|
}
|
||||||
|
|
||||||
|
// endregion
|
||||||
|
|
||||||
|
// region: Quick helpers
|
||||||
|
|
||||||
|
const spot = () => {
|
||||||
|
const d = new Date(startDt.value.getTime());
|
||||||
|
d.setMinutes(d.getMinutes() + 1);
|
||||||
|
endDt.value = d;
|
||||||
|
}
|
||||||
|
|
||||||
|
const fullDay = () => {
|
||||||
|
const s = new Date(startDt.value.getTime());
|
||||||
|
s.setMinutes(0);
|
||||||
|
s.setHours(0);
|
||||||
|
startDt.value = s;
|
||||||
|
const e = new Date(s.getTime());
|
||||||
|
e.setMinutes(59);
|
||||||
|
e.setHours(23);
|
||||||
|
endDt.value = e;
|
||||||
|
}
|
||||||
|
|
||||||
|
// endregion
|
||||||
|
|
||||||
|
// region: Form collection + submit
|
||||||
|
|
||||||
|
// returns [belongTo, title, description, start, end, tzOffset, loopRules]
|
||||||
|
// or undefined on validation failure.
|
||||||
|
const getForm = (): [string, string, string, number, number, number, string] | undefined => {
|
||||||
|
// basic
|
||||||
|
if (title.value === '') return undefined;
|
||||||
|
if (description.value === '') return undefined;
|
||||||
|
if (color.value === '') return undefined;
|
||||||
|
if (!belongTo.value) return undefined;
|
||||||
|
|
||||||
|
const isAdd = typeof editingEvent.value === 'undefined';
|
||||||
|
const keepTimezone = timezoneOption.value === 'keep';
|
||||||
|
const isStrict = strictMode.value === 'strict';
|
||||||
|
|
||||||
|
// time
|
||||||
|
let eventDateTimeStart: number;
|
||||||
|
let eventDateTimeEnd: number;
|
||||||
|
let timezoneOffset: number;
|
||||||
|
if (!isAdd && !keepTimezone) {
|
||||||
|
// keep the event's original timezone (event[7]); recompute the instant
|
||||||
|
// from the wall-clock components (NOT getTime, which uses browser tz).
|
||||||
|
timezoneOffset = editingEvent.value![7];
|
||||||
|
eventDateTimeStart = Math.floor(Date.UTC(
|
||||||
|
startDt.value.getFullYear(), startDt.value.getMonth(), startDt.value.getDate(),
|
||||||
|
startDt.value.getHours(), startDt.value.getMinutes(),
|
||||||
|
) / 60000) - timezoneOffset;
|
||||||
|
eventDateTimeEnd = Math.floor(Date.UTC(
|
||||||
|
endDt.value.getFullYear(), endDt.value.getMonth(), endDt.value.getDate(),
|
||||||
|
endDt.value.getHours(), endDt.value.getMinutes(),
|
||||||
|
) / 60000) - timezoneOffset;
|
||||||
|
} else {
|
||||||
|
// use the browser timezone; the wall-clock IS local so getTime is correct.
|
||||||
|
timezoneOffset = -startDt.value.getTimezoneOffset();
|
||||||
|
eventDateTimeStart = Math.floor(startDt.value.getTime() / 60000);
|
||||||
|
eventDateTimeEnd = Math.floor(endDt.value.getTime() / 60000);
|
||||||
|
}
|
||||||
|
|
||||||
|
// loopRules
|
||||||
|
let loopRules: string;
|
||||||
|
if (loopMethod.value === 'never') {
|
||||||
|
loopRules = '';
|
||||||
|
} else if (loopMethod.value === 'day') {
|
||||||
|
loopRules = `D${loopDaySpan.value}`;
|
||||||
|
} else if (loopMethod.value === 'week') {
|
||||||
|
let cache = '';
|
||||||
|
for (let i = 0; i < 7; i++) cache += loopWeekChecks.value[i] ? 'T' : 'F';
|
||||||
|
loopRules = `W${cache}${loopWeekSpan.value}`;
|
||||||
|
} else if (loopMethod.value === 'month') {
|
||||||
|
loopRules = `M${isStrict ? 'S' : 'R'}${loopMonthMode.value}${loopMonthSpan.value}`;
|
||||||
|
} else {
|
||||||
|
// year
|
||||||
|
loopRules = `Y${isStrict ? 'S' : 'R'}${loopYearSpan.value}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// loop stop (only when looping)
|
||||||
|
if (loopRules !== '') {
|
||||||
|
loopRules += '-';
|
||||||
|
if (loopStop.value === 'forever') {
|
||||||
|
loopRules += 'F';
|
||||||
|
} else if (loopStop.value === 'datetime') {
|
||||||
|
let ts: number;
|
||||||
|
if (!isAdd && !keepTimezone) {
|
||||||
|
ts = Math.floor(Date.UTC(
|
||||||
|
loopStopDt.value.getFullYear(), loopStopDt.value.getMonth(), loopStopDt.value.getDate(),
|
||||||
|
23, 59,
|
||||||
|
) / 60000) - timezoneOffset;
|
||||||
|
} else {
|
||||||
|
ts = Math.floor(loopStopDt.value.getTime() / 60000);
|
||||||
|
}
|
||||||
|
loopRules += `D${ts}`;
|
||||||
|
} else {
|
||||||
|
// times
|
||||||
|
loopRules += `T${loopStopTimes.value}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
belongTo.value,
|
||||||
|
title.value,
|
||||||
|
serializeDescription(description.value, color.value),
|
||||||
|
eventDateTimeStart,
|
||||||
|
eventDateTimeEnd,
|
||||||
|
timezoneOffset,
|
||||||
|
loopRules,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
const submit = async () => {
|
||||||
|
const submitData = getForm();
|
||||||
|
if (typeof submitData === 'undefined') {
|
||||||
|
messagebox.value?.show("Your filled event form is not fufilled or have error. Please check it and try again.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const isAdd = typeof editingEvent.value === 'undefined';
|
||||||
|
if (isAdd) {
|
||||||
|
const result = await apiCalendarAdd(
|
||||||
|
token.currentToken,
|
||||||
|
submitData[0],
|
||||||
|
submitData[1],
|
||||||
|
submitData[2],
|
||||||
|
submitData[3],
|
||||||
|
submitData[4],
|
||||||
|
submitData[6],
|
||||||
|
submitData[5],
|
||||||
|
);
|
||||||
|
if (typeof result === 'undefined') {
|
||||||
|
messagebox.value?.show("An add operation failed. It may caused by wrong arguments. Refreshing page may fix system problem. Before refreshing page, please backup all your unsaved data.");
|
||||||
|
} else {
|
||||||
|
goToCalendar();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const ev = editingEvent.value!;
|
||||||
|
const result = await apiCalendarUpdate(
|
||||||
|
token.currentToken,
|
||||||
|
ev[0],
|
||||||
|
ev[4],
|
||||||
|
ev[1] === submitData[0] ? undefined : submitData[0],
|
||||||
|
ev[2] === submitData[1] ? undefined : submitData[1],
|
||||||
|
ev[3] === submitData[2] ? undefined : submitData[2],
|
||||||
|
ev[5] === submitData[3] ? undefined : submitData[3],
|
||||||
|
ev[6] === submitData[4] ? undefined : submitData[4],
|
||||||
|
ev[8] === submitData[6] ? undefined : submitData[6],
|
||||||
|
ev[7] === submitData[5] ? undefined : submitData[5],
|
||||||
|
);
|
||||||
|
if (typeof result === 'undefined') {
|
||||||
|
messagebox.value?.show("An update operation failed. It may caused by wrong arguments or lost target. Refreshing page may fix system problem. Before refreshing page, please backup all your unsaved data.");
|
||||||
|
} else {
|
||||||
|
goToCalendar();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const cancel = () => {
|
||||||
|
goToCalendar();
|
||||||
|
}
|
||||||
|
|
||||||
|
// endregion
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
await init();
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<h1>Congratulations</h1>
|
<div class="eventFormBody container" style="margin-top: 20px;">
|
||||||
<p>This is calendar event.</p>
|
<h1 class="title">Edit Event</h1>
|
||||||
|
|
||||||
|
<section class="section">
|
||||||
|
<div class="field">
|
||||||
|
<label class="label">Title</label>
|
||||||
|
<div class="control">
|
||||||
|
<input v-model="title" class="input" type="text">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="field">
|
||||||
|
<label class="label">Description</label>
|
||||||
|
<div class="control">
|
||||||
|
<textarea v-model="description" class="textarea"></textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="field">
|
||||||
|
<label class="label">Color</label>
|
||||||
|
<div class="control">
|
||||||
|
<input v-model="color" class="input" type="color">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="field">
|
||||||
|
<label class="label">Collection</label>
|
||||||
|
<div class="control">
|
||||||
|
<div class="select">
|
||||||
|
<select v-model="belongTo">
|
||||||
|
<option v-for="c in collections" :key="c[0]" :value="c[0]">{{ c[1] }}</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="section">
|
||||||
|
<h2 class="subtitle">Start Date Time</h2>
|
||||||
|
<a class="button" @click="openStartPicker">
|
||||||
|
<span>{{ startDt.toLocaleString() }}</span>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<h2 class="subtitle">Stop Date Time</h2>
|
||||||
|
<div class="button-list">
|
||||||
|
<div class="control">
|
||||||
|
<a class="button is-link" @click="spot">Spot</a>
|
||||||
|
</div>
|
||||||
|
<div class="control">
|
||||||
|
<a class="button is-link" @click="fullDay">Full day</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<a class="button" @click="openEndPicker">
|
||||||
|
<span>{{ endDt.toLocaleString() }}</span>
|
||||||
|
</a>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="section">
|
||||||
|
<h2 class="subtitle">Event Loop</h2>
|
||||||
|
<div class="button-list">
|
||||||
|
<label class="radio">
|
||||||
|
<input type="radio" value="never" v-model="loopMethod">
|
||||||
|
<span>Never</span>
|
||||||
|
</label>
|
||||||
|
<label class="radio">
|
||||||
|
<input type="radio" value="day" v-model="loopMethod">
|
||||||
|
<span>Day</span>
|
||||||
|
</label>
|
||||||
|
<label class="radio">
|
||||||
|
<input type="radio" value="week" v-model="loopMethod">
|
||||||
|
<span>Week</span>
|
||||||
|
</label>
|
||||||
|
<label class="radio">
|
||||||
|
<input type="radio" value="month" v-model="loopMethod">
|
||||||
|
<span>Month</span>
|
||||||
|
</label>
|
||||||
|
<label class="radio">
|
||||||
|
<input type="radio" value="year" v-model="loopMethod">
|
||||||
|
<span>Year</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-show="showBoxLoopDay">
|
||||||
|
<div class="field">
|
||||||
|
<label class="label">Day span</label>
|
||||||
|
<div class="control">
|
||||||
|
<input v-model="loopDaySpan" class="input spanpicker" type="number" min="1" max="100" step="1">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-show="showBoxLoopWeek">
|
||||||
|
<div class="field">
|
||||||
|
<label class="label">Week span</label>
|
||||||
|
<div class="control">
|
||||||
|
<input v-model="loopWeekSpan" class="input spanpicker" type="number" min="1" max="100" step="1">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="field">
|
||||||
|
<label class="label">Week options</label>
|
||||||
|
<div class="button-list">
|
||||||
|
<label class="checkbox">
|
||||||
|
<input type="checkbox" v-model="loopWeekChecks[0]">
|
||||||
|
<span>{{ WEEK_NAMES[0] }}</span>
|
||||||
|
</label>
|
||||||
|
<label class="checkbox">
|
||||||
|
<input type="checkbox" v-model="loopWeekChecks[1]">
|
||||||
|
<span>{{ WEEK_NAMES[1] }}</span>
|
||||||
|
</label>
|
||||||
|
<label class="checkbox">
|
||||||
|
<input type="checkbox" v-model="loopWeekChecks[2]">
|
||||||
|
<span>{{ WEEK_NAMES[2] }}</span>
|
||||||
|
</label>
|
||||||
|
<label class="checkbox">
|
||||||
|
<input type="checkbox" v-model="loopWeekChecks[3]">
|
||||||
|
<span>{{ WEEK_NAMES[3] }}</span>
|
||||||
|
</label>
|
||||||
|
<label class="checkbox">
|
||||||
|
<input type="checkbox" v-model="loopWeekChecks[4]">
|
||||||
|
<span>{{ WEEK_NAMES[4] }}</span>
|
||||||
|
</label>
|
||||||
|
<label class="checkbox">
|
||||||
|
<input type="checkbox" v-model="loopWeekChecks[5]">
|
||||||
|
<span>{{ WEEK_NAMES[5] }}</span>
|
||||||
|
</label>
|
||||||
|
<label class="checkbox">
|
||||||
|
<input type="checkbox" v-model="loopWeekChecks[6]">
|
||||||
|
<span>{{ WEEK_NAMES[6] }}</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-show="showBoxLoopMonth">
|
||||||
|
<div class="field">
|
||||||
|
<label class="label">Month span</label>
|
||||||
|
<div class="control">
|
||||||
|
<input v-model="loopMonthSpan" class="input spanpicker" type="number" min="1" max="100" step="1">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="field">
|
||||||
|
<label class="label">Month mode</label>
|
||||||
|
<div class="button-list">
|
||||||
|
<label class="radio">
|
||||||
|
<input type="radio" value="A" v-model="loopMonthMode">
|
||||||
|
<span>{{ monthOptionLabels.a }}</span>
|
||||||
|
</label>
|
||||||
|
<label class="radio">
|
||||||
|
<input type="radio" value="B" v-model="loopMonthMode">
|
||||||
|
<span>{{ monthOptionLabels.b }}</span>
|
||||||
|
</label>
|
||||||
|
<label class="radio">
|
||||||
|
<input type="radio" value="C" v-model="loopMonthMode">
|
||||||
|
<span>{{ monthOptionLabels.c }}</span>
|
||||||
|
</label>
|
||||||
|
<label class="radio">
|
||||||
|
<input type="radio" value="D" v-model="loopMonthMode">
|
||||||
|
<span>{{ monthOptionLabels.d }}</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-show="showBoxLoopYear">
|
||||||
|
<div class="field">
|
||||||
|
<label class="label">Year span</label>
|
||||||
|
<div class="control">
|
||||||
|
<input v-model="loopYearSpan" class="input spanpicker" type="number" min="1" max="100" step="1">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section v-show="showBoxLoopStop" class="section">
|
||||||
|
<h2 class="subtitle">Event Loop Stop</h2>
|
||||||
|
<div class="button-list">
|
||||||
|
<label class="radio">
|
||||||
|
<input type="radio" value="forever" v-model="loopStop">
|
||||||
|
<span>Forever</span>
|
||||||
|
</label>
|
||||||
|
<label class="radio">
|
||||||
|
<input type="radio" value="datetime" v-model="loopStop">
|
||||||
|
<span>Date Time</span>
|
||||||
|
</label>
|
||||||
|
<label class="radio">
|
||||||
|
<input type="radio" value="times" v-model="loopStop">
|
||||||
|
<span>Times</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div v-show="showBoxLoopStopDateTime">
|
||||||
|
<a class="button" @click="openLoopStopPicker">
|
||||||
|
<span>{{ loopStopDt.toLocaleDateString() }}</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div v-show="showBoxLoopStopTimes">
|
||||||
|
<div class="field">
|
||||||
|
<div class="control">
|
||||||
|
<input v-model="loopStopTimes" class="input spanpicker" type="number" min="1" max="100" step="1">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section v-show="showBoxStrictMode" class="section">
|
||||||
|
<h2 class="subtitle">Strict Mode in Event Loop</h2>
|
||||||
|
<p>You can choose strict mode or rough mode in following content. This is only effect on looped event.</p>
|
||||||
|
<div class="button-list">
|
||||||
|
<label class="radio">
|
||||||
|
<input type="radio" value="strict" v-model="strictMode">
|
||||||
|
<span>Strict Mode. If ordered day is not existing, skip it.</span>
|
||||||
|
</label>
|
||||||
|
<label class="radio">
|
||||||
|
<input type="radio" value="rough" v-model="strictMode">
|
||||||
|
<span>Rough mode. If ordered day is not existing, choose the day closing with original day to arrange
|
||||||
|
event.</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section v-show="showTimezoneBox" class="section">
|
||||||
|
<h2 class="subtitle">Timezone</h2>
|
||||||
|
<p>The timezone of this event is not corresponding with your current timezone. All of date and time in this page
|
||||||
|
are shown as the original timezone of this event. You can choose a timezone option in follwing content. If you
|
||||||
|
are not familar with this, please pick keep timezone.</p>
|
||||||
|
<div class="button-list">
|
||||||
|
<label class="radio">
|
||||||
|
<input type="radio" value="keep" v-model="timezoneOption">
|
||||||
|
<span>Keep timezone</span>
|
||||||
|
</label>
|
||||||
|
<label class="radio">
|
||||||
|
<input type="radio" value="replace" v-model="timezoneOption">
|
||||||
|
<span>Use my timezone</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="section">
|
||||||
|
<div class="button-list">
|
||||||
|
<a class="button is-success" @click="submit">Submit</a>
|
||||||
|
<a class="button" @click="cancel">Cancel</a>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<MessageBox ref="messagebox" />
|
||||||
|
<DateTimePicker ref="picker" @confirm="onPickerConfirm" />
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped></style>
|
<style scoped>
|
||||||
|
.eventFormBody > section {
|
||||||
|
border-top: 1px solid rgba(219, 219, 219, 0.5);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
Reference in New Issue
Block a user