mirror of
https://github.com/laurent22/joplin.git
synced 2024-12-24 10:27:10 +02:00
Desktop: Seamless-Updates - creation of update notification (#10791)
This commit is contained in:
parent
24731edf92
commit
88b3c7f526
@ -476,7 +476,7 @@ export default class ElectronAppWrapper {
|
||||
|
||||
this.createWindow();
|
||||
|
||||
if (!shim.isLinux) {
|
||||
if (!shim.isLinux()) {
|
||||
this.updaterService_ = new AutoUpdaterService();
|
||||
this.updaterService_.startPeriodicUpdateCheck();
|
||||
}
|
||||
|
@ -28,6 +28,7 @@ interface Font {
|
||||
declare global {
|
||||
interface Window {
|
||||
queryLocalFonts(): Promise<Font[]>;
|
||||
openChangelogLink: ()=> void;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -48,6 +48,7 @@ import NotePropertiesDialog from '../NotePropertiesDialog';
|
||||
import { NoteListColumns } from '@joplin/lib/services/plugins/api/noteListType';
|
||||
import validateColumns from '../NoteListHeader/utils/validateColumns';
|
||||
import TrashNotification from '../TrashNotification/TrashNotification';
|
||||
import UpdateNotification from '../UpdateNotification/UpdateNotification';
|
||||
|
||||
const PluginManager = require('@joplin/lib/services/PluginManager');
|
||||
const ipcRenderer = require('electron').ipcRenderer;
|
||||
@ -935,6 +936,7 @@ class MainScreenComponent extends React.Component<Props, State> {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
|
||||
dispatch={this.props.dispatch as any}
|
||||
/>
|
||||
<UpdateNotification themeId={this.props.themeId} />
|
||||
{messageComp}
|
||||
{layoutComp}
|
||||
{pluginDialog}
|
||||
|
@ -0,0 +1,106 @@
|
||||
import * as React from 'react';
|
||||
import { useCallback, useContext, useEffect, useMemo, useRef } from 'react';
|
||||
import { themeStyle } from '@joplin/lib/theme';
|
||||
import NotyfContext from '../NotyfContext';
|
||||
import { UpdateInfo } from 'electron-updater';
|
||||
import { ipcRenderer, IpcRendererEvent } from 'electron';
|
||||
import { AutoUpdaterEvents } from '../../services/autoUpdater/AutoUpdaterService';
|
||||
import { NotyfNotification } from 'notyf';
|
||||
import { _ } from '@joplin/lib/locale';
|
||||
import { htmlentities } from '@joplin/utils/html';
|
||||
|
||||
interface UpdateNotificationProps {
|
||||
themeId: number;
|
||||
}
|
||||
|
||||
export enum UpdateNotificationEvents {
|
||||
ApplyUpdate = 'apply-update',
|
||||
Dismiss = 'dismiss-update-notification',
|
||||
}
|
||||
|
||||
const changelogLink = 'https://github.com/laurent22/joplin/releases';
|
||||
|
||||
window.openChangelogLink = () => {
|
||||
ipcRenderer.send('open-link', changelogLink);
|
||||
};
|
||||
|
||||
const UpdateNotification = ({ themeId }: UpdateNotificationProps) => {
|
||||
const notyfContext = useContext(NotyfContext);
|
||||
const notificationRef = useRef<NotyfNotification | null>(null); // Use ref to hold the current notification
|
||||
|
||||
const theme = useMemo(() => themeStyle(themeId), [themeId]);
|
||||
|
||||
const notyf = useMemo(() => {
|
||||
const output = notyfContext;
|
||||
output.options.types = notyfContext.options.types.map(type => {
|
||||
if (type.type === 'success') {
|
||||
type.background = theme.backgroundColor5;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
|
||||
(type.icon as any).color = theme.backgroundColor5;
|
||||
}
|
||||
return type;
|
||||
});
|
||||
return output;
|
||||
}, [notyfContext, theme]);
|
||||
|
||||
const handleDismissNotification = useCallback(() => {
|
||||
notyf.dismiss(notificationRef.current);
|
||||
notificationRef.current = null;
|
||||
}, [notyf]);
|
||||
|
||||
const handleApplyUpdate = useCallback(() => {
|
||||
ipcRenderer.send('apply-update-now');
|
||||
handleDismissNotification();
|
||||
}, [handleDismissNotification]);
|
||||
|
||||
|
||||
const handleUpdateDownloaded = useCallback((_event: IpcRendererEvent, info: UpdateInfo) => {
|
||||
if (notificationRef.current) return;
|
||||
|
||||
const updateAvailableHtml = htmlentities(_('A new update (%s) is available', info.version));
|
||||
const seeChangelogHtml = htmlentities(_('See changelog'));
|
||||
const restartNowHtml = htmlentities(_('Restart now'));
|
||||
const updateLaterHtml = htmlentities(_('Update later'));
|
||||
|
||||
const messageHtml = `
|
||||
<div class="update-notification" style="color: ${theme.color2};">
|
||||
${updateAvailableHtml} <a href="#" onclick="openChangelogLink()" style="color: ${theme.color2};">${seeChangelogHtml}</a>
|
||||
<div style="display: flex; gap: 10px; margin-top: 8px;">
|
||||
<button onclick="document.dispatchEvent(new CustomEvent('${UpdateNotificationEvents.ApplyUpdate}'))" class="notyf__button notyf__button--confirm" style="color: ${theme.color2};">${restartNowHtml}</button>
|
||||
<button onclick="document.dispatchEvent(new CustomEvent('${UpdateNotificationEvents.Dismiss}'))" class="notyf__button notyf__button--dismiss" style="color: ${theme.color2};">${updateLaterHtml}</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
const notification: NotyfNotification = notyf.open({
|
||||
type: 'success',
|
||||
message: messageHtml,
|
||||
position: {
|
||||
x: 'right',
|
||||
y: 'bottom',
|
||||
},
|
||||
duration: 0,
|
||||
});
|
||||
|
||||
notificationRef.current = notification;
|
||||
}, [notyf, theme]);
|
||||
|
||||
useEffect(() => {
|
||||
ipcRenderer.on(AutoUpdaterEvents.UpdateDownloaded, handleUpdateDownloaded);
|
||||
document.addEventListener(UpdateNotificationEvents.ApplyUpdate, handleApplyUpdate);
|
||||
document.addEventListener(UpdateNotificationEvents.Dismiss, handleDismissNotification);
|
||||
|
||||
return () => {
|
||||
ipcRenderer.removeListener(AutoUpdaterEvents.UpdateDownloaded, handleUpdateDownloaded);
|
||||
document.removeEventListener(UpdateNotificationEvents.ApplyUpdate, handleApplyUpdate);
|
||||
document.removeEventListener(UpdateNotificationEvents.Dismiss, handleDismissNotification);
|
||||
};
|
||||
}, [handleApplyUpdate, handleDismissNotification, handleUpdateDownloaded]);
|
||||
|
||||
|
||||
return (
|
||||
<div style={{ display: 'none' }}/>
|
||||
);
|
||||
};
|
||||
|
||||
export default UpdateNotification;
|
27
packages/app-desktop/gui/UpdateNotification/style.scss
Normal file
27
packages/app-desktop/gui/UpdateNotification/style.scss
Normal file
@ -0,0 +1,27 @@
|
||||
.update-notification {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
|
||||
.button-container {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.notyf__button {
|
||||
padding: 5px 10px;
|
||||
border: 1px solid;
|
||||
border-radius: 4px;
|
||||
background-color: transparent;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
background-color: rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
@ -8,6 +8,7 @@
|
||||
@use 'gui/NoteList/style.scss' as note-list;
|
||||
@use 'gui/JoplinCloudLoginScreen.scss' as joplin-cloud-login-screen;
|
||||
@use 'gui/NoteListHeader/style.scss' as note-list-header;
|
||||
@use 'gui/UpdateNotification/style.scss' as update-notification;
|
||||
@use 'gui/TrashNotification/style.scss' as trash-notification;
|
||||
@use 'gui/Sidebar/style.scss' as sidebar-styles;
|
||||
@use 'gui/styles/index.scss';
|
||||
|
@ -174,6 +174,13 @@ const processStartFlags = async (argv: string[], setDefaults = true) => {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (arg === '--updated') {
|
||||
// Electron-specific flag - ignore it
|
||||
// Allows to restart with the updated application after the update option is selected by the user
|
||||
argv.splice(0, 1);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (arg.length && arg[0] === '-') {
|
||||
throw new JoplinError(_('Unknown flag: %s', arg), 'flagError');
|
||||
} else {
|
||||
|
Loading…
Reference in New Issue
Block a user