feat: redesign reader page and disable canvas object caching for improved rendering

This commit is contained in:
ramvignesh-b
2026-04-15 19:20:18 +05:30
parent 8f6fd6a529
commit e8dac65468
4 changed files with 78 additions and 40 deletions
+1 -7
View File
@@ -174,14 +174,9 @@ test.describe("Letter Drafting (Real Backend)", () => {
timeout: 10000, timeout: 10000,
}); });
await expect( await expect(
page.getByText(new RegExp(`A sealed message for ${recipientName}`, "i")), page.getByText(new RegExp(`A sealed letter for ${recipientName}`, "i")),
).toBeVisible(); ).toBeVisible();
// Verify content is decrypted (using author's masterKey automatically)
await expect(page.getByText(/decrypting/i)).toBeHidden();
// In the Reader, we check if the recipient name is visible in the Reader header.
await expect(page.getByText(/Drawer Test Recipient/i)).toBeVisible();
// Also check if we are redirected to the Reader if we manually go to the Editor URL // Also check if we are redirected to the Reader if we manually go to the Editor URL
const readerUrl = page.url(); const readerUrl = page.url();
const quillUrl = readerUrl.replace("/read/", "/quill/"); const quillUrl = readerUrl.replace("/read/", "/quill/");
@@ -192,6 +187,5 @@ test.describe("Letter Drafting (Real Backend)", () => {
// It should redirect back to the reader // It should redirect back to the reader
await expect(page).toHaveURL(readerUrl); await expect(page).toHaveURL(readerUrl);
await expect(page.getByText(/Drawer Test Recipient/i)).toBeVisible();
}); });
}); });
@@ -110,6 +110,8 @@ const initializeCanvas = (
selection: !readOnly, selection: !readOnly,
preserveObjectStacking: true, preserveObjectStacking: true,
allowTouchScrolling: true, allowTouchScrolling: true,
enableRetinaScaling: true,
objectCaching: false,
}); });
const wrapperEl = canvas.getElement().parentElement; const wrapperEl = canvas.getElement().parentElement;
@@ -279,6 +281,7 @@ export const ComposeCanvas = forwardRef<
textbox.lockScalingX = true; textbox.lockScalingX = true;
textbox.lockScalingY = true; textbox.lockScalingY = true;
textbox.lockRotation = true; textbox.lockRotation = true;
textbox.objectCaching = false;
logicalSizeRef.current.height = measureLogicalContentHeight( logicalSizeRef.current.height = measureLogicalContentHeight(
canvas, canvas,
+3 -1
View File
@@ -5,15 +5,17 @@ interface LogModalContent {
message: string; message: string;
log: string; log: string;
onClose: () => void; onClose: () => void;
isOpen: boolean;
} }
export const LogModal = ({ export const LogModal = ({
isOpen,
message, message,
log, log,
onClose, onClose,
status, status,
}: LogModalContent) => { }: LogModalContent) => {
return status === "RESET" ? ( return status === "RESET" || !isOpen ? (
<div></div> <div></div>
) : ( ) : (
<div className="modal modal-open modal-bottom sm:modal-middle bg-base-100/20 backdrop-blur-md z-100"> <div className="modal modal-open modal-bottom sm:modal-middle bg-base-100/20 backdrop-blur-md z-100">
+71 -32
View File
@@ -1,12 +1,13 @@
import { CrossIcon } from "@phosphor-icons/react";
import { useEffect, useRef, useState } from "react"; import { useEffect, useRef, useState } from "react";
import { useLocation, useParams } from "react-router-dom"; import { useLocation, useParams } from "react-router-dom";
import { api } from "../api/apiClient"; import { api } from "../api/apiClient";
import Logo from "../components/Logo";
import { import {
type CanvasJSON, type CanvasJSON,
type CanvasTools, type CanvasTools,
ComposeCanvas, ComposeCanvas,
} from "../components/ui/ComposeCanvas"; } from "../components/ui/ComposeCanvas";
import { LogModal } from "../components/ui/LogModal";
import { endpoints } from "../config/endpoints"; import { endpoints } from "../config/endpoints";
import { useKeyStore } from "../store/useKeyStore"; import { useKeyStore } from "../store/useKeyStore";
import { CryptoUtils } from "../utils/crypto"; import { CryptoUtils } from "../utils/crypto";
@@ -114,7 +115,6 @@ export default function Reader() {
log: err instanceof Error ? err.message : "Unknown error", log: err instanceof Error ? err.message : "Unknown error",
}); });
} }
setDecryptedCanvasData(canvasData); setDecryptedCanvasData(canvasData);
} catch (err) { } catch (err) {
const message = err instanceof Error ? err.message : "Unknown error"; const message = err instanceof Error ? err.message : "Unknown error";
@@ -135,9 +135,16 @@ export default function Reader() {
if (isDecrypting) { if (isDecrypting) {
return ( return (
<div className="min-h-screen flex items-center justify-center bg-base-200"> <div className="min-h-screen flex items-center justify-center bg-base-100 font-serif">
<div className="text-center space-y-4"> <div className="fixed inset-0 bg-[radial-gradient(circle_at_center,transparent_0%,rgba(0,0,0,0.4)_100%)] pointer-events-none z-0" />
<p className="text-base-content/60">Decrypting...</p> <div className="text-center space-y-6 z-10">
<Logo />
<div className="flex flex-col items-center gap-2">
<span className="loading loading-ring loading-md text-primary/40"></span>
<p className="text-[10px] uppercase tracking-[0.4em] text-base-content/20 animate-pulse">
Breaking the seal...
</p>
</div>
</div> </div>
</div> </div>
); );
@@ -145,15 +152,23 @@ export default function Reader() {
if (error) { if (error) {
return ( return (
<div className="min-h-screen flex items-center justify-center bg-base-200 px-6"> <div className="min-h-screen flex items-center justify-center bg-base-100 px-6 font-serif">
<div className="max-w-md w-full bg-base-100 shadow-xl rounded-2xl p-8 text-center space-y-4"> <div className="fixed inset-0 bg-[radial-gradient(circle_at_center,transparent_0%,rgba(0,0,0,0.4)_100%)] pointer-events-none z-0" />
<p className="text-error font-medium">{error}</p> <div className="max-w-md w-full glass-card p-12 text-center space-y-6 z-10 animate-in fade-in zoom-in-95 duration-700">
<div className="space-y-2">
<h2 className="text-error font-display text-xl">
Something went wrong
</h2>
<p className="text-base-content/60 text-sm leading-relaxed">
{error}
</p>
</div>
<button <button
type="button" type="button"
className="btn btn-primary" className="btn btn-ghost btn-sm text-xs uppercase tracking-widest hover:text-primary transition-colors"
onClick={() => (window.location.href = "/")} onClick={() => (window.location.href = "/")}
> >
Back to Home Return Home
</button> </button>
</div> </div>
</div> </div>
@@ -161,40 +176,64 @@ export default function Reader() {
} }
return ( return (
<section className="min-h-screen w-full bg-base-200 px-4 py-8"> <section className="min-h-screen w-full bg-base-100 px-4 py-8 md:py-16 font-serif relative overflow-hidden">
{warning && ( {/* Background Ambience */}
<div className="alert alert-warning"> <div className="fixed inset-0 bg-[radial-gradient(circle_at_center,transparent_0%,rgba(0,0,0,0.5)_100%)] pointer-events-none z-0" />
<div className="flex-1">
<p>{warning.message}</p> <LogModal
<p className="text-xs opacity-70">{warning.log}</p> isOpen={!!warning}
</div> onClose={() => setWarning(null)}
</div> message={warning?.message}
)} log={warning?.log}
<div className="max-w-4xl mx-auto space-y-6"> status="WARN"
<div className="flex items-center justify-between"> />
<div>
<div className="max-w-4xl mx-auto space-y-8 relative z-10">
{/* Floating Header */}
<div className="glass-card px-6 py-4 flex items-center justify-between animate-in fade-in slide-in-from-top-6 duration-1000">
<div className="flex items-center gap-4">
<Logo />
<div className="h-4 w-px bg-base-content/10 hidden sm:block" />
{metadata?.recipient && ( {metadata?.recipient && (
<p className="text-base-content/60"> <p className="text-[11px] uppercase tracking-[0.2em] text-base-content/40 hidden sm:block">
A sealed message for{" "} A sealed letter for{" "}
<span className="font-semibold"> <span className="text-base-content/60 font-semibold">
{metadata.recipient || "Anonymous"} {metadata.recipient}
</span> </span>
</p> </p>
)} )}
</div> </div>
<button <button
type="button" type="button"
className="btn btn-ghost btn-sm" className="btn btn-ghost btn-circle btn-sm hover:rotate-90 transition-transform duration-500"
onClick={() => (window.location.href = "/")} onClick={() => (window.location.href = "/")}
> aria-label="Close"
<CrossIcon size={18} /> ></button>
</button>
</div> </div>
<div className="bg-paper rounded-sm shadow-primary-content overflow-hidden"> {/* The Letter */}
<ComposeCanvas ref={canvasRef} readOnly /> <div className="relative group perspective-1000">
<div className="absolute inset-0 bg-primary/5 blur-3xl rounded-full scale-75 opacity-0 group-hover:opacity-100 transition-opacity duration-1000 pointer-events-none" />
<div className="bg-paper shadow-warm rounded-sm overflow-hidden animate-in fade-in zoom-in-95 slide-in-from-bottom-8 duration-1000 delay-300 fill-mode-backwards rotate-[-0.3deg] hover:rotate-0 transition-transform">
<div className="p-1 md:p-2 bg-base-content/5 opacity-10 pointer-events-none absolute inset-0 z-10" />
<ComposeCanvas ref={canvasRef} readOnly />
</div>
{metadata?.recipient && (
<p className="text-center sm:hidden text-[10px] uppercase tracking-[0.3em] text-base-content/20 mt-8">
For {metadata.recipient}
</p>
)}
</div> </div>
</div> </div>
{/* Atmospheric Footer */}
<footer className="mt-16 text-center z-10 opacity-10 pointer-events-none">
<p className="text-[9px] uppercase tracking-[0.5em]">
Read. Remember. Release.
</p>
</footer>
</section> </section>
); );
} }