your personal website on atproto - mirror blento.app
25
fork

Configure Feed

Select the types of activity you want to include in your feed.

add password modal

Florian 644364a6 f06dc60e

+56 -5
src/lib/cards/core/SecretImageCard/EditingSecretImageCard.svelte src/lib/cards/media/SecretImageCard/EditingSecretImageCard.svelte
+54 -3
src/lib/cards/core/SecretImageCard/SecretImageCard.svelte src/lib/cards/media/SecretImageCard/SecretImageCard.svelte
··· 4 4 import { getBlobURL } from '$lib/atproto'; 5 5 import { decryptBlob } from './crypto'; 6 6 import type { ContentComponentProps } from '../../types'; 7 + import Modal from '$lib/components/modal/Modal.svelte'; 8 + import { Button, Input, Subheading } from '@foxui/core'; 7 9 8 10 let { item = $bindable() }: ContentComponentProps = $props(); 9 11 ··· 11 13 12 14 let decryptedUrl = $state<string | null>(null); 13 15 let decrypting = $state(false); 16 + let showPasswordModal = $state(false); 17 + let passwordInput = $state(''); 18 + let wrongPassword = $state(false); 14 19 15 20 $effect(() => { 16 21 const secret = page.url.searchParams.get('secret'); ··· 28 33 }; 29 34 }); 30 35 31 - async function decryptImage(password: string, blob: any) { 36 + async function decryptImage(password: string, blob: any): Promise<boolean> { 32 37 try { 33 38 const url = await getBlobURL({ did, blob }); 34 39 const response = await fetch(url); ··· 36 41 const encryptedBlob = await response.blob(); 37 42 const decrypted = await decryptBlob(encryptedBlob, password); 38 43 decryptedUrl = URL.createObjectURL(decrypted); 44 + return true; 39 45 } catch { 40 46 // Wrong password or fetch error - stay pixelated 47 + return false; 41 48 } finally { 42 49 decrypting = false; 50 + } 51 + } 52 + 53 + async function handleSubmit(e: SubmitEvent) { 54 + e.preventDefault(); 55 + wrongPassword = false; 56 + decrypting = true; 57 + 58 + const blob = item.cardData.encryptedImage; 59 + if (!blob || blob.$type !== 'blob') { 60 + decrypting = false; 61 + return; 62 + } 63 + 64 + const success = await decryptImage(passwordInput, blob); 65 + if (success) { 66 + showPasswordModal = false; 67 + passwordInput = ''; 68 + } else { 69 + wrongPassword = true; 43 70 } 44 71 } 45 72 </script> ··· 62 89 {/if} 63 90 64 91 {#if !decryptedUrl} 65 - <div class="absolute inset-0 flex items-center justify-center"> 92 + <button 93 + class="absolute inset-0 flex cursor-pointer items-center justify-center" 94 + onclick={() => { 95 + if (!decrypting) { 96 + wrongPassword = false; 97 + showPasswordModal = true; 98 + } 99 + }} 100 + aria-label="Unlock secret image" 101 + > 66 102 <div class="bg-base-900/40 rounded-full p-3 backdrop-blur-sm"> 67 103 {#if decrypting} 68 104 <svg ··· 96 132 </svg> 97 133 {/if} 98 134 </div> 99 - </div> 135 + </button> 100 136 {/if} 137 + 138 + <Modal bind:open={showPasswordModal}> 139 + <form onsubmit={handleSubmit} class="flex flex-col gap-3"> 140 + <Subheading>Enter password</Subheading> 141 + <Input type="password" bind:value={passwordInput} placeholder="Password" /> 142 + {#if wrongPassword} 143 + <p class="text-sm text-red-500">Wrong password</p> 144 + {/if} 145 + <div class="flex justify-end"> 146 + <Button type="submit" disabled={decrypting}> 147 + {decrypting ? 'Decrypting...' : 'Unlock'} 148 + </Button> 149 + </div> 150 + </form> 151 + </Modal>
src/lib/cards/core/SecretImageCard/SecretImageCardSettings.svelte src/lib/cards/media/SecretImageCard/SecretImageCardSettings.svelte
src/lib/cards/core/SecretImageCard/crypto.ts src/lib/cards/media/SecretImageCard/crypto.ts
+1 -1
src/lib/cards/core/SecretImageCard/index.ts src/lib/cards/media/SecretImageCard/index.ts
··· 63 63 name: 'Secret Image', 64 64 65 65 keywords: ['secret', 'encrypted', 'password', 'hidden', 'private', 'locked'], 66 - groups: ['Core'], 66 + groups: ['Media'], 67 67 68 68 icon: `<svg 69 69 xmlns="http://www.w3.org/2000/svg"
+1 -1
src/lib/cards/index.ts
··· 56 56 import { KichRecipeCardDefinition } from './social/KichRecipeCard'; 57 57 import { KichRecipeCollectionCardDefinition } from './social/KichRecipeCollectionCard'; 58 58 import { KichCookingLogCardDefinition } from './social/KichCookingLogCard'; 59 - import { SecretImageCardDefinition } from './core/SecretImageCard'; 59 + import { SecretImageCardDefinition } from './media/SecretImageCard'; 60 60 // import { Model3DCardDefinition } from './visual/Model3DCard'; 61 61 62 62 export const AllCardDefinitions = [