diff --git a/apps/desktop/resources/icon.png b/apps/desktop/resources/icon.png new file mode 100644 index 0000000..92a6400 Binary files /dev/null and b/apps/desktop/resources/icon.png differ diff --git a/apps/desktop/resources/icon.svg b/apps/desktop/resources/icon.svg new file mode 100644 index 0000000..c965741 --- /dev/null +++ b/apps/desktop/resources/icon.svg @@ -0,0 +1,18 @@ + diff --git a/apps/desktop/src/main/index.ts b/apps/desktop/src/main/index.ts index 56a2eb1..f293fe5 100644 --- a/apps/desktop/src/main/index.ts +++ b/apps/desktop/src/main/index.ts @@ -1,4 +1,4 @@ -import { app, BrowserWindow, powerMonitor, session, shell } from 'electron'; +import { app, BrowserWindow, nativeImage, powerMonitor, session, shell } from 'electron'; import * as path from 'path'; import { CHANNELS } from '../shared/api'; @@ -7,6 +7,10 @@ import { VaultService } from './vault-service'; const DEV_SERVER_URL = process.env.ELECTRON_RENDERER_URL; +const APP_ICON = nativeImage.createFromPath( + path.join(__dirname, '../../resources/icon.png') +); + const service = new VaultService(); /** Minutes of user idle time before the vault locks itself. */ @@ -27,6 +31,7 @@ const createWindow = (): BrowserWindow => { minHeight: 600, show: false, autoHideMenuBar: true, + icon: APP_ICON, webPreferences: { preload: path.join(__dirname, '../preload/index.js'), contextIsolation: true, @@ -90,6 +95,10 @@ app.whenReady().then(() => { callback({ cancel: !allowed }); }); + if (process.platform === 'darwin' && !APP_ICON.isEmpty()) { + app.dock?.setIcon(APP_ICON); + } + registerIpc(service); mainWindow = createWindow(); watchIdle(); diff --git a/apps/desktop/src/renderer/src/components/Loader.tsx b/apps/desktop/src/renderer/src/components/Loader.tsx new file mode 100644 index 0000000..45ca075 --- /dev/null +++ b/apps/desktop/src/renderer/src/components/Loader.tsx @@ -0,0 +1,73 @@ +const KEYFRAMES = [0, 1, 2, 3, 4, 5] + .map( + (i) => ` +@keyframes dcrypt-cube${i} { + 0%, ${(i * 8.57).toFixed(1)}% { + transform: translateY(${-220 - i * 40}px); + opacity: 0; + } + ${(35.7 + i * 8.57).toFixed(1)}%, 100% { + transform: translateY(0); + opacity: 1; + } +} +.dcrypt-cube-${i} { + animation: dcrypt-cube${i} 1.4s cubic-bezier(0.65, 0, 0.35, 1) infinite alternate; + transform-origin: center; +}` + ) + .join('\n'); + +const CUBES: [string, string, string][] = [ + [ + 'M103.923 300 L207.846 360 L207.846 480 L103.923 420 Z', + 'M311.769 300 L207.846 360 L207.846 480 L311.769 420 Z', + 'M207.846 240 L311.769 300 L207.846 360 L103.923 300 Z', + ], + [ + 'M0 360 L103.923 420 L103.923 540 L0 480 Z', + 'M207.846 360 L103.923 420 L103.923 540 L207.846 480 Z', + 'M103.923 300 L207.846 360 L103.923 420 L0 360 Z', + ], + [ + 'M103.923 -60 L207.846 0 L207.846 120 L103.923 60 Z', + 'M311.769 -60 L207.846 0 L207.846 120 L311.769 60 Z', + 'M207.846 -120 L311.769 -60 L207.846 0 L103.923 -60 Z', + ], + [ + 'M-103.923 300 L0 360 L0 480 L-103.923 420 Z', + 'M103.923 300 L0 360 L0 480 L103.923 420 Z', + 'M0 240 L103.923 300 L0 360 L-103.923 300 Z', + ], + [ + 'M0 0 L103.923 60 L103.923 180 L0 120 Z', + 'M207.846 0 L103.923 60 L103.923 180 L207.846 120 Z', + 'M103.923 -60 L207.846 0 L103.923 60 L0 0 Z', + ], + [ + 'M-103.923 180 L0 240 L0 360 L-103.923 300 Z', + 'M103.923 180 L0 240 L0 360 L103.923 300 Z', + 'M0 120 L103.923 180 L0 240 L-103.923 180 Z', + ], +]; + +/** The animated dcrypt cube-stack loader. */ +export const Loader = ({ className }: { className?: string }) => ( + +); diff --git a/apps/desktop/src/renderer/src/screens/UnlockScreen.tsx b/apps/desktop/src/renderer/src/screens/UnlockScreen.tsx index 3c3aec6..bf6ee24 100644 --- a/apps/desktop/src/renderer/src/screens/UnlockScreen.tsx +++ b/apps/desktop/src/renderer/src/screens/UnlockScreen.tsx @@ -12,6 +12,7 @@ import { Label } from '@constructive-io/ui/label'; import { ShieldCheck } from 'lucide-react'; import { FormEvent, useEffect, useState } from 'react'; +import { Loader } from '../components/Loader'; import { dcrypt } from '../lib/ipc'; export const UnlockScreen = ({ onUnlocked }: { onUnlocked: () => void }) => { @@ -58,6 +59,24 @@ export const UnlockScreen = ({ onUnlocked }: { onUnlocked: () => void }) => { const creating = exists === false; + if (busy) { + return ( +
+ {creating ? 'Setting up your encrypted databaseā¦' : 'Unlocking your vaultā¦'} +
++ {creating + ? 'Deploying the local database and sealing it under your master password. This first run takes a little longer.' + : 'Deriving your key and loading the encrypted database.'} +
+