rewrite frontend with DaisyUI

This commit is contained in:
Joseph Montanaro 2023-04-23 22:29:12 -07:00
parent fd60899f16
commit 049b81610d
18 changed files with 1982 additions and 561 deletions

View File

@ -1,25 +1,13 @@
<!DOCTYPE html>
<html lang="en">
<html lang="en" data-theme="dark">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite + Svelte</title>
<style>
body {
margin: 0;
display: grid;
align-items: center;
justify-items: center;
min-width: 100vw;
min-height: 100vh;
}
</style>
</head>
<body class="bg-zinc-800">
<div id="app"></div>
<body id="app" class="m-0">
<script type="module" src="/src/main.js"></script>
</body>
</html>

1326
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -17,6 +17,7 @@
"vite": "^3.0.7"
},
"dependencies": {
"@tauri-apps/api": "^1.0.2"
"@tauri-apps/api": "^1.0.2",
"daisyui": "^2.51.5"
}
}

View File

@ -1,3 +1,7 @@
[target.x86_64-unknown-linux-gnu]
linker = "clang"
rustflags = ["-C", "link-arg=--ld-path=/usr/bin/mold"]
[target.x86_64-pc-windows-msvc]
rustflags = ["-C", "link-arg=-fuse-ld=lld"]

View File

