mirror of
https://codeberg.org/forgejo/forgejo.git
synced 2026-06-09 12:12:18 -04:00
fix: don't stretch activity top author image (#10556)
This is a followup to !10524. In addition I changed that the tooltip triggers for the whole height, instead for only the bar height, because otherwise it is esp for small bars nearly impossible to get the tooltip to open. Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/10556 Reviewed-by: Gusted <gusted@noreply.codeberg.org> Co-authored-by: Beowulf <beowulf@beocode.eu> Co-committed-by: Beowulf <beowulf@beocode.eu>
This commit is contained in:
parent
f7d2f51bf7
commit
4b0130584f
2 changed files with 103 additions and 50 deletions
28
web_src/js/components/RepoActivityTopAuthors.test.js
Normal file
28
web_src/js/components/RepoActivityTopAuthors.test.js
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
// Copyright 2025 The Forgejo Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
import {flushPromises, mount} from '@vue/test-utils';
|
||||
import RepoActivityTopAuthors from './RepoActivityTopAuthors.vue';
|
||||
import {expect, test, vi} from 'vitest';
|
||||
|
||||
test('calc image size and shift', async () => {
|
||||
vi.spyOn(RepoActivityTopAuthors.methods, 'init').mockResolvedValue({});
|
||||
|
||||
const repoActivityTopAuthors = mount(RepoActivityTopAuthors, {
|
||||
props: {
|
||||
locale: {
|
||||
commitActivity: '',
|
||||
},
|
||||
},
|
||||
});
|
||||
await flushPromises();
|
||||
|
||||
const square = repoActivityTopAuthors.vm.calcImageSizeAndShift({naturalWidth: 50, naturalHeight: 50});
|
||||
expect(square).toEqual([20, 20, 0, 0]);
|
||||
|
||||
const portrait = repoActivityTopAuthors.vm.calcImageSizeAndShift({naturalWidth: 5, naturalHeight: 50});
|
||||
expect(portrait).toEqual([2, 20, 9, 0]);
|
||||
|
||||
const landscape = repoActivityTopAuthors.vm.calcImageSizeAndShift({naturalWidth: 500, naturalHeight: 5});
|
||||
expect(landscape).toEqual([20, 0.2, 0, 9.9]);
|
||||
});
|
||||
|
|
@ -42,56 +42,7 @@ export default {
|
|||
i18nCommitActivity: this,
|
||||
}),
|
||||
mounted() {
|
||||
const refStyle = window.getComputedStyle(this.$refs.style);
|
||||
this.colors.barColor = refStyle.backgroundColor;
|
||||
|
||||
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;
|
||||
}
|
||||
},
|
||||
});
|
||||
this.init();
|
||||
},
|
||||
methods: {
|
||||
graphPoints() {
|
||||
|
|
@ -137,8 +88,82 @@ export default {
|
|||
},
|
||||
},
|
||||
},
|
||||
plugins: {
|
||||
tooltip: {
|
||||
intersect: false,
|
||||
},
|
||||
},
|
||||
};
|
||||
},
|
||||
init() {
|
||||
const refStyle = window.getComputedStyle(this.$refs.style);
|
||||
this.colors.barColor = refStyle.backgroundColor;
|
||||
|
||||
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();
|
||||
const [width, height, dx, dy] = this.calcImageSizeAndShift(img);
|
||||
chart.ctx.drawImage(img, 0, 0, img.naturalWidth, img.naturalHeight, x - 10 + dx, yAxis.bottom + 10 + dy, width, height);
|
||||
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;
|
||||
}
|
||||
},
|
||||
});
|
||||
},
|
||||
calcImageSizeAndShift(img) {
|
||||
const targetSize = 20;
|
||||
const [imgWidth, imgHeight] = [img.naturalWidth, img.naturalHeight];
|
||||
|
||||
// The image should be contained in a square,
|
||||
// so the scale depends on the longer dimension.
|
||||
const scale = targetSize / (Math.max(imgWidth, imgHeight));
|
||||
const calcScale = (size) => size * scale;
|
||||
const [width, height] = [calcScale(imgWidth), calcScale(imgHeight)];
|
||||
|
||||
// The image should be centered in the 20x20 square.
|
||||
const calcShift = (size) => (targetSize - size) / 2;
|
||||
const [dx, dy] = [calcShift(width), calcShift(height)];
|
||||
|
||||
return [width, height, dx, dy];
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
|
|||
Loading…
Reference in a new issue