feat(templates): replace library with microfrontend app for templates

This commit is contained in:
Amruth Pillai
2023-11-07 16:37:16 +01:00
parent fca61543c5
commit 1aa8aa6900
87 changed files with 1512 additions and 1835 deletions

View File

@ -22,7 +22,7 @@ export const BuilderToolbar = () => {
const setValue = useResumeStore((state) => state.setValue);
const undo = useTemporalResumeStore((state) => state.undo);
const redo = useTemporalResumeStore((state) => state.redo);
const transformRef = useBuilderStore((state) => state.transform.ref);
const frameRef = useBuilderStore((state) => state.frame.ref);
const id = useResumeStore((state) => state.resume.id);
const isPublic = useResumeStore((state) => state.resume.visibility === "public");
@ -41,6 +41,11 @@ export const BuilderToolbar = () => {
openInNewTab(url);
};
const onZoomIn = () => frameRef?.contentWindow?.postMessage({ type: "ZOOM_IN" }, "*");
const onZoomOut = () => frameRef?.contentWindow?.postMessage({ type: "ZOOM_OUT" }, "*");
const onResetView = () => frameRef?.contentWindow?.postMessage({ type: "RESET_VIEW" }, "*");
const onCenterView = () => frameRef?.contentWindow?.postMessage({ type: "CENTER_VIEW" }, "*");
return (
<motion.div
initial={{ opacity: 0, bottom: -64 }}
@ -65,49 +70,26 @@ export const BuilderToolbar = () => {
<Separator orientation="vertical" className="h-9" />
{/* Zoom In */}
<Tooltip content="Zoom In">
<Button
size="icon"
variant="ghost"
className="rounded-none"
onClick={() => transformRef?.zoomIn(0.2)}
>
<Button size="icon" variant="ghost" className="rounded-none" onClick={onZoomIn}>
<MagnifyingGlassPlus />
</Button>
</Tooltip>
{/* Zoom Out */}
<Tooltip content="Zoom Out">
<Button
size="icon"
variant="ghost"
className="rounded-none"
onClick={() => transformRef?.zoomOut(0.2)}
>
<Button size="icon" variant="ghost" className="rounded-none" onClick={onZoomOut}>
<MagnifyingGlassMinus />
</Button>
</Tooltip>
<Tooltip content="Reset Zoom">
<Button
size="icon"
variant="ghost"
className="rounded-none"
onClick={() => transformRef?.resetTransform()}
>
<Button size="icon" variant="ghost" className="rounded-none" onClick={onResetView}>
<ClockClockwise />
</Button>
</Tooltip>
{/* Center Artboard */}
<Tooltip content="Center Artboard">
<Button
size="icon"
variant="ghost"
className="rounded-none"
onClick={() => transformRef?.centerView()}
>
<Button size="icon" variant="ghost" className="rounded-none" onClick={onCenterView}>
<CubeFocus />
</Button>
</Tooltip>

View File

@ -33,7 +33,7 @@ export const BuilderLayout = () => {
if (isDesktop) {
return (
<div className="relative h-full w-full overflow-hidden">
<PanelGroup direction="horizontal" autoSaveId="builder-layout">
<PanelGroup direction="horizontal">
<Panel
collapsible
minSize={20}

View File

@ -1,19 +1,7 @@
import { ResumeDto } from "@reactive-resume/dto";
import { SectionKey } from "@reactive-resume/schema";
import {
Artboard,
PageBreakLine,
PageGrid,
PageNumber,
PageWrapper,
templatesList,
} from "@reactive-resume/templates";
import { pageSizeMap } from "@reactive-resume/utils";
import { AnimatePresence, motion } from "framer-motion";
import { useMemo } from "react";
import { useCallback, useEffect } from "react";
import { Helmet } from "react-helmet-async";
import { LoaderFunction, redirect } from "react-router-dom";
import { ReactZoomPanPinchRef, TransformComponent, TransformWrapper } from "react-zoom-pan-pinch";
import { queryClient } from "@/client/libs/query-client";
import { findResumeById } from "@/client/services/resume";
@ -21,28 +9,27 @@ import { useBuilderStore } from "@/client/stores/builder";
import { useResumeStore } from "@/client/stores/resume";
export const BuilderPage = () => {
const frameRef = useBuilderStore((state) => state.frame.ref);
const setFrameRef = useBuilderStore((state) => state.frame.setRef);
const resume = useResumeStore((state) => state.resume);
const title = useResumeStore((state) => state.resume.title);
const resume = useResumeStore((state) => state.resume.data);
const setTransformRef = useBuilderStore((state) => state.transform.setRef);
const { pageHeight, showBreakLine, showPageNumbers } = useMemo(() => {
const { format, options } = resume.metadata.page;
const updateResumeInFrame = useCallback(() => {
if (!frameRef || !frameRef.contentWindow) return;
const message = { type: "SET_RESUME", payload: resume.data };
(() => frameRef.contentWindow.postMessage(message, "*"))();
}, [frameRef, resume.data]);
return {
pageHeight: pageSizeMap[format].height,
showBreakLine: options.breakLine,
showPageNumbers: options.pageNumbers,
};
}, [resume.metadata.page]);
// Send resume data to iframe on initial load
useEffect(() => {
if (!frameRef) return;
frameRef.addEventListener("load", updateResumeInFrame);
return () => frameRef.removeEventListener("load", updateResumeInFrame);
}, [frameRef]);
const Template = useMemo(() => {
const Component = templatesList.find((template) => template.id === resume.metadata.template)
?.Component;
if (!Component) return null;
return Component;
}, [resume.metadata.template]);
// Send resume data to iframe on change of resume data
useEffect(updateResumeInFrame, [resume.data]);
return (
<>
@ -50,45 +37,13 @@ export const BuilderPage = () => {
<title>{title} - Reactive Resume</title>
</Helmet>
<TransformWrapper
centerOnInit
minScale={0.4}
initialScale={1}
limitToBounds={false}
velocityAnimation={{ disabled: true }}
ref={(ref: ReactZoomPanPinchRef) => setTransformRef(ref)}
>
<TransformComponent wrapperClass="!w-screen !h-screen">
<PageGrid $offset={32}>
<AnimatePresence presenceAffectsLayout>
{resume.metadata.layout.map((columns, pageIndex) => (
<motion.div
layout
key={pageIndex}
initial={{ opacity: 0, x: -100 }}
animate={{ opacity: 1, x: 0 }}
exit={{ opacity: 0, x: -100 }}
>
<Artboard resume={resume}>
<PageWrapper>
{showPageNumbers && <PageNumber>Page {pageIndex + 1}</PageNumber>}
{Template !== null && (
<Template
isFirstPage={pageIndex === 0}
columns={columns as SectionKey[][]}
/>
)}
{showBreakLine && <PageBreakLine $pageHeight={pageHeight} />}
</PageWrapper>
</Artboard>
</motion.div>
))}
</AnimatePresence>
</PageGrid>
</TransformComponent>
</TransformWrapper>
<iframe
ref={setFrameRef}
title={resume.id}
src="/artboard/builder"
className="mt-16 w-screen"
style={{ height: `calc(100vh - 64px)` }}
/>
</>
);
};

View File

@ -1,6 +1,5 @@
import { templatesList } from "@reactive-resume/templates";
import { Button, HoverCard, HoverCardContent, HoverCardTrigger } from "@reactive-resume/ui";
import { cn } from "@reactive-resume/utils";
import { cn, templatesList } from "@reactive-resume/utils";
import { useResumeStore } from "@/client/stores/resume";
@ -20,7 +19,7 @@ export const TemplateSection = () => {
</header>
<main className="grid grid-cols-2 gap-4">
{templatesList.map(({ id, name }) => (
{templatesList.map(({ id, name, image }) => (
<HoverCard key={id} openDelay={0} closeDelay={0}>
<HoverCardTrigger asChild>
<Button
@ -36,12 +35,7 @@ export const TemplateSection = () => {
</HoverCardTrigger>
<HoverCardContent className="max-w-xs overflow-hidden border-none bg-white p-0">
<img
alt={name}
loading="lazy"
src={`/templates/${id}.jpg`}
className="aspect-[1/1.4142]"
/>
<img alt={name} src={image} loading="lazy" className="aspect-[1/1.4142]" />
</HoverCardContent>
</HoverCard>
))}