forked from
leaflet.pub/leaflet
a tool for shared writing and social publishing
1"use client";
2
3import React from "react";
4import { useUIState } from "src/useUIState";
5
6import { elementId } from "src/utils/elementId";
7
8import { useEntity, useReferenceToEntity, useReplicache } from "src/replicache";
9
10import { DesktopPageFooter } from "../DesktopFooter";
11import { Canvas } from "../Canvas";
12import { Blocks } from "components/Blocks";
13import { PublicationMetadata } from "./PublicationMetadata";
14import { useCardBorderHidden } from "./useCardBorderHidden";
15import { focusPage } from ".";
16import { PageOptions } from "./PageOptions";
17import { CardThemeProvider } from "components/ThemeManager/ThemeProvider";
18import { useDrawerOpen } from "app/lish/[did]/[publication]/[rkey]/Interactions/InteractionDrawer";
19
20export function Page(props: {
21 entityID: string;
22 first?: boolean;
23 fullPageScroll: boolean;
24}) {
25 let { rep } = useReplicache();
26
27 let isFocused = useUIState((s) => {
28 let focusedElement = s.focusedEntity;
29 let focusedPageID =
30 focusedElement?.entityType === "page"
31 ? focusedElement.entityID
32 : focusedElement?.parent;
33 return focusedPageID === props.entityID;
34 });
35 let pageType = useEntity(props.entityID, "page/type")?.data.value || "doc";
36 let cardBorderHidden = useCardBorderHidden(props.entityID);
37
38 let drawerOpen = useDrawerOpen(props.entityID);
39 return (
40 <CardThemeProvider entityID={props.entityID}>
41 <PageWrapper
42 onClickAction={(e) => {
43 if (e.defaultPrevented) return;
44 if (rep) {
45 if (isFocused) return;
46 focusPage(props.entityID, rep);
47 }
48 }}
49 id={elementId.page(props.entityID).container}
50 drawerOpen={!!drawerOpen}
51 cardBorderHidden={!!cardBorderHidden}
52 isFocused={isFocused}
53 fullPageScroll={props.fullPageScroll}
54 pageType={pageType}
55 pageOptions={
56 <PageOptions
57 entityID={props.entityID}
58 first={props.first}
59 isFocused={isFocused}
60 />
61 }
62 >
63 {props.first && (
64 <>
65 <PublicationMetadata />
66 </>
67 )}
68 <PageContent entityID={props.entityID} first={props.first} />
69 </PageWrapper>
70 <DesktopPageFooter pageID={props.entityID} />
71 </CardThemeProvider>
72 );
73}
74
75export const PageWrapper = (props: {
76 id: string;
77 children: React.ReactNode;
78 pageOptions?: React.ReactNode;
79 cardBorderHidden: boolean;
80 fullPageScroll: boolean;
81 isFocused?: boolean;
82 onClickAction?: (e: React.MouseEvent) => void;
83 pageType: "canvas" | "doc";
84 drawerOpen: boolean | undefined;
85}) => {
86 return (
87 // this div wraps the contents AND the page options.
88 // it needs to be its own div because this container does NOT scroll, and therefore doesn't clip the absolutely positioned pageOptions
89 <div
90 className={`pageWrapper relative shrink-0 ${props.fullPageScroll ? "w-full" : "w-max"}`}
91 >
92 {/*
93 this div is the scrolling container that wraps only the contents div.
94
95 it needs to be a separate div so that the user can scroll from anywhere on the page if there isn't a card border
96 */}
97 <div
98 onClick={props.onClickAction}
99 id={props.id}
100 className={`
101 pageScrollWrapper
102 grow
103 shrink-0 snap-center
104 overflow-y-scroll
105 ${
106 !props.cardBorderHidden &&
107 `h-full border
108 bg-[rgba(var(--bg-page),var(--bg-page-alpha))]
109 ${props.drawerOpen ? "rounded-l-lg " : "rounded-lg"}
110 ${props.isFocused ? "shadow-md border-border" : "border-border-light"}`
111 }
112 ${props.cardBorderHidden && "sm:h-[calc(100%+48px)] h-[calc(100%+20px)] sm:-my-6 -my-3 sm:pt-6 pt-3"}
113 ${props.fullPageScroll && "max-w-full "}
114 ${props.pageType === "doc" && !props.fullPageScroll && "w-[10000px] sm:mx-0 max-w-[var(--page-width-units)]"}
115 ${
116 props.pageType === "canvas" &&
117 !props.fullPageScroll &&
118 "max-w-[var(--page-width-units)] sm:max-w-[calc(100vw-128px)] lg:max-w-fit lg:w-[calc(var(--page-width-units)*2 + 24px))]"
119 }
120
121`}
122 >
123 <div
124 className={`postPageContent
125 ${props.fullPageScroll ? "sm:max-w-[var(--page-width-units)] mx-auto" : "w-full h-full"}
126 `}
127 >
128 {props.children}
129 {props.pageType === "doc" && <div className="h-4 sm:h-6 w-full" />}
130 </div>
131 </div>
132 {props.pageOptions}
133 </div>
134 );
135};
136
137const PageContent = (props: { entityID: string; first?: boolean }) => {
138 let pageType = useEntity(props.entityID, "page/type")?.data.value || "doc";
139 if (pageType === "doc") return <DocContent entityID={props.entityID} />;
140 return <Canvas entityID={props.entityID} first={props.first} />;
141};
142
143const DocContent = (props: { entityID: string }) => {
144 let { rootEntity } = useReplicache();
145
146 let cardBorderHidden = useCardBorderHidden(props.entityID);
147 let rootBackgroundImage = useEntity(
148 rootEntity,
149 "theme/card-background-image",
150 );
151 let rootBackgroundRepeat = useEntity(
152 rootEntity,
153 "theme/card-background-image-repeat",
154 );
155 let rootBackgroundOpacity = useEntity(
156 rootEntity,
157 "theme/card-background-image-opacity",
158 );
159
160 let cardBackgroundImage = useEntity(
161 props.entityID,
162 "theme/card-background-image",
163 );
164
165 let cardBackgroundImageRepeat = useEntity(
166 props.entityID,
167 "theme/card-background-image-repeat",
168 );
169
170 let cardBackgroundImageOpacity = useEntity(
171 props.entityID,
172 "theme/card-background-image-opacity",
173 );
174
175 let backgroundImage = cardBackgroundImage || rootBackgroundImage;
176 let backgroundImageRepeat = cardBackgroundImage
177 ? cardBackgroundImageRepeat?.data?.value
178 : rootBackgroundRepeat?.data.value;
179 let backgroundImageOpacity = cardBackgroundImage
180 ? cardBackgroundImageOpacity?.data.value
181 : rootBackgroundOpacity?.data.value || 1;
182
183 return (
184 <>
185 {!cardBorderHidden ? (
186 <div
187 className={`pageBackground
188 absolute top-0 left-0 right-0 bottom-0
189 pointer-events-none
190 rounded-lg
191 `}
192 style={{
193 backgroundImage: backgroundImage
194 ? `url(${backgroundImage.data.src}), url(${backgroundImage.data.fallback})`
195 : undefined,
196 backgroundRepeat: backgroundImageRepeat ? "repeat" : "no-repeat",
197 backgroundPosition: "center",
198 backgroundSize: !backgroundImageRepeat
199 ? "cover"
200 : backgroundImageRepeat,
201 opacity: backgroundImage?.data.src ? backgroundImageOpacity : 1,
202 }}
203 />
204 ) : null}
205 <Blocks entityID={props.entityID} />
206 <div className="h-4 sm:h-6 w-full" />
207 {/* we handle page bg in this sepate div so that
208 we can apply an opacity the background image
209 without affecting the opacity of the rest of the page */}
210 </>
211 );
212};