mirror of
				https://codeberg.org/forgejo/forgejo.git
				synced 2025-10-31 06:21:11 +00:00 
			
		
		
		
	Merge pull request '[v8.0/forgejo] [UI] Replace vue-bar-graph with chart.js' (#4590) from gusted/forgejo-bp-licensed into v8.0/forgejo
		
	Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/4590 Reviewed-by: Beowulf <beowulf@noreply.codeberg.org> Reviewed-by: 0ko <0ko@noreply.codeberg.org>
This commit is contained in:
		
				commit
				
					
						6c35127705
					
				
			
		
					 7 changed files with 121 additions and 94 deletions
				
			
		|  | @ -2116,6 +2116,7 @@ activity.git_stats_addition_n = %d additions | |||
| activity.git_stats_and_deletions = and | ||||
| activity.git_stats_deletion_1 = %d deletion | ||||
| activity.git_stats_deletion_n = %d deletions | ||||
| activity.commit = Commit activity | ||||
| 
 | ||||
| contributors.contribution_type.filter_label = Contribution type: | ||||
| contributors.contribution_type.commits = Commits | ||||
|  |  | |||
							
								
								
									
										17
									
								
								package-lock.json
									
										
									
										generated
									
									
									
								
							
							
						
						
									
										17
									
								
								package-lock.json
									
										
									
										generated
									
									
									
								
							|  | @ -55,7 +55,6 @@ | |||
|         "uint8-to-base64": "0.2.0", | ||||
|         "vanilla-colorful": "0.7.2", | ||||
|         "vue": "3.4.31", | ||||
|         "vue-bar-graph": "2.0.0", | ||||
|         "vue-chartjs": "5.3.1", | ||||
|         "vue-loader": "17.4.2", | ||||
|         "vue3-calendar-heatmap": "2.0.5", | ||||
|  | @ -7301,12 +7300,6 @@ | |||
|       "dev": true, | ||||
|       "license": "MIT" | ||||
|     }, | ||||
|     "node_modules/gsap": { | ||||
|       "version": "3.12.5", | ||||
|       "resolved": "https://registry.npmjs.org/gsap/-/gsap-3.12.5.tgz", | ||||
|       "integrity": "sha512-srBfnk4n+Oe/ZnMIOXt3gT605BX9x5+rh/prT2F1SsNJsU1XuMiP0E2aptW481OnonOGACZWBqseH5Z7csHxhQ==", | ||||
|       "license": "Standard 'no charge' license: https://gsap.com/standard-license. Club GSAP members get more: https://gsap.com/licensing/. Why GreenSock doesn't employ an MIT license: https://gsap.com/why-license/" | ||||
|     }, | ||||
|     "node_modules/hammerjs": { | ||||
|       "version": "2.0.8", | ||||
|       "resolved": "https://registry.npmjs.org/hammerjs/-/hammerjs-2.0.8.tgz", | ||||
|  | @ -14074,16 +14067,6 @@ | |||
|         } | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/vue-bar-graph": { | ||||
|       "version": "2.0.0", | ||||
|       "resolved": "https://registry.npmjs.org/vue-bar-graph/-/vue-bar-graph-2.0.0.tgz", | ||||
|       "integrity": "sha512-IoYP+r5Ggjys6QdUNYFPh7qD41wi/uDOJj9nMawvDgvV6niOz3Dw8O2/98ZnUgjTpcgcGFDaaAaK6qa9x1jgpw==", | ||||
|       "license": "MIT", | ||||
|       "dependencies": { | ||||
|         "gsap": "^3.10.4", | ||||
|         "vue": "^3.2.37" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/vue-chartjs": { | ||||
|       "version": "5.3.1", | ||||
|       "resolved": "https://registry.npmjs.org/vue-chartjs/-/vue-chartjs-5.3.1.tgz", | ||||
|  |  | |||
|  | @ -54,7 +54,6 @@ | |||
|     "uint8-to-base64": "0.2.0", | ||||
|     "vanilla-colorful": "0.7.2", | ||||
|     "vue": "3.4.31", | ||||
|     "vue-bar-graph": "2.0.0", | ||||
|     "vue-chartjs": "5.3.1", | ||||
|     "vue-loader": "17.4.2", | ||||
|     "vue3-calendar-heatmap": "2.0.5", | ||||
|  |  | |||
|  | @ -105,7 +105,7 @@ | |||
| 				<strong class="text red">{{ctx.Locale.TrN .Activity.Code.Deletions "repo.activity.git_stats_deletion_1" "repo.activity.git_stats_deletion_n" .Activity.Code.Deletions}}</strong>. | ||||
| 			</div> | ||||
| 			<div class="ui attached segment"> | ||||
| 				<div id="repo-activity-top-authors-chart"></div> | ||||
| 				<div id="repo-activity-top-authors-chart" data-locale-commit-activity="{{ctx.Locale.Tr "repo.activity.commit"}}"></div> | ||||
| 			</div> | ||||
| 		</div> | ||||
| 	{{end}} | ||||
|  |  | |||
|  | @ -1138,10 +1138,6 @@ overflow-menu .ui.label { | |||
|   color: var(--color-primary-contrast); | ||||
| } | ||||
| 
 | ||||
| .activity-bar-graph-alt { | ||||
|   color: var(--color-primary-contrast); | ||||
| } | ||||
| 
 | ||||
| .archived-icon { | ||||
|   color: var(--color-secondary-dark-2) !important; | ||||
| } | ||||
|  |  | |||
|  | @ -2995,3 +2995,7 @@ tbody.commit-list { | |||
|   font-size: inherit; | ||||
|   line-height: inherit; | ||||
| } | ||||
| 
 | ||||
| #repo-activity-top-authors-chart { | ||||
|   height: 150px; /* Pre-allocate the height that will be taken up by the chart, to avoid the container 'jumping'. */ | ||||
| } | ||||
|  |  | |||
|  | @ -1,14 +1,36 @@ | |||
| <script> | ||||
| import VueBarGraph from 'vue-bar-graph'; | ||||
| import {Bar} from 'vue-chartjs'; | ||||
| import { | ||||
|   Chart, | ||||
|   Tooltip, | ||||
|   BarElement, | ||||
|   CategoryScale, | ||||
|   LinearScale, | ||||
| } from 'chart.js'; | ||||
| import {chartJsColors} from '../utils/color.js'; | ||||
| import {createApp} from 'vue'; | ||||
| 
 | ||||