@ -35,3 +35,4 @@ listen('credentials-request', (tauriEvent) => {
</script>
<svelte:component this={currentView} on:navigate={navigate} bind:appState={appState} />
<!-- <svelte:component this="{VIEWS['./views/EnterCredentials.svelte'].default}" bind:appState="{appState}" /> -->

View File

@ -3,7 +3,7 @@
export let icon = null;
</script>
<button on:click class="text-gray-200 bg-indigo-600 hover:bg-indigo-700 px-2 py-1 rounded-md">
<button>
{#if icon}<Icon name={icon} class="w-4 text-gray-200" />{/if}
<slot></slot>
</button>

60
src/ui/ErrorAlert.svelte Normal file
View File

@ -0,0 +1,60 @@
<script>
import { onMount } from 'svelte';
import { slide } from 'svelte/transition';
export let message;
let animationClass = "";
export function shake() {
animationClass = 'shake';
window.setTimeout(() => animationClass = "", 400);
}
// onMount(() => {
// shake();
// });
</script>
<style>
/* animation from https://svelte.dev/repl/e606c27c864045e5a9700691a7417f99?version=3.58.0 */
@keyframes shake {
0% {
transform: translateX(0px);
}
20% {
transform: translateX(10px);
}
40% {
transform: translateX(-10px);
}
60% {
transform: translateX(5px);
}
80% {
transform: translateX(-5px);
}
90% {
transform: translateX(2px);
}
95% {
transform: translateX(-2px);
}
100% {
transform: translateX(0px);
}
}
.shake {
animation-name: shake;
animation-play-state: running;
animation-duration: 0.4s;
}
</style>
<div transition:slide="{{duration: 150}}" class="alert alert-error shadow-lg {animationClass}">
<div>
<svg xmlns="http://www.w3.org/2000/svg" class="stroke-current flex-shrink-0 h-6 w-6" fill="none" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z" /></svg>
<span>{message}</span>
</div>
</div>

View File

@ -0,0 +1,9 @@
<script>
let classes = "";
export {classes as class};
</script>
<svg class="w-6 h-6 {classes}" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="M10.343 3.94c.09-.542.56-.94 1.11-.94h1.093c.55 0 1.02.398 1.11.94l.149.894c.07.424.384.764.78.93.398.164.855.142 1.205-.108l.737-.527a1.125 1.125 0 011.45.12l.773.774c.39.389.44 1.002.12 1.45l-.527.737c-.25.35-.272.806-.107 1.204.165.397.505.71.93.78l.893.15c.543.09.94.56.94 1.109v1.094c0 .55-.397 1.02-.94 1.11l-.893.149c-.425.07-.765.383-.93.78-.165.398-.143.854.107 1.204l.527.738c.32.447.269 1.06-.12 1.45l-.774.773a1.125 1.125 0 01-1.449.12l-.738-.527c-.35-.25-.806-.272-1.203-.107-.397.165-.71.505-.781.929l-.149.894c-.09.542-.56.94-1.11.94h-1.094c-.55 0-1.019-.398-1.11-.94l-.148-.894c-.071-.424-.384-.764-.781-.93-.398-.164-.854-.142-1.204.108l-.738.527c-.447.32-1.06.269-1.45-.12l-.773-.774a1.125 1.125 0 01-.12-1.45l.527-.737c.25-.35.273-.806.108-1.204-.165-.397-.505-.71-.93-.78l-.894-.15c-.542-.09-.94-.56-.94-1.109v-1.094c0-.55.398-1.02.94-1.11l.894-.149c.424-.07.765-.383.93-.78.165-.398.143-.854-.107-1.204l-.527-.738a1.125 1.125 0 01.12-1.45l.773-.773a1.125 1.125 0 011.45-.12l.737.527c.35.25.807.272 1.204.107.397-.165.71-.505.78-.929l.15-.894z" />
<path stroke-linecap="round" stroke-linejoin="round" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
</svg>

8
src/ui/icons/home.svelte Normal file
View File

@ -0,0 +1,8 @@
<script>
let classes = "";
export {classes as class};
</script>
<svg class="w-6 h-6 {classes}" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="M2.25 12l8.954-8.955c.44-.439 1.152-.439 1.591 0L21.75 12M4.5 9.75v10.125c0 .621.504 1.125 1.125 1.125H9.75v-4.875c0-.621.504-1.125 1.125-1.125h2.25c.621 0 1.125.504 1.125 1.125V21h4.125c.621 0 1.125-.504 1.125-1.125V9.75M8.25 21h8.25" />
</svg>

View File

@ -33,25 +33,43 @@
deny();
}
}
var appName = null;
console.log(appName);
if (appState.currentRequest.clients.length === 1) {
let path = appState.currentRequest.clients[0].exe;
let m = path.match(/\/([^/]+?$)|\\([^\\]+?$)/);
appName = m[1] || m[2];
}
</script>
<svelte:window on:keydown={handleHotkey} />
<h2 class="text-3xl text-gray-200">An application would like to access your AWS credentials.</h2>
<div class="text-center">
<ul class="text-gray-200">
<div class="flex flex-col space-y-4 p-4 m-auto max-w-max h-screen justify-center">
<!-- <div class="p-4 rounded-box border-2 border-neutral-content"> -->
<div class="space-y-1 mb-4">
<h2 class="text-xl font-bold">{appName ? `"${appName}"` : 'An appplication'} would like to access your AWS credentials.</h2>
{#each appState.currentRequest.clients as client}
<li>PID: {client ? client.pid : 'Unknown'}</li>
<li>Path: {client ? client.exe : 'Unknown'}</li>
<p>Path: {client ? client.exe : 'Unknown'}</p>
<p>PID: {client ? client.pid : 'Unknown'}</p>
{/each}
</ul>
</div>
<button on:click={approve}>
<Icon name="check-circle" class="w-32 stroke-green-500" />
<div class="grid grid-cols-2">
<button class="btn btn-error justify-self-start" on:click={deny}>
Deny
&nbsp;
<!-- <kbd class="kbd kbd-xs bg-error border-base-100">Esc</kbd> -->
<kbd class="normal-case px-1 py-0.5 rounded border border-neutral">Esc</kbd>
</button>
<button on:click={deny}>
<Icon name="x-circle" class="w-32 stroke-red-600" />
<button class="btn btn-success justify-self-end" on:click={approve}>
Approve
&nbsp;
<kbd class="normal-case px-1 py-0.5 rounded border border-neutral">Shift</kbd>
<span class="mx-0.5">+</span>
<kbd class="normal-case px-1 py-0.5 rounded border border-neutral">Enter</kbd>
</button>
</div>
<!-- </div> -->
</div>

View File

@ -35,19 +35,24 @@
}
</script>
{#if errorMsg}
<div class="text-red-400">{errorMsg}</div>
<div class="alert alert-error shadow-lg">
<div>
<svg xmlns="http://www.w3.org/2000/svg" class="stroke-current flex-shrink-0 h-6 w-6" fill="none" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z" /></svg>
<span>{errorMsg}</span>
</div>
</div>
{/if}
<form action="#" on:submit|preventDefault="{save}">
<div class="text-gray-200">AWS Access Key ID</div>
<input class="text-gray-200 bg-zinc-800" type="text" bind:value="{AccessKeyId}" />
<form action="#" on:submit|preventDefault="{save}" class="form-control space-y-4 max-w-sm m-auto p-4 h-screen justify-center">
<h2 class="text-2xl font-bold text-center">Enter your credentials</h2>
<div class="text-gray-200">AWS Secret Access Key</div>
<input class="text-gray-200 bg-zinc-800" type="password" bind:value="{SecretAccessKey}" />
<input type="text" placeholder="AWS Access Key ID" bind:value="{AccessKeyId}" class="input input-bordered" />
<div class="text-gray-200">Passphrase</div>
<input class="text-gray-200 bg-zinc-800" type="password" bind:value="{passphrase}" />
<input type="password" placeholder="AWS Secret Access Key" bind:value="{SecretAccessKey}" class="input input-bordered" />
<input class="text-gray-200" type="submit" />
<input type="password" placeholder="Passphrase" bind:value="{passphrase}" class="input input-bordered" />
<input type="submit" class="btn btn-primary" />
</form>

View File

@ -1,7 +1,8 @@
<script>
import { onMount, createEventDispatcher } from 'svelte';
import { invoke } from '@tauri-apps/api/tauri';
import Button from '../ui/Button.svelte';
import Icon from '../ui/Icon.svelte';
export let appState;
@ -21,14 +22,35 @@
})
</script>
<h1 class="text-4xl text-gray-300">Creddy</h1>
<p>Credential status: {status}</p>
<nav class="fixed top-0 grid grid-cols-2 w-full p-2">
<div id="nav-left flex">
<button class="btn btn-squre btn-ghost align-middle">
<Icon name="home" class="w-8 h-8 stroke-2" />
</button>
</div>
<div id="nav-right" class="justify-self-end">
<button class="align-middle btn btn-square btn-ghost">
<Icon name="cog-8-tooth" class="w-8 h-8 stroke-2" />
</button>
</div>
</nav>
{#if status === 'locked'}
<Button on:click={() => dispatch('navigate', {target: 'Unlock'})}>Unlock</Button>
<div class="flex flex-col h-screen justify-center items-center space-y-4">
<img src="/static/padlock-closed.svg" alt="An unlocked padlock" class="w-32" />
<h2 class="text-2xl font-bold">Creddy is locked</h2>
<button class="btn btn-primary" on:click={() => dispatch('navigate', {target: 'Unlock'})}>Unlock</button>
</div>
{:else if status === 'unlocked'}
<div class="flex flex-col h-screen justify-center items-center space-y-4">
<img src="/static/padlock-open.svg" alt="An unlocked padlock" class="w-24" />
<h2 class="text-2xl font-bold">Creddy is ready</h2>
</div>
{:else if status === 'empty'}
<Button on:click={() => dispatch('navigate', {target: 'EnterCredentials'})}>
<button class="btn btn-primary" on:click={() => dispatch('navigate', {target: 'EnterCredentials'})}>
Enter Credentials
</Button>
</button>
{/if}

View File

@ -1,8 +1,13 @@
<script>
import { onMount, createEventDispatcher } from 'svelte';
import { fly } from 'svelte/transition';
import { expoOut } from 'svelte/easing';
import { emit } from '@tauri-apps/api/event';
import { invoke } from '@tauri-apps/api/tauri';
import ErrorAlert from '../ui/ErrorAlert.svelte';
import Icon from '../ui/Icon.svelte';
export let appState;
let error = null;
@ -21,14 +26,33 @@
error = e;
}
window.setTimeout(() => dispatch('navigate', {target: 'Home'}), 3000);
window.setTimeout(() => dispatch('navigate', {target: 'Home'}), 1000);
}
onMount(respond);
</script>
<style>
:global(body) {
overflow: hidden;
}
</style>
{#if error}
<ErrorAlert message="{error}" />
{:else}
<div in:fly="{{x: '60vw', duration: 400, easing: expoOut}}" class="flex flex-col h-screen items-center justify-center max-w-max m-auto">
<Icon name="check-circle" class="w-36 h-36" />
<div class="text-2xl font-bold">Approved!</div>
</div>
{/if}
<!--
{#if error}
<div class="text-red-400">{error}</div>
{:else}
<h1 class="text-4xl text-gray-300">Approved!</h1>
{/if}
-->

View File

@ -1,8 +1,13 @@
<script>
import { onMount, createEventDispatcher } from 'svelte';
import { fly, fade } from 'svelte/transition';
import { expoOut } from 'svelte/easing';
import { emit } from '@tauri-apps/api/event';
import { invoke } from '@tauri-apps/api/tauri';
import ErrorAlert from '../ui/ErrorAlert.svelte';
import Icon from '../ui/Icon.svelte';
export let appState;
let error = null;
@ -21,14 +26,20 @@
error = e;
}
window.setTimeout(() => dispatch('navigate', {target: 'Home'}), 3000);
window.setTimeout(() => dispatch('navigate', {target: 'Home'}), 1000);
}
onMount(respond);
let k = 0;
</script>
{#if error}
<div class="text-red-400">{error}</div>
<ErrorAlert message="{error}" />
{:else}
<h1 class="text-4xl text-gray-300">Denied!</h1>
{#key k}
<div in:fly="{{x: '60vw', duration: 400, easing: expoOut}}" class="flex flex-col items-center justify-center h-screen max-w-max m-auto" on:click="{() => k += 1}">
<Icon name="x-circle" class="w-36 h-36" />
<div class="text-2xl font-bold">Denied!</div>
</div>
{/key}
{/if}

View File

@ -1,20 +1,20 @@
<script>
import { invoke } from '@tauri-apps/api/tauri';
import { createEventDispatcher } from 'svelte';
import { getRootCause } from '../lib/errors.js';
import Button from '../ui/Button.svelte';
import ErrorAlert from '../ui/ErrorAlert.svelte';
export let appState;
const dispatch = createEventDispatcher();
let errorMsg = null;
let alert = null;
let passphrase = '';
async function unlock() {
console.log('invoking unlock command.')
try {
await invoke('unlock', {passphrase});
let r = await invoke('unlock', {passphrase});
appState.credentialStatus = 'unlocked';
if (appState.currentRequest) {
dispatch('navigate', {target: 'ShowApproved'});
@ -32,16 +32,57 @@
else {
errorMsg = e.msg;
}
if (alert) {
alert.shake();
}
}
}
</script>
<style>
@keyframes shake {
0% {
transform: translateX(0px);
}
20% {
transform: translateX(10px);
}
40% {
transform: translateX(-10px);
}
60% {
transform: translateX(5px);
}
80% {
transform: translateX(-5px);
}
90% {
transform: translateX(2px);
}
95% {
transform: translateX(-2px);
}
100% {
transform: translateX(0px);
}
}
.shake {
animation-name: shake;
animation-play-state: running;
animation-duration: 0.4s;
}
</style>
<form action="#" on:submit|preventDefault="{unlock}" class="form-control space-y-4 max-w-sm m-auto p-4 h-screen justify-center">
<h2 class="font-bold text-2xl text-center">Enter your passphrase</h2>
{#if errorMsg}
<div class="text-red-400">{errorMsg}</div>
<ErrorAlert message="{errorMsg}" bind:this="{alert}" />
{/if}
<form action="#" on:submit|preventDefault="{unlock}">
<div class="text-gray-200">Enter your passphrase:</div>
<input autofocus class="text-gray-200 bg-zinc-800" type="password" placeholder="correct horse battery staple" bind:value="{passphrase}" />
<Button on:click={unlock}>Submit</Button>
<input autofocus name="password" type="password" placeholder="correct horse battery staple" bind:value="{passphrase}" class="input input-bordered" />
<input type="submit" class="btn btn-primary" />
</form>

430
static/padlock-closed.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 34 KiB

467
static/padlock-open.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 46 KiB

View File

@ -7,5 +7,7 @@ module.exports = {
theme: {
extend: {},
},
plugins: [],
plugins: [
require('daisyui'),
],
}