mirror of
https://github.com/unraid/api.git
synced 2025-12-30 13:09:52 -06:00
refactor: reuse ChangelogModal in HeaderOsVersion component (#1607)
<!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - New Features - View OS release notes directly for the currently displayed version, with a link to open in a new tab. - Changelog modal supports viewing a specific release outside the update flow. - Improvements - Changelog modal prioritizes a prettier docs view when available, with fallback to raw notes. - Enhanced theming with better dark mode support, including Azure theme alignment. - Clearer modal behavior: consistent open/close handling and titles based on the selected release. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"version": "4.14.0",
|
||||
"version": "4.15.1",
|
||||
"extraOrigins": [],
|
||||
"sandbox": true,
|
||||
"ssoSubIds": [],
|
||||
|
||||
@@ -1545,7 +1545,7 @@ type InfoVersions implements Node {
|
||||
core: CoreVersions!
|
||||
|
||||
"""Software package versions"""
|
||||
packages: PackageVersions!
|
||||
packages: PackageVersions
|
||||
}
|
||||
|
||||
type Info implements Node {
|
||||
|
||||
@@ -6,14 +6,14 @@ import { useQuery } from '@vue/apollo-composable';
|
||||
|
||||
import { BellAlertIcon, ExclamationTriangleIcon, InformationCircleIcon, DocumentTextIcon, ArrowTopRightOnSquareIcon } from '@heroicons/vue/24/solid';
|
||||
import { Badge, DropdownMenuRoot, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator } from '@unraid/ui';
|
||||
import { WEBGUI_TOOLS_DOWNGRADE, WEBGUI_TOOLS_UPDATE } from '~/helpers/urls';
|
||||
import { WEBGUI_TOOLS_DOWNGRADE, WEBGUI_TOOLS_UPDATE, getReleaseNotesUrl } from '~/helpers/urls';
|
||||
|
||||
import { useActivationCodeDataStore } from '~/components/Activation/store/activationCodeData';
|
||||
import { useServerStore } from '~/store/server';
|
||||
import { useUpdateOsStore } from '~/store/updateOs';
|
||||
import { useUpdateOsActionsStore } from '~/store/updateOsActions';
|
||||
import { INFO_VERSIONS_QUERY } from './UserProfile/versions.query';
|
||||
import ReleaseNotesModal from '~/components/ReleaseNotesModal.vue';
|
||||
import ChangelogModal from '~/components/UpdateOs/ChangelogModal.vue';
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
@@ -36,6 +36,23 @@ const displayOsVersion = computed(() => versionsResult.value?.info?.versions?.co
|
||||
const apiVersion = computed(() => versionsResult.value?.info?.versions?.core?.api || null);
|
||||
const showOsReleaseNotesModal = ref(false);
|
||||
|
||||
// Create release object for current version to pass to ChangelogModal
|
||||
const currentVersionRelease = computed(() => {
|
||||
if (!displayOsVersion.value) return null;
|
||||
|
||||
const version = displayOsVersion.value;
|
||||
const releaseNotesUrl = getReleaseNotesUrl(version).toString();
|
||||
|
||||
return {
|
||||
version,
|
||||
name: `Unraid ${version}`,
|
||||
date: undefined, // We don't know the release date for current version
|
||||
changelog: null, // No raw changelog for current version
|
||||
changelogPretty: releaseNotesUrl,
|
||||
sha256: null, // No update functionality for current version
|
||||
};
|
||||
});
|
||||
|
||||
const openApiChangelog = () => {
|
||||
window.open('https://github.com/unraid/api/releases', '_blank');
|
||||
};
|
||||
@@ -184,10 +201,10 @@ const updateOsStatus = computed(() => {
|
||||
</div>
|
||||
|
||||
<!-- OS Release Notes Modal -->
|
||||
<ReleaseNotesModal
|
||||
v-if="displayOsVersion"
|
||||
<ChangelogModal
|
||||
:open="showOsReleaseNotesModal"
|
||||
:version="displayOsVersion"
|
||||
:release="currentVersionRelease"
|
||||
view-docs-label="Open in new tab"
|
||||
:t="t"
|
||||
@close="showOsReleaseNotesModal = false"
|
||||
/>
|
||||
|
||||
@@ -23,23 +23,61 @@ import Modal from '~/components/Modal.vue';
|
||||
export interface Props {
|
||||
open?: boolean;
|
||||
t: ComposerTranslation;
|
||||
// When provided, uses prop data instead of store data (for viewing release notes)
|
||||
release?: {
|
||||
version: string;
|
||||
name?: string;
|
||||
date?: string;
|
||||
changelog?: string | null;
|
||||
changelogPretty?: string;
|
||||
sha256?: string | null;
|
||||
} | null;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
open: false,
|
||||
release: null,
|
||||
});
|
||||
|
||||
const emit = defineEmits<{
|
||||
close: [];
|
||||
}>();
|
||||
|
||||
const purchaseStore = usePurchaseStore();
|
||||
const updateOsStore = useUpdateOsStore();
|
||||
const themeStore = useThemeStore();
|
||||
const { darkMode } = storeToRefs(themeStore);
|
||||
const { darkMode, theme } = storeToRefs(themeStore);
|
||||
const isDarkMode = computed(() => {
|
||||
if (theme.value.name === 'azure') {
|
||||
return true;
|
||||
}
|
||||
return darkMode.value;
|
||||
});
|
||||
const { availableWithRenewal, releaseForUpdate, changelogModalVisible } = storeToRefs(updateOsStore);
|
||||
const { setReleaseForUpdate, fetchAndConfirmInstall } = updateOsStore;
|
||||
|
||||
// Determine if we're in prop mode (viewing specific release) or store mode (update workflow)
|
||||
const isPropMode = computed(() => !!props.release);
|
||||
|
||||
// Use prop data when provided, store data when not
|
||||
const currentRelease = computed(() => props.release || releaseForUpdate.value);
|
||||
|
||||
// Modal visibility: use prop when in prop mode, store visibility when in store mode
|
||||
const modalVisible = computed(() => isPropMode.value ? props.open : changelogModalVisible.value);
|
||||
|
||||
const showExtendKeyButton = computed(() => {
|
||||
return availableWithRenewal.value;
|
||||
return !isPropMode.value && availableWithRenewal.value;
|
||||
});
|
||||
|
||||
// Handle modal closing - emit event in prop mode, clear store in store mode
|
||||
const handleClose = () => {
|
||||
if (isPropMode.value) {
|
||||
emit('close');
|
||||
} else {
|
||||
setReleaseForUpdate(null);
|
||||
}
|
||||
};
|
||||
|
||||
// iframe navigation handling
|
||||
const iframeRef = ref<HTMLIFrameElement | null>(null);
|
||||
const hasNavigated = ref(false);
|
||||
@@ -47,11 +85,11 @@ const currentIframeUrl = ref<string | null>(null);
|
||||
const actualIframeSrc = ref<string | null>(null);
|
||||
|
||||
const docsChangelogUrl = computed(() => {
|
||||
return releaseForUpdate.value?.changelogPretty ?? null;
|
||||
return currentRelease.value?.changelogPretty ?? null;
|
||||
});
|
||||
|
||||
const showRawChangelog = computed<boolean>(() => {
|
||||
return !docsChangelogUrl.value && !!releaseForUpdate.value?.changelog;
|
||||
return Boolean(!docsChangelogUrl.value && currentRelease.value?.changelog);
|
||||
});
|
||||
|
||||
const handleDocsPostMessages = (event: MessageEvent) => {
|
||||
@@ -80,7 +118,7 @@ const handleDocsPostMessages = (event: MessageEvent) => {
|
||||
const sendThemeToIframe = () => {
|
||||
if (iframeRef.value && iframeRef.value.contentWindow) {
|
||||
try {
|
||||
const message = { type: 'theme-update', theme: darkMode.value ? 'dark' : 'light' };
|
||||
const message = { type: 'theme-update', theme: isDarkMode.value ? 'dark' : 'light' };
|
||||
iframeRef.value.contentWindow.postMessage(message, '*');
|
||||
} catch (error) {
|
||||
console.error('Failed to send theme to iframe:', error);
|
||||
@@ -120,7 +158,7 @@ watch(docsChangelogUrl, (newUrl) => {
|
||||
});
|
||||
|
||||
// Only need to watch for theme changes
|
||||
watch(darkMode, () => {
|
||||
watch(isDarkMode, () => {
|
||||
// The iframe will only pick up the message if it has sent theme-ready
|
||||
sendThemeToIframe();
|
||||
});
|
||||
@@ -129,16 +167,16 @@ watch(darkMode, () => {
|
||||
|
||||
<template>
|
||||
<Modal
|
||||
v-if="releaseForUpdate?.version"
|
||||
v-if="currentRelease?.version"
|
||||
:center-content="false"
|
||||
max-width="max-w-[800px]"
|
||||
:open="changelogModalVisible"
|
||||
:open="modalVisible"
|
||||
:show-close-x="true"
|
||||
:t="t"
|
||||
:tall-content="true"
|
||||
:title="t('Unraid OS {0} Changelog', [releaseForUpdate.version])"
|
||||
:title="t('Unraid OS {0} Changelog', [currentRelease.version])"
|
||||
:disable-overlay-close="false"
|
||||
@close="setReleaseForUpdate(null)"
|
||||
@close="handleClose"
|
||||
>
|
||||
<template #main>
|
||||
<div class="flex flex-col gap-4 min-w-[280px] sm:min-w-[400px]">
|
||||
@@ -156,12 +194,12 @@ watch(darkMode, () => {
|
||||
|
||||
<!-- Fallback to raw changelog -->
|
||||
<RawChangelogRenderer
|
||||
v-else-if="showRawChangelog && releaseForUpdate?.changelog"
|
||||
:changelog="releaseForUpdate?.changelog"
|
||||
:version="releaseForUpdate?.version"
|
||||
:date="releaseForUpdate?.date"
|
||||
v-else-if="showRawChangelog && currentRelease?.changelog"
|
||||
:changelog="currentRelease?.changelog"
|
||||
:version="currentRelease?.version"
|
||||
:date="currentRelease?.date"
|
||||
:t="t"
|
||||
:changelog-pretty="releaseForUpdate?.changelogPretty"
|
||||
:changelog-pretty="currentRelease?.changelogPretty"
|
||||
/>
|
||||
|
||||
<!-- Loading state -->
|
||||
@@ -189,32 +227,34 @@ watch(darkMode, () => {
|
||||
|
||||
<!-- View on docs button -->
|
||||
<BrandButton
|
||||
v-if="currentIframeUrl || releaseForUpdate?.changelogPretty"
|
||||
v-if="currentIframeUrl || currentRelease?.changelogPretty"
|
||||
variant="underline"
|
||||
:external="true"
|
||||
:href="currentIframeUrl || releaseForUpdate?.changelogPretty"
|
||||
:icon="ArrowTopRightOnSquareIcon"
|
||||
:href="currentIframeUrl || currentRelease?.changelogPretty"
|
||||
:icon-right="ArrowTopRightOnSquareIcon"
|
||||
aria-label="View on Docs"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Action buttons -->
|
||||
<BrandButton
|
||||
v-if="showExtendKeyButton"
|
||||
:icon="KeyIcon"
|
||||
:icon-right="ArrowTopRightOnSquareIcon"
|
||||
@click="purchaseStore.renew()"
|
||||
>
|
||||
{{ props.t('Extend License to Update') }}
|
||||
</BrandButton>
|
||||
<BrandButton
|
||||
v-else-if="releaseForUpdate?.sha256"
|
||||
:icon="ServerStackIcon"
|
||||
:icon-right="ArrowRightIcon"
|
||||
@click="fetchAndConfirmInstall(releaseForUpdate.sha256)"
|
||||
>
|
||||
{{ props.t('Continue') }}
|
||||
</BrandButton>
|
||||
<!-- Action buttons (only in store mode for update workflow) -->
|
||||
<template v-if="!isPropMode">
|
||||
<BrandButton
|
||||
v-if="showExtendKeyButton"
|
||||
:icon="KeyIcon"
|
||||
:icon-right="ArrowTopRightOnSquareIcon"
|
||||
@click="purchaseStore.renew()"
|
||||
>
|
||||
{{ props.t('Extend License to Update') }}
|
||||
</BrandButton>
|
||||
<BrandButton
|
||||
v-else-if="currentRelease?.sha256"
|
||||
:icon="ServerStackIcon"
|
||||
:icon-right="ArrowRightIcon"
|
||||
@click="fetchAndConfirmInstall(currentRelease.sha256)"
|
||||
>
|
||||
{{ props.t('Continue') }}
|
||||
</BrandButton>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
</Modal>
|
||||
|
||||
Reference in New Issue
Block a user