mirror of
				https://codeberg.org/forgejo/forgejo.git
				synced 2025-10-30 22:11:07 +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_and_deletions = and | ||||||
| activity.git_stats_deletion_1 = %d deletion | activity.git_stats_deletion_1 = %d deletion | ||||||
| activity.git_stats_deletion_n = %d deletions | activity.git_stats_deletion_n = %d deletions | ||||||
|  | activity.commit = Commit activity | ||||||
| 
 | 
 | ||||||
| contributors.contribution_type.filter_label = Contribution type: | contributors.contribution_type.filter_label = Contribution type: | ||||||
| contributors.contribution_type.commits = Commits | 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", |         "uint8-to-base64": "0.2.0", | ||||||
|         "vanilla-colorful": "0.7.2", |         "vanilla-colorful": "0.7.2", | ||||||
|         "vue": "3.4.31", |         "vue": "3.4.31", | ||||||
|         "vue-bar-graph": "2.0.0", |  | ||||||
|         "vue-chartjs": "5.3.1", |         "vue-chartjs": "5.3.1", | ||||||
|         "vue-loader": "17.4.2", |         "vue-loader": "17.4.2", | ||||||
|         "vue3-calendar-heatmap": "2.0.5", |         "vue3-calendar-heatmap": "2.0.5", | ||||||
|  | @ -7301,12 +7300,6 @@ | ||||||
|       "dev": true, |       "dev": true, | ||||||
|       "license": "MIT" |       "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": { |     "node_modules/hammerjs": { | ||||||
|       "version": "2.0.8", |       "version": "2.0.8", | ||||||
|       "resolved": "https://registry.npmjs.org/hammerjs/-/hammerjs-2.0.8.tgz", |       "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": { |     "node_modules/vue-chartjs": { | ||||||
|       "version": "5.3.1", |       "version": "5.3.1", | ||||||
|       "resolved": "https://registry.npmjs.org/vue-chartjs/-/vue-chartjs-5.3.1.tgz", |       "resolved": "https://registry.npmjs.org/vue-chartjs/-/vue-chartjs-5.3.1.tgz", | ||||||
|  |  | ||||||
|  | @ -54,7 +54,6 @@ | ||||||
|     "uint8-to-base64": "0.2.0", |     "uint8-to-base64": "0.2.0", | ||||||
|     "vanilla-colorful": "0.7.2", |     "vanilla-colorful": "0.7.2", | ||||||
|     "vue": "3.4.31", |     "vue": "3.4.31", | ||||||
|     "vue-bar-graph": "2.0.0", |  | ||||||
|     "vue-chartjs": "5.3.1", |     "vue-chartjs": "5.3.1", | ||||||
|     "vue-loader": "17.4.2", |     "vue-loader": "17.4.2", | ||||||
|     "vue3-calendar-heatmap": "2.0.5", |     "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>. | 				<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> | ||||||
| 			<div class="ui attached segment"> | 			<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> | ||||||
| 		</div> | 		</div> | ||||||
| 	{{end}} | 	{{end}} | ||||||
|  |  | ||||||
|  | @ -1138,10 +1138,6 @@ overflow-menu .ui.label { | ||||||
|   color: var(--color-primary-contrast); |   color: var(--color-primary-contrast); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| .activity-bar-graph-alt { |  | ||||||
|   color: var(--color-primary-contrast); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .archived-icon { | .archived-icon { | ||||||
|   color: var(--color-secondary-dark-2) !important; |   color: var(--color-secondary-dark-2) !important; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -2995,3 +2995,7 @@ tbody.commit-list { | ||||||
|   font-size: inherit; |   font-size: inherit; | ||||||
|   line-height: 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> | <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'; | import {createApp} from 'vue'; | ||||||
| 
 | 
 | ||||||
|  | Chart.defaults.color = chartJsColors.text; | ||||||
|  | Chart.defaults.borderColor = chartJsColors.border; | ||||||
|  | 
 | ||||||
|  | Chart.register( | ||||||
|  |   CategoryScale, | ||||||
|  |   LinearScale, | ||||||
|  |   BarElement, | ||||||
|  |   Tooltip, | ||||||
|  | ); | ||||||
|  | 
 | ||||||
| const sfc = { | const sfc = { | ||||||
|   components: {VueBarGraph}, |   components: {Bar}, | ||||||
|  |   props: { | ||||||
|  |     locale: { | ||||||
|  |       type: Object, | ||||||
|  |       required: true, | ||||||
|  |     }, | ||||||
|  |   }, | ||||||
|   data: () => ({ |   data: () => ({ | ||||||
|     colors: { |     colors: { | ||||||
|       barColor: 'green', |       barColor: 'green', | ||||||
|       textColor: 'black', |  | ||||||
|       textAltColor: 'white', |  | ||||||
|     }, |     }, | ||||||
| 
 | 
 | ||||||
|     // possible keys: |     // possible keys: | ||||||
|  | @ -18,42 +40,108 @@ const sfc = { | ||||||
|     // * login: (...) |     // * login: (...) | ||||||
|     // * name: (...) |     // * name: (...) | ||||||
|     activityTopAuthors: window.config.pageData.repoActivityTopAuthors || [], |     activityTopAuthors: window.config.pageData.repoActivityTopAuthors || [], | ||||||
|  |     i18nCommitActivity: this, | ||||||
|   }), |   }), | ||||||
|   computed: { |   methods: { | ||||||
|     graphPoints() { |     graphPoints() { | ||||||
|       return this.activityTopAuthors.map((item) => { |  | ||||||
|       return { |       return { | ||||||
|           value: item.commits, |         datasets: [{ | ||||||
|           label: item.name, |           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() { |     getOptions() { | ||||||
|       return this.activityTopAuthors.map((item, idx) => { |  | ||||||
|       return { |       return { | ||||||
|           position: idx + 1, |         responsive: true, | ||||||
|           ...item, |         maintainAspectRatio: false, | ||||||
|         }; |         animation: true, | ||||||
|       }); |         scales: { | ||||||
|  |           x: { | ||||||
|  |             type: 'category', | ||||||
|  |             grid: { | ||||||
|  |               display: false, | ||||||
|             }, |             }, | ||||||
|     graphWidth() { |             ticks: { | ||||||
|       return this.activityTopAuthors.length * 40; |               color: 'transparent', // Disable drawing of labels on the x-axis. | ||||||
|  |             }, | ||||||
|  |           }, | ||||||
|  |           y: { | ||||||
|  |             ticks: { | ||||||
|  |               stepSize: 1, | ||||||
|  |             }, | ||||||
|  |           }, | ||||||
|  |         }, | ||||||
|  |       }; | ||||||
|     }, |     }, | ||||||
|   }, |   }, | ||||||
|   mounted() { |   mounted() { | ||||||
|     const refStyle = window.getComputedStyle(this.$refs.style); |     const refStyle = window.getComputedStyle(this.$refs.style); | ||||||
|     const refAltStyle = window.getComputedStyle(this.$refs.altStyle); |  | ||||||
| 
 |  | ||||||
|     this.colors.barColor = refStyle.backgroundColor; |     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() { | export function initRepoActivityTopAuthorsChart() { | ||||||
|   const el = document.getElementById('repo-activity-top-authors-chart'); |   const el = document.getElementById('repo-activity-top-authors-chart'); | ||||||
|   if (el) { |   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> | <template> | ||||||
|   <div> |   <div> | ||||||
|     <div class="activity-bar-graph" ref="style" style="width: 0; height: 0;"/> |     <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;"/> |     <Bar height="150px" :data="graphPoints()" :options="getOptions()"/> | ||||||
|     <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> |  | ||||||
|   </div> |   </div> | ||||||
| </template> | </template> | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue