Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(ui): release notes modal #1345

Open
wants to merge 13 commits into
base: main
Choose a base branch
from
25 changes: 25 additions & 0 deletions src-tauri/src/app_config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ use tari_common::configuration::Network;
use tokio::fs;

const LOG_TARGET: &str = "tari::universe::app_config";
const UNIVERSE_VERSION: &str = env!("CARGO_PKG_VERSION");

#[derive(Debug, Clone, Serialize, Deserialize)]
#[allow(clippy::struct_excessive_bools)]
Expand Down Expand Up @@ -112,6 +113,8 @@ pub struct AppConfigFromFile {
p2pool_stats_server_port: Option<u16>,
#[serde(default = "default_false")]
pre_release: bool,
#[serde(default = "default_changelog_version")]
last_changelog_version: String,
}

impl Default for AppConfigFromFile {
Expand Down Expand Up @@ -154,6 +157,7 @@ impl Default for AppConfigFromFile {
show_experimental_settings: false,
p2pool_stats_server_port: default_p2pool_stats_server_port(),
pre_release: false,
last_changelog_version: default_changelog_version(),
}
}
}
Expand Down Expand Up @@ -267,6 +271,7 @@ pub(crate) struct AppConfig {
show_experimental_settings: bool,
p2pool_stats_server_port: Option<u16>,
pre_release: bool,
last_changelog_version: String,
}

impl AppConfig {
Expand Down Expand Up @@ -312,6 +317,7 @@ impl AppConfig {
keyring_accessed: false,
p2pool_stats_server_port: default_p2pool_stats_server_port(),
pre_release: false,
last_changelog_version: default_changelog_version(),
}
}

Expand Down Expand Up @@ -389,6 +395,7 @@ impl AppConfig {
self.show_experimental_settings = config.show_experimental_settings;
self.p2pool_stats_server_port = config.p2pool_stats_server_port;
self.pre_release = config.pre_release;
self.last_changelog_version = config.last_changelog_version;

KEYRING_ACCESSED.store(
config.keyring_accessed,
Expand Down Expand Up @@ -468,6 +475,10 @@ impl AppConfig {
&self.anon_id
}

pub fn last_changelog_version(&self) -> &str {
&self.last_changelog_version
}

pub async fn set_mode(
&mut self,
mode: String,
Expand Down Expand Up @@ -744,6 +755,15 @@ impl AppConfig {
Ok(())
}

pub async fn set_last_changelog_version(
&mut self,
version: String,
) -> Result<(), anyhow::Error> {
self.last_changelog_version = version;
self.update_config_file().await?;
Ok(())
}

// Allow needless update because in future there may be fields that are
// missing
#[allow(clippy::needless_update)]
Expand Down Expand Up @@ -791,6 +811,7 @@ impl AppConfig {
show_experimental_settings: self.show_experimental_settings,
p2pool_stats_server_port: self.p2pool_stats_server_port,
pre_release: self.pre_release,
last_changelog_version: self.last_changelog_version.clone(),
};
let config = serde_json::to_string(config)?;
debug!(target: LOG_TARGET, "Updating config file: {:?} {:?}", file, self.clone());
Expand Down Expand Up @@ -886,3 +907,7 @@ fn default_window_settings() -> Option<WindowSettings> {
fn default_p2pool_stats_server_port() -> Option<u16> {
None
}

fn default_changelog_version() -> String {
UNIVERSE_VERSION.clone().into()
}
35 changes: 35 additions & 0 deletions src-tauri/src/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -744,6 +744,18 @@ pub async fn get_transaction_history(
Ok(transactions)
}

#[tauri::command]
pub async fn get_last_changelog_version(
state: tauri::State<'_, UniverseAppState>,
) -> Result<String, String> {
let timer = Instant::now();
let last_changelog_version = state.config.read().await.last_changelog_version().into();
if timer.elapsed() > MAX_ACCEPTABLE_COMMAND_TIME {
warn!(target: LOG_TARGET, "get_last_changelog_version took too long: {:?}", timer.elapsed());
}
Ok(last_changelog_version)
}

#[tauri::command]
pub async fn import_seed_words(
seed_words: Vec<String>,
Expand Down Expand Up @@ -1729,3 +1741,26 @@ pub async fn proceed_with_update(
}
Ok(())
}

#[tauri::command]
pub async fn set_last_changelog_version(
version: String,
state: tauri::State<'_, UniverseAppState>,
) -> Result<(), String> {
let timer = Instant::now();

state
.config
.write()
.await
.set_last_changelog_version(version)
.await
.inspect_err(|e| error!("error at set_last_changelog_version{:?}", e))
.map_err(|e| e.to_string())?;

if timer.elapsed() > MAX_ACCEPTABLE_COMMAND_TIME {
warn!(target: LOG_TARGET, "set_last_changelog_version took too long: {:?}", timer.elapsed());
}

Ok(())
}
4 changes: 3 additions & 1 deletion src-tauri/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -947,7 +947,9 @@ fn main() {
commands::try_update,
commands::get_network,
commands::sign_ws_data,
])
commands::get_last_changelog_version,
commands::set_last_changelog_version
])
.build(tauri::generate_context!())
.inspect_err(
|e| error!(target: LOG_TARGET, "Error while building tauri application: {:?}", e),
Expand Down
6 changes: 6 additions & 0 deletions src/components/AdminUI/groups/DialogsGroup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,12 @@ export function DialogsGroup() {
>
External Dependencies
</Button>
<Button
onClick={() => setDialogToShow(dialogToShow === 'releaseNotes' ? undefined : 'releaseNotes')}
$isActive={dialogToShow === 'releaseNotes'}
>
Release Notes
</Button>
</ButtonGroup>
</>
);
Expand Down
2 changes: 2 additions & 0 deletions src/containers/floating/FloatingElements.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import AdminUI from '@app/components/AdminUI/AdminUI.tsx';
import { ToastStack } from '@app/components/ToastStack/ToastStack.tsx';
import { CriticalProblemDialog } from './CriticalProblemDialog/CriticalProblemDialog.tsx';
import ShellOfSecrets from '../main/ShellOfSecrets/ShellOfSecrets.tsx';
import ReleaseNotesDialog from './ReleaseNotesDialog/ReleaseNotesDialog.tsx';

const environment = import.meta.env.MODE;

Expand All @@ -27,6 +28,7 @@ export default function FloatingElements() {
<ShellOfSecrets />
<ToastStack />
<CriticalProblemDialog />
<ReleaseNotesDialog />
{environment === 'development' && <AdminUI />}
</FloatingTree>
);
Expand Down
25 changes: 25 additions & 0 deletions src/containers/floating/ReleaseNotesDialog/ReleaseNotesDialog.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { useUIStore } from '@app/store/useUIStore';
import { DialogContent, Dialog } from '@app/components/elements/dialog/Dialog';
import { useCallback } from 'react';
import { ReleaseNotes } from '../Settings/sections';
import { Wrapper } from './styles';

export default function ReleaseNotesDialog() {
const open = useUIStore((s) => s.dialogToShow === 'releaseNotes');
const setDialogToShow = useUIStore((s) => s.setDialogToShow);

const handleClose = useCallback(() => {
setDialogToShow(null);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);

return (
<Dialog open={open} onOpenChange={handleClose}>
<DialogContent>
<Wrapper>
<ReleaseNotes />
</Wrapper>
</DialogContent>
</Dialog>
);
}
6 changes: 6 additions & 0 deletions src/containers/floating/ReleaseNotesDialog/styles.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import styled from 'styled-components';

export const Wrapper = styled.div`
width: 576px;
padding: 15px;
`;
5 changes: 4 additions & 1 deletion src/containers/floating/Settings/SettingsModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,14 +51,17 @@ export default function SettingsModal() {
setIsSettingsOpen(!isSettingsOpen);
}

const sectionTitle = t(`tabs.${activeSection}`);
const title = activeSection === 'releaseNotes' ? sectionTitle : `${sectionTitle} ${t('settings')}`;

return (
<Dialog open={isSettingsOpen} onOpenChange={onOpenChange}>
<DialogContent $unPadded>
<Container>
<SettingsNavigation activeSection={activeSection} onChangeActiveSection={setActiveSection} />
<ContentContainer>
<HeaderContainer>
<Typography variant="h4">{`${t(`tabs.${activeSection}`)} ${t('settings')}`}</Typography>
<Typography variant="h4">{title}</Typography>
<IconButton onClick={() => onOpenChange()}>
<IoClose size={18} />
</IconButton>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,10 @@ import tariIcon from './tari-icon.png';
import packageInfo from '../../../../../../package.json';
import { useTranslation } from 'react-i18next';
import { invoke } from '@tauri-apps/api/core';
import { useReleaseNotes } from './useReleaseNotes';

const appVersion = packageInfo.version;
const versionString = `v${appVersion}`;
const CHANGELOG_URL = `https://cdn.jsdelivr.net/gh/tari-project/universe@main/CHANGELOG.md`;

const parseMarkdownSections = (markdown: string): ReleaseSection[] => {
const sections = markdown.split(/\n---\n/);
Expand Down Expand Up @@ -52,15 +52,15 @@ export const ReleaseNotes = () => {

const { t } = useTranslation(['common', 'settings'], { useSuspense: false });

const { fetchReleaseNotes } = useReleaseNotes();

useEffect(() => {
const loadReleaseNotes = async () => {
try {
setIsLoading(true);
const response = await fetch(CHANGELOG_URL);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const text = await response.text();

const text = await fetchReleaseNotes();

const parsedSections = parseMarkdownSections(text);
setSections(parsedSections);
} catch (err) {
Expand All @@ -71,6 +71,8 @@ export const ReleaseNotes = () => {
};

loadReleaseNotes();

// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);

useEffect(() => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ export const MarkdownWrapper = styled('div')`
overflow: hidden;
overflow-y: auto;
height: calc(70vh - 210px);
padding: 0px 20px 60px 0;
padding: 0px 0px 60px 0;

@media (min-width: 1200px) {
height: calc(80vh - 210px);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { useEffect } from 'react';
import { useUIStore } from '@app/store/useUIStore';
import packageInfo from '../../../../../../package.json';
import { invoke } from '@tauri-apps/api/core';

interface UseReleaseNotesOptions {
triggerEffect?: boolean;
}

function getLatestVersionFromChangelog(changelog: string): string | null {
const versionRegex = /v(\d+\.\d+\.\d+)/;
const match = changelog.match(versionRegex);
return match ? match[1] : null;
}

const CHANGELOG_URL = `https://cdn.jsdelivr.net/gh/tari-project/universe@main/CHANGELOG.md`;

export function useReleaseNotes(options: UseReleaseNotesOptions = {}) {
const { triggerEffect } = options;
const { setDialogToShow } = useUIStore();

const fetchReleaseNotes = async () => {
const response = await fetch(CHANGELOG_URL);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return await response.text();
};

useEffect(() => {
if (!triggerEffect) return;

const currentAppVersion = packageInfo.version;

// TODO: Need to save the fetched releaseNotesVersion to persistant storage
// and compare it with the appVersion to determine if the release notes
// dialog has already been shown to the user.
peps marked this conversation as resolved.
Show resolved Hide resolved

fetchReleaseNotes()
.then((notes) => {
const releaseNotesVersion = getLatestVersionFromChangelog(notes);

invoke('get_last_changelog_version').then((lastSavedChangelogVersion: unknown) => {
if (releaseNotesVersion != lastSavedChangelogVersion && currentAppVersion === releaseNotesVersion) {
invoke('set_last_changelog_version', { version: releaseNotesVersion });
setDialogToShow('releaseNotes');
}
});
})
.catch((error) => {
console.error('Failed to fetch release notes:', error);
});
}, [triggerEffect, setDialogToShow]);

return { fetchReleaseNotes, CHANGELOG_URL };
}
3 changes: 3 additions & 0 deletions src/containers/phase/Setup/Setup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,12 @@ import InfoNav from './components/InfoNav/InfoNav';
import { SetupWrapper } from '@app/containers/phase/Setup/Setup.styles';
import grain from '/assets/img/grain.png';
import AppVersion from './components/AppVersion';
import { useReleaseNotes } from '@app/containers/floating/Settings/sections/releaseNotes/useReleaseNotes';

export default function Setup() {
useSetUp();
useReleaseNotes({ triggerEffect: true });

return (
<SetupWrapper $bg={grain}>
<HeroText />
Expand Down
2 changes: 1 addition & 1 deletion src/store/useUIStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { Theme } from '@app/theme/types.ts';
import { animationDarkBg, animationLightBg, setAnimationProperties } from '@app/visuals.ts';
import { useAppConfigStore } from './useAppConfigStore.ts';

export const DIALOG_TYPES = ['logs', 'restart', 'autoUpdate'] as const;
export const DIALOG_TYPES = ['logs', 'restart', 'autoUpdate', 'releaseNotes'] as const;
type DialogTypeTuple = typeof DIALOG_TYPES;
export type DialogType = DialogTypeTuple[number];

Expand Down
Loading