Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added apps/desktop/resources/icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
18 changes: 18 additions & 0 deletions apps/desktop/resources/icon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
11 changes: 10 additions & 1 deletion apps/desktop/src/main/index.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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. */
Expand All @@ -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,
Expand Down Expand Up @@ -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();
Expand Down
73 changes: 73 additions & 0 deletions apps/desktop/src/renderer/src/components/Loader.tsx
Original file line number Diff line number Diff line change
@@ -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 }) => (
<svg viewBox="-125 -140 460 700" fill="none" className={className} role="status" aria-label="Loading">
<style>{KEYFRAMES}</style>
{CUBES.map((paths, i) => (
<g key={i} className={`dcrypt-cube-${i}`}>
{paths.map((d, j) => (
<path
key={j}
d={d}
fill={j === 1 ? '#01A1FF' : '#FFFFFF'}
stroke="#01A1FF"
strokeWidth={10}
strokeLinejoin="round"
/>
))}
</g>
))}
</svg>
);
19 changes: 19 additions & 0 deletions apps/desktop/src/renderer/src/screens/UnlockScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 }) => {
Expand Down Expand Up @@ -58,6 +59,24 @@ export const UnlockScreen = ({ onUnlocked }: { onUnlocked: () => void }) => {

const creating = exists === false;

if (busy) {
return (
<div className="flex h-screen flex-col items-center justify-center gap-6 bg-muted/30">
<Loader className="h-48" />
<div className="flex flex-col items-center gap-1 text-center">
<p className="font-medium">
{creating ? 'Setting up your encrypted database…' : 'Unlocking your vault…'}
</p>
<p className="text-sm text-muted-foreground">
{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.'}
</p>
</div>
</div>
);
}

return (
<div className="flex h-screen items-center justify-center bg-muted/30">
<Card className="w-96">
Expand Down
Loading