mirror of
https://github.com/AmruthPillai/Reactive-Resume.git
synced 2025-11-16 09:41:31 +10:00
feat(templates): replace library with microfrontend app for templates
This commit is contained in:
@ -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>
|
||||
|
||||
@ -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}
|
||||
|
||||
@ -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)` }}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@ -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>
|
||||
))}
|
||||
|
||||
Reference in New Issue
Block a user