mirror of
https://github.com/ramvignesh-b/pi-ku.git
synced 2026-05-04 08:56:52 +00:00
feat: redesign reader page and disable canvas object caching for improved rendering
This commit is contained in:
@@ -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,
|
||||||
|
|||||||
@@ -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">
|
||||||
|
|||||||
@@ -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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user