Skip to content

Commit fd895ca

Browse files
authored
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 -->
1 parent 6edd3a3 commit fd895ca

File tree

4 files changed

+99
-42
lines changed

4 files changed

+99
-42
lines changed

api/dev/configs/api.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"version": "4.14.0",
2+
"version": "4.15.1",
33
"extraOrigins": [],
44
"sandbox": true,
55
"ssoSubIds": [],

api/generated-schema.graphql

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1545,7 +1545,7 @@ type InfoVersions implements Node {
15451545
core: CoreVersions!
15461546

15471547
"""Software package versions"""
1548-
packages: PackageVersions!
1548+
packages: PackageVersions
15491549
}
15501550

15511551
type Info implements Node {

web/components/HeaderOsVersion.ce.vue

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,14 @@ import { useQuery } from '@vue/apollo-composable';
66
77
import { BellAlertIcon, ExclamationTriangleIcon, InformationCircleIcon, DocumentTextIcon, ArrowTopRightOnSquareIcon } from '@heroicons/vue/24/solid';
88
import { Badge, DropdownMenuRoot, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator } from '@unraid/ui';
9-
import { WEBGUI_TOOLS_DOWNGRADE, WEBGUI_TOOLS_UPDATE } from '~/helpers/urls';
9+
import { WEBGUI_TOOLS_DOWNGRADE, WEBGUI_TOOLS_UPDATE, getReleaseNotesUrl } from '~/helpers/urls';
1010
1111
import { useActivationCodeDataStore } from '~/components/Activation/store/activationCodeData';
1212
import { useServerStore } from '~/store/server';
1313
import { useUpdateOsStore } from '~/store/updateOs';
1414
import { useUpdateOsActionsStore } from '~/store/updateOsActions';
1515
import { INFO_VERSIONS_QUERY } from './UserProfile/versions.query';
16-
import ReleaseNotesModal from '~/components/ReleaseNotesModal.vue';
16+
import ChangelogModal from '~/components/UpdateOs/ChangelogModal.vue';
1717
1818
const { t } = useI18n();
1919
@@ -36,6 +36,23 @@ const displayOsVersion = computed(() => versionsResult.value?.info?.versions?.co
3636
const apiVersion = computed(() => versionsResult.value?.info?.versions?.core?.api || null);
3737
const showOsReleaseNotesModal = ref(false);
3838
39+
// Create release object for current version to pass to ChangelogModal
40+
const currentVersionRelease = computed(() => {
41+
if (!displayOsVersion.value) return null;
42+
43+
const version = displayOsVersion.value;
44+
const releaseNotesUrl = getReleaseNotesUrl(version).toString();
45+
46+
return {
47+
version,
48+
name: `Unraid ${version}`,
49+
date: undefined, // We don't know the release date for current version
50+
changelog: null, // No raw changelog for current version
51+
changelogPretty: releaseNotesUrl,
52+
sha256: null, // No update functionality for current version
53+
};
54+
});
55+
3956
const openApiChangelog = () => {
4057
window.open('https://github.com/unraid/api/releases', '_blank');
4158
};
@@ -184,10 +201,10 @@ const updateOsStatus = computed(() => {
184201
</div>
185202

186203
<!-- OS Release Notes Modal -->
187-
<ReleaseNotesModal
188-
v-if="displayOsVersion"
204+
<ChangelogModal
189205
:open="showOsReleaseNotesModal"
190-
:version="displayOsVersion"
206+
:release="currentVersionRelease"
207+
view-docs-label="Open in new tab"
191208
:t="t"
192209
@close="showOsReleaseNotesModal = false"
193210
/>

web/components/UpdateOs/ChangelogModal.vue

Lines changed: 75 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -23,35 +23,73 @@ import Modal from '~/components/Modal.vue';
2323
export interface Props {
2424
open?: boolean;
2525
t: ComposerTranslation;
26+
// When provided, uses prop data instead of store data (for viewing release notes)
27+
release?: {
28+
version: string;
29+
name?: string;
30+
date?: string;
31+
changelog?: string | null;
32+
changelogPretty?: string;
33+
sha256?: string | null;
34+
} | null;
2635
}
2736
2837
const props = withDefaults(defineProps<Props>(), {
2938
open: false,
39+
release: null,
3040
});
3141
42+
const emit = defineEmits<{
43+
close: [];
44+
}>();
45+
3246
const purchaseStore = usePurchaseStore();
3347
const updateOsStore = useUpdateOsStore();
3448
const themeStore = useThemeStore();
35-
const { darkMode } = storeToRefs(themeStore);
49+
const { darkMode, theme } = storeToRefs(themeStore);
50+
const isDarkMode = computed(() => {
51+
if (theme.value.name === 'azure') {
52+
return true;
53+
}
54+
return darkMode.value;
55+
});
3656
const { availableWithRenewal, releaseForUpdate, changelogModalVisible } = storeToRefs(updateOsStore);
3757
const { setReleaseForUpdate, fetchAndConfirmInstall } = updateOsStore;
3858
59+
// Determine if we're in prop mode (viewing specific release) or store mode (update workflow)
60+
const isPropMode = computed(() => !!props.release);
61+
62+
// Use prop data when provided, store data when not
63+
const currentRelease = computed(() => props.release || releaseForUpdate.value);
64+
65+
// Modal visibility: use prop when in prop mode, store visibility when in store mode
66+
const modalVisible = computed(() => isPropMode.value ? props.open : changelogModalVisible.value);
67+
3968
const showExtendKeyButton = computed(() => {
40-
return availableWithRenewal.value;
69+
return !isPropMode.value && availableWithRenewal.value;
4170
});
4271
72+
// Handle modal closing - emit event in prop mode, clear store in store mode
73+
const handleClose = () => {
74+
if (isPropMode.value) {
75+
emit('close');
76+
} else {
77+
setReleaseForUpdate(null);
78+
}
79+
};
80+
4381
// iframe navigation handling
4482
const iframeRef = ref<HTMLIFrameElement | null>(null);
4583
const hasNavigated = ref(false);
4684
const currentIframeUrl = ref<string | null>(null);
4785
const actualIframeSrc = ref<string | null>(null);
4886
4987
const docsChangelogUrl = computed(() => {
50-
return releaseForUpdate.value?.changelogPretty ?? null;
88+
return currentRelease.value?.changelogPretty ?? null;
5189
});
5290
5391
const showRawChangelog = computed<boolean>(() => {
54-
return !docsChangelogUrl.value && !!releaseForUpdate.value?.changelog;
92+
return Boolean(!docsChangelogUrl.value && currentRelease.value?.changelog);
5593
});
5694
5795
const handleDocsPostMessages = (event: MessageEvent) => {
@@ -80,7 +118,7 @@ const handleDocsPostMessages = (event: MessageEvent) => {
80118
const sendThemeToIframe = () => {
81119
if (iframeRef.value && iframeRef.value.contentWindow) {
82120
try {
83-
const message = { type: 'theme-update', theme: darkMode.value ? 'dark' : 'light' };
121+
const message = { type: 'theme-update', theme: isDarkMode.value ? 'dark' : 'light' };
84122
iframeRef.value.contentWindow.postMessage(message, '*');
85123
} catch (error) {
86124
console.error('Failed to send theme to iframe:', error);
@@ -120,7 +158,7 @@ watch(docsChangelogUrl, (newUrl) => {
120158
});
121159
122160
// Only need to watch for theme changes
123-
watch(darkMode, () => {
161+
watch(isDarkMode, () => {
124162
// The iframe will only pick up the message if it has sent theme-ready
125163
sendThemeToIframe();
126164
});
@@ -129,16 +167,16 @@ watch(darkMode, () => {
129167

130168
<template>
131169
<Modal
132-
v-if="releaseForUpdate?.version"
170+
v-if="currentRelease?.version"
133171
:center-content="false"
134172
max-width="max-w-[800px]"
135-
:open="changelogModalVisible"
173+
:open="modalVisible"
136174
:show-close-x="true"
137175
:t="t"
138176
:tall-content="true"
139-
:title="t('Unraid OS {0} Changelog', [releaseForUpdate.version])"
177+
:title="t('Unraid OS {0} Changelog', [currentRelease.version])"
140178
:disable-overlay-close="false"
141-
@close="setReleaseForUpdate(null)"
179+
@close="handleClose"
142180
>
143181
<template #main>
144182
<div class="flex flex-col gap-4 min-w-[280px] sm:min-w-[400px]">
@@ -156,12 +194,12 @@ watch(darkMode, () => {
156194

157195
<!-- Fallback to raw changelog -->
158196
<RawChangelogRenderer
159-
v-else-if="showRawChangelog && releaseForUpdate?.changelog"
160-
:changelog="releaseForUpdate?.changelog"
161-
:version="releaseForUpdate?.version"
162-
:date="releaseForUpdate?.date"
197+
v-else-if="showRawChangelog && currentRelease?.changelog"
198+
:changelog="currentRelease?.changelog"
199+
:version="currentRelease?.version"
200+
:date="currentRelease?.date"
163201
:t="t"
164-
:changelog-pretty="releaseForUpdate?.changelogPretty"
202+
:changelog-pretty="currentRelease?.changelogPretty"
165203
/>
166204

167205
<!-- Loading state -->
@@ -189,32 +227,34 @@ watch(darkMode, () => {
189227

190228
<!-- View on docs button -->
191229
<BrandButton
192-
v-if="currentIframeUrl || releaseForUpdate?.changelogPretty"
230+
v-if="currentIframeUrl || currentRelease?.changelogPretty"
193231
variant="underline"
194232
:external="true"
195-
:href="currentIframeUrl || releaseForUpdate?.changelogPretty"
196-
:icon="ArrowTopRightOnSquareIcon"
233+
:href="currentIframeUrl || currentRelease?.changelogPretty"
234+
:icon-right="ArrowTopRightOnSquareIcon"
197235
aria-label="View on Docs"
198236
/>
199237
</div>
200238

201-
<!-- Action buttons -->
202-
<BrandButton
203-
v-if="showExtendKeyButton"
204-
:icon="KeyIcon"
205-
:icon-right="ArrowTopRightOnSquareIcon"
206-
@click="purchaseStore.renew()"
207-
>
208-
{{ props.t('Extend License to Update') }}
209-
</BrandButton>
210-
<BrandButton
211-
v-else-if="releaseForUpdate?.sha256"
212-
:icon="ServerStackIcon"
213-
:icon-right="ArrowRightIcon"
214-
@click="fetchAndConfirmInstall(releaseForUpdate.sha256)"
215-
>
216-
{{ props.t('Continue') }}
217-
</BrandButton>
239+
<!-- Action buttons (only in store mode for update workflow) -->
240+
<template v-if="!isPropMode">
241+
<BrandButton
242+
v-if="showExtendKeyButton"
243+
:icon="KeyIcon"
244+
:icon-right="ArrowTopRightOnSquareIcon"
245+
@click="purchaseStore.renew()"
246+
>
247+
{{ props.t('Extend License to Update') }}
248+
</BrandButton>
249+
<BrandButton
250+
v-else-if="currentRelease?.sha256"
251+
:icon="ServerStackIcon"
252+
:icon-right="ArrowRightIcon"
253+
@click="fetchAndConfirmInstall(currentRelease.sha256)"
254+
>
255+
{{ props.t('Continue') }}
256+
</BrandButton>
257+
</template>
218258
</div>
219259
</template>
220260
</Modal>

0 commit comments

Comments
 (0)