mirror of
				https://github.com/go-gitea/gitea.git
				synced 2025-10-30 18:36:22 +01:00 
			
		
		
		
	Remove fetch request from heatmap (#13623)
* Remove fetch request from heatmap Render heatmap data directly to HTML, eliminating one HTTP request on frontpage and user profile. Also added min-height to the container so the page content will no longer move after loading. * rename and error display * also log the js error * add error handler * remove useless inline style and hide divider on small screens * Update routers/user/home.go * Update routers/user/profile.go
This commit is contained in:
		| @@ -114,8 +114,14 @@ func Dashboard(ctx *context.Context) { | |||||||
| 	ctx.Data["SearchLimit"] = setting.UI.User.RepoPagingNum | 	ctx.Data["SearchLimit"] = setting.UI.User.RepoPagingNum | ||||||
| 	// no heatmap access for admins; GetUserHeatmapDataByUser ignores the calling user | 	// no heatmap access for admins; GetUserHeatmapDataByUser ignores the calling user | ||||||
| 	// so everyone would get the same empty heatmap | 	// so everyone would get the same empty heatmap | ||||||
| 	ctx.Data["EnableHeatmap"] = setting.Service.EnableUserHeatmap && !ctxUser.KeepActivityPrivate | 	if setting.Service.EnableUserHeatmap && !ctxUser.KeepActivityPrivate { | ||||||
| 	ctx.Data["HeatmapUser"] = ctxUser.Name | 		data, err := models.GetUserHeatmapDataByUser(ctxUser) | ||||||
|  | 		if err != nil { | ||||||
|  | 			ctx.ServerError("GetUserHeatmapDataByUser", err) | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  | 		ctx.Data["HeatmapData"] = data | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	var err error | 	var err error | ||||||
| 	var mirrors []*models.Repository | 	var mirrors []*models.Repository | ||||||
|   | |||||||
| @@ -94,10 +94,18 @@ func Profile(ctx *context.Context) { | |||||||
| 	ctx.Data["PageIsUserProfile"] = true | 	ctx.Data["PageIsUserProfile"] = true | ||||||
| 	ctx.Data["Owner"] = ctxUser | 	ctx.Data["Owner"] = ctxUser | ||||||
| 	ctx.Data["OpenIDs"] = openIDs | 	ctx.Data["OpenIDs"] = openIDs | ||||||
|  |  | ||||||
| 	// no heatmap access for admins; GetUserHeatmapDataByUser ignores the calling user | 	// no heatmap access for admins; GetUserHeatmapDataByUser ignores the calling user | ||||||
| 	// so everyone would get the same empty heatmap | 	// so everyone would get the same empty heatmap | ||||||
| 	ctx.Data["EnableHeatmap"] = setting.Service.EnableUserHeatmap && !ctxUser.KeepActivityPrivate | 	if setting.Service.EnableUserHeatmap && !ctxUser.KeepActivityPrivate { | ||||||
| 	ctx.Data["HeatmapUser"] = ctxUser.Name | 		data, err := models.GetUserHeatmapDataByUser(ctxUser) | ||||||
|  | 		if err != nil { | ||||||
|  | 			ctx.ServerError("GetUserHeatmapDataByUser", err) | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  | 		ctx.Data["HeatmapData"] = data | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	if len(ctxUser.Description) != 0 { | 	if len(ctxUser.Description) != 0 { | ||||||
| 		ctx.Data["RenderedDescription"] = string(markdown.Render([]byte(ctxUser.Description), ctx.Repo.RepoLink, map[string]string{"mode": "document"})) | 		ctx.Data["RenderedDescription"] = string(markdown.Render([]byte(ctxUser.Description), ctx.Repo.RepoLink, map[string]string{"mode": "document"})) | ||||||
| 	} | 	} | ||||||
|   | |||||||
| @@ -36,8 +36,6 @@ | |||||||
| 			SimpleMDE: {{if .RequireSimpleMDE}}true{{else}}false{{end}}, | 			SimpleMDE: {{if .RequireSimpleMDE}}true{{else}}false{{end}}, | ||||||
| 			Tribute: {{if .RequireTribute}}true{{else}}false{{end}}, | 			Tribute: {{if .RequireTribute}}true{{else}}false{{end}}, | ||||||
| 			U2F: {{if .RequireU2F}}true{{else}}false{{end}}, | 			U2F: {{if .RequireU2F}}true{{else}}false{{end}}, | ||||||
| 			Heatmap: {{if .EnableHeatmap}}true{{else}}false{{end}}, |  | ||||||
| 			heatmapUser: {{if .HeatmapUser}}'{{.HeatmapUser}}'{{else}}null{{end}}, |  | ||||||
| 			NotificationSettings: { | 			NotificationSettings: { | ||||||
| 				MinTimeout: {{NotificationSettings.MinTimeout}}, | 				MinTimeout: {{NotificationSettings.MinTimeout}}, | ||||||
| 				TimeoutStep:  {{NotificationSettings.TimeoutStep}}, | 				TimeoutStep:  {{NotificationSettings.TimeoutStep}}, | ||||||
|   | |||||||
| @@ -5,10 +5,7 @@ | |||||||
| 		{{template "base/alert" .}} | 		{{template "base/alert" .}} | ||||||
| 		<div class="ui mobile reversed stackable grid"> | 		<div class="ui mobile reversed stackable grid"> | ||||||
| 			<div class="ui container ten wide column"> | 			<div class="ui container ten wide column"> | ||||||
| 				{{if .EnableHeatmap}} | 				{{template "user/heatmap" .}} | ||||||
| 					{{template "user/dashboard/heatmap" .}} |  | ||||||
| 					<div class="ui divider"></div> |  | ||||||
| 				{{end}} |  | ||||||
| 				{{template "user/dashboard/feeds" .}} | 				{{template "user/dashboard/feeds" .}} | ||||||
| 			</div> | 			</div> | ||||||
| 			{{template "user/dashboard/repolist" .}} | 			{{template "user/dashboard/repolist" .}} | ||||||
|   | |||||||
| @@ -1,7 +1,8 @@ | |||||||
| <div id="user-heatmap" style="padding-right: 40px"> | {{if .HeatmapData}} | ||||||
| 	<activity-heatmap :locale="locale" :suburl="suburl" :user="heatmapUser"> | 	<div id="user-heatmap" data-heatmap-data="{{Json .HeatmapData}}"> | ||||||
| 		<div slot="loading"> | 		<div slot="loading"> | ||||||
| 			<div class="ui active centered inline indeterminate text loader" id="loading-heatmap">{{.i18n.Tr "user.heatmap.loading"}}</div> | 			<div class="ui active centered inline indeterminate text loader" id="loading-heatmap">{{.i18n.Tr "user.heatmap.loading"}}</div> | ||||||
| 		</div> | 		</div> | ||||||
| 	</activity-heatmap> | 	</div> | ||||||
| </div> | 	<div class="ui divider"></div> | ||||||
|  | {{end}} | ||||||
| @@ -108,10 +108,7 @@ | |||||||
| 							<p>{{.i18n.Tr "user.disabled_public_activity"}}</p> | 							<p>{{.i18n.Tr "user.disabled_public_activity"}}</p> | ||||||
| 						</div> | 						</div> | ||||||
| 					{{end}} | 					{{end}} | ||||||
| 					{{if .EnableHeatmap}} | 					{{template "user/heatmap" .}} | ||||||
| 						{{template "user/dashboard/heatmap" .}} |  | ||||||
| 						<div class="ui divider"></div> |  | ||||||
| 					{{end}} |  | ||||||
| 					<div class="feeds"> | 					<div class="feeds"> | ||||||
| 						{{template "user/dashboard/feeds" .}} | 						{{template "user/dashboard/feeds" .}} | ||||||
| 					</div> | 					</div> | ||||||
|   | |||||||
| @@ -1,13 +1,9 @@ | |||||||
| <template> | <template> | ||||||
|   <div class="heatmap-container"> |   <div id="user-heatmap"> | ||||||
|     <div v-show="isLoading"> |     <div class="total-contributions"> | ||||||
|       <slot name="loading"/> |  | ||||||
|     </div> |  | ||||||
|     <div v-if="!isLoading" class="total-contributions"> |  | ||||||
|       {{ values.length }} contributions in the last 12 months |       {{ values.length }} contributions in the last 12 months | ||||||
|     </div> |     </div> | ||||||
|     <calendar-heatmap |     <calendar-heatmap | ||||||
|       v-show="!isLoading" |  | ||||||
|       :locale="locale" |       :locale="locale" | ||||||
|       :no-data-text="locale.no_contributions" |       :no-data-text="locale.no_contributions" | ||||||
|       :tooltip-unit="locale.contributions" |       :tooltip-unit="locale.contributions" | ||||||
| @@ -19,13 +15,17 @@ | |||||||
| </template> | </template> | ||||||
| <script> | <script> | ||||||
| import {CalendarHeatmap} from 'vue-calendar-heatmap'; | import {CalendarHeatmap} from 'vue-calendar-heatmap'; | ||||||
| const {AppSubUrl, heatmapUser} = window.config; |  | ||||||
|  |  | ||||||
| export default { | export default { | ||||||
|   name: 'ActivityHeatmap', |   name: 'ActivityHeatmap', | ||||||
|   components: {CalendarHeatmap}, |   components: {CalendarHeatmap}, | ||||||
|  |   props: { | ||||||
|  |     values: { | ||||||
|  |       type: Array, | ||||||
|  |       default: () => [], | ||||||
|  |     }, | ||||||
|  |   }, | ||||||
|   data: () => ({ |   data: () => ({ | ||||||
|     isLoading: true, |  | ||||||
|     colorRange: [ |     colorRange: [ | ||||||
|       'var(--color-secondary-alpha-70)', |       'var(--color-secondary-alpha-70)', | ||||||
|       'var(--color-primary-light-4)', |       'var(--color-primary-light-4)', | ||||||
| @@ -35,20 +35,11 @@ export default { | |||||||
|       'var(--color-primary-dark-4)', |       'var(--color-primary-dark-4)', | ||||||
|     ], |     ], | ||||||
|     endDate: new Date(), |     endDate: new Date(), | ||||||
|     values: [], |  | ||||||
|     locale: { |     locale: { | ||||||
|       contributions: 'contributions', |       contributions: 'contributions', | ||||||
|       no_contributions: 'No contributions', |       no_contributions: 'No contributions', | ||||||
|     }, |     }, | ||||||
|   }), |   }), | ||||||
|   async mounted() { |  | ||||||
|     const res = await fetch(`${AppSubUrl}/api/v1/users/${heatmapUser}/heatmap`); |  | ||||||
|     const data = await res.json(); |  | ||||||
|     this.values = data.map(({contributions, timestamp}) => { |  | ||||||
|       return {date: new Date(timestamp * 1000), count: contributions}; |  | ||||||
|     }); |  | ||||||
|     this.isLoading = false; |  | ||||||
|   }, |  | ||||||
| }; | }; | ||||||
| </script> | </script> | ||||||
| <style scoped/> | <style scoped/> | ||||||
|   | |||||||
							
								
								
									
										23
									
								
								web_src/js/features/heatmap.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								web_src/js/features/heatmap.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,23 @@ | |||||||
|  | import Vue from 'vue'; | ||||||
|  |  | ||||||
|  | import ActivityHeatmap from '../components/ActivityHeatmap.vue'; | ||||||
|  |  | ||||||
|  | export default async function initHeatmap() { | ||||||
|  |   const el = document.getElementById('user-heatmap'); | ||||||
|  |   if (!el) return; | ||||||
|  |  | ||||||
|  |   try { | ||||||
|  |     const values = JSON.parse(el.dataset.heatmapData).map(({contributions, timestamp}) => { | ||||||
|  |       return {date: new Date(timestamp * 1000), count: contributions}; | ||||||
|  |     }); | ||||||
|  |  | ||||||
|  |     const View = Vue.extend({ | ||||||
|  |       render: (createElement) => createElement(ActivityHeatmap, {props: {values}}), | ||||||
|  |     }); | ||||||
|  |  | ||||||
|  |     new View().$mount(el); | ||||||
|  |   } catch (err) { | ||||||
|  |     console.error(err); | ||||||
|  |     el.textContent = 'Heatmap failed to load'; | ||||||
|  |   } | ||||||
|  | } | ||||||
| @@ -1,10 +0,0 @@ | |||||||
| import Vue from 'vue'; |  | ||||||
|  |  | ||||||
| import ActivityHeatmap from '../components/ActivityHeatmap.vue'; |  | ||||||
|  |  | ||||||
| export default async function initUserHeatmap() { |  | ||||||
|   const el = document.getElementById('user-heatmap'); |  | ||||||
|   if (!el) return; |  | ||||||
|   const View = Vue.extend(ActivityHeatmap); |  | ||||||
|   new View().$mount(el); |  | ||||||
| } |  | ||||||
| @@ -12,7 +12,7 @@ import initMigration from './features/migration.js'; | |||||||
| import initContextPopups from './features/contextpopup.js'; | import initContextPopups from './features/contextpopup.js'; | ||||||
| import initGitGraph from './features/gitgraph.js'; | import initGitGraph from './features/gitgraph.js'; | ||||||
| import initClipboard from './features/clipboard.js'; | import initClipboard from './features/clipboard.js'; | ||||||
| import initUserHeatmap from './features/userheatmap.js'; | import initHeatmap from './features/heatmap.js'; | ||||||
| import initProject from './features/projects.js'; | import initProject from './features/projects.js'; | ||||||
| import initServiceWorker from './features/serviceworker.js'; | import initServiceWorker from './features/serviceworker.js'; | ||||||
| import initMarkdownAnchors from './markdown/anchors.js'; | import initMarkdownAnchors from './markdown/anchors.js'; | ||||||
| @@ -2553,7 +2553,7 @@ $(document).ready(async () => { | |||||||
|     attachTribute(document.querySelectorAll('#content, .emoji-input')), |     attachTribute(document.querySelectorAll('#content, .emoji-input')), | ||||||
|     initGitGraph(), |     initGitGraph(), | ||||||
|     initClipboard(), |     initClipboard(), | ||||||
|     initUserHeatmap(), |     initHeatmap(), | ||||||
|     initProject(), |     initProject(), | ||||||
|     initServiceWorker(), |     initServiceWorker(), | ||||||
|     initNotificationCount(), |     initNotificationCount(), | ||||||
|   | |||||||
| @@ -1,49 +1,50 @@ | |||||||
| #user-heatmap { | #user-heatmap { | ||||||
|   width: 107%; // Fixes newest contributions not showing |   width: 100%; | ||||||
|   text-align: center; |   text-align: center; | ||||||
|  |   position: relative; | ||||||
|  |   min-height: 125px; | ||||||
|  |   display: flex; | ||||||
|  |   align-items: center; | ||||||
|  |   justify-content: center; | ||||||
|  |  | ||||||
|   svg:not(:root) { |   svg:not(:root) { | ||||||
|     overflow: inherit; |     overflow: inherit; | ||||||
|     padding: 0 !important; |     padding: 0 !important; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   text { | ||||||
|  |     fill: currentColor !important; | ||||||
|  |   } | ||||||
|  |  | ||||||
|   @media @mediaLgAndDown { |   @media @mediaLgAndDown { | ||||||
|     & { |     &, | ||||||
|  |     & + .divider { | ||||||
|       display: none; |       display: none; | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   .total-contributions { |   .total-contributions { | ||||||
|     text-align: left; |     font-size: 11px; | ||||||
|     margin-top: 0; |     position: absolute; | ||||||
|  |     bottom: 0; | ||||||
|  |     left: 25px; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @media @mediaLgAndDown { | ||||||
|  |     .total-contributions { | ||||||
|  |       left: 21px; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @media (max-width: 1000px) { | ||||||
|  |     .total-contributions { | ||||||
|  |       font-size: 10px; | ||||||
|  |       left: 17px; | ||||||
|  |       bottom: -2px; | ||||||
|  |     } | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
| .heatmap-container { | .user.profile #user-heatmap { | ||||||
|   position: relative; |   min-height: 135px; | ||||||
| } |  | ||||||
|  |  | ||||||
| .heatmap-container .total-contributions { |  | ||||||
|   font-size: 11px; |  | ||||||
|   position: absolute; |  | ||||||
|   bottom: 0; |  | ||||||
|   left: 25px; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| @media @mediaLgAndDown { |  | ||||||
|   .heatmap-container .total-contributions { |  | ||||||
|     left: 21px; |  | ||||||
|   } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| @media (max-width: 1000px) { |  | ||||||
|   .heatmap-container .total-contributions { |  | ||||||
|     font-size: 10px; |  | ||||||
|     left: 17px; |  | ||||||
|     bottom: -2px; |  | ||||||
|   } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| .heatmap-container text { |  | ||||||
|   fill: currentColor !important; |  | ||||||
| } | } | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user