| Chart.defaults.color = chartJsColors.text; | ||||
| Chart.defaults.borderColor = chartJsColors.border; | ||||
| 
 | ||||
| Chart.register( | ||||
|   CategoryScale, | ||||
|   LinearScale, | ||||
|   BarElement, | ||||
|   Tooltip, | ||||
| ); | ||||
| 
 | ||||
| const sfc = { | ||||
|   components: {VueBarGraph}, | ||||
|   components: {Bar}, | ||||
|   props: { | ||||
|     locale: { | ||||
|       type: Object, | ||||
|       required: true, | ||||
|     }, | ||||
|   }, | ||||
|   data: () => ({ | ||||
|     colors: { | ||||
|       barColor: 'green', | ||||
|       textColor: 'black', | ||||
|       textAltColor: 'white', | ||||
|     }, | ||||
| 
 | ||||
|     // possible keys: | ||||
|  | @ -18,42 +40,108 @@ const sfc = { | |||
|     // * login: (...) | ||||
|     // * name: (...) | ||||
|     activityTopAuthors: window.config.pageData.repoActivityTopAuthors || [], | ||||
|     i18nCommitActivity: this, | ||||
|   }), | ||||
|   computed: { | ||||
|   methods: { | ||||
|     graphPoints() { | ||||
|       return this.activityTopAuthors.map((item) => { | ||||
|       return { | ||||
|           value: item.commits, | ||||
|           label: item.name, | ||||
|         datasets: [{ | ||||
|           label: this.locale.commitActivity, | ||||
|           data: this.activityTopAuthors.map((item) => item.commits), | ||||
|           backgroundColor: this.colors.barColor, | ||||
|           barThickness: 40, | ||||
|           borderWidth: 0, | ||||
|           tension: 0.3, | ||||
|         }], | ||||
|         labels: this.activityTopAuthors.map((item) => item.name), | ||||
|       }; | ||||
|       }); | ||||
|     }, | ||||
|     graphAuthors() { | ||||
|       return this.activityTopAuthors.map((item, idx) => { | ||||
|     getOptions() { | ||||
|       return { | ||||
|           position: idx + 1, | ||||
|           ...item, | ||||
|         }; | ||||
|       }); | ||||
|         responsive: true, | ||||
|         maintainAspectRatio: false, | ||||
|         animation: true, | ||||
|         scales: { | ||||
|           x: { | ||||
|             type: 'category', | ||||
|             grid: { | ||||
|               display: false, | ||||
|             }, | ||||
|     graphWidth() { | ||||
|       return this.activityTopAuthors.length * 40; | ||||
|             ticks: { | ||||
|               color: 'transparent', // Disable drawing of labels on the x-axis. | ||||
|             }, | ||||
|           }, | ||||
|           y: { | ||||
|             ticks: { | ||||
|               stepSize: 1, | ||||
|             }, | ||||
|           }, | ||||
|         }, | ||||
|       }; | ||||
|     }, | ||||
|   }, | ||||
|   mounted() { | ||||
|     const refStyle = window.getComputedStyle(this.$refs.style); | ||||
|     const refAltStyle = window.getComputedStyle(this.$refs.altStyle); | ||||
| 
 | ||||
|     this.colors.barColor = refStyle.backgroundColor; | ||||
|     this.colors.textColor = refStyle.color; | ||||
|     this.colors.textAltColor = refAltStyle.color; | ||||
| 
 | ||||
|     for (const item of this.activityTopAuthors) { | ||||
|       const img = new Image(); | ||||
|       img.src = item.avatar_link; | ||||
|       item.avatar_img = img; | ||||
|     } | ||||
| 
 | ||||
|     Chart.register({ | ||||
|       id: 'image_label', | ||||
|       afterDraw: (chart) => { | ||||
|         const xAxis = chart.boxes[0]; | ||||
|         const yAxis = chart.boxes[1]; | ||||
|         for (const [index] of xAxis.ticks.entries()) { | ||||
|           const x = xAxis.getPixelForTick(index); | ||||
|           const img = this.activityTopAuthors[index].avatar_img; | ||||
| 
 | ||||
|           chart.ctx.save(); | ||||
|           chart.ctx.drawImage(img, 0, 0, img.naturalWidth, img.naturalHeight, x - 10, yAxis.bottom + 10, 20, 20); | ||||
|           chart.ctx.restore(); | ||||
|         } | ||||
|       }, | ||||
|       beforeEvent: (chart, args) => { | ||||
|         const event = args.event; | ||||
|         if (event.type !== 'mousemove' && event.type !== 'click') return; | ||||
| 
 | ||||
|         const yAxis = chart.boxes[1]; | ||||
|         if (event.y < yAxis.bottom + 10 || event.y > yAxis.bottom + 30) { | ||||
|           chart.canvas.style.cursor = ''; | ||||
|           return; | ||||
|         } | ||||
| 
 | ||||
|         const xAxis = chart.boxes[0]; | ||||
|         const pointIdx = xAxis.ticks.findIndex((_, index) => { | ||||
|           const x = xAxis.getPixelForTick(index); | ||||
|           return event.x >= x - 10 && event.x <= x + 10; | ||||
|         }); | ||||
| 
 | ||||
|         if (pointIdx === -1) { | ||||
|           chart.canvas.style.cursor = ''; | ||||
|           return; | ||||
|         } | ||||
| 
 | ||||
|         chart.canvas.style.cursor = 'pointer'; | ||||
|         if (event.type === 'click' && this.activityTopAuthors[pointIdx].home_link) { | ||||
|           window.location.href = this.activityTopAuthors[pointIdx].home_link; | ||||
|         } | ||||
|       }, | ||||
|     }); | ||||
|   }, | ||||
| }; | ||||
| 
 | ||||
| export function initRepoActivityTopAuthorsChart() { | ||||
|   const el = document.getElementById('repo-activity-top-authors-chart'); | ||||
|   if (el) { | ||||
|     createApp(sfc).mount(el); | ||||
|     createApp(sfc, { | ||||
|       locale: { | ||||
|         commitActivity: el.getAttribute('data-locale-commit-activity'), | ||||
|       }, | ||||
|     }).mount(el); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
|  | @ -62,50 +150,6 @@ export default sfc; // activate the IDE's Vue plugin | |||
| <template> | ||||
|   <div> | ||||
|     <div class="activity-bar-graph" ref="style" style="width: 0; height: 0;"/> | ||||
|     <div class="activity-bar-graph-alt" ref="altStyle" style="width: 0; height: 0;"/> | ||||
|     <vue-bar-graph | ||||
|       :points="graphPoints" | ||||
|       :show-x-axis="true" | ||||
|       :show-y-axis="false" | ||||
|       :show-values="true" | ||||
|       :width="graphWidth" | ||||
|       :bar-color="colors.barColor" | ||||
|       :text-color="colors.textColor" | ||||
|       :text-alt-color="colors.textAltColor" | ||||
|       :height="100" | ||||
|       :label-height="20" | ||||
|     > | ||||
|       <template #label="opt"> | ||||
|         <g v-for="(author, idx) in graphAuthors" :key="author.position"> | ||||
|           <a | ||||
|             v-if="opt.bar.index === idx && author.home_link" | ||||
|             :href="author.home_link" | ||||
|           > | ||||
|             <image | ||||
|               :x="`${opt.bar.midPoint - 10}px`" | ||||
|               :y="`${opt.bar.yLabel}px`" | ||||
|               height="20" | ||||
|               width="20" | ||||
|               :href="author.avatar_link" | ||||
|             /> | ||||
|           </a> | ||||
|           <image | ||||
|             v-else-if="opt.bar.index === idx" | ||||
|             :x="`${opt.bar.midPoint - 10}px`" | ||||
|             :y="`${opt.bar.yLabel}px`" | ||||
|             height="20" | ||||
|             width="20" | ||||
|             :href="author.avatar_link" | ||||
|           /> | ||||
|         </g> | ||||
|       </template> | ||||
|       <template #title="opt"> | ||||
|         <tspan v-for="(author, idx) in graphAuthors" :key="author.position"> | ||||
|           <tspan v-if="opt.bar.index === idx"> | ||||
|             {{ author.name }} | ||||
|           </tspan> | ||||
|         </tspan> | ||||
|       </template> | ||||
|     </vue-bar-graph> | ||||
|     <Bar height="150px" :data="graphPoints()" :options="getOptions()"/> | ||||
|   </div> | ||||
| </template> | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue