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

chore: Add PDF viewer #2796

Merged
merged 11 commits into from
Feb 9, 2024
29 changes: 29 additions & 0 deletions src/documentViewer/ipc.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { handle } from '../ipc/main';
import { SERVER_DOCUMENT_VIEWER_OPEN_URL } from '../servers/actions';
import { dispatch, select } from '../store';

export const startDocumentViewerHandler = (): void => {
handle(
'document-viewer/open-window',
async (event, url, _format, _options) => {
const validUrl = new URL(url);
const allowedProtocols = ['http:', 'https:'];
if (!allowedProtocols.includes(validUrl.protocol)) {
return;
}
const server = select(({ servers }) =>
servers.find(
(s) => new URL(s.url).origin === new URL(event.getURL()).origin
)
);
if (!server) {
return;
}

dispatch({
type: SERVER_DOCUMENT_VIEWER_OPEN_URL,
payload: { server: server.url, documentUrl: url },
});
}
);
};
5 changes: 5 additions & 0 deletions src/ipc/channels.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,11 @@ type ChannelToArgsMap = {
'outlook-calendar/has-credentials': () => Promise<boolean>;
'outlook-calendar/clear-credentials': () => void;
'outlook-calendar/set-user-token': (token: string, userId: string) => void;
'document-viewer/open-window': (
url: string,
format: string,
options: any
) => void;
};

