Refactor About.vue to simplify collaborator display and remove update checking logic

This commit is contained in:
2026-05-12 18:42:38 +02:00
parent 7314e73a1b
commit d8d3c7f03c
+141 -243
View File
@@ -1,280 +1,178 @@
<script setup lang="ts">
import { useHead } from '@unhead/vue';
import { computed, onMounted, ref } from 'vue';
import { t } from '../i18n';
defineOptions({ name: 'AboutView' });
useHead(() => ({ title: t('aboutTitle') }));
type ReleaseResponse = {
html_url: string;
name: string | null;
tag_name: string;
published_at: string;
};
const appName = 'Scoreko-dev';
const currentVersion = import.meta.env.PACKAGE_VERSION;
const updateRepoOwner = 'Pandipipas';
const updateRepoName = 'scoreko';
const checkingUpdates = ref(false);
const updateError = ref('');
const latestRelease = ref<ReleaseResponse | null>(null);
const collaborators = [
{
name: 'Pandipipas',
role: 'Development and maintenance of Scoreko-dev',
url: 'https://github.com/Pandipipas/scoreko-dev'
url: 'http://10.0.0.10:3002/Pandipipas/scoreko-dev',
icon: 'code',
},
{
name: 'Dan Shields',
role: 'nodecg-vue-composable helper',
url: 'https://github.com/Dan-Shields/nodecg-vue-composable'
url: 'https://github.com/Dan-Shields/nodecg-vue-composable',
icon: 'extension',
},
{
name: 'NodeCG',
role: 'Broadcast graphics framework',
url: 'https://github.com/nodecg/nodecg'
}
url: 'https://github.com/nodecg/nodecg',
icon: 'layers',
},
];
const releaseLabel = computed(() => {
if (!latestRelease.value) {
return '';
}
return latestRelease.value.name?.trim().length
? latestRelease.value.name
: latestRelease.value.tag_name;
});
const hasUpdate = computed(() => {
if (!latestRelease.value) {
return false;
}
return compareVersions(normalizeVersion(latestRelease.value.tag_name), normalizeVersion(currentVersion)) > 0;
});
const repoUrl = computed(() => `https://github.com/${updateRepoOwner}/${updateRepoName}`);
const releaseUrl = computed(() => latestRelease.value?.html_url ?? `${repoUrl.value}/releases`);
function normalizeVersion(version: string) {
return version.replace(/^v/i, '');
}
function compareVersions(a: string, b: string) {
const aParts = a.split('.').map((value) => Number(value));
const bParts = b.split('.').map((value) => Number(value));
const max = Math.max(aParts.length, bParts.length);
for (let index = 0; index < max; index += 1) {
const aPart = Number.isFinite(aParts[index]) ? aParts[index]! : 0;
const bPart = Number.isFinite(bParts[index]) ? bParts[index]! : 0;
if (aPart > bPart) {
return 1;
}
if (aPart < bPart) {
return -1;
}
}
return 0;
}
async function checkForUpdates() {
checkingUpdates.value = true;
updateError.value = '';
try {
const response = await fetch(
`https://api.github.com/repos/${encodeURIComponent(updateRepoOwner)}/${encodeURIComponent(updateRepoName)}/releases/latest`,
{
headers: {
Accept: 'application/vnd.github+json'
}
}
);
if (!response.ok) {
throw new Error(`${t('aboutGitHubStatusError')} ${response.status}.`);
}
latestRelease.value = await response.json() as ReleaseResponse;
} catch (error) {
latestRelease.value = null;
updateError.value = error instanceof Error ? error.message : t('aboutUnknownReleaseError');
} finally {
checkingUpdates.value = false;
}
}
onMounted(() => {
void checkForUpdates();
});
</script>
<template>
<QPage class="q-pa-lg">
<div class="text-h4 q-mb-md">
{{ t('aboutTitle') }}
<div class="q-mb-lg">
<div class="text-h5 text-weight-medium">
{{ t('aboutTitle') }}
</div>
</div>
<div class="row q-col-gutter-lg">
<div class="col-12 col-md-6">
<QCard
flat
bordered
>
<QCardSection class="row items-center q-col-gutter-md">
<div class="col-auto">
<QImg
src="../image.png"
alt="Scoreko logo"
width="72px"
height="72px"
fit="contain"
/>
<QCard
flat
bordered
class="about-card"
>
<!-- App identity -->
<QCardSection class="q-pa-lg">
<div class="row items-center q-gutter-md">
<QImg
src="../image.png"
alt="Scoreko logo"
class="app-logo"
fit="contain"
/>
<div>
<div class="text-h6 text-weight-bold">
{{ appName }}
</div>
<div class="col">
<div class="text-h6">
{{ appName }}
</div>
<div class="text-caption text-grey-7">
{{ t('aboutVersion') }} {{ currentVersion }}
</div>
</div>
</QCardSection>
<QSeparator />
<QCardSection>
<p class="q-mb-sm">
{{ t('aboutDescription') }}
</p>
<div class="column q-gutter-sm">
<QBtn
href="https://github.com/nodecg/nodecg"
target="_blank"
rel="noopener noreferrer"
icon="open_in_new"
:label="t('aboutFrameworkNodeCG')"
color="primary"
flat
no-caps
align="left"
/>
</div>
</QCardSection>
<QSeparator />
<QCardSection>
<div class="text-subtitle2 q-mb-sm">
{{ t('aboutCollaboratorsTitle') }}
</div>
<QList dense>
<QItem
v-for="person in collaborators"
:key="person.name"
tag="a"
:href="person.url"
target="_blank"
rel="noopener noreferrer"
>
<QItemSection>
<QItemLabel>{{ person.name }}</QItemLabel>
<QItemLabel caption>
{{ person.role }}
</QItemLabel>
</QItemSection>
</QItem>
</QList>
</QCardSection>
</QCard>
</div>
<div class="col-12 col-md-6">
<QCard
flat
bordered
>
<QCardSection>
<div class="text-h6">
{{ t('aboutUpdateSystemTitle') }}
</div>
<div class="text-body2 text-grey-7 q-mt-xs">
{{ t('aboutUpdateSystemDescription') }}
</div>
</QCardSection>
<QSeparator />
<QCardSection class="q-gutter-md">
<QBtn
:label="t('aboutCheckUpdates')"
<QBadge
outline
color="primary"
icon="sync"
:loading="checkingUpdates"
no-caps
@click="checkForUpdates"
/>
<QBanner
v-if="latestRelease"
rounded
class="bg-grey-2"
class="q-mt-xs version-badge"
>
<template #avatar>
<QIcon
:name="hasUpdate ? 'system_update_alt' : 'check_circle'"
:color="hasUpdate ? 'warning' : 'positive'"
/>
</template>
<div class="text-subtitle2">
{{ t('aboutLatestRelease') }}: {{ releaseLabel }}
</div>
<div class="text-caption text-grey-7">
{{ t('aboutPublished') }}: {{ new Date(latestRelease.published_at).toLocaleString() }}
</div>
<div class="q-mt-sm">
{{ hasUpdate ? t('aboutUpdateAvailable') : t('aboutUpToDate') }}
</div>
<template #action>
<QBtn
flat
color="primary"
:label="t('aboutViewRelease')"
:href="releaseUrl"
target="_blank"
rel="noopener noreferrer"
no-caps
/>
</template>
</QBanner>
v{{ currentVersion }}
</QBadge>
</div>
</div>
</QCardSection>
<QBanner
v-if="updateError"
rounded
class="bg-red-1 text-red-10"
>
{{ updateError }}
</QBanner>
<QSeparator />
<QBanner
rounded
class="bg-blue-1 text-blue-10"
>
{{ t('aboutElectronNote') }}
</QBanner>
</QCardSection>
</QCard>
</div>
</div>
<!-- Description + framework -->
<QCardSection class="q-pa-lg">
<p class="text-body2 text-grey-7 q-mb-md">
{{ t('aboutDescription') }}
</p>
<QBtn
href="https://github.com/nodecg/nodecg"
target="_blank"
rel="noopener noreferrer"
icon="open_in_new"
:label="t('aboutFrameworkNodeCG')"
color="primary"
flat
dense
no-caps
/>
</QCardSection>
<QSeparator />
<!-- Collaborators -->
<QCardSection class="q-pa-lg">
<div class="text-overline text-grey-6 q-mb-sm">
{{ t('aboutCollaboratorsTitle') }}
</div>
<QList
dense
class="collaborators-list"
>
<QItem
v-for="person in collaborators"
:key="person.name"
tag="a"
:href="person.url"
target="_blank"
rel="noopener noreferrer"
class="collaborator-item rounded-borders"
>
<QItemSection avatar>
<QIcon
:name="person.icon"
size="18px"
color="primary"
class="collaborator-icon"
/>
</QItemSection>
<QItemSection>
<QItemLabel class="text-weight-medium">
{{ person.name }}
</QItemLabel>
<QItemLabel
caption
class="text-grey-6"
>
{{ person.role }}
</QItemLabel>
</QItemSection>
<QItemSection side>
<QIcon
name="arrow_forward_ios"
size="12px"
color="grey-5"
/>
</QItemSection>
</QItem>
</QList>
</QCardSection>
</QCard>
</QPage>
</template>
<style scoped>
.about-card {
max-width: 520px;
}
.app-logo {
width: 56px;
height: 56px;
border-radius: 12px;
}
.version-badge {
font-size: 11px;
letter-spacing: 0.03em;
}
.collaborators-list {
margin: 0 -8px;
}
.collaborator-item {
border-radius: 8px;
transition: background 0.15s ease;
padding: 6px 8px;
text-decoration: none;
}
.collaborator-item:hover {
background: rgba(0, 0, 0, 0.04);
}
.collaborator-icon {
opacity: 0.8;
}
</style>