Skip to content

Commit d775e93

Browse files
JohnMcLearclaude
andcommitted
feat(renderer): About dialog + wire Help > About Etherpad Desktop
Help > About fired the IPC but the renderer had no handler and no AboutDialog component existed. The menu entry was dead. This adds the dialog (name, version, license, links) and the handler in App.tsx. Version is injected at build time via electron-vite's \`define\` from package.json. AboutDialog falls back to '0.1.0' in environments where the define isn't applied (e.g. unit tests). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 0cb5622 commit d775e93

7 files changed

Lines changed: 131 additions & 0 deletions

File tree

electron.vite.config.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
import { defineConfig } from 'electron-vite';
22
import react from '@vitejs/plugin-react';
33
import { resolve } from 'node:path';
4+
import { readFileSync } from 'node:fs';
5+
6+
const pkg = JSON.parse(readFileSync('./package.json', 'utf8')) as { version: string };
47

58
export default defineConfig({
69
main: {
@@ -39,5 +42,8 @@ export default defineConfig({
3942
resolve: {
4043
alias: { '@shared': resolve('src/shared') },
4144
},
45+
define: {
46+
__APP_VERSION__: JSON.stringify(pkg.version),
47+
},
4248
},
4349
});

src/renderer/App.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { OpenPadDialog } from './dialogs/OpenPadDialog.js';
88
import { SettingsDialog } from './dialogs/SettingsDialog.js';
99
import { RemoveWorkspaceDialog } from './dialogs/RemoveWorkspaceDialog.js';
1010
import { HttpAuthDialog } from './dialogs/HttpAuthDialog.js';
11+
import { AboutDialog } from './dialogs/AboutDialog.js';
1112
import { WorkspaceRail } from './rail/WorkspaceRail.js';
1213
import { PadSidebar } from './sidebar/PadSidebar.js';
1314
import { TabStrip } from './tabs/TabStrip.js';
@@ -99,6 +100,7 @@ export function App(): JSX.Element {
99100
const k = (p as { kind: string }).kind;
100101
if (k === 'menu.newTab' || k === 'menu.openPad') dialogActions.openDialog('openPad');
101102
if (k === 'menu.settings') dialogActions.openDialog('settings');
103+
if (k === 'menu.about') dialogActions.openDialog('about');
102104
if (k === 'menu.reload') {
103105
const { activeTabId: activeId } = useShellStore.getState();
104106
if (activeId) void ipc.tab.reload({ tabId: activeId });
@@ -138,6 +140,7 @@ export function App(): JSX.Element {
138140
{openDialog === 'settings' && <SettingsDialog />}
139141
{openDialog === 'removeWorkspace' && <RemoveWorkspaceDialog />}
140142
{openDialog === 'httpAuth' && <HttpAuthDialog />}
143+
{openDialog === 'about' && <AboutDialog />}
141144
</ErrorBoundary>
142145
);
143146
}
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
import React from 'react';
2+
import { dialogActions } from '../state/store.js';
3+
4+
export function AboutDialog(): JSX.Element {
5+
// App version comes from package.json at build time. Vite injects __APP_VERSION__
6+
// via define; if not configured, fall back to a constant.
7+
const version =
8+
typeof __APP_VERSION__ !== 'undefined' ? __APP_VERSION__ : '0.1.0';
9+
10+
return (
11+
<div role="dialog" aria-modal="true" aria-labelledby="about-title" style={overlayStyle}>
12+
<div style={panelStyle}>
13+
<h2 id="about-title" style={{ margin: 0, fontSize: 22, fontWeight: 700 }}>
14+
Etherpad Desktop
15+
</h2>
16+
<p style={{ margin: 0, color: 'var(--text-muted)' }}>Version {version}</p>
17+
<p style={{ margin: '8px 0 0' }}>
18+
Native desktop client for{' '}
19+
<a
20+
href="https://etherpad.org/"
21+
target="_blank"
22+
rel="noopener noreferrer"
23+
style={{ color: 'var(--accent)', textDecoration: 'none' }}
24+
>
25+
Etherpad
26+
</a>
27+
.
28+
</p>
29+
<p style={{ margin: '8px 0 0', color: 'var(--text-muted)', fontSize: 13 }}>
30+
Released under the Apache-2.0 license. Source on{' '}
31+
<a
32+
href="https://github.com/ether/etherpad-desktop"
33+
target="_blank"
34+
rel="noopener noreferrer"
35+
style={{ color: 'var(--accent)', textDecoration: 'none' }}
36+
>
37+
GitHub
38+
</a>
39+
.
40+
</p>
41+
<p style={{ margin: '8px 0 0', color: 'var(--text-muted)', fontSize: 13 }}>
42+
Made by the Etherpad Foundation and contributors.
43+
</p>
44+
<div style={{ display: 'flex', justifyContent: 'flex-end', marginTop: 16 }}>
45+
<button className="btn-primary" onClick={() => dialogActions.closeDialog()}>
46+
Close
47+
</button>
48+
</div>
49+
</div>
50+
</div>
51+
);
52+
}
53+
54+
const overlayStyle: React.CSSProperties = {
55+
position: 'fixed',
56+
inset: 0,
57+
background: 'var(--modal-overlay-bg)',
58+
display: 'grid',
59+
placeItems: 'center',
60+
zIndex: 100,
61+
};
62+
const panelStyle: React.CSSProperties = {
63+
background: 'var(--panel-bg)',
64+
color: 'var(--panel-fg)',
65+
padding: 24,
66+
borderRadius: 12,
67+
width: 380,
68+
display: 'flex',
69+
flexDirection: 'column',
70+
gap: 4,
71+
boxShadow: 'var(--panel-shadow)',
72+
border: '1px solid var(--panel-border)',
73+
};

src/renderer/global.d.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@ declare global {
44
interface Window {
55
etherpadDesktop: EtherpadDesktopApi;
66
}
7+
8+
// Build-time constant injected by electron-vite's define option
9+
const __APP_VERSION__: string;
710
}
811

912
export {};

src/renderer/state/store.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ export type DialogKind =
1010
| 'settings'
1111
| 'removeWorkspace'
1212
| 'httpAuth'
13+
| 'about'
1314
| null;
1415

1516
export type ShellState = {

tests/e2e/menu-click.spec.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,3 +82,29 @@ test('File > Settings opens SettingsDialog (the user-reported bug)', async () =>
8282
await h.close();
8383
}
8484
});
85+
86+
async function clickHelpMenuItem(h: AppHandle, label: string): Promise<boolean> {
87+
return await h.app.evaluate(({ Menu }, { lbl }) => {
88+
const menu = Menu.getApplicationMenu();
89+
if (!menu) return false;
90+
const help = menu.items.find((m) => m.label === 'Help');
91+
if (!help || !help.submenu) return false;
92+
const item = help.submenu.items.find((m) => m.label === lbl);
93+
if (!item) return false;
94+
item.click();
95+
return true;
96+
}, { lbl: label });
97+
}
98+
99+
test('Help > About Etherpad Desktop opens AboutDialog (the user-reported bug)', async () => {
100+
const h = await launchApp();
101+
try {
102+
await setupWorkspace(h, 'AboutTest');
103+
const ok = await clickHelpMenuItem(h, 'About Etherpad Desktop');
104+
expect(ok).toBe(true);
105+
await expect(h.shell.getByRole('heading', { name: /^etherpad desktop$/i })).toBeVisible();
106+
await expect(h.shell.getByText(/version 0\.1\.0/i)).toBeVisible();
107+
} finally {
108+
await h.close();
109+
}
110+
});
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { describe, it, expect, beforeEach } from 'vitest';
2+
import { render, screen } from '@testing-library/react';
3+
import userEvent from '@testing-library/user-event';
4+
import { AboutDialog } from '../../../src/renderer/dialogs/AboutDialog';
5+
import { useShellStore, dialogActions } from '../../../src/renderer/state/store';
6+
7+
beforeEach(() => {
8+
useShellStore.setState(useShellStore.getInitialState());
9+
dialogActions.openDialog('about');
10+
});
11+
12+
describe('AboutDialog', () => {
13+
it('shows the app name + Close dismisses', async () => {
14+
render(<AboutDialog />);
15+
expect(screen.getByRole('heading', { name: /etherpad desktop/i })).toBeInTheDocument();
16+
await userEvent.click(screen.getByRole('button', { name: /close/i }));
17+
expect(useShellStore.getState().openDialog).toBeNull();
18+
});
19+
});

0 commit comments

Comments
 (0)