-
- {title?.data.value}
-
+
+
+
+
+ {title?.data.value}
+
-
- {description?.data.value}
-
-
- {url?.data.value}
+
+ {description?.data.value}
+
+
+ {url?.data.value}
+
-
-
-
+
+
+
);
};
diff --git a/components/Blocks/ImageBlock.tsx b/components/Blocks/ImageBlock.tsx
index 1d913838..ab225d03 100644
--- a/components/Blocks/ImageBlock.tsx
+++ b/components/Blocks/ImageBlock.tsx
@@ -1,7 +1,7 @@
"use client";
import { useEntity, useReplicache } from "src/replicache";
-import { BlockProps } from "./Block";
+import { BlockProps, BlockLayout } from "./Block";
import { useUIState } from "src/useUIState";
import Image from "next/image";
import { v7 } from "uuid";
@@ -64,10 +64,7 @@ export function ImageBlock(props: BlockProps & { preview?: boolean }) {
factID: v7(),
permission_set: entity_set.set,
type: "text",
- position: generateKeyBetween(
- props.position,
- props.nextPosition,
- ),
+ position: generateKeyBetween(props.position, props.nextPosition),
newEntityID: entity,
});
}
@@ -85,15 +82,17 @@ export function ImageBlock(props: BlockProps & { preview?: boolean }) {
if (!image) {
if (!entity_set.permissions.write) return null;
return (
-
+
e.preventDefault()}
onDragOver={(e) => {
@@ -107,7 +106,7 @@ export function ImageBlock(props: BlockProps & { preview?: boolean }) {
const files = e.dataTransfer.files;
if (files && files.length > 0) {
const file = files[0];
- if (file.type.startsWith('image/')) {
+ if (file.type.startsWith("image/")) {
await handleImageUpload(file);
}
}
@@ -131,11 +130,11 @@ export function ImageBlock(props: BlockProps & { preview?: boolean }) {
}}
/>
-
+
);
}
- let className = isFullBleed
+ let imageClassName = isFullBleed
? ""
: isSelected
? "block-border-selected border-transparent! "
@@ -143,15 +142,15 @@ export function ImageBlock(props: BlockProps & { preview?: boolean }) {
let isLocalUpload = localImages.get(image.data.src);
+ let blockClassName = `
+ relative group/image border-transparent! p-0! w-fit!
+ ${isFullBleed && "-mx-3 sm:-mx-4"}
+ ${isFullBleed ? (isFirst ? "-mt-3 sm:-mt-4" : prevIsFullBleed ? "-mt-1" : "") : ""}
+ ${isFullBleed ? (isLast ? "-mb-4" : nextIsFullBleed ? "-mb-2" : "") : ""}
+ `;
+
return (
-
- {isFullBleed && isSelected ?
: null}
+
{isLocalUpload || image.data.local ? (
)}
{altText !== undefined && !props.preview ? (
) : null}
{!props.preview ? : null}
-
+
);
}
@@ -200,10 +199,18 @@ const CoverImageButton = (props: { entityID: string }) => {
let coverImage = useSubscribe(rep, (tx) =>
tx.get
("publication_cover_image"),
);
- let isFocused = useUIState((s) => s.focusedEntity?.entityID === props.entityID);
+ let isFocused = useUIState(
+ (s) => s.focusedEntity?.entityID === props.entityID,
+ );
// Only show if focused, in a publication, has write permissions, and no cover image is set
- if (!isFocused || !pubData?.publications || !entity_set.permissions.write || coverImage) return null;
+ if (
+ !isFocused ||
+ !pubData?.publications ||
+ !entity_set.permissions.write ||
+ coverImage
+ )
+ return null;
return (
diff --git a/components/Blocks/MailboxBlock.tsx b/components/Blocks/MailboxBlock.tsx
index 17236337..1bc591cd 100644
--- a/components/Blocks/MailboxBlock.tsx
+++ b/components/Blocks/MailboxBlock.tsx
@@ -1,10 +1,11 @@
import { ButtonPrimary } from "components/Buttons";
import { Popover } from "components/Popover";
-import { Menu, MenuItem, Separator } from "components/Layout";
+import { MenuItem } from "components/Menu";
+import { Separator } from "components/Layout";
import { useUIState } from "src/useUIState";
import { useState } from "react";
import { useSmoker, useToaster } from "components/Toast";
-import { BlockProps } from "./Block";
+import { BlockProps, BlockLayout } from "./Block";
import { useEntity, useReplicache } from "src/replicache";
import { useEntitySetContext } from "components/EntitySetProvider";
import { subscribeToMailboxWithEmail } from "actions/subscriptions/subscribeToMailboxWithEmail";
@@ -45,40 +46,35 @@ export const MailboxBlock = (props: BlockProps) => {
return (
-
-
- {
- let entity;
- if (draft) {
- entity = draft.data.value;
- } else {
- entity = v7();
- await rep?.mutate.createDraft({
- mailboxEntity: props.entityID,
- permission_set: entity_set.set,
- newEntity: entity,
- firstBlockEntity: v7(),
- firstBlockFactID: v7(),
- });
- }
- useUIState.getState().openPage(props.parent, entity);
- if (rep) focusPage(entity, rep, "focusFirstBlock");
- return;
- }}
- >
- {draft ? "Edit Draft" : "Write a Post"}
-
-
-
-
+
{
+ let entity;
+ if (draft) {
+ entity = draft.data.value;
+ } else {
+ entity = v7();
+ await rep?.mutate.createDraft({
+ mailboxEntity: props.entityID,
+ permission_set: entity_set.set,
+ newEntity: entity,
+ firstBlockEntity: v7(),
+ firstBlockFactID: v7(),
+ });
+ }
+ useUIState.getState().openPage(props.parent, entity);
+ if (rep) focusPage(entity, rep, "focusFirstBlock");
+ return;
+ }}
+ >
+ {draft ? "Edit Draft" : "Write a Post"}
+
+
+
{
<>
@@ -134,69 +130,59 @@ const MailboxReaderView = (props: { entityID: string; parent: string }) => {
let { rep } = useReplicache();
return (
-
-
- {!isSubscribed?.confirmed ? (
- <>
-
- >
- ) : (
-
-
- You're Subscribed!
-
-
- {archive ? (
-
{
- e.preventDefault();
- if (rep) {
- useUIState
- .getState()
- .openPage(props.parent, archive.data.value);
- focusPage(archive.data.value, rep);
- }
- }}
- >
- See All Posts
-
- ) : (
-
- Nothing has been posted yet
-
- )}
-
{
- let rect = e.currentTarget.getBoundingClientRect();
- unsubscribe(isSubscribed);
- smoke({
- text: "unsubscribed!",
- position: { x: rect.left, y: rect.top - 8 },
- });
+ {!isSubscribed?.confirmed ? (
+ <>
+
+ >
+ ) : (
+
+
+ You're Subscribed!
+
+
+ {archive ? (
+ {
+ e.preventDefault();
+ if (rep) {
+ useUIState
+ .getState()
+ .openPage(props.parent, archive.data.value);
+ focusPage(archive.data.value, rep);
+ }
}}
>
- unsubscribe
-
-
+ See All Posts
+
+ ) : (
+
Nothing has been posted yet
+ )}
+
{
+ let rect = e.currentTarget.getBoundingClientRect();
+ unsubscribe(isSubscribed);
+ smoke({
+ text: "unsubscribed!",
+ position: { x: rect.left, y: rect.top - 8 },
+ });
+ }}
+ >
+ unsubscribe
+
- )}
-
-
+
+ )}
+
);
};
diff --git a/components/Blocks/MathBlock.tsx b/components/Blocks/MathBlock.tsx
index ecd60e6d..286912e7 100644
--- a/components/Blocks/MathBlock.tsx
+++ b/components/Blocks/MathBlock.tsx
@@ -1,6 +1,6 @@
import { useEntity, useReplicache } from "src/replicache";
import "katex/dist/katex.min.css";
-import { BlockProps } from "./Block";
+import { BlockLayout, BlockProps } from "./Block";
import Katex from "katex";
import { useMemo } from "react";
import { useUIState } from "src/useUIState";
@@ -32,32 +32,42 @@ export function MathBlock(props: BlockProps) {
}
}, [content?.data.value]);
return focusedBlock ? (
-
{
- // Update the entity with the new value
- await rep?.mutate.assertFact({
- attribute: "block/math",
- entity: props.entityID,
- data: { type: "string", value: e.target.value },
- });
- }}
- />
+
+ {
+ // Update the entity with the new value
+ await rep?.mutate.assertFact({
+ attribute: "block/math",
+ entity: props.entityID,
+ data: { type: "string", value: e.target.value },
+ });
+ }}
+ />
+
) : html && content?.data.value ? (
) : (
-
- write some Tex here...
-
+
+ write some Tex here...
+
);
}
diff --git a/components/Blocks/PageLinkBlock.tsx b/components/Blocks/PageLinkBlock.tsx
index b3b8d64b..df27bd24 100644
--- a/components/Blocks/PageLinkBlock.tsx
+++ b/components/Blocks/PageLinkBlock.tsx
@@ -1,5 +1,5 @@
"use client";
-import { BlockProps, BaseBlock, ListMarker, Block } from "./Block";
+import { BlockProps, ListMarker, Block, BlockLayout } from "./Block";
import { focusBlock } from "src/utils/focusBlock";
import { focusPage } from "src/utils/focusPage";
@@ -29,30 +29,34 @@ export function PageLinkBlock(props: BlockProps & { preview?: boolean }) {
return (
- {
- if (!page) return;
- if (e.isDefaultPrevented()) return;
- if (e.shiftKey) return;
- e.preventDefault();
- e.stopPropagation();
- useUIState.getState().openPage(props.parent, page.data.value);
- if (rep) focusPage(page.data.value, rep);
- }}
>
- {type === "canvas" && page ? (
-
- ) : (
-
- )}
-
+ {
+ if (!page) return;
+ if (e.isDefaultPrevented()) return;
+ if (e.shiftKey) return;
+ e.preventDefault();
+ e.stopPropagation();
+ useUIState.getState().openPage(props.parent, page.data.value);
+ if (rep) focusPage(page.data.value, rep);
+ }}
+ >
+ {type === "canvas" && page ? (
+
+ ) : (
+
+ )}
+
+
);
}
diff --git a/components/Blocks/PollBlock/index.tsx b/components/Blocks/PollBlock/index.tsx
index 35a780ad..0438841b 100644
--- a/components/Blocks/PollBlock/index.tsx
+++ b/components/Blocks/PollBlock/index.tsx
@@ -1,5 +1,5 @@
import { useUIState } from "src/useUIState";
-import { BlockProps } from "../Block";
+import { BlockProps, BlockLayout } from "../Block";
import { ButtonPrimary, ButtonSecondary } from "components/Buttons";
import { useCallback, useEffect, useState } from "react";
import { Input } from "components/Input";
@@ -61,13 +61,10 @@ export const LeafletPollBlock = (props: BlockProps) => {
let totalVotes = votes.length;
return (
-
{pollState === "editing" ? (
{
hasVoted={!!hasVoted}
/>
)}
-
+
);
};
@@ -486,7 +483,7 @@ const PollStateToggle = (props: {
}) => {
return (
{
props.setPollState(props.pollState === "voting" ? "results" : "voting");
}}
diff --git a/components/Blocks/PublicationPollBlock.tsx b/components/Blocks/PublicationPollBlock.tsx
index a7ed7274..002095c5 100644
--- a/components/Blocks/PublicationPollBlock.tsx
+++ b/components/Blocks/PublicationPollBlock.tsx
@@ -1,5 +1,5 @@
import { useUIState } from "src/useUIState";
-import { BlockProps } from "./Block";
+import { BlockLayout, BlockProps } from "./Block";
import { useMemo } from "react";
import { AsyncValueInput } from "components/Input";
import { focusElement } from "src/utils/focusElement";
@@ -53,19 +53,16 @@ export const PublicationPollBlock = (props: BlockProps) => {
}, [publicationData, props.entityID]);
return (
-
-
+
);
};
diff --git a/components/Blocks/RSVPBlock/index.tsx b/components/Blocks/RSVPBlock/index.tsx
index 44ad0453..f3c62668 100644
--- a/components/Blocks/RSVPBlock/index.tsx
+++ b/components/Blocks/RSVPBlock/index.tsx
@@ -1,6 +1,6 @@
"use client";
import { Database } from "supabase/database.types";
-import { BlockProps } from "components/Blocks/Block";
+import { BlockProps, BlockLayout } from "components/Blocks/Block";
import { useState } from "react";
import { submitRSVP } from "actions/phone_rsvp_to_event";
import { useRSVPData } from "components/PageSWRDataProvider";
@@ -29,15 +29,13 @@ export function RSVPBlock(props: BlockProps) {
s.selectedBlocks.find((b) => b.value === props.entityID),
);
return (
-
-
+
);
}
diff --git a/components/Buttons.tsx b/components/Buttons.tsx
index 895051bd..966c2e9e 100644
--- a/components/Buttons.tsx
+++ b/components/Buttons.tsx
@@ -38,7 +38,7 @@ export const ButtonPrimary = forwardRef<
${compact ? "py-0 px-1" : "px-2 py-0.5 "}
bg-accent-1 disabled:bg-border-light
border border-accent-1 rounded-md disabled:border-border-light
- outline outline-transparent outline-offset-1 focus:outline-accent-1 hover:outline-accent-1
+ outline-2 outline-transparent outline-offset-1 focus:outline-accent-1 hover:outline-accent-1
text-base font-bold text-accent-2 disabled:text-border disabled:hover:text-border
flex gap-2 items-center justify-center shrink-0
${className}
@@ -77,7 +77,7 @@ export const ButtonSecondary = forwardRef<
${compact ? "py-0 px-1" : "px-2 py-0.5 "}
bg-bg-page disabled:bg-border-light
border border-accent-contrast rounded-md
- outline outline-transparent focus:outline-accent-contrast hover:outline-accent-contrast outline-offset-1
+ outline-2 outline-transparent focus:outline-accent-contrast hover:outline-accent-contrast outline-offset-1
text-base font-bold text-accent-contrast disabled:text-border disabled:hover:text-border
flex gap-2 items-center justify-center shrink-0
${props.className}
@@ -116,7 +116,7 @@ export const ButtonTertiary = forwardRef<
${compact ? "py-0 px-1" : "px-2 py-0.5 "}
bg-transparent hover:bg-[var(--accent-light)]
border border-transparent rounded-md hover:border-[var(--accent-light)]
- outline outline-transparent focus:outline-[var(--accent-light)] hover:outline-[var(--accent-light)] outline-offset-1
+ outline-2 outline-transparent focus:outline-[var(--accent-light)] hover:outline-[var(--accent-light)] outline-offset-1
text-base font-bold text-accent-contrast disabled:text-border
flex gap-2 items-center justify-center shrink-0
${props.className}
@@ -165,7 +165,11 @@ export const TooltipButton = (props: {
side={props.side ? props.side : undefined}
sideOffset={6}
alignOffset={12}
- className="z-10 bg-border rounded-md py-1 px-[6px] font-bold text-secondary text-sm"
+ className="z-10 rounded-md py-1 px-[6px] font-bold text-secondary text-sm"
+ style={{
+ backgroundColor:
+ "color-mix(in oklab, rgb(var(--primary)), rgb(var(--bg-page)) 85%)",
+ }}
>
{props.tooltipContent}
diff --git a/components/Layout.tsx b/components/Layout.tsx
index cd754906..c465b185 100644
--- a/components/Layout.tsx
+++ b/components/Layout.tsx
@@ -1,101 +1,7 @@
-"use client";
-import * as DropdownMenu from "@radix-ui/react-dropdown-menu";
-import { theme } from "tailwind.config";
-import { NestedCardThemeProvider } from "./ThemeManager/ThemeProvider";
-import { PopoverArrow } from "./Icons/PopoverArrow";
-import { PopoverOpenContext } from "./Popover/PopoverContext";
-import { useState } from "react";
-
export const Separator = (props: { classname?: string }) => {
return
;
};
-export const Menu = (props: {
- open?: boolean;
- trigger: React.ReactNode;
- children: React.ReactNode;
- align?: "start" | "end" | "center" | undefined;
- alignOffset?: number;
- side?: "top" | "bottom" | "right" | "left" | undefined;
- background?: string;
- border?: string;
- className?: string;
- onOpenChange?: (o: boolean) => void;
- asChild?: boolean;
-}) => {
- let [open, setOpen] = useState(props.open || false);
- return (
- {
- setOpen(o);
- props.onOpenChange?.(o);
- }}
- open={props.open}
- >
-
-
- {props.trigger}
-
-
-
-
- {props.children}
-
-
-
-
-
-
-
-
- );
-};
-
-export const MenuItem = (props: {
- children?: React.ReactNode;
- className?: string;
- onSelect: (e: Event) => void;
- id?: string;
-}) => {
- return (
- {
- props.onSelect(event);
- }}
- className={`
- menuItem
- z-10 py-1! px-2!
- flex gap-2
- ${props.className}
- `}
- >
- {props.children}
-
- );
-};
-
export const ShortcutKey = (props: { children: React.ReactNode }) => {
return (
diff --git a/components/Menu.tsx b/components/Menu.tsx
new file mode 100644
index 00000000..7a2e1fbd
--- /dev/null
+++ b/components/Menu.tsx
@@ -0,0 +1,97 @@
+"use client";
+import * as DropdownMenu from "@radix-ui/react-dropdown-menu";
+import { theme } from "tailwind.config";
+import { NestedCardThemeProvider } from "./ThemeManager/ThemeProvider";
+import { PopoverArrow } from "./Icons/PopoverArrow";
+import { PopoverOpenContext } from "./Popover/PopoverContext";
+import { useState } from "react";
+
+export const Menu = (props: {
+ open?: boolean;
+ trigger: React.ReactNode;
+ children: React.ReactNode;
+ align?: "start" | "end" | "center" | undefined;
+ alignOffset?: number;
+ side?: "top" | "bottom" | "right" | "left" | undefined;
+ background?: string;
+ border?: string;
+ className?: string;
+ onOpenChange?: (o: boolean) => void;
+ asChild?: boolean;
+}) => {
+ let [open, setOpen] = useState(props.open || false);
+
+ return (
+ {
+ setOpen(o);
+ props.onOpenChange?.(o);
+ }}
+ open={props.open}
+ >
+
+
+ {props.trigger}
+
+
+
+
+ {props.children}
+
+
+
+
+
+
+
+
+ );
+};
+
+export const MenuItem = (props: {
+ children?: React.ReactNode;
+ className?: string;
+ onSelect: (e: Event) => void;
+ id?: string;
+}) => {
+ return (
+ {
+ props.onSelect(event);
+ }}
+ className={`
+ menuItem
+ ${props.className}
+ `}
+ >
+ {props.children}
+
+ );
+};
diff --git a/components/Pages/PageOptions.tsx b/components/Pages/PageOptions.tsx
index cb91d346..df608f46 100644
--- a/components/Pages/PageOptions.tsx
+++ b/components/Pages/PageOptions.tsx
@@ -7,7 +7,7 @@ import { useEntitySetContext } from "../EntitySetProvider";
import { useReplicache } from "src/replicache";
import { Media } from "../Media";
-import { MenuItem, Menu } from "../Layout";
+import { MenuItem, Menu } from "../Menu";
import { PageThemeSetter } from "../ThemeManager/PageThemeSetter";
import { PageShareMenu } from "./PageShareMenu";
import { useUndoState } from "src/undoManager";
@@ -61,7 +61,7 @@ export const PageOptions = (props: {
{!props.first && (
diff --git a/components/ThemeManager/PageThemeSetter.tsx b/components/ThemeManager/PageThemeSetter.tsx
index dc6fe5be..f34d07b1 100644
--- a/components/ThemeManager/PageThemeSetter.tsx
+++ b/components/ThemeManager/PageThemeSetter.tsx
@@ -3,7 +3,7 @@ import { useEntitySetContext } from "components/EntitySetProvider";
import { pickers, SectionArrow, setColorAttribute } from "./ThemeSetter";
import {
- PageBackgroundPicker,
+ SubpageBackgroundPicker,
PageThemePickers,
} from "./Pickers/PageThemePickers";
import { useMemo, useState } from "react";
@@ -54,11 +54,10 @@ export const PageThemeSetter = (props: { entityID: string }) => {
className="pageThemeBG flex flex-col gap-2 h-full text-primary bg-bg-leaflet p-2 rounded-md border border-primary shadow-[0_0_0_1px_rgb(var(--bg-page))]"
style={{ backgroundColor: "rgba(var(--bg-page), 0.6)" }}
>
-
setOpenPicker(pickers)}
- setValue={set("theme/card-background")}
+ setOpenPicker={setOpenPicker}
/>
@@ -147,7 +146,7 @@ const SamplePage = (props: { entityID: string }) => {
{
<>
+ {props.helpText && (
+
+ {props.helpText}
+
+ )}
void;
- closePicker: () => void;
- setValue: (c: Color) => void;
-}) => {
- let bgImage = useEntity(props.entityID, "theme/background-image");
- let bgRepeat = useEntity(props.entityID, "theme/background-image-repeat");
- let bgColor = useColorAttribute(props.entityID, "theme/page-background");
- let open = props.openPicker == props.thisPicker;
- let { rep } = useReplicache();
-
- return (
- <>
-
-
-
{
- if (props.openPicker === props.thisPicker) {
- props.setOpenPicker("null");
- } else {
- props.setOpenPicker(props.thisPicker);
- }
- }}
- className="flex gap-2 items-center"
- >
-
- {"Background"}
-
-
-
- {bgImage ? (
-
Image
- ) : (
- <>
-
- {
- e.currentTarget.setSelectionRange(
- 1,
- e.currentTarget.value.length,
- );
- }}
- onPaste={(e) => {
- console.log(e);
- }}
- onKeyDown={(e) => {
- if (e.key === "Enter") {
- e.currentTarget.blur();
- } else return;
- }}
- onBlur={(e) => {
- props.setValue(parseColor(e.currentTarget.value));
- }}
- className={`w-[72px] bg-transparent outline-nonetext-[#595959]`}
- />
-
- >
- )}
-
-
-
- {bgImage && (
-
{
- if (bgImage) rep?.mutate.retractFact({ factID: bgImage.id });
- if (bgRepeat) rep?.mutate.retractFact({ factID: bgRepeat.id });
- }}
- >
-
-
- )}
-
-
-
- {
- props.setOpenPicker(props.thisPicker);
- }}
- />
-
-
-
-
- {open && (
-
-
- {bgImage ? (
-
- ) : (
- <>
-
-
-
-
-
-
-
-
- >
- )}
-
-
- )}
- >
- );
-};
diff --git a/components/ThemeManager/Pickers/PageThemePickers.tsx b/components/ThemeManager/Pickers/PageThemePickers.tsx
index 8aaf4a61..5066067e 100644
--- a/components/ThemeManager/Pickers/PageThemePickers.tsx
+++ b/components/ThemeManager/Pickers/PageThemePickers.tsx
@@ -61,49 +61,71 @@ export const PageThemePickers = (props: {
);
};
-export const PageBackgroundPicker = (props: {
+// Page background picker for subpages - shows Page/Containers color with optional background image
+export const SubpageBackgroundPicker = (props: {
entityID: string;
- setValue: (c: Color) => void;
openPicker: pickers;
setOpenPicker: (p: pickers) => void;
- home?: boolean;
}) => {
+ let { rep, rootEntity } = useReplicache();
+ let set = useMemo(() => {
+ return setColorAttribute(rep, props.entityID);
+ }, [rep, props.entityID]);
+
let pageValue = useColorAttribute(props.entityID, "theme/card-background");
let pageBGImage = useEntity(props.entityID, "theme/card-background-image");
- let pageBorderHidden = useEntity(props.entityID, "theme/card-border-hidden");
+ let rootPageBorderHidden = useEntity(rootEntity, "theme/card-border-hidden");
+ let entityPageBorderHidden = useEntity(
+ props.entityID,
+ "theme/card-border-hidden",
+ );
+ let pageBorderHidden =
+ (entityPageBorderHidden || rootPageBorderHidden)?.data.value || false;
+ let hasPageBackground = !pageBorderHidden;
+
+ // Label is "Page" when page background is visible, "Containers" when hidden
+ let label = hasPageBackground ? "Page" : "Containers";
+
+ // If root page border is hidden, only show color picker (no image support)
+ if (!hasPageBackground) {
+ return (
+ props.setOpenPicker("null")}
+ alpha
+ />
+ );
+ }
return (
<>
- {pageBGImage && pageBGImage !== null && (
- props.setOpenPicker("null")}
- setValue={props.setValue}
- home={props.home}
+ setValue={set("theme/card-background")}
/>
)}
-
props.setOpenPicker("null")}
alpha
/>
- {(pageBGImage === null ||
- (!pageBGImage && !pageBorderHidden?.data.value && !props.home)) && (
-
+ {!pageBGImage && (
+
void;
+ setValue: (c: Color) => void;
+}) => {
+ let { rep } = useReplicache();
+ let bgImage = useEntity(props.entityID, "theme/card-background-image");
+ let bgRepeat = useEntity(
+ props.entityID,
+ "theme/card-background-image-repeat",
+ );
+ let bgColor = useColorAttribute(props.entityID, "theme/card-background");
+ let bgAlpha =
+ useEntity(props.entityID, "theme/card-background-image-opacity")?.data
+ .value || 1;
+ let alphaColor = useMemo(() => {
+ return parseColor(`rgba(0,0,0,${bgAlpha})`);
+ }, [bgAlpha]);
+ let open = props.openPicker === "page-background-image";
+
+ return (
+ <>
+
+
{
+ props.setOpenPicker(open ? "null" : "page-background-image");
+ }}
+ className="flex gap-2 items-center grow"
+ >
+
+ Page
+ image
+
+
+
{
+ let alpha = c.getChannelValue("alpha");
+ rep?.mutate.assertFact({
+ entity: props.entityID,
+ attribute: "theme/card-background-image-opacity",
+ data: { type: "number", value: alpha },
+ });
+ }}
+ >
+
+
+ {
+ e.currentTarget.setSelectionRange(
+ 0,
+ e.currentTarget.value.length - 1,
+ );
+ }}
+ onKeyDown={(e) => {
+ if (e.key === "Enter") {
+ e.currentTarget.blur();
+ } else return;
+ }}
+ className="w-[48px] bg-transparent outline-hidden"
+ />
+
+
+
+
+
{
+ if (bgImage) rep?.mutate.retractFact({ factID: bgImage.id });
+ if (bgRepeat) rep?.mutate.retractFact({ factID: bgRepeat.id });
+ }}
+ >
+
+
+
+
+
+ props.setOpenPicker("page-background-image")}
+ card
+ />
+
+
+
+
+ {open && (
+
+
+
+
+ {
+ let alpha = c.getChannelValue("alpha");
+ rep?.mutate.assertFact({
+ entity: props.entityID,
+ attribute: "theme/card-background-image-opacity",
+ data: { type: "number", value: alpha },
+ });
+ }}
+ >
+
+
+
+
+
+
+
+
+ )}
+ >
+ );
+};
+
+// Unified background picker for leaflets - matches structure of BackgroundPicker for publications
+export const LeafletBackgroundPicker = (props: {
+ entityID: string;
+ openPicker: pickers;
+ setOpenPicker: (p: pickers) => void;
+}) => {
+ let { rep } = useReplicache();
+ let set = useMemo(() => {
+ return setColorAttribute(rep, props.entityID);
+ }, [rep, props.entityID]);
+
+ let leafletBgValue = useColorAttribute(
+ props.entityID,
+ "theme/page-background",
+ );
+ let pageValue = useColorAttribute(props.entityID, "theme/card-background");
+ let leafletBGImage = useEntity(props.entityID, "theme/background-image");
+ let leafletBGRepeat = useEntity(
+ props.entityID,
+ "theme/background-image-repeat",
+ );
+ let pageBorderHidden = useEntity(props.entityID, "theme/card-border-hidden");
+ let hasPageBackground = !pageBorderHidden?.data.value;
+
+ // When page background is hidden and no background image, only show the Background picker
+ let showPagePicker = hasPageBackground || !!leafletBGImage;
+
+ return (
+ <>
+ {/* Background color/image picker */}
+ {leafletBGImage ? (
+
+ ) : (
+
+
props.setOpenPicker("null")}
+ />
+
+
+
+ props.setOpenPicker("leaflet")}
+ />
+
+
+
+ )}
+
+ {/* Page/Containers color picker - only shown when page background is visible OR there's a bg image */}
+ {showPagePicker && (
+ props.setOpenPicker("null")}
+ alpha
+ />
+ )}
+
+
+
+ {/* Page Background toggle */}
+
+ >
+ );
+};
+
+const LeafletBackgroundImagePicker = (props: {
+ entityID: string;
+ openPicker: pickers;
+ setOpenPicker: (p: pickers) => void;
+}) => {
+ let { rep } = useReplicache();
+ let bgImage = useEntity(props.entityID, "theme/background-image");
+ let bgRepeat = useEntity(props.entityID, "theme/background-image-repeat");
+ let bgColor = useColorAttribute(props.entityID, "theme/page-background");
+ let open = props.openPicker === "leaflet";
+
+ return (
+ <>
+
+
{
+ props.setOpenPicker(open ? "null" : "leaflet");
+ }}
+ className="flex gap-2 items-center grow"
+ >
+
+ Background
+ image
+
+
+
{
+ if (bgImage) rep?.mutate.retractFact({ factID: bgImage.id });
+ if (bgRepeat) rep?.mutate.retractFact({ factID: bgRepeat.id });
+ }}
+ >
+
+
+
+
+
+ props.setOpenPicker("leaflet")}
+ />
+
+
+
+
+ {open && (
+
+ {}} />
+
+ )}
+ >
+ );
+};
+
export const PageBackgroundColorPicker = (props: {
disabled?: boolean;
label: string;
@@ -128,11 +442,13 @@ export const PageBackgroundColorPicker = (props: {
setValue: (c: Color) => void;
value: Color;
alpha?: boolean;
+ helpText?: string;
}) => {
return (
void;
}) => {
+ // When showPageBackground is false (hasPageBackground=false) and no background image, show leafletBg picker
+ let showLeafletBgPicker = !props.hasPageBackground && !props.bgImage;
+
return (
<>
{props.bgImage && props.bgImage !== null ? (
@@ -83,15 +86,23 @@ export const BackgroundPicker = (props: {
)}
)}
-
+ {!showLeafletBgPicker && (
+ // When there's a background image and page background hidden, label should say "Containers"
+
+ )}
+
@@ -98,7 +101,7 @@ export function LeafletThemeProvider(props: {
export const BaseThemeProvider = ({
local,
bgLeaflet,
- bgPage,
+ bgPage: bgPageProp,
primary,
accent1,
accent2,
@@ -106,10 +109,12 @@ export const BaseThemeProvider = ({
highlight2,
highlight3,
showPageBackground,
+ hasBackgroundImage,
children,
}: {
local?: boolean;
showPageBackground?: boolean;
+ hasBackgroundImage?: boolean;
bgLeaflet: AriaColor;
bgPage: AriaColor;
primary: AriaColor;
@@ -120,6 +125,10 @@ export const BaseThemeProvider = ({
highlight3: AriaColor;
children: React.ReactNode;
}) => {
+ // When showPageBackground is false and there's no background image,
+ // pageBg should inherit from leafletBg
+ const bgPage =
+ !showPageBackground && !hasBackgroundImage ? bgLeaflet : bgPageProp;
// set accent contrast to the accent color that has the highest contrast with the page background
let accentContrast;
diff --git a/components/ThemeManager/ThemeSetter.tsx b/components/ThemeManager/ThemeSetter.tsx
index 4afc5917..a3d63d68 100644
--- a/components/ThemeManager/ThemeSetter.tsx
+++ b/components/ThemeManager/ThemeSetter.tsx
@@ -4,10 +4,8 @@ import { theme } from "../../tailwind.config";
import { Color } from "react-aria-components";
-import { LeafletBGPicker } from "./Pickers/LeafletBGPicker";
import {
- PageBackgroundPicker,
- PageBorderHider,
+ LeafletBackgroundPicker,
PageThemePickers,
} from "./Pickers/PageThemePickers";
import { useMemo, useState } from "react";
@@ -117,23 +115,7 @@ export const ThemeSetterContent = (props: {
-
setOpenPicker("null")}
- setValue={set("theme/page-background")}
- />
-
-
-