Skip to content

Commit

Permalink
feat: ensure settings are initialized + app splash screen (#309)
Browse files Browse the repository at this point in the history
* Rename EmptyView to WelcomeView

* Add splashscreen and load settings and project structure before app renders

Note that the webkit white background is a pending tauri issue: tauri-apps/tauri#1564

* Fix imports

* Add splash.tsx to the entry list of knip

* Fix PR comments and move AppStartup to own file

* Add unit tests

* Make transparent splashscreen

* Revert "Make transparent splashscreen"

This reverts commit 0b267ce.
  • Loading branch information
cguedes committed Jul 18, 2023
1 parent 934806a commit bbc32df
Show file tree
Hide file tree
Showing 15 changed files with 223 additions and 79 deletions.
2 changes: 1 addition & 1 deletion knip.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { KnipConfig } from 'knip';

const config: KnipConfig = {
entry: ['src/main.tsx'],
entry: ['src/main.tsx', 'src/splash.tsx'],
project: ['src/**/*.ts', 'src/**/*.tsx'],
ignore: ['src/api/types.ts', 'src/vite-env.d.ts', 'src/**/__mocks__/*.ts'],
ignoreBinaries: ['poetry'],
Expand Down
11 changes: 11 additions & 0 deletions splashscreen.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/splash.tsx"></script>
</body>
</html>
16 changes: 15 additions & 1 deletion src-tauri/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,16 @@ fn get_environment_variable(name: &str) -> String {
std::env::var(name).unwrap_or_else(|_| "".to_string())
}

#[tauri::command]
async fn close_splashscreen(window: tauri::Window) {
// Close splashscreen
if let Some(splashscreen) = window.get_window("splashscreen") {
splashscreen.close().unwrap();
}
// Show main window
window.get_window("main").unwrap().show().unwrap();
}

use dotenv::dotenv;
use std::env;

Expand All @@ -27,12 +37,16 @@ fn main() {
{
let dev_tools_visible = env::var("DEV_TOOLS").is_ok();
if dev_tools_visible {
_app.get_window("splashscreen").unwrap().open_devtools();
_app.get_window("main").unwrap().open_devtools();
};
}
Ok(())
})
.invoke_handler(tauri::generate_handler![get_environment_variable])
.invoke_handler(tauri::generate_handler![
close_splashscreen,
get_environment_variable
])
.menu(core::menu::AppMenu::get_menu(&context))
.on_menu_event(core::menu::AppMenu::on_menu_event)
.run(context)
Expand Down
10 changes: 9 additions & 1 deletion src-tauri/tauri.conf.json
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,15 @@
"width": 1280,
"height": 800,
"maximized": true,
"fileDropEnabled": false
"fileDropEnabled": false,
"visible": false
},
{
"width": 640,
"height": 400,
"decorations": false,
"url": "splashscreen.html",
"label": "splashscreen"
}
]
}
Expand Down
44 changes: 44 additions & 0 deletions src/AppStartup.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { invoke } from '@tauri-apps/api';
import { useState } from 'react';

import { App } from './application/App';
import { useAsyncEffect } from './hooks/useAsyncEffect';
import { ensureProjectFileStructure } from './io/filesystem';
import { noop } from './lib/noop';
import { notifyInfo } from './notifications/notifications';
import { interceptConsoleMessages } from './notifications/notifications.console';
import { initSettings } from './settings/settingsManager';

// Note: Intercepting INFO, WARN and ERROR console.*
interceptConsoleMessages(true, true, true);

export function AppStartup() {
const [initialized, setInitialized] = useState(false);

useAsyncEffect(
async (isMounted) => {
if (initialized) {
return;
}

notifyInfo('Application Startup');
await initSettings();
await ensureProjectFileStructure();
await invoke('close_splashscreen');

notifyInfo('Application Initialized');

if (isMounted()) {
setInitialized(true);
}
},
noop,
[initialized],
);

if (!initialized) {
return null;
}

return <App />;
}
45 changes: 45 additions & 0 deletions src/__tests__/AppStartup.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { clearMocks, mockIPC } from '@tauri-apps/api/mocks';

import { App } from '../application/App';
import { AppStartup } from '../AppStartup';
import { render, waitFor } from '../test/test-utils';

vi.mock('../events');
vi.mock('../io/filesystem');
vi.mock('../settings/settingsManager');
vi.mock('../application/App', () => {
const FakeApp = vi.fn(() => <div>App</div>);
return {
App: FakeApp,
};
});

describe('AppStartup', () => {
beforeEach(() => {
clearMocks();
vi.clearAllMocks();
});

it('should render null while initializing', () => {
const { container } = render(<AppStartup />);
expect(vi.mocked(App)).not.toHaveBeenCalled();
expect(container).toBeEmptyDOMElement();
});

it('should render <App /> while initializing', async () => {
mockIPC((cmd) => {
switch (cmd) {
case 'close_splashscreen':
return;
default:
break;
}
});

render(<AppStartup />);

await waitFor(() => {
expect(vi.mocked(App)).toHaveBeenCalled();
});
});
});
20 changes: 2 additions & 18 deletions src/application/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,6 @@ import { AIPanel } from '../features/ai/AIPanel';
import { ReferencesDropZone } from '../features/references/components/ReferencesDropZone';
import { ReferencesPanel } from '../features/references/sidebar/ReferencesPanel';
import { useDebouncedCallback } from '../hooks/useDebouncedCallback';
import { ensureProjectFileStructure } from '../io/filesystem';
import { notifyInfo } from '../notifications/notifications';
import { interceptConsoleMessages } from '../notifications/notifications.console';
import { initSettings } from '../settings/settingsManager';
import { SettingsModalOpener } from '../settings/SettingsModalOpener';
import { ApplicationFrame } from '../wrappers/ApplicationFrame';
import { ContextMenus } from '../wrappers/ContextMenus';
Expand All @@ -25,18 +21,8 @@ import { CommandPalette } from './CommandPalette';
import { MainPanel } from './components/MainPanel';
import { ExplorerPanel } from './sidebar/ExplorerPanel';

// Note: Intercepting INFO, WARN and ERROR console.*
interceptConsoleMessages(true, true, true);

function App() {
useEffectOnce(() => {
notifyInfo('Application Startup');
void ensureProjectFileStructure()
.then(initSettings)
.then(() => {
emitEvent('refstudio://references/load');
});
});
export function App() {
useEffectOnce(() => emitEvent('refstudio://references/load'));

const handleLayoutUpdate = useDebouncedCallback(
useCallback(() => emitEvent('refstudio://layout/update'), []),
Expand Down Expand Up @@ -161,5 +147,3 @@ function RightPanelWrapper() {
</>
);
}

export default App;
4 changes: 2 additions & 2 deletions src/application/components/MainPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@ import { ReferenceView } from '../../features/references/editor/ReferenceView';
import { TipTapView } from '../../features/textEditor/editor/TipTapView';
import { useDebouncedCallback } from '../../hooks/useDebouncedCallback';
import { assertNever } from '../../lib/assertNever';
import { EmptyView } from '../views/EmptyView';
import { PdfViewer } from '../views/PdfViewer';
import { TextView } from '../views/TextView';
import { WelcomeView } from '../views/WelcomeView';
import { OpenEditorsTabPane } from './OpenEditorsTabPane';

export function MainPanel() {
Expand Down Expand Up @@ -64,7 +64,7 @@ const MainPanelPane = memo(({ paneId }: MainPanelPaneProps) => {
<OpenEditorsTabPane paneId={paneId} />
</div>
<div className="flex w-full grow overflow-hidden">
{activeEditorAtoms ? <MainPaneViewContent activeEditorAtoms={activeEditorAtoms} /> : <EmptyView />}
{activeEditorAtoms ? <MainPaneViewContent activeEditorAtoms={activeEditorAtoms} /> : <WelcomeView />}
</div>
</div>
);
Expand Down
42 changes: 0 additions & 42 deletions src/application/views/EmptyView.tsx

This file was deleted.

51 changes: 51 additions & 0 deletions src/application/views/WelcomeView.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { cx } from '../../lib/cx';

export function WelcomeView({ hideShortcuts, className }: { hideShortcuts?: boolean; className?: string }) {
return (
<div
className={cx(
'pointer-events-none select-none',
'flex w-full flex-col items-center justify-center gap-2',
'bg-slate-300',
className,
)}
>
<div className="relative border border-slate-500 text-[160px] font-extrabold leading-none">
<WelcomeViewLetter className="bg-slate-500/50 text-slate-200/50" letter="R" />
<WelcomeViewLetter className="bg-slate-200/50 text-slate-500/50" letter="S" />
</div>

{!hideShortcuts && (
<div className="mt-10 space-y-4">
<WelcomeViewShortcut keys={['⌘', 'K']} text="Show Commands" />
<WelcomeViewShortcut keys={['⌘', 'R']} text="Show References" />
<WelcomeViewShortcut keys={['⌘', 'N']} text="New File" />
<WelcomeViewShortcut keys={['⌘', ',']} text="Show Settings" />
</div>
)}
</div>
);
}

function WelcomeViewShortcut({ text, keys }: { text: string; keys: string[] }) {
return (
<div className="flex items-center gap-2">
<div className="min-w-[135px] px-1 text-right text-slate-500">{text}</div>
<div className="flex gap-1">
{keys.map((key) => (
<kbd className="rounded-sm bg-slate-500 px-2 py-0.5 font-mono text-slate-200" key={key}>
{key}
</kbd>
))}
</div>
</div>
);
}

function WelcomeViewLetter({ letter, className = '' }: { letter: string; className?: string }) {
return (
<div className={cx('inline-flex items-center justify-center px-4 text-center', className)}>
<span>{letter}</span>
</div>
);
}
12 changes: 0 additions & 12 deletions src/application/views/__tests__/EmptyView.test.tsx

This file was deleted.

22 changes: 22 additions & 0 deletions src/application/views/__tests__/WelcomeView.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { render, screen } from '../../../test/test-utils';
import { WelcomeView } from '../WelcomeView';

describe('WelcomeView', () => {
it('should render with shortcuts', () => {
render(<WelcomeView />);
expect(screen.getByText('Show Commands')).toBeInTheDocument();
expect(screen.getByText('Show References')).toBeInTheDocument();
expect(screen.getByText('New File')).toBeInTheDocument();
expect(screen.getByText('Show Settings')).toBeInTheDocument();
});

it('should render without shortcuts', () => {
render(<WelcomeView hideShortcuts />);
expect(screen.getByText('R')).toBeInTheDocument();
expect(screen.getByText('S')).toBeInTheDocument();
expect(screen.queryByText('Show Commands')).not.toBeInTheDocument();
expect(screen.queryByText('Show References')).not.toBeInTheDocument();
expect(screen.queryByText('New File')).not.toBeInTheDocument();
expect(screen.queryByText('Show Settings')).not.toBeInTheDocument();
});
});
4 changes: 2 additions & 2 deletions src/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,15 @@ import { Provider } from 'jotai';
import React from 'react';
import ReactDOM from 'react-dom/client';

import App from './application/App';
import { AppStartup } from './AppStartup';

const queryClient = new QueryClient();

ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<Provider>
<QueryClientProvider client={queryClient}>
<App />
<AppStartup />
</QueryClientProvider>
</Provider>
</React.StrictMode>,
Expand Down
12 changes: 12 additions & 0 deletions src/splash.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import './index.css';

import React from 'react';
import ReactDOM from 'react-dom/client';

import { WelcomeView } from './application/views/WelcomeView';

ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<WelcomeView className="h-screen bg-white" hideShortcuts />
</React.StrictMode>,
);
7 changes: 7 additions & 0 deletions vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,13 @@ export default defineConfig(async () => ({
minify: !process.env.TAURI_DEBUG ? 'esbuild' : false,
// produce sourcemaps for debug builds
sourcemap: !!process.env.TAURI_DEBUG,

rollupOptions: {
input: {
index: './index.html',
splashscreen: './splashscreen.html',
},
},
},
test: {
coverage: {
Expand Down

0 comments on commit bbc32df

Please sign in to comment.