| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151 |
- <script lang="ts" setup>
- import {SvgIcon} from '../svg.ts';
- import {
- Chart,
- Tooltip,
- BarElement,
- LinearScale,
- TimeScale,
- type ChartOptions,
- type ChartData,
- } from 'chart.js';
- import {GET} from '../modules/fetch.ts';
- import {Bar} from 'vue-chartjs';
- import {
- startDaysBetween,
- firstStartDateAfterDate,
- fillEmptyStartDaysWithZeroes,
- type DayData,
- type DayDataObject,
- } from '../utils/time.ts';
- import {chartJsColors} from '../utils/color.ts';
- import {sleep} from '../utils.ts';
- import 'chartjs-adapter-dayjs-4/dist/chartjs-adapter-dayjs-4.esm';
- import {onMounted, ref, shallowRef} from 'vue';
-
- const {pageData} = window.config;
-
- Chart.defaults.color = chartJsColors.text;
- Chart.defaults.borderColor = chartJsColors.border;
-
- Chart.register(
- TimeScale,
- LinearScale,
- BarElement,
- Tooltip,
- );
-
- defineProps<{
- locale: {
- loadingTitle: string;
- loadingTitleFailed: string;
- loadingInfo: string;
- };
- }>();
-
- const isLoading = shallowRef(false);
- const errorText = shallowRef('');
- const repoLink = pageData.repoLink;
- const data = ref<DayData[]>([]);
-
- onMounted(() => {
- fetchGraphData();
- });
-
- async function fetchGraphData() {
- isLoading.value = true;
- try {
- let response: Response;
- do {
- response = await GET(`${repoLink}/activity/recent-commits/data`);
- if (response.status === 202) {
- await sleep(1000); // wait for 1 second before retrying
- }
- } while (response.status === 202);
- if (response.ok) {
- const dayDataObj: DayDataObject = await response.json();
- const start = Object.values(dayDataObj)[0].week;
- const end = firstStartDateAfterDate(new Date());
- const startDays = startDaysBetween(start, end);
- data.value = fillEmptyStartDaysWithZeroes(startDays, dayDataObj).slice(-52);
- errorText.value = '';
- } else {
- errorText.value = response.statusText;
- }
- } catch (err) {
- errorText.value = err.message;
- } finally {
- isLoading.value = false;
- }
- }
-
- function toGraphData(data: DayData[]): ChartData<'bar'> {
- return {
- datasets: [
- {
- // @ts-expect-error -- bar chart expects one-dimensional data, but apparently x/y still works
- data: data.map((i) => ({x: i.week, y: i.commits})),
- label: 'Commits',
- backgroundColor: chartJsColors['commits'],
- borderWidth: 0,
- tension: 0.3,
- },
- ],
- };
- }
-
- const options: ChartOptions<'bar'> = {
- responsive: true,
- maintainAspectRatio: false,
- scales: {
- x: {
- type: 'time',
- grid: {
- display: false,
- },
- time: {
- minUnit: 'week',
- },
- ticks: {
- maxRotation: 0,
- maxTicksLimit: 52,
- },
- },
- y: {
- ticks: {
- maxTicksLimit: 6,
- },
- },
- },
- } satisfies ChartOptions;
- </script>
-
- <template>
- <div>
- <div class="ui header tw-flex tw-items-center tw-justify-between">
- {{ isLoading ? locale.loadingTitle : errorText ? locale.loadingTitleFailed: "Number of commits in the past year" }}
- </div>
- <div class="tw-flex ui segment main-graph">
- <div v-if="isLoading || errorText !== ''" class="gt-tc tw-m-auto">
- <div v-if="isLoading">
- <SvgIcon name="octicon-sync" class="tw-mr-2 circular-spin"/>
- {{ locale.loadingInfo }}
- </div>
- <div v-else class="text red">
- <SvgIcon name="octicon-x-circle-fill"/>
- {{ errorText }}
- </div>
- </div>
- <Bar
- v-memo="data" v-if="data.length !== 0"
- :data="toGraphData(data)" :options="options"
- />
- </div>
- </div>
- </template>
- <style scoped>
- .main-graph {
- height: 250px;
- }
- </style>
|