revert: revoke all ai-generated content
This commit is contained in:
@@ -1,344 +1,8 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted, ref, watch } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { useTokenStore } from '@/stores/token';
|
||||
import MessageBox from '@/components/MessageBox.vue';
|
||||
import DateTimePicker, { TabType } from '@/components/DateTimePicker.vue';
|
||||
import CalendarGrid from '@/components/calendar/CalendarGrid.vue';
|
||||
import ScheduleList from '@/components/calendar/ScheduleList.vue';
|
||||
import CollectionToggleItem from '@/components/calendar/CollectionToggleItem.vue';
|
||||
import type { DisplayDay, DisplayEvent } from '@/components/calendar/types';
|
||||
import {
|
||||
getFull as apiCalendarGetFull,
|
||||
deserializeDescription,
|
||||
type CalendarRow,
|
||||
} from '@/api/calendar';
|
||||
import {
|
||||
getFullOwn as apiCollectionGetFullOwn,
|
||||
getShared as apiCollectionGetShared,
|
||||
type CollectionRow,
|
||||
type SharedCollectionRow,
|
||||
} from '@/api/collection';
|
||||
import {
|
||||
DAY1_SPAN,
|
||||
dayOfWeek,
|
||||
resolveLoopRules2Event,
|
||||
resolveLoopRules4Text,
|
||||
} from '@/utils/datetime';
|
||||
import { MONTH_NAMES, WEEK_NAMES } from '@/utils/i18n';
|
||||
|
||||
const token = useTokenStore();
|
||||
const router = useRouter();
|
||||
|
||||
const messagebox = ref<InstanceType<typeof MessageBox>>();
|
||||
const picker = ref<InstanceType<typeof DateTimePicker>>();
|
||||
|
||||
const activeTab = ref<number>(1);
|
||||
|
||||
// The viewed month cursor. Only year + month are meaningful; the time-of-day
|
||||
// participates in the grid start timestamp exactly like the legacy code.
|
||||
const currentMonth = ref<Date>(new Date());
|
||||
|
||||
// Raw event rows covering the current grid window.
|
||||
const eventsCache = ref<CalendarRow[]>([]);
|
||||
|
||||
// Collection lists for tab 2.
|
||||
const ownedCollections = ref<CollectionRow[]>([]);
|
||||
const sharedCollections = ref<SharedCollectionRow[]>([]);
|
||||
|
||||
// Per-collection visibility flags, keyed by collection uuid. Default (absent)
|
||||
// means hidden, matching the legacy behaviour where missing cache entries are
|
||||
// treated as false.
|
||||
const ownedVisible = ref<Map<string, boolean>>(new Map());
|
||||
const sharedVisible = ref<Map<string, boolean>>(new Map());
|
||||
|
||||
// region: Computed grid window and analyzed display data
|
||||
|
||||
const gridYear = computed(() => currentMonth.value.getFullYear());
|
||||
const gridMonth = computed(() => currentMonth.value.getMonth() + 1);
|
||||
const firstDow = computed(() => dayOfWeek(gridYear.value, gridMonth.value, 1));
|
||||
const gridStartTimestamp = computed(() =>
|
||||
Math.floor(currentMonth.value.getTime() / 60000) - firstDow.value * DAY1_SPAN
|
||||
);
|
||||
const gridEndTimestamp = computed(() => gridStartTimestamp.value + DAY1_SPAN * 6 * 7 - 1);
|
||||
|
||||
const textMonth = computed(() => `${gridYear.value} - ${MONTH_NAMES[gridMonth.value - 1]}`);
|
||||
|
||||
const myTimezoneOffset = computed(() => -(new Date().getTimezoneOffset()));
|
||||
|
||||
// Port of ccn_calendar_calendar_Analyse: build the 42-cell display cache,
|
||||
// expand loop events and split occurrences that cross day boundaries.
|
||||
const displayDays = computed<DisplayDay[]>(() => {
|
||||
const startTimestamp = gridStartTimestamp.value;
|
||||
const endTimestamp = gridEndTimestamp.value;
|
||||
const gottenMonth = gridMonth.value;
|
||||
const myTz = myTimezoneOffset.value;
|
||||
|
||||
// Build the 42 day cells.
|
||||
const days: DisplayDay[] = [];
|
||||
const cursor = new Date(startTimestamp * 60000);
|
||||
for (let i = 0; i < 6 * 7; i++) {
|
||||
days.push({
|
||||
month: cursor.getMonth() + 1,
|
||||
day: cursor.getDate(),
|
||||
dayOfWeek: (cursor.getDay() === 0 ? 6 : cursor.getDay() - 1) + 1,
|
||||
isCurrentMonth: (cursor.getMonth() + 1) === gottenMonth,
|
||||
subcalendar: '',
|
||||
events: [],
|
||||
});
|
||||
cursor.setTime(cursor.getTime() + DAY1_SPAN * 60000);
|
||||
}
|
||||
|
||||
// Expand and place each event.
|
||||
for (const item of eventsCache.value) {
|
||||
const deserialized = deserializeDescription(item[3]);
|
||||
const minStartTimestamp = startTimestamp - (item[6] - item[5]);
|
||||
const result = resolveLoopRules2Event(
|
||||
item[8],
|
||||
item[9] < minStartTimestamp ? minStartTimestamp : item[9],
|
||||
Math.min(item[10], endTimestamp),
|
||||
item[5],
|
||||
item[6],
|
||||
item[7],
|
||||
startTimestamp
|
||||
);
|
||||
if (typeof result === 'undefined') continue;
|
||||
|
||||
const loopText = resolveLoopRules4Text(item[8], item[5], item[7]);
|
||||
const timezoneWarning = myTz !== item[7];
|
||||
|
||||
for (const it of result) {
|
||||
const eventDateTime = new Date(it[0] * 60000);
|
||||
let count = Math.floor((it[0] - startTimestamp) / DAY1_SPAN);
|
||||
let exitFlag = false;
|
||||
while (count < 6 * 7) {
|
||||
// NOTE: legacy indexes the owned cache by the *event* uuid (item[0]),
|
||||
// which is collection-keyed, so this is always false. Kept faithful.
|
||||
const isLocked = ownedVisible.value.has(item[0]);
|
||||
const belongVisible = ownedVisible.value.get(item[1]) || sharedVisible.value.get(item[1]) || false;
|
||||
const eventItem: DisplayEvent = {
|
||||
uuid: item[0],
|
||||
belongTo: item[1],
|
||||
title: item[2],
|
||||
description: deserialized.description,
|
||||
color: deserialized.color,
|
||||
isVisible: belongVisible,
|
||||
isLocked,
|
||||
loopText,
|
||||
timezoneWarning,
|
||||
start: eventDateTime.toLocaleTimeString(),
|
||||
end: '',
|
||||
};
|
||||
eventDateTime.setHours(23, 59, 0, 0);
|
||||
if (it[1] <= Math.floor(eventDateTime.getTime() / 60000)) {
|
||||
exitFlag = true;
|
||||
eventDateTime.setTime(it[1] * 60000);
|
||||
}
|
||||
eventItem.end = eventDateTime.toLocaleTimeString();
|
||||
days[count]!.events.push(eventItem);
|
||||
if (exitFlag) break;
|
||||
eventDateTime.setMinutes(eventDateTime.getMinutes() + 1, 0, 0);
|
||||
count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return days;
|
||||
});
|
||||
|
||||
// endregion
|
||||
|
||||
// region: Data fetching
|
||||
|
||||
const refreshEvents = async () => {
|
||||
const rv = await apiCalendarGetFull(token.currentToken, gridStartTimestamp.value, gridEndTimestamp.value);
|
||||
eventsCache.value = typeof rv === 'undefined' ? [] : rv;
|
||||
};
|
||||
|
||||
const refreshCollections = async () => {
|
||||
const newOwned = new Map<string, boolean>();
|
||||
const newShared = new Map<string, boolean>();
|
||||
|
||||
const sharedRv = await apiCollectionGetShared(token.currentToken);
|
||||
sharedCollections.value = typeof sharedRv === 'undefined' ? [] : sharedRv;
|
||||
if (sharedCollections.value.length > 0) {
|
||||
for (const item of sharedCollections.value) {
|
||||
newShared.set(item[0], true);
|
||||
}
|
||||
}
|
||||
|
||||
const ownedRv = await apiCollectionGetFullOwn(token.currentToken);
|
||||
ownedCollections.value = typeof ownedRv === 'undefined' ? [] : ownedRv;
|
||||
if (ownedCollections.value.length > 0) {
|
||||
for (const item of ownedCollections.value) {
|
||||
newOwned.set(item[0], true);
|
||||
}
|
||||
}
|
||||
|
||||
ownedVisible.value = newOwned;
|
||||
sharedVisible.value = newShared;
|
||||
};
|
||||
|
||||
// endregion
|
||||
|
||||
// region: Navigation
|
||||
|
||||
const prevMonth = () => {
|
||||
const d = new Date(currentMonth.value);
|
||||
d.setMonth(d.getMonth() - 1);
|
||||
currentMonth.value = d;
|
||||
};
|
||||
|
||||
const nextMonth = () => {
|
||||
const d = new Date(currentMonth.value);
|
||||
d.setMonth(d.getMonth() + 1);
|
||||
currentMonth.value = d;
|
||||
};
|
||||
|
||||
const today = () => {
|
||||
currentMonth.value = new Date();
|
||||
};
|
||||
|
||||
const jump = () => {
|
||||
picker.value?.modal(currentMonth.value, TabType.Month);
|
||||
};
|
||||
|
||||
const onPickerConfirm = (date: Date) => {
|
||||
currentMonth.value = date;
|
||||
};
|
||||
|
||||
const addEvent = () => {
|
||||
router.push({ name: 'CalendarEventAdd' });
|
||||
};
|
||||
|
||||
const editEvent = (uuid: string) => {
|
||||
router.push({ name: 'CalendarEventUpdate', params: { uuid } });
|
||||
};
|
||||
|
||||
// endregion
|
||||
|
||||
// region: Collection visibility toggles
|
||||
|
||||
const toggleOwned = (uuid: string) => {
|
||||
const m = new Map(ownedVisible.value);
|
||||
m.set(uuid, !m.get(uuid));
|
||||
ownedVisible.value = m;
|
||||
};
|
||||
|
||||
const toggleShared = (uuid: string) => {
|
||||
const m = new Map(sharedVisible.value);
|
||||
m.set(uuid, !m.get(uuid));
|
||||
sharedVisible.value = m;
|
||||
};
|
||||
|
||||
// endregion
|
||||
|
||||
watch(currentMonth, () => { refreshEvents(); });
|
||||
|
||||
onMounted(async () => {
|
||||
await refreshCollections();
|
||||
await refreshEvents();
|
||||
});
|
||||
</script>
|
||||
<script setup lang="ts"></script>
|
||||
|
||||
<template>
|
||||
<div class="container" style="margin-top: 20px;">
|
||||
<div class="tabs">
|
||||
<ul>
|
||||
<li :class="{ 'is-active': activeTab === 1 }" @click="activeTab = 1"><a>Calendar</a></li>
|
||||
<li :class="{ 'is-active': activeTab === 2 }" @click="activeTab = 2"><a>Collection</a></li>
|
||||
<li :class="{ 'is-active': activeTab === 3 }" @click="activeTab = 3"><a>Display</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Tab 1: Calendar -->
|
||||
<div v-show="activeTab === 1" class="container" style="margin-top: 20px;">
|
||||
<nav class="level is-mobile">
|
||||
<div class="level-left">
|
||||
<div class="level-item control">
|
||||
<a class="button" @click="prevMonth">
|
||||
<span class="icon is-small"><font-awesome-icon icon="fas fa-chevron-circle-left"></font-awesome-icon></span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="level-item control">
|
||||
<a class="button" @click="jump">
|
||||
<span>{{ textMonth }}</span>
|
||||
</a>
|
||||
</div>
|
||||
<div class="level-right">
|
||||
<div class="level-item control">
|
||||
<a class="button" @click="nextMonth">
|
||||
<span class="icon is-small"><font-awesome-icon icon="fas fa-chevron-circle-right"></font-awesome-icon></span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
<nav class="level is-mobile">
|
||||
<div class="level-item control">
|
||||
<a class="button is-info" @click="today">Today</a>
|
||||
</div>
|
||||
<div class="level-item control">
|
||||
<a class="button is-primary" @click="addEvent">Add...</a>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<div class="card" style="padding: 1.25rem;">
|
||||
<CalendarGrid :cells="displayDays" :week-names="WEEK_NAMES" @event-click="editEvent" />
|
||||
</div>
|
||||
|
||||
<div class="container" style="padding: 1.25rem; display: flex; flex-flow: column; margin-top: 1.25rem;">
|
||||
<h1 class="title">Schedule</h1>
|
||||
<ScheduleList :days="displayDays" @event-click="editEvent" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Tab 2: Collection -->
|
||||
<div v-show="activeTab === 2" class="container" style="margin-top: 20px;">
|
||||
<div class="control" style="margin: 0.75rem;">
|
||||
<a class="button is-primary" @click="refreshCollections">
|
||||
<span class="icon is-small"><font-awesome-icon icon="fas fa-sync"></font-awesome-icon></span>
|
||||
</a>
|
||||
</div>
|
||||
<h1 class="title">My collections</h1>
|
||||
<div style="display: flex; flex-flow: column; margin-top: 1.25rem; margin-bottom: 1.25rem;">
|
||||
<CollectionToggleItem v-for="item in ownedCollections" :key="item[0]" :name="item[1]"
|
||||
:is-show="ownedVisible.get(item[0]) || false" @toggle="toggleOwned(item[0])" />
|
||||
</div>
|
||||
<h1 class="title">Shared collections</h1>
|
||||
<div style="display: flex; flex-flow: column; margin-top: 1.25rem; margin-bottom: 1.25rem;">
|
||||
<CollectionToggleItem v-for="item in sharedCollections" :key="item[0]" :name="item[1]" :subtitle="item[2]"
|
||||
:is-show="sharedVisible.get(item[0]) || false" @toggle="toggleShared(item[0])" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Tab 3: Display (legacy dead UI, reproduced as-is) -->
|
||||
<div v-show="activeTab === 3" class="container" style="margin-top: 20px;">
|
||||
<div class="field">
|
||||
<label class="label">The first day of week</label>
|
||||
<div class="control">
|
||||
<div class="select">
|
||||
<select>
|
||||
<option v-for="(name, i) in WEEK_NAMES" :key="i">{{ name }}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="label">Sub-Calendar</label>
|
||||
<div class="control">
|
||||
<div class="select">
|
||||
<select>
|
||||
<option>Chinese Lunisolar Calendar</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<MessageBox ref="messagebox" />
|
||||
<DateTimePicker ref="picker" @confirm="onPickerConfirm" />
|
||||
<h1>Congratulations</h1>
|
||||
<p>This is calendar.</p>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
|
||||
Reference in New Issue
Block a user