/
uzer_007
/
Rixea
Обзор
Документация
Войти
/
uzer_007
/
Rixea
Код
Запросы
0
Задачи
Вики
Пакеты
5
Релизы
2
CI/CD
Аналитика
Безопасность
master
web_src/js/components/RepoCodeFrequency.vue
200 строк
5 KB
uzer_007
feat: первая версия Rixea
02 авг 2026, 22:16
02 авг 2026, 22:16
d4f7504
Код
Авторство
О чём код?
<script lang="ts" setup> import {SvgIcon} from '../svg.ts'; import { Chart, Legend, LinearScale, TimeScale, PointElement, LineElement, Filler, type ChartOptions, type ChartData, } from 'chart.js'; import {Line as ChartLine} from 'vue-chartjs'; import { startDaysBetween, firstStartDateAfterDate, fillEmptyStartDaysWithZeroes, type DayData, type DayDataObject, } from '../utils/time.ts'; import {chartJsColors} from '../utils/color.ts'; import {errorName} from '../modules/errors.ts'; import {fetchActivityData} from '../features/repo-activity.ts'; import 'chartjs-adapter-dayjs-4/dist/chartjs-adapter-dayjs-4.esm'; import {computed, onMounted, onUnmounted, shallowRef} from 'vue'; const {pageData} = window.config; Chart.defaults.color = chartJsColors.text; Chart.defaults.borderColor = chartJsColors.border; Chart.register( TimeScale, LinearScale, Legend, PointElement, LineElement, Filler, ); const props = defineProps<{ locale: { additionsLabel: string; deletionsLabel: string; emptyInfo: string; errorInfo: string; loadingTitle: string; loadingTitleFailed: string; loadingInfo: string; retry: string; title: string; }; }>(); const isLoading = shallowRef(false); const hasError = shallowRef(false); const isEmpty = shallowRef(false); const repoLink = pageData.repoLink!; const data = shallowRef<DayData[]>([]); const requestController = new AbortController(); const heading = computed(() => { if (isLoading.value) return props.locale.loadingTitle; if (hasError.value) return props.locale.loadingTitleFailed; return props.locale.title; }); onMounted(() => { fetchGraphData(); }); onUnmounted(() => requestController.abort()); async function fetchGraphData() { if (isLoading.value) return; isLoading.value = true; hasError.value = false; isEmpty.value = false; try { const dayDataObject = await fetchActivityData<DayDataObject>( `${repoLink}/activity/code-frequency/data`, {signal: requestController.signal}, ); const weekValues = Object.values(dayDataObject); if (weekValues.length === 0) { data.value = []; isEmpty.value = true; return; } const start = weekValues[0].week; const end = firstStartDateAfterDate(new Date()); const startDays = startDaysBetween(start, end); data.value = fillEmptyStartDaysWithZeroes(startDays, dayDataObject); } catch (err) { if (errorName(err) === 'AbortError') return; console.error('Code frequency graph data failed to load', err); data.value = []; hasError.value = true; } finally { isLoading.value = false; } } function toGraphData(data: Array<Record<string, any>>): ChartData<'line'> { return { datasets: [ { data: data.map((i) => ({x: i.week, y: i.additions})), pointRadius: 0, pointHitRadius: 0, fill: true, label: props.locale.additionsLabel, backgroundColor: chartJsColors['additions'], borderWidth: 0, tension: 0.3, }, { data: data.map((i) => ({x: i.week, y: -i.deletions})), pointRadius: 0, pointHitRadius: 0, fill: true, label: props.locale.deletionsLabel, backgroundColor: chartJsColors['deletions'], borderWidth: 0, tension: 0.3, }, ], }; } const options: ChartOptions<'line'> = { responsive: true, maintainAspectRatio: false, plugins: { legend: { display: true, }, }, scales: { x: { type: 'time', grid: { display: false, }, time: { minUnit: 'month', }, ticks: { maxRotation: 0, maxTicksLimit: 12, }, }, y: { ticks: { maxTicksLimit: 6, }, }, }, }; </script> <template> <div> <div class="ui header"> {{ heading }} </div> <div class="tw-flex ui segment main-graph" :aria-busy="isLoading"> <div v-if="isLoading || hasError || isEmpty" class="tw-m-auto tw-text-center" aria-live="polite"> <div v-if="isLoading" role="status"> <SvgIcon name="gitea-running" class="tw-mr-2 rotate-clockwise"/> {{ locale.loadingInfo }} </div> <div v-else-if="hasError" role="alert"> <div class="tw-text-red"> <SvgIcon name="octicon-x-circle-fill"/> {{ locale.errorInfo }} </div> <button type="button" class="ui button tw-mt-3" @click="fetchGraphData"> <SvgIcon name="octicon-sync"/> {{ locale.retry }} </button> </div> <div v-else role="status"> <SvgIcon name="octicon-meter"/> {{ locale.emptyInfo }} </div> </div> <ChartLine v-memo="data" v-if="data.length !== 0" :data="toGraphData(data)" :options="options" role="img" :aria-label="locale.title" /> </div> </div> </template> <style scoped> .main-graph { height: 440px; } </style>