Merge pull request #72 from Pandipipas/add-about-section-and-update-system

feat(about): redesign About and add GitHub release update checker
This commit is contained in:
Pandipipas
2026-02-14 22:45:54 +01:00
committed by GitHub
3 changed files with 306 additions and 2 deletions
+292 -2
View File
@@ -1,9 +1,124 @@
<script setup lang="ts"> <script setup lang="ts">
import { useHead } from '@unhead/vue'; import { useHead } from '@unhead/vue';
import { computed, onMounted, ref } from 'vue';
defineOptions({ name: 'AboutView' }); defineOptions({ name: 'AboutView' });
useHead({ title: 'About' }); useHead({ title: 'About' });
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 repoOwner = ref(localStorage.getItem('scoreko:repoOwner') ?? 'Pandipipas');
const repoName = ref(localStorage.getItem('scoreko:repoName') ?? 'scoreko-dev');
const checkingUpdates = ref(false);
const updateError = ref('');
const latestRelease = ref<ReleaseResponse | null>(null);
const collaborators = [
{
name: 'zoton2',
role: 'Template base (NodeCG + Vue + TS)',
url: 'https://github.com/zoton2/nodecg-vue-ts-template'
},
{
name: 'Dan Shields',
role: 'nodecg-vue-composable helper',
url: 'https://github.com/Dan-Shields/nodecg-vue-composable'
},
{
name: 'NodeCG',
role: 'Broadcast graphics framework',
url: 'https://github.com/nodecg/nodecg'
}
];
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/${repoOwner.value}/${repoName.value}`);
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 {
localStorage.setItem('scoreko:repoOwner', repoOwner.value.trim());
localStorage.setItem('scoreko:repoName', repoName.value.trim());
const response = await fetch(
`https://api.github.com/repos/${encodeURIComponent(repoOwner.value.trim())}/${encodeURIComponent(repoName.value.trim())}/releases/latest`,
{
headers: {
Accept: 'application/vnd.github+json'
}
}
);
if (!response.ok) {
throw new Error(`GitHub respondió con estado ${response.status}.`);
}
latestRelease.value = await response.json() as ReleaseResponse;
} catch (error) {
latestRelease.value = null;
updateError.value = error instanceof Error ? error.message : 'Error desconocido revisando releases.';
} finally {
checkingUpdates.value = false;
}
}
onMounted(() => {
void checkForUpdates();
});
</script> </script>
<template> <template>
@@ -11,8 +126,183 @@ useHead({ title: 'About' });
<div class="text-h4 q-mb-md"> <div class="text-h4 q-mb-md">
About About
</div> </div>
<div class="text-body1">
Bundle information and relevant links. <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"
/>
</div>
<div class="col">
<div class="text-h6">
{{ appName }}
</div>
<div class="text-caption text-grey-7">
Versión {{ currentVersion }}
</div>
</div>
</QCardSection>
<QSeparator />
<QCardSection>
<p class="q-mb-sm">
Dashboard para producción de overlays de fighting games usando NodeCG, Vue y Quasar.
</p>
<div class="column q-gutter-sm">
<QBtn
:href="repoUrl"
target="_blank"
rel="noopener noreferrer"
icon="open_in_new"
label="Repositorio en GitHub"
color="primary"
flat
no-caps
align="left"
/>
<QBtn
href="https://github.com/nodecg/nodecg"
target="_blank"
rel="noopener noreferrer"
icon="open_in_new"
label="Framework NodeCG"
color="primary"
flat
no-caps
align="left"
/>
</div>
</QCardSection>
<QSeparator />
<QCardSection>
<div class="text-subtitle2 q-mb-sm">
Colaboradores y agradecimientos
</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">
Sistema de updates (GitHub Releases)
</div>
<div class="text-body2 text-grey-7 q-mt-xs">
Este chequeo consulta el último release del repo y lo compara con la versión actual.
</div>
</QCardSection>
<QSeparator />
<QCardSection class="q-gutter-md">
<QInput
v-model="repoOwner"
label="GitHub owner"
outlined
dense
/>
<QInput
v-model="repoName"
label="GitHub repo"
outlined
dense
/>
<QBtn
label="Buscar updates"
color="primary"
icon="sync"
:loading="checkingUpdates"
no-caps
@click="checkForUpdates"
/>
<QBanner
v-if="latestRelease"
rounded
class="bg-grey-2"
>
<template #avatar>
<QIcon
:name="hasUpdate ? 'system_update_alt' : 'check_circle'"
:color="hasUpdate ? 'warning' : 'positive'"
/>
</template>
<div class="text-subtitle2">
Último release: {{ releaseLabel }}
</div>
<div class="text-caption text-grey-7">
Publicado: {{ new Date(latestRelease.published_at).toLocaleString() }}
</div>
<div class="q-mt-sm">
{{ hasUpdate ? 'Hay una versión más nueva disponible.' : 'Tu versión está actualizada frente al último release.' }}
</div>
<template #action>
<QBtn
flat
color="primary"
label="Ver release"
:href="releaseUrl"
target="_blank"
rel="noopener noreferrer"
no-caps
/>
</template>
</QBanner>
<QBanner
v-if="updateError"
rounded
class="bg-red-1 text-red-10"
>
{{ updateError }}
</QBanner>
<QBanner
rounded
class="bg-blue-1 text-blue-10"
>
Nota para Electron: este panel implementa solo <strong>detección y aviso</strong>. Para updates
automáticos reales en desktop, hay que integrar `autoUpdater` en el proceso principal de Electron
y publicar artefactos firmados por plataforma.
</QBanner>
</QCardSection>
</QCard>
</div>
</div> </div>
</QPage> </QPage>
</template> </template>
+8
View File
@@ -1 +1,9 @@
/// <reference types="vite/client" /> /// <reference types="vite/client" />
interface ImportMetaEnv {
readonly PACKAGE_VERSION: string;
}
interface ImportMeta {
readonly env: ImportMetaEnv;
}
+6
View File
@@ -1,12 +1,18 @@
import { quasar, transformAssetUrls } from '@quasar/vite-plugin'; import { quasar, transformAssetUrls } from '@quasar/vite-plugin';
import vue from '@vitejs/plugin-vue'; import vue from '@vitejs/plugin-vue';
import { readFileSync } from 'node:fs';
import { fileURLToPath } from 'node:url'; import { fileURLToPath } from 'node:url';
import { defineConfig } from 'vite'; import { defineConfig } from 'vite';
import checker from 'vite-plugin-checker'; import checker from 'vite-plugin-checker';
import NodeCGPlugin from 'vite-plugin-nodecg'; import NodeCGPlugin from 'vite-plugin-nodecg';
const packageJson = JSON.parse(readFileSync(new URL('./package.json', import.meta.url), 'utf-8')) as { version: string };
// https://vitejs.dev/config/ // https://vitejs.dev/config/
export default defineConfig({ export default defineConfig({
define: {
'import.meta.env.PACKAGE_VERSION': JSON.stringify(packageJson.version),
},
plugins: [ plugins: [
vue({ template: { transformAssetUrls } }), vue({ template: { transformAssetUrls } }),
quasar({ quasar({