diff --git a/frontend/src/App.vue b/frontend/src/App.vue index 3348807..b0963c1 100644 --- a/frontend/src/App.vue +++ b/frontend/src/App.vue @@ -33,7 +33,7 @@ const logout = async () => { // Check for click events on the navbar burger icon const toggleBurger = () => { // Toggle the "is-active" class on both the "navbar-burger" and the "navbar-menu" - isBurgerActive.value = !isBurgerActive + isBurgerActive.value = !isBurgerActive.value } diff --git a/frontend/src/components/DateTimePicker.vue b/frontend/src/components/DateTimePicker.vue new file mode 100644 index 0000000..be4df76 --- /dev/null +++ b/frontend/src/components/DateTimePicker.vue @@ -0,0 +1,678 @@ + + + + + + + diff --git a/frontend/src/components/MessageBox.vue b/frontend/src/components/MessageBox.vue index 1a2d0dd..2a1e3fa 100644 --- a/frontend/src/components/MessageBox.vue +++ b/frontend/src/components/MessageBox.vue @@ -5,14 +5,24 @@ const isVisible = ref(false); const title = ref(""); const content = ref(""); +const emit = defineEmits<{ + (e: 'closed', isOk: boolean): void +}>() + const show = (_content: string, _title?: string) => { title.value = _title ?? "Notification"; content.value = _content; isVisible.value = true; } -const hide = () => { +const close = () => { isVisible.value = false; + emit('closed', false); +} + +const ok = () => { + isVisible.value = false; + emit('closed', true); } defineExpose({ @@ -26,13 +36,13 @@ defineExpose({ diff --git a/frontend/src/components/collection/OwnedItem.vue b/frontend/src/components/collection/OwnedItem.vue index 364572e..bdff1a6 100644 --- a/frontend/src/components/collection/OwnedItem.vue +++ b/frontend/src/components/collection/OwnedItem.vue @@ -35,7 +35,7 @@ const deleteItem = () => { } const updateItem = () => { - let new_name = editingName.value; + const new_name = editingName.value; editingName.value = ""; isEditing.value = false; emit('update', props.uuid, new_name); diff --git a/frontend/src/utils/datetime.ts b/frontend/src/utils/datetime.ts index a01aae1..2fee3c3 100644 --- a/frontend/src/utils/datetime.ts +++ b/frontend/src/utils/datetime.ts @@ -62,7 +62,7 @@ type LoopStopRule = InfinityStopRule | DatetimeStopRule | TimesStopRule; export function resolveLoopRules4UI(strl: string): [LoopRule, LoopStopRule] | undefined { if (strl == '') return undefined; - let sp = strl.split('-'); + const sp = strl.split('-'); if (sp.length != 2) return undefined; let loopRules: LoopRule | undefined = undefined; let loopStopRules: LoopStopRule | undefined = undefined; @@ -130,35 +130,35 @@ export function resolveLoopRules2Event( Math.max(loopDateTimeEnd, eventDateTimeEnd)] ]; - let sp = fullLoopRules.split('-'); + const sp = fullLoopRules.split('-'); if (sp.length != 2) return undefined; - let loopRules = sp[0]!; // we don't need consider stop flag - let result: TimeRange[] = new Array(); + const loopRules = sp[0]!; // we don't need consider stop flag + const result: TimeRange[] = []; // compute offset and duration - let eventDateTime = new Date((eventDateTimeStart + timezoneOffset) * 60000); + const eventDateTime = new Date((eventDateTimeStart + timezoneOffset) * 60000); eventDateTime.setUTCHours(0, 0, 0, 0); - let eventOffset = eventDateTimeStart - (Math.floor(eventDateTime.getTime() / 60000) - timezoneOffset); - let eventDuration = eventDateTimeEnd - eventDateTimeStart; + const eventOffset = eventDateTimeStart - (Math.floor(eventDateTime.getTime() / 60000) - timezoneOffset); + const eventDuration = eventDateTimeEnd - eventDateTimeStart; - let detectDateTime = new Date(loopDateTimeStart * 60000); + const detectDateTime = new Date(loopDateTimeStart * 60000); detectDateTime.setUTCHours(0, 0, 0, 0); - let originalYear = eventDateTime.getUTCFullYear(); - let originalMonth = eventDateTime.getUTCMonth() + 1; - let originalDay = eventDateTime.getUTCDate(); + const originalYear = eventDateTime.getUTCFullYear(); + const originalMonth = eventDateTime.getUTCMonth() + 1; + const originalDay = eventDateTime.getUTCDate(); // compute event let match: RegExpExecArray | null; if ((match = PRECOMPILED_LOOP_RULES.year.exec(loopRules)) !== null) { - let isStrict = match[1]! == 'S'; - let loopSpan = parseInt(match[2]!); + const isStrict = match[1]! == 'S'; + const loopSpan = parseInt(match[2]!); - let yearCount = detectDateTime.getFullYear() - originalYear; - let isSpecial = (originalMonth == 2 && originalDay == 29); - let realLoopSpan = (isSpecial && isStrict) ? lcm(4, loopSpan) : loopSpan; + const yearCount = detectDateTime.getFullYear() - originalYear; + const isSpecial = (originalMonth == 2 && originalDay == 29); + const realLoopSpan = (isSpecial && isStrict) ? lcm(4, loopSpan) : loopSpan; //let fullSpanCount = Math.floor(yearCount / realLoopSpan); - let remainYear = yearCount % realLoopSpan; + const remainYear = yearCount % realLoopSpan; //detectDateTime.setUTCFullYear(fullSpanCount + detectDateTime.getUTCFullYear(), 1, 1); if (remainYear != 0) detectDateTime.setUTCFullYear(realLoopSpan - remainYear + detectDateTime.getUTCFullYear(), 1 - 1, 1); @@ -189,22 +189,22 @@ export function resolveLoopRules2Event( } } else if ((match = PRECOMPILED_LOOP_RULES.month.exec(loopRules)) !== null) { - let isStrict = match[1]! == 'S'; - let loopMethod = match[2]!; - let loopSpan = parseInt(match[3]!); + const isStrict = match[1]! == 'S'; + const loopMethod = match[2]!; + const loopSpan = parseInt(match[3]!); - let monthsCountValue = monthsCount(detectDateTime.getUTCFullYear(), detectDateTime.getUTCMonth() + 1) - + const monthsCountValue = monthsCount(detectDateTime.getUTCFullYear(), detectDateTime.getUTCMonth() + 1) - monthsCount(originalYear, originalMonth); //let fullSpanCount = Math.floor(monthsCountValue / loopSpan); - let remainMonth = monthsCountValue % loopSpan; + const remainMonth = monthsCountValue % loopSpan; //detectDateTime.setUTCMonth(fullSpanCount * loopSpan + detectDateTime.getUTCMonth(), 1); detectDateTime.setUTCDate(1); if (remainMonth != 0) detectDateTime.setUTCMonth(loopSpan - remainMonth + detectDateTime.getUTCMonth(), 1); while (Math.floor(detectDateTime.getTime() / 60000) + eventOffset - timezoneOffset <= loopDateTimeEnd) { - let data = getRemanagedDayInMonth(originalYear, originalMonth, originalDay, detectDateTime.getUTCFullYear(), detectDateTime.getUTCMonth() + 1, isStrict); + const data = getRemanagedDayInMonth(originalYear, originalMonth, originalDay, detectDateTime.getUTCFullYear(), detectDateTime.getUTCMonth() + 1, isStrict); let predictedDay: number | undefined = undefined; switch (loopMethod) { case 'A': @@ -233,15 +233,15 @@ export function resolveLoopRules2Event( } else if ((match = PRECOMPILED_LOOP_RULES.week.exec(loopRules)) !== null) { - let loopSpan = parseInt(match[2]!); - let weekOption: boolean[] = []; + const loopSpan = parseInt(match[2]!); + const weekOption: boolean[] = []; let weekEventCount = 0 for (let i = 0; i < 7; i++) { weekOption.push(match[1]![i] == 'T'); if (match[1]![i] == 'T') weekEventCount++; } - let originalWeek = dayOfWeek(originalYear, originalMonth, originalDay); + const originalWeek = dayOfWeek(originalYear, originalMonth, originalDay); // try insert original event if (!weekOption[originalWeek]) { @@ -250,11 +250,11 @@ export function resolveLoopRules2Event( ); } - let daysCountValue = daysCount(detectDateTime.getUTCFullYear(), detectDateTime.getUTCMonth() + 1, detectDateTime.getDate()) - + const daysCountValue = daysCount(detectDateTime.getUTCFullYear(), detectDateTime.getUTCMonth() + 1, detectDateTime.getDate()) - daysCount(originalYear, originalMonth, originalDay); //let fullSpanCount = Math.floor(daysCountValue / (7 * loopSpan)); - let remainFullSpanCount = Math.floor((daysCountValue % (7 * loopSpan)) / 7); - let remainDays = (daysCountValue % (7 * loopSpan)) % 7; + const remainFullSpanCount = Math.floor((daysCountValue % (7 * loopSpan)) / 7); + const remainDays = (daysCountValue % (7 * loopSpan)) % 7; //detectDateTime.setUTCDate((7 * loopSpan * fullSpanCount) + detectDateTime.getUTCDate()); if (remainFullSpanCount != 0) { @@ -276,12 +276,12 @@ export function resolveLoopRules2Event( } } else if ((match = PRECOMPILED_LOOP_RULES.day.exec(loopRules)) !== null) { - let loopSpan = parseInt(match[1]!); + const loopSpan = parseInt(match[1]!); - let daysCountValue = daysCount(detectDateTime.getUTCFullYear(), detectDateTime.getUTCMonth() + 1, detectDateTime.getUTCDate()) - + const daysCountValue = daysCount(detectDateTime.getUTCFullYear(), detectDateTime.getUTCMonth() + 1, detectDateTime.getUTCDate()) - daysCount(originalYear, originalMonth, originalDay); //let fullSpanCount = Math.floor(daysCountValue / loopSpan); - let remainDays = daysCountValue % loopSpan; + const remainDays = daysCountValue % loopSpan; //detectDateTime.setUTCDate(fullSpanCount * loopSpan + detectDateTime.getUTCDate()); if (remainDays != 0) detectDateTime.setUTCDate(loopSpan - remainDays + detectDateTime.getUTCDate()); @@ -296,10 +296,10 @@ export function resolveLoopRules2Event( } else return undefined; // clamp item - let realResult: TimeRange[] = new Array(); - for (let i of result) { - let start = i[0]; - let end = i[1]; + const realResult: TimeRange[] = []; + for (const i of result) { + const start = i[0]; + const end = i[1]; if (end > clampStartDateTime && start <= loopDateTimeEnd) realResult.push([Math.max(start, clampStartDateTime), Math.min(end, loopDateTimeEnd)]); } @@ -319,11 +319,11 @@ export function resolveLoopRules2Event( export function resolveLoopRules4Text(strl: string, startDateTime: number, timezoneOffset: number): string { if (strl == '') return ""; - let sp = strl.split('-'); + const sp = strl.split('-'); if (sp.length != 2) return ""; let loopRules: string; let loopStopRules: string; - let datetimeInstance = new Date((startDateTime + timezoneOffset) * 60000) + const datetimeInstance = new Date((startDateTime + timezoneOffset) * 60000) let match: RegExpExecArray | null; if ((match = PRECOMPILED_LOOP_RULES.year.exec(sp[0]!)) !== null) { @@ -340,7 +340,7 @@ export function resolveLoopRules4Text(strl: string, startDateTime: number, timez else loopRules = "宽松模式。"; - let dayInMonth = getDayInMonth( + const dayInMonth = getDayInMonth( datetimeInstance.getUTCFullYear(), datetimeInstance.getUTCMonth() + 1, datetimeInstance.getUTCDate()); @@ -367,7 +367,7 @@ export function resolveLoopRules4Text(strl: string, startDateTime: number, timez break; } } else if ((match = PRECOMPILED_LOOP_RULES.week.exec(sp[0]!)) !== null) { - let weekOfDayCache = []; + const weekOfDayCache = []; for (let i = 0; i < 7; i++) { if (match[1]![i] == 'T') weekOfDayCache.push(universalGetDayOfWeek(i)); @@ -414,7 +414,7 @@ function leapYearCountEx(endYear: number, includeThis: boolean, baseYear: number } function daysCount(year: number, month: number, day: number): number { - let ly = leapYearCountEx(year, false, 1, true); + const ly = leapYearCountEx(year, false, 1, true); let days = 365 * (year - 1); days += ly; @@ -432,7 +432,7 @@ function monthsCount(year: number, month: number): number { return (year - 1) * 12 + (month - 1); } -function dayOfWeek(year: number, month: number, day: number): number { +export function dayOfWeek(year: number, month: number, day: number): number { return daysCount(year, month, day) % 7; } @@ -452,15 +452,15 @@ type DayInMonthInfo = [ ]; function getDayInMonth(year: number, month: number, day: number): DayInMonthInfo { - let days = MONTH_DAY_COUNT[month - 1]! + ((month == 2 && isLeapYear(year)) ? 1 : 0); - let firstDayOfWeek = dayOfWeek(year, month, 1); - let bilateralDayOfWeek = (firstDayOfWeek + day - 1) % 7; + const days = MONTH_DAY_COUNT[month - 1]! + ((month == 2 && isLeapYear(year)) ? 1 : 0); + const firstDayOfWeek = dayOfWeek(year, month, 1); + const bilateralDayOfWeek = (firstDayOfWeek + day - 1) % 7; - let dayForwards = day; - let dayBackwards = days - day + 1; + const dayForwards = day; + const dayBackwards = days - day + 1; - let weeksForward = Math.floor((dayForwards - 1) / 7) + 1; - let weeksBackwards = Math.floor((dayBackwards - 1) / 7) + 1; + const weeksForward = Math.floor((dayForwards - 1) / 7) + 1; + const weeksBackwards = Math.floor((dayBackwards - 1) / 7) + 1; return [dayForwards, dayBackwards, weeksForward, bilateralDayOfWeek, weeksBackwards, bilateralDayOfWeek]; } @@ -493,10 +493,10 @@ type RemanagedDayInMonth = [ * @returns */ function getRemanagedDayInMonth(oldYear: number, oldMonth: number, oldDay: number, newYear: number, newMonth: number, isStrict: boolean): RemanagedDayInMonth { - let ddata = getDayInMonth(oldYear, oldMonth, oldDay); - let mdata = getMonthWeekStatistics(newYear, newMonth); - let days = MONTH_DAY_COUNT[newMonth - 1]! + ((newMonth == 2 && isLeapYear(newYear)) ? 1 : 0); - let firstDayOfWeek = dayOfWeek(newYear, newMonth, 1); + const ddata = getDayInMonth(oldYear, oldMonth, oldDay); + const mdata = getMonthWeekStatistics(newYear, newMonth); + const days = MONTH_DAY_COUNT[newMonth - 1]! + ((newMonth == 2 && isLeapYear(newYear)) ? 1 : 0); + const firstDayOfWeek = dayOfWeek(newYear, newMonth, 1); //let lastDayOfWeek = (firstDayOfWeek + days - 1) % 7; let methodA = undefined; @@ -511,14 +511,14 @@ function getRemanagedDayInMonth(oldYear: number, oldMonth: number, oldDay: numbe let methodC = undefined; if (ddata[2] <= mdata[ddata[3]]! || !isStrict) { - let targetWeek = Math.min(ddata[2], mdata[ddata[3]]!); + const targetWeek = Math.min(ddata[2], mdata[ddata[3]]!); methodC = 1 + (targetWeek - 1) * 7 + ((ddata[3] + 7 - firstDayOfWeek) % 7); } let methodD = undefined; if (ddata[4] <= mdata[ddata[5]]! || !isStrict) { // convert to type c and calc - let targetWeek = mdata[ddata[5]]! - Math.min(ddata[4], mdata[ddata[5]]!) + 1; + const targetWeek = mdata[ddata[5]]! - Math.min(ddata[4], mdata[ddata[5]]!) + 1; methodD = 1 + (targetWeek - 1) * 7 + ((ddata[5] + 7 - firstDayOfWeek) % 7); } @@ -543,10 +543,10 @@ type MonthWeekStatistics = [ ]; function getMonthWeekStatistics(year: number, month: number): MonthWeekStatistics { - let days = MONTH_DAY_COUNT[month - 1]! + ((month == 2 && isLeapYear(year)) ? 1 : 0); - let firstDayOfWeek = dayOfWeek(year, month, 1); + const days = MONTH_DAY_COUNT[month - 1]! + ((month == 2 && isLeapYear(year)) ? 1 : 0); + const firstDayOfWeek = dayOfWeek(year, month, 1); - let result: MonthWeekStatistics = [4, 4, 4, 4, 4, 4, 4]; + const result: MonthWeekStatistics = [4, 4, 4, 4, 4, 4, 4]; let remain = days % 7; let week = firstDayOfWeek; while (remain > 0) { @@ -558,7 +558,7 @@ function getMonthWeekStatistics(year: number, month: number): MonthWeekStatistic return result; } -function isLeapYear(year: number): boolean { +export function isLeapYear(year: number): boolean { let isLeap = false; if (year % 4 == 0) isLeap = true; if (year % 100 == 0) isLeap = false; diff --git a/frontend/src/utils/utils.ts b/frontend/src/utils/utils.ts index 7fcb5a9..a323c25 100644 --- a/frontend/src/utils/utils.ts +++ b/frontend/src/utils/utils.ts @@ -34,7 +34,7 @@ export function format(str: string, ...args: any[]): string { * @returns The zero-based weekday. 0 stands for Monday. */ export function getWeekday(date: Date): number { - let day = date.getDay(); + const day = date.getDay(); if (day == 0) return 6; else return day - 1; } diff --git a/frontend/src/views/Collection.vue b/frontend/src/views/Collection.vue index 38f2dd9..6848f08 100644 --- a/frontend/src/views/Collection.vue +++ b/frontend/src/views/Collection.vue @@ -37,13 +37,13 @@ const refreshOwnedItem = async () => { ownedItems.value.clear(); // Fetch data - let rv = await apiCollectionGetFullOwn(token.currentToken); + const rv = await apiCollectionGetFullOwn(token.currentToken); if (typeof rv === 'undefined') { console.error("fail to fetch owned collection"); return; } // Add into dict for rendering - for (let item of rv) { + for (const item of rv) { ownedItems.value.set(item[0], item); } @@ -53,20 +53,20 @@ const refreshOwnedItem = async () => { } const addOwnedItem = async () => { - let new_name = pendingOwnedItemName.value; + const new_name = pendingOwnedItemName.value; if (new_name === '') { return } // first add it - let rv = await apiCollectionAddOwn(token.currentToken, new_name); + const rv = await apiCollectionAddOwn(token.currentToken, new_name); if (typeof rv === '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."); return; } // second get its detail - let rv2 = await apiCollectionGetDetailOwn(token.currentToken, rv); + const rv2 = await apiCollectionGetDetailOwn(token.currentToken, rv); if (typeof rv2 === 'undefined') { messagebox.value?.show("A get operation failed. It may caused by server internal error or your limited permission. Refreshing page may fix system problem. Before refreshing page, please backup all your unsaved data."); return; @@ -77,7 +77,7 @@ const addOwnedItem = async () => { } const deleteOwnedItem = async (uuid: string) => { - let rv = await apiCollectionDeleteOwn(token.currentToken, uuid, ownedItems.value.get(uuid)![2]); + const rv = await apiCollectionDeleteOwn(token.currentToken, uuid, ownedItems.value.get(uuid)![2]); if (rv) { messagebox.value?.show("A delete operation failed. It may caused by no matched item. Refreshing page may fix system problem. Before refreshing page, please backup all your unsaved data."); return; @@ -97,7 +97,7 @@ const deleteOwnedItem = async (uuid: string) => { const shareOwnedItem = async (uuid: string) => { // if there is no editing sharing target, set it. // otherwise override it or toggle it according to its old value. - let old_value = sharingItemUuid.value; + const old_value = sharingItemUuid.value; if (typeof old_value === 'string') { if (old_value === uuid) { sharingItemUuid.value = null; @@ -112,15 +112,15 @@ const shareOwnedItem = async (uuid: string) => { } const updateOwnedItem = async (uuid: string, name: string) => { - let lastChange = ownedItems.value.get(uuid)![2]; - let rv = await apiCollectionUpdateOwn(token.currentToken, uuid, name, lastChange); + const lastChange = ownedItems.value.get(uuid)![2]; + const rv = await apiCollectionUpdateOwn(token.currentToken, uuid, name, lastChange); if (typeof rv === '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."); return; } // return value is new lastChange, so we update it. - let row = ownedItems.value.get(uuid)!; + const row = ownedItems.value.get(uuid)!; row[1] = name; row[2] = rv; @@ -140,8 +140,8 @@ const refreshSharingItem = async () => { } // fetch data - let uuid = sharingItemUuid.value!; - let rv = await apiCollectionGetSharing(token.currentToken, uuid); + const uuid = sharingItemUuid.value!; + const rv = await apiCollectionGetSharing(token.currentToken, uuid); if (typeof rv === 'undefined') { console.error(`fail to fetch sharing target for collection ${uuid}`); return; @@ -152,15 +152,15 @@ const refreshSharingItem = async () => { } const addSharingItem = async () => { - let uuid = sharingItemUuid.value!; - let lastChange = ownedItems.value.get(uuid)![2]; - let new_username = pendingSharingItemUsername.value; + const uuid = sharingItemUuid.value!; + const lastChange = ownedItems.value.get(uuid)![2]; + const new_username = pendingSharingItemUsername.value; if (new_username === '') { return; } // order adding - let rv = await apiCollectionAddSharing(token.currentToken, uuid, new_username, lastChange); + const rv = await apiCollectionAddSharing(token.currentToken, uuid, new_username, lastChange); if (typeof rv === '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."); return; @@ -173,11 +173,11 @@ const addSharingItem = async () => { } const deleteSharingItem = async (username: string) => { - let uuid = sharingItemUuid.value!; - let lastChange = ownedItems.value.get(uuid)![2]; + const uuid = sharingItemUuid.value!; + const lastChange = ownedItems.value.get(uuid)![2]; // order deleting - let rv = await apiCollectionDeleteSharing(token.currentToken, uuid, username, lastChange); + const rv = await apiCollectionDeleteSharing(token.currentToken, uuid, username, lastChange); if (typeof rv === 'undefined') { messagebox.value?.show("A delete operation failed. It may caused by no matched item. Refreshing page may fix system problem. Before refreshing page, please backup all your unsaved data."); return;