export type Channel = keyof ChannelToArgsMap;
Expand Down
2 changes: 2 additions & 0 deletions src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
} from './app/main/data';
import { setUserDataDirectory } from './app/main/dev';
import { setupDeepLinks, processDeepLinksInArgs } from './deepLinks/main';
import { startDocumentViewerHandler } from './documentViewer/ipc';
import { setupDownloads } from './downloads/main';
import { setupMainErrorHandling } from './errors';
import i18n from './i18n/main';
Expand Down Expand Up @@ -98,6 +99,7 @@ const start = async (): Promise<void> => {
handleJitsiDesktopCapturerGetSources();
handleDesktopCapturerGetSources();
startOutlookCalendarUrlHandler();
startDocumentViewerHandler();
checkSupportedVersionServers();

await processDeepLinksInArgs();
Expand Down
3 changes: 1 addition & 2 deletions src/preload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ declare global {
}

contextBridge.exposeInMainWorld('JitsiMeetElectron', JitsiMeetElectron);
contextBridge.exposeInMainWorld('RocketChatDesktop', RocketChatDesktop);

const start = async (): Promise<void> => {
console.log('[Rocket.Chat Desktop] preload.ts start');
Expand All @@ -36,8 +37,6 @@ const start = async (): Promise<void> => {

window.removeEventListener('load', start);

contextBridge.exposeInMainWorld('RocketChatDesktop', RocketChatDesktop);

setServerUrl(serverUrl);

await whenReady();
Expand Down
6 changes: 6 additions & 0 deletions src/servers/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ export const SERVERS_LOADED = 'servers/loaded';
export const SERVER_URL_RESOLUTION_REQUESTED =
'server/url-resolution-requested';
export const SERVER_URL_RESOLVED = 'server/url-resolved';
export const SERVER_DOCUMENT_VIEWER_OPEN_URL =
'server/document-viewer/open-url';

export type ServersActionTypeToPayloadMap = {
[SERVERS_LOADED]: {
Expand All @@ -12,4 +14,8 @@ export type ServersActionTypeToPayloadMap = {
};
[SERVER_URL_RESOLUTION_REQUESTED]: Server['url'];
[SERVER_URL_RESOLVED]: ServerUrlResolutionResult;
[SERVER_DOCUMENT_VIEWER_OPEN_URL]: {
server: Server['url'];
documentUrl: string;
};
};
1 change: 1 addition & 0 deletions src/servers/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ export type Server = {
supportedVersionsSource?: 'server' | 'cloud' | 'builtin';
supportedVersions?: SupportedVersions;
expirationMessageLastTimeShown?: Date;
documentViewerOpenUrl?: string;
};

export const enum ServerUrlResolutionStatus {
Expand Down
3 changes: 3 additions & 0 deletions src/servers/preload/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { setUserPresenceDetection } from '../../userPresence/preload';
import type { Server } from '../common';
import { setBadge } from './badge';
import { writeTextToClipboard } from './clipboard';
import { openDocumentViewer } from './documentViewer';
import { setFavicon } from './favicon';
import { setGitCommitHash } from './gitCommitHash';
import type { videoCallWindowOptions } from './internalVideoChatWindow';
Expand Down Expand Up @@ -72,6 +73,7 @@ export type RocketChatDesktopAPI = {
hasOutlookCredentials: () => Promise<boolean>;
clearOutlookCredentials: () => void;
setUserToken: (token: string, userId: string) => void;
openDocumentViewer: (url: string, format: string, options: any) => void;
};

export const RocketChatDesktop: RocketChatDesktopAPI = {
Expand Down Expand Up @@ -105,4 +107,5 @@ export const RocketChatDesktop: RocketChatDesktopAPI = {
clearOutlookCredentials,
setUserToken,
setSidebarCustomTheme,
openDocumentViewer,
};
9 changes: 9 additions & 0 deletions src/servers/preload/documentViewer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { ipcRenderer } from 'electron';

export const openDocumentViewer = (
url: string,
format: string,
options: any
): void => {
ipcRenderer.invoke('document-viewer/open-window', url, format, options);
};
10 changes: 8 additions & 2 deletions src/servers/reducers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import {
WEBVIEW_SIDEBAR_CUSTOM_THEME_CHANGED,
WEBVIEW_SERVER_SUPPORTED_VERSIONS_SOURCE_UPDATED,
} from '../ui/actions';
import { SERVERS_LOADED } from './actions';
import { SERVERS_LOADED, SERVER_DOCUMENT_VIEWER_OPEN_URL } from './actions';
import type { Server } from './common';

const ensureUrlFormat = (serverUrl: string | null): string => {
Expand Down Expand Up @@ -65,7 +65,8 @@ type ServersActionTypes =
| ActionOf<typeof WEBVIEW_SERVER_IS_SUPPORTED_VERSION>
| ActionOf<typeof WEBVIEW_SERVER_VERSION_UPDATED>
| ActionOf<typeof SUPPORTED_VERSION_DIALOG_DISMISS>
| ActionOf<typeof WEBVIEW_SERVER_SUPPORTED_VERSIONS_SOURCE_UPDATED>;
| ActionOf<typeof WEBVIEW_SERVER_SUPPORTED_VERSIONS_SOURCE_UPDATED>
| ActionOf<typeof SERVER_DOCUMENT_VIEWER_OPEN_URL>;

const upsert = (state: Server[], server: Server): Server[] => {
const index = state.findIndex(({ url }) => url === server.url);
Expand Down Expand Up @@ -238,6 +239,11 @@ export const servers: Reducer<Server[], ServersActionTypes> = (
return upsert(state, { url, outlookCredentials });
}

case SERVER_DOCUMENT_VIEWER_OPEN_URL: {
const { server, documentUrl } = action.payload;
return upsert(state, { url: server, documentViewerOpenUrl: documentUrl });
}

default:
return state;
}
Expand Down
66 changes: 66 additions & 0 deletions src/ui/components/ServersView/DocumentViewer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { Box, IconButton } from '@rocket.chat/fuselage';
import { useState, useEffect } from 'react';

const DynamicWebview = ({
url,
isActive,
partition,
closeDocumentViewer,
}: {
url: string;
isActive: boolean;
partition: string;
closeDocumentViewer: () => void;
}) => {
const [webView, setWebView] = useState<JSX.Element | null>(null);

useEffect(() => {
if (isActive && url) {
setWebView(
<webview
src={url}
style={{
width: '100%',
height: '100%',
position: 'absolute',
left: 0,
top: 50,
right: 0,
bottom: 0,
}}
partition={partition}
/>
);
} else {
setWebView(null);
}
}, [url, isActive, partition]);

return (
<>
{isActive && (
<Box
bg='light'
width='100%'
height='100%'
position='absolute'
content='center'
alignItems='center'
>
<Box content='center' alignItems='center' display='flex'>
<IconButton
icon='arrow-back'
onClick={closeDocumentViewer}
mi='x8'
/>
<h2>PDF Viewer</h2>
</Box>

<Box>{webView}</Box>
</Box>
)}
</>
);
};

export default DynamicWebview;
36 changes: 35 additions & 1 deletion src/ui/components/ServersView/ServerPane.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import { useRef, useEffect } from 'react';
import { useRef, useEffect, useState } from 'react';
import { useDispatch } from 'react-redux';
import type { Dispatch } from 'redux';

import { SERVER_DOCUMENT_VIEWER_OPEN_URL } from '../../../servers/actions';
import type { RootAction } from '../../../store/actions';
import {
LOADING_ERROR_VIEW_RELOAD_SERVER_CLICKED,
WEBVIEW_ATTACHED,
WEBVIEW_READY,
} from '../../actions';
import DocumentViewer from './DocumentViewer';
import ErrorView from './ErrorView';
import UnsupportedServer from './UnsupportedServer';
import { StyledWebView, Wrapper } from './styles';
Expand All @@ -19,6 +21,7 @@ type ServerPaneProps = {
isFailed: boolean;
isSupported: boolean | undefined;
title: string | undefined;
documentViewerOpenUrl: string | undefined;
};

export const ServerPane = ({
Expand All @@ -27,9 +30,12 @@ export const ServerPane = ({
isSelected,
isFailed,
isSupported,
documentViewerOpenUrl,
}: ServerPaneProps) => {
const dispatch = useDispatch<Dispatch<RootAction>>();

const [documentViewerActive, setDocumentViewerActive] = useState(false);

const webviewRef =
useRef<ReturnType<(typeof document)['createElement']>>(null);

Expand Down Expand Up @@ -136,6 +142,19 @@ export const ServerPane = ({
}
}, [lastPath, serverUrl]);

useEffect(() => {
const webview = webviewRef.current;
if (!webview) {
return;
}

if (isSelected && documentViewerOpenUrl && documentViewerOpenUrl !== '') {
setDocumentViewerActive(true);
} else {
setDocumentViewerActive(false);
}
}, [documentViewerOpenUrl, isSelected]);

const handleReload = (): void => {
dispatch({
type: LOADING_ERROR_VIEW_RELOAD_SERVER_CLICKED,
Expand All @@ -152,8 +171,17 @@ export const ServerPane = ({
} else {
webview?.blur();
}
// setDocumentViewerActive(true);
}, [isSelected]);

const closeDocumentViewer = () => {
dispatch({
type: SERVER_DOCUMENT_VIEWER_OPEN_URL,
payload: { server: serverUrl, documentUrl: '' },
});
setDocumentViewerActive(false);
};

return (
<Wrapper isVisible={isSelected}>
<StyledWebView
Expand All @@ -162,6 +190,12 @@ export const ServerPane = ({
partition={`persist:${serverUrl}`}
{...({ allowpopups: 'allowpopups' } as any)}
/>{' '}
<DocumentViewer
url={documentViewerOpenUrl || ''}
isActive={documentViewerActive}
partition={`persist:${serverUrl}`}
closeDocumentViewer={closeDocumentViewer}
/>
<UnsupportedServer
isSupported={isSupported}
instanceDomain={new URL(serverUrl).hostname}
Expand Down
1 change: 1 addition & 0 deletions src/ui/components/ServersView/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export const ServersView = () => {
isFailed={server.failed ?? false}
isSupported={server.isSupportedVersion}
title={server.title}
documentViewerOpenUrl={server.documentViewerOpenUrl}
/>
))}
</ReparentingContainer>
Expand Down
4 changes: 2 additions & 2 deletions src/ui/main/rootWindow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -394,7 +394,7 @@ export const showRootWindow = async (): Promise<void> => {
}

return new Promise((resolve) => {
browserWindow.addListener('ready-to-show', () => {
browserWindow.once('ready-to-show', () => {
applyRootWindowState(browserWindow);

const isTrayIconEnabled = select(
Expand Down Expand Up @@ -422,7 +422,7 @@ export const exportLocalStorage = async (): Promise<Record<string, string>> => {
tempWindow.loadFile(path.join(app.getAppPath(), 'app/index.html'));

await new Promise<void>((resolve) => {
tempWindow.addListener('ready-to-show', () => {
tempWindow.once('ready-to-show', () => {
resolve();
});
});
Expand Down
1 change: 0 additions & 1 deletion src/ui/main/serverView/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -325,7 +325,6 @@ export const attachGuestWebContentsEvents = async (): Promise<void> => {
guestWebContents.session.on(
'will-download',
(event, item, _webContents) => {
console.log('will-download', item);
const fileName = item.getFilename();
const extension = path.extname(fileName)?.slice(1).toLowerCase();
const savePath = dialog.showSaveDialogSync(rootWindow, {
Expand Down
Loading