174 lines
3.5 KiB
Vue
174 lines
3.5 KiB
Vue
<!--
|
|
SPDX-FileCopyrightText: syuilo and misskey-project
|
|
SPDX-License-Identifier: AGPL-3.0-only
|
|
-->
|
|
|
|
<template>
|
|
<div :class="$style.items">
|
|
<template v-for="(item, i) in items" :key="item.id">
|
|
<div :class="$style.left">
|
|
<slot v-if="item.type === 'event'" name="left" :event="item.data" :timestamp="item.timestamp" :delta="item.delta"></slot>
|
|
</div>
|
|
<div :class="[$style.center, item.type === 'date' ? $style.date : '']">
|
|
<div :class="$style.centerLine"></div>
|
|
<div :class="$style.centerPoint"></div>
|
|
</div>
|
|
<div :class="$style.right">
|
|
<slot v-if="item.type === 'event'" name="right" :event="item.data" :timestamp="item.timestamp" :delta="item.delta"></slot>
|
|
<div v-else :class="$style.dateLabel"><i class="ti ti-chevron-up"></i> {{ item.prevText }}</div>
|
|
</div>
|
|
</template>
|
|
</div>
|
|
</template>
|
|
|
|
<script lang="ts" setup>
|
|
import { computed } from 'vue';
|
|
|
|
const props = defineProps<{
|
|
events: {
|
|
id: string;
|
|
timestamp: number;
|
|
data: any;
|
|
}[];
|
|
}>();
|
|
|
|
const events = computed(() => {
|
|
return props.events.toSorted((a, b) => b.timestamp - a.timestamp);
|
|
});
|
|
|
|
function getDateText(dateInstance: Date) {
|
|
const year = dateInstance.getFullYear();
|
|
const month = dateInstance.getMonth() + 1;
|
|
const date = dateInstance.getDate();
|
|
const hour = dateInstance.getHours();
|
|
return `${year.toString()}/${month.toString()}/${date.toString()} ${hour.toString().padStart(2, '0')}:00:00`;
|
|
}
|
|
|
|
const items = computed<({
|
|
id: string;
|
|
type: 'event';
|
|
timestamp: number;
|
|
delta: number;
|
|
data: any;
|
|
} | {
|
|
id: string;
|
|
type: 'date';
|
|
prev: Date;
|
|
prevText: string;
|
|
next: Date | null;
|
|
nextText: string;
|
|
})[]>(() => {
|
|
const results = [];
|
|
for (let i = 0; i < events.value.length; i++) {
|
|
const item = events.value[i];
|
|
|
|
const date = new Date(item.timestamp);
|
|
const nextDate = events.value[i + 1] ? new Date(events.value[i + 1].timestamp) : null;
|
|
|
|
results.push({
|
|
id: item.id,
|
|
type: 'event',
|
|
timestamp: item.timestamp,
|
|
delta: i === events.value.length - 1 ? 0 : item.timestamp - events.value[i + 1].timestamp,
|
|
data: item.data,
|
|
});
|
|
|
|
if (
|
|
i !== events.value.length - 1 &&
|
|
nextDate != null && (
|
|
date.getFullYear() !== nextDate.getFullYear() ||
|
|
date.getMonth() !== nextDate.getMonth() ||
|
|
date.getDate() !== nextDate.getDate() ||
|
|
date.getHours() !== nextDate.getHours()
|
|
)
|
|
) {
|
|
results.push({
|
|
id: `date-${item.id}`,
|
|
type: 'date',
|
|
prev: date,
|
|
prevText: getDateText(date),
|
|
next: nextDate,
|
|
nextText: getDateText(nextDate),
|
|
});
|
|
}
|
|
}
|
|
return results;
|
|
});
|
|
</script>
|
|
|
|
<style lang="scss" module>
|
|
.root {
|
|
|
|
}
|
|
|
|
.items {
|
|
display: grid;
|
|
grid-template-columns: max-content 18px 1fr;
|
|
gap: 0 8px;
|
|
}
|
|
|
|
.item {
|
|
}
|
|
|
|
.center {
|
|
position: relative;
|
|
|
|
&.date {
|
|
.centerPoint::before {
|
|
position: absolute;
|
|
content: "";
|
|
top: 0;
|
|
left: 0;
|
|
right: 0;
|
|
bottom: 0;
|
|
margin: auto;
|
|
width: 7px;
|
|
height: 7px;
|
|
background: var(--MI_THEME-bg);
|
|
border-radius: 50%;
|
|
}
|
|
}
|
|
}
|
|
|
|
.centerLine {
|
|
position: absolute;
|
|
top: 0;
|
|
left: 0;
|
|
right: 0;
|
|
margin: auto;
|
|
width: 3px;
|
|
height: 100%;
|
|
background: color-mix(in srgb, var(--MI_THEME-accent), var(--MI_THEME-bg) 75%);
|
|
}
|
|
.centerPoint {
|
|
position: absolute;
|
|
top: 0;
|
|
left: 0;
|
|
right: 0;
|
|
bottom: 0;
|
|
margin: auto;
|
|
width: 13px;
|
|
height: 13px;
|
|
background: color-mix(in srgb, var(--MI_THEME-accent), var(--MI_THEME-bg) 75%);
|
|
border-radius: 50%;
|
|
}
|
|
|
|
.left {
|
|
min-width: 0;
|
|
align-self: center;
|
|
justify-self: right;
|
|
}
|
|
|
|
.right {
|
|
min-width: 0;
|
|
align-self: center;
|
|
}
|
|
|
|
.dateLabel {
|
|
opacity: 0.7;
|
|
font-size: 90%;
|
|
padding: 4px;
|
|
margin: 8px 0;
|
|
}
|
|
</style>
|