gitea源码

RepoCodeFrequency.vue 4.0KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174
  1. <script lang="ts" setup>
  2. import {SvgIcon} from '../svg.ts';
  3. import {
  4. Chart,
  5. Legend,
  6. LinearScale,
  7. TimeScale,
  8. PointElement,
  9. LineElement,
  10. Filler,
  11. type ChartOptions,
  12. type ChartData,
  13. } from 'chart.js';
  14. import {GET} from '../modules/fetch.ts';
  15. import {Line as ChartLine} from 'vue-chartjs';
  16. import {
  17. startDaysBetween,
  18. firstStartDateAfterDate,
  19. fillEmptyStartDaysWithZeroes,
  20. type DayData,
  21. type DayDataObject,
  22. } from '../utils/time.ts';
  23. import {chartJsColors} from '../utils/color.ts';
  24. import {sleep} from '../utils.ts';
  25. import 'chartjs-adapter-dayjs-4/dist/chartjs-adapter-dayjs-4.esm';
  26. import {onMounted, shallowRef} from 'vue';
  27. const {pageData} = window.config;
  28. Chart.defaults.color = chartJsColors.text;
  29. Chart.defaults.borderColor = chartJsColors.border;
  30. Chart.register(
  31. TimeScale,
  32. LinearScale,
  33. Legend,
  34. PointElement,
  35. LineElement,
  36. Filler,
  37. );
  38. defineProps<{
  39. locale: {
  40. loadingTitle: string;
  41. loadingTitleFailed: string;
  42. loadingInfo: string;
  43. };
  44. }>();
  45. const isLoading = shallowRef(false);
  46. const errorText = shallowRef('');
  47. const repoLink = pageData.repoLink;
  48. const data = shallowRef<DayData[]>([]);
  49. onMounted(() => {
  50. fetchGraphData();
  51. });
  52. async function fetchGraphData() {
  53. isLoading.value = true;
  54. try {
  55. let response: Response;
  56. do {
  57. response = await GET(`${repoLink}/activity/code-frequency/data`);
  58. if (response.status === 202) {
  59. await sleep(1000); // wait for 1 second before retrying
  60. }
  61. } while (response.status === 202);
  62. if (response.ok) {
  63. const dayDataObject: DayDataObject = await response.json();
  64. const weekValues = Object.values(dayDataObject);
  65. const start = weekValues[0].week;
  66. const end = firstStartDateAfterDate(new Date());
  67. const startDays = startDaysBetween(start, end);
  68. data.value = fillEmptyStartDaysWithZeroes(startDays, dayDataObject);
  69. errorText.value = '';
  70. } else {
  71. errorText.value = response.statusText;
  72. }
  73. } catch (err) {
  74. errorText.value = err.message;
  75. } finally {
  76. isLoading.value = false;
  77. }
  78. }
  79. function toGraphData(data: Array<Record<string, any>>): ChartData<'line'> {
  80. return {
  81. datasets: [
  82. {
  83. data: data.map((i) => ({x: i.week, y: i.additions})),
  84. pointRadius: 0,
  85. pointHitRadius: 0,
  86. fill: true,
  87. label: 'Additions',
  88. backgroundColor: chartJsColors['additions'],
  89. borderWidth: 0,
  90. tension: 0.3,
  91. },
  92. {
  93. data: data.map((i) => ({x: i.week, y: -i.deletions})),
  94. pointRadius: 0,
  95. pointHitRadius: 0,
  96. fill: true,
  97. label: 'Deletions',
  98. backgroundColor: chartJsColors['deletions'],
  99. borderWidth: 0,
  100. tension: 0.3,
  101. },
  102. ],
  103. };
  104. }
  105. const options: ChartOptions<'line'> = {
  106. responsive: true,
  107. maintainAspectRatio: false,
  108. plugins: {
  109. legend: {
  110. display: true,
  111. },
  112. },
  113. scales: {
  114. x: {
  115. type: 'time',
  116. grid: {
  117. display: false,
  118. },
  119. time: {
  120. minUnit: 'month',
  121. },
  122. ticks: {
  123. maxRotation: 0,
  124. maxTicksLimit: 12,
  125. },
  126. },
  127. y: {
  128. ticks: {
  129. maxTicksLimit: 6,
  130. },
  131. },
  132. },
  133. };
  134. </script>
  135. <template>
  136. <div>
  137. <div class="ui header tw-flex tw-items-center tw-justify-between">
  138. {{ isLoading ? locale.loadingTitle : errorText ? locale.loadingTitleFailed: `Code frequency over the history of ${repoLink.slice(1)}` }}
  139. </div>
  140. <div class="tw-flex ui segment main-graph">
  141. <div v-if="isLoading || errorText !== ''" class="gt-tc tw-m-auto">
  142. <div v-if="isLoading">
  143. <SvgIcon name="octicon-sync" class="tw-mr-2 circular-spin"/>
  144. {{ locale.loadingInfo }}
  145. </div>
  146. <div v-else class="text red">
  147. <SvgIcon name="octicon-x-circle-fill"/>
  148. {{ errorText }}
  149. </div>
  150. </div>
  151. <ChartLine
  152. v-memo="data" v-if="data.length !== 0"
  153. :data="toGraphData(data)" :options="options"
  154. />
  155. </div>
  156. </div>
  157. </template>
  158. <style scoped>
  159. .main-graph {
  160. height: 440px;
  161. }
  162. </style>