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"> <script setup lang="ts">
import { useHead } from '@unhead/vue'; import { useHead } from '@unhead/vue';
import { computed, onMounted, ref } from 'vue';
import { t } from '../i18n'; import { t } from '../i18n';
defineOptions({ name: 'AboutView' }); defineOptions({ name: 'AboutView' });
useHead(() => ({ title: t('aboutTitle') })); useHead(() => ({ title: t('aboutTitle') }));
type ReleaseResponse = {
html_url: string;
name: string | null;
tag_name: string;
published_at: string;
};
const appName = 'Scoreko-dev'; const appName = 'Scoreko-dev';
const currentVersion = import.meta.env.PACKAGE_VERSION; 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 = [ const collaborators = [
{ {
name: 'Pandipipas', name: 'Pandipipas',
role: 'Development and maintenance of Scoreko-dev', 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', name: 'Dan Shields',
role: 'nodecg-vue-composable helper', 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', name: 'NodeCG',
role: 'Broadcast graphics framework', 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> </script>
<template> <template>
<QPage class="q-pa-lg"> <QPage class="q-pa-lg">
<div class="text-h4 q-mb-md"> <div class="q-mb-lg">
{{ t('aboutTitle') }} <div class="text-h5 text-weight-medium">
{{ t('aboutTitle') }}
</div>
</div> </div>
<div class="row q-col-gutter-lg"> <QCard
<div class="col-12 col-md-6"> flat
<QCard bordered
flat class="about-card"
bordered >
> <!-- App identity -->
<QCardSection class="row items-center q-col-gutter-md"> <QCardSection class="q-pa-lg">
<div class="col-auto"> <div class="row items-center q-gutter-md">
<QImg <QImg
src="../image.png" src="../image.png"
alt="Scoreko logo" alt="Scoreko logo"
width="72px" class="app-logo"
height="72px" fit="contain"
fit="contain" />
/> <div>
<div class="text-h6 text-weight-bold">
{{ appName }}
</div> </div>
<div class="col"> <QBadge
<div class="text-h6"> outline
{{ 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')"
color="primary" color="primary"
icon="sync" class="q-mt-xs version-badge"
:loading="checkingUpdates"
no-caps
@click="checkForUpdates"
/>
<QBanner
v-if="latestRelease"
rounded
class="bg-grey-2"
> >
<template #avatar> v{{ currentVersion }}
<QIcon </QBadge>
:name="hasUpdate ? 'system_update_alt' : 'check_circle'" </div>
:color="hasUpdate ? 'warning' : 'positive'" </div>
/> </QCardSection>
</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>
<QBanner <QSeparator />
v-if="updateError"
rounded
class="bg-red-1 text-red-10"
>
{{ updateError }}
</QBanner>
<QBanner <!-- Description + framework -->
rounded <QCardSection class="q-pa-lg">
class="bg-blue-1 text-blue-10" <p class="text-body2 text-grey-7 q-mb-md">
> {{ t('aboutDescription') }}
{{ t('aboutElectronNote') }} </p>
</QBanner> <QBtn
</QCardSection> href="https://github.com/nodecg/nodecg"
</QCard> target="_blank"
</div> rel="noopener noreferrer"
</div> 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> </QPage>
</template> </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>