Skip to content

Commit 348d1d0

Browse files
authored
Migrate vue components to setup (#32329)
Migrated a handful Vue components to the `setup` syntax using composition api as it has better Typescript support and is becoming the new default in the Vue ecosystem. - [x] ActionRunStatus.vue - [x] ActivityHeatmap.vue - [x] ContextPopup.vue - [x] DiffFileList.vue - [x] DiffFileTree.vue - [x] DiffFileTreeItem.vue - [x] PullRequestMergeForm.vue - [x] RepoActivityTopAuthors.vue - [x] RepoCodeFrequency.vue - [x] RepoRecentCommits.vue - [x] ScopedAccessTokenSelector.vue Left some larger components untouched for now to not go to crazy in this single PR: - [ ] DiffCommitSelector.vue - [ ] RepoActionView.vue - [ ] RepoContributors.vue - [ ] DashboardRepoList.vue - [ ] RepoBranchTagSelector.vue
1 parent a920fcf commit 348d1d0

15 files changed

+708
-714
lines changed

web_src/js/components/ActionRunStatus.vue

+12-22
Original file line numberDiff line numberDiff line change
@@ -2,31 +2,21 @@
22
Please also update the template file above if this vue is modified.
33
action status accepted: success, skipped, waiting, blocked, running, failure, cancelled, unknown
44
-->
5-
<script lang="ts">
5+
<script lang="ts" setup>
66
import {SvgIcon} from '../svg.ts';
77

8-
export default {
9-
components: {SvgIcon},
10-
props: {
11-
status: {
12-
type: String,
13-
required: true,
14-
},
15-
size: {
16-
type: Number,
17-
default: 16,
18-
},
19-
className: {
20-
type: String,
21-
default: '',
22-
},
23-
localeStatus: {
24-
type: String,
25-
default: '',
26-
},
27-
},
28-
};
8+
withDefaults(defineProps<{
9+
status: '',
10+
size?: number,
11+
className?: string,
12+
localeStatus?: string,
13+
}>(), {
14+
size: 16,
15+
className: undefined,
16+
localeStatus: undefined,
17+
});
2918
</script>
19+
3020
<template>
3121
<span class="tw-flex tw-items-center" :data-tooltip-content="localeStatus" v-if="status">
3222
<SvgIcon name="octicon-check-circle-fill" class="text green" :size="size" :class-name="className" v-if="status === 'success'"/>

web_src/js/components/ActivityHeatmap.vue

+47-49
Original file line numberDiff line numberDiff line change
@@ -1,58 +1,56 @@
1-
<script lang="ts">
1+
<script lang="ts" setup>
22
// TODO: Switch to upstream after https://github.com/razorness/vue3-calendar-heatmap/pull/34 is merged
33
import {CalendarHeatmap} from '@silverwind/vue3-calendar-heatmap';
4+
import {onMounted, ref} from 'vue';
5+
import type {Value as HeatmapValue, Locale as HeatmapLocale} from '@silverwind/vue3-calendar-heatmap';
46
5-
export default {
6-
components: {CalendarHeatmap},
7-
props: {
8-
values: {
9-
type: Array,
10-
default: () => [],
11-
},
12-
locale: {
13-
type: Object,
14-
default: () => {},
15-
},
16-
},
17-
data: () => ({
18-
colorRange: [
19-
'var(--color-secondary-alpha-60)',
20-
'var(--color-secondary-alpha-60)',
21-
'var(--color-primary-light-4)',
22-
'var(--color-primary-light-2)',
23-
'var(--color-primary)',
24-
'var(--color-primary-dark-2)',
25-
'var(--color-primary-dark-4)',
26-
],
27-
endDate: new Date(),
28-
}),
29-
mounted() {
30-
// work around issue with first legend color being rendered twice and legend cut off
31-
const legend = document.querySelector('.vch__external-legend-wrapper');
32-
legend.setAttribute('viewBox', '12 0 80 10');
33-
legend.style.marginRight = '-12px';
34-
},
35-
methods: {
36-
handleDayClick(e) {
37-
// Reset filter if same date is clicked
38-
const params = new URLSearchParams(document.location.search);
39-
const queryDate = params.get('date');
40-
// Timezone has to be stripped because toISOString() converts to UTC
41-
const clickedDate = new Date(e.date - (e.date.getTimezoneOffset() * 60000)).toISOString().substring(0, 10);
7+
defineProps<{
8+
values?: HeatmapValue[];
9+
locale: {
10+
textTotalContributions: string;
11+
heatMapLocale: Partial<HeatmapLocale>;
12+
noDataText: string;
13+
tooltipUnit: string;
14+
};
15+
}>();
4216
43-
if (queryDate && queryDate === clickedDate) {
44-
params.delete('date');
45-
} else {
46-
params.set('date', clickedDate);
47-
}
17+
const colorRange = [
18+
'var(--color-secondary-alpha-60)',
19+
'var(--color-secondary-alpha-60)',
20+
'var(--color-primary-light-4)',
21+
'var(--color-primary-light-2)',
22+
'var(--color-primary)',
23+
'var(--color-primary-dark-2)',
24+
'var(--color-primary-dark-4)',
25+
];
4826
49-
params.delete('page');
27+
const endDate = ref(new Date());
5028
51-
const newSearch = params.toString();
52-
window.location.search = newSearch.length ? `?${newSearch}` : '';
53-
},
54-
},
55-
};
29+
onMounted(() => {
30+
// work around issue with first legend color being rendered twice and legend cut off
31+
const legend = document.querySelector<HTMLElement>('.vch__external-legend-wrapper');
32+
legend.setAttribute('viewBox', '12 0 80 10');
33+
legend.style.marginRight = '-12px';
34+
});
35+
36+
function handleDayClick(e: Event & {date: Date}) {
37+
// Reset filter if same date is clicked
38+
const params = new URLSearchParams(document.location.search);
39+
const queryDate = params.get('date');
40+
// Timezone has to be stripped because toISOString() converts to UTC
41+
const clickedDate = new Date(e.date.getTime() - (e.date.getTimezoneOffset() * 60000)).toISOString().substring(0, 10);
42+
43+
if (queryDate && queryDate === clickedDate) {
44+
params.delete('date');
45+
} else {
46+
params.set('date', clickedDate);
47+
}
48+
49+
params.delete('page');
50+
51+
const newSearch = params.toString();
52+
window.location.search = newSearch.length ? `?${newSearch}` : '';
53+
}
5654
</script>
5755
<template>
5856
<div class="total-contributions">

web_src/js/components/ContextPopup.vue

+76-80
Original file line numberDiff line numberDiff line change
@@ -1,100 +1,96 @@
1-
<script lang="ts">
1+
<script lang="ts" setup>
22
import {SvgIcon} from '../svg.ts';
33
import {GET} from '../modules/fetch.ts';
4+
import {computed, onMounted, ref} from 'vue';
5+
import type {Issue} from '../types';
46
57
const {appSubUrl, i18n} = window.config;
68
7-
export default {
8-
components: {SvgIcon},
9-
data: () => ({
10-
loading: false,
11-
issue: null,
12-
renderedLabels: '',
13-
i18nErrorOccurred: i18n.error_occurred,
14-
i18nErrorMessage: null,
15-
}),
16-
computed: {
17-
createdAt() {
18-
return new Date(this.issue.created_at).toLocaleDateString(undefined, {year: 'numeric', month: 'short', day: 'numeric'});
19-
},
9+
const loading = ref(false);
10+
const issue = ref(null);
11+
const renderedLabels = ref('');
12+
const i18nErrorOccurred = i18n.error_occurred;
13+
const i18nErrorMessage = ref(null);
2014
21-
body() {
22-
const body = this.issue.body.replace(/\n+/g, ' ');
23-
if (body.length > 85) {
24-
return `${body.substring(0, 85)}…`;
25-
}
26-
return body;
27-
},
15+
const createdAt = computed(() => new Date(issue.value.created_at).toLocaleDateString(undefined, {year: 'numeric', month: 'short', day: 'numeric'}));
16+
const body = computed(() => {
17+
const body = issue.value.body.replace(/\n+/g, ' ');
18+
if (body.length > 85) {
19+
return `${body.substring(0, 85)}…`;
20+
}
21+
return body;
22+
});
2823
29-
icon() {
30-
if (this.issue.pull_request !== null) {
31-
if (this.issue.state === 'open') {
32-
if (this.issue.pull_request.draft === true) {
33-
return 'octicon-git-pull-request-draft'; // WIP PR
34-
}
35-
return 'octicon-git-pull-request'; // Open PR
36-
} else if (this.issue.pull_request.merged === true) {
37-
return 'octicon-git-merge'; // Merged PR
38-
}
39-
return 'octicon-git-pull-request'; // Closed PR
40-
} else if (this.issue.state === 'open') {
41-
return 'octicon-issue-opened'; // Open Issue
24+
function getIssueIcon(issue: Issue) {
25+
if (issue.pull_request) {
26+
if (issue.state === 'open') {
27+
if (issue.pull_request.draft === true) {
28+
return 'octicon-git-pull-request-draft'; // WIP PR
4229
}
43-
return 'octicon-issue-closed'; // Closed Issue
44-
},
30+
return 'octicon-git-pull-request'; // Open PR
31+
} else if (issue.pull_request.merged === true) {
32+
return 'octicon-git-merge'; // Merged PR
33+
}
34+
return 'octicon-git-pull-request'; // Closed PR
35+
} else if (issue.state === 'open') {
36+
return 'octicon-issue-opened'; // Open Issue
37+
}
38+
return 'octicon-issue-closed'; // Closed Issue
39+
}
4540
46-
color() {
47-
if (this.issue.pull_request !== null) {
48-
if (this.issue.pull_request.draft === true) {
49-
return 'grey'; // WIP PR
50-
} else if (this.issue.pull_request.merged === true) {
51-
return 'purple'; // Merged PR
52-
}
53-
}
54-
if (this.issue.state === 'open') {
55-
return 'green'; // Open Issue
56-
}
57-
return 'red'; // Closed Issue
58-
},
59-
},
60-
mounted() {
61-
this.$refs.root.addEventListener('ce-load-context-popup', (e) => {
62-
const data = e.detail;
63-
if (!this.loading && this.issue === null) {
64-
this.load(data);
65-
}
66-
});
67-
},
68-
methods: {
69-
async load(data) {
70-
this.loading = true;
71-
this.i18nErrorMessage = null;
41+
function getIssueColor(issue: Issue) {
42+
if (issue.pull_request) {
43+
if (issue.pull_request.draft === true) {
44+
return 'grey'; // WIP PR
45+
} else if (issue.pull_request.merged === true) {
46+
return 'purple'; // Merged PR
47+
}
48+
}
49+
if (issue.state === 'open') {
50+
return 'green'; // Open Issue
51+
}
52+
return 'red'; // Closed Issue
53+
}
7254
73-
try {
74-
const response = await GET(`${appSubUrl}/${data.owner}/${data.repo}/issues/${data.index}/info`); // backend: GetIssueInfo
75-
const respJson = await response.json();
76-
if (!response.ok) {
77-
this.i18nErrorMessage = respJson.message ?? i18n.network_error;
78-
return;
79-
}
80-
this.issue = respJson.convertedIssue;
81-
this.renderedLabels = respJson.renderedLabels;
82-
} catch {
83-
this.i18nErrorMessage = i18n.network_error;
84-
} finally {
85-
this.loading = false;
86-
}
87-
},
88-
},
89-
};
55+
const root = ref<HTMLElement | null>(null);
56+
57+
onMounted(() => {
58+
root.value.addEventListener('ce-load-context-popup', (e: CustomEvent) => {
59+
const data = e.detail;
60+
if (!loading.value && issue.value === null) {
61+
load(data);
62+
}
63+
});
64+
});
65+
66+
async function load(data) {
67+
loading.value = true;
68+
i18nErrorMessage.value = null;
69+
70+
try {
71+
const response = await GET(`${appSubUrl}/${data.owner}/${data.repo}/issues/${data.index}/info`); // backend: GetIssueInfo
72+
const respJson = await response.json();
73+
if (!response.ok) {
74+
i18nErrorMessage.value = respJson.message ?? i18n.network_error;
75+
return;
76+
}
77+
issue.value = respJson.convertedIssue;
78+
renderedLabels.value = respJson.renderedLabels;
79+
} catch {
80+
i18nErrorMessage.value = i18n.network_error;
81+
} finally {
82+
loading.value = false;
83+
}
84+
}
9085
</script>
86+
9187
<template>
9288
<div ref="root">
9389
<div v-if="loading" class="tw-h-12 tw-w-12 is-loading"/>
9490
<div v-if="!loading && issue !== null" class="tw-flex tw-flex-col tw-gap-2">
9591
<div class="tw-text-12">{{ issue.repository.full_name }} on {{ createdAt }}</div>
9692
<div class="flex-text-block">
97-
<svg-icon :name="icon" :class="['text', color]"/>
93+
<svg-icon :name="getIssueIcon(issue)" :class="['text', getIssueColor(issue)]"/>
9894
<span class="issue-title tw-font-semibold tw-break-anywhere">
9995
{{ issue.title }}
10096
<span class="index">#{{ issue.number }}</span>

0 commit comments

Comments
 (0)