"use client";
import { useMemo, useState } from "react";
import { parseColor } from "react-aria-components";
import { useEntity } from "src/replicache";
import { useColorAttribute, colorToString } from "./useColorAttribute";
import { BaseThemeProvider, CardBorderHiddenContext } from "./ThemeProvider";
import { PubLeafletPublication, PubLeafletThemeColor } from "lexicons/api";
import type * as SiteStandardThemeBasic from "lexicons/api/types/site/standard/theme/basic";
import { resolvePublicationTheme } from "lexicons/src/normalize";
import {
usePublicationData,
useNormalizedPublicationRecord,
} from "app/(app)/lish/[did]/[publication]/dashboard/PublicationSWRProvider";
import { blobRefToSrc } from "src/utils/blobRefToSrc";
import { PubThemeDefaults } from "./themeDefaults";
export type PubThemeSource =
| {
theme?: PubLeafletPublication.Theme | null;
basicTheme?: SiteStandardThemeBasic.Main | null;
}
| null
| undefined;
// Default page background for standalone leaflets (matches editor default)
const StandalonePageBackground = "#FFFFFF";
function parseThemeColor(
c: PubLeafletThemeColor.Rgb | PubLeafletThemeColor.Rgba,
) {
if (c.$type === "pub.leaflet.theme.color#rgba") {
return parseColor(`rgba(${c.r}, ${c.g}, ${c.b}, ${c.a / 100})`);
}
return parseColor(`rgb(${c.r}, ${c.g}, ${c.b})`);
}
let useColor = (
theme: PubLeafletPublication.Record["theme"] | null | undefined,
c: keyof typeof PubThemeDefaults,
) => {
return useMemo(() => {
let v = theme?.[c];
if (isColor(v)) {
return parseThemeColor(v);
} else return parseColor(PubThemeDefaults[c]);
}, [theme?.[c]]);
};
let isColor = (
c: any,
): c is PubLeafletThemeColor.Rgb | PubLeafletThemeColor.Rgba => {
return (
c?.$type === "pub.leaflet.theme.color#rgb" ||
c?.$type === "pub.leaflet.theme.color#rgba"
);
};
export function PublicationThemeProviderDashboard(props: {
children: React.ReactNode;
}) {
let { data } = usePublicationData();
let { publication: pub } = data || {};
const normalizedPub = useNormalizedPublicationRecord();
return (
{props.children}
);
}
export function PublicationBackgroundProvider(props: {
record?: PubThemeSource;
pub_creator: string;
className?: string;
children: React.ReactNode;
localBgImage?: string | null;
localBgImageRepeat?: number | null;
}) {
let backgroundImage =
props.localBgImage !== undefined
? props.localBgImage
: props.record?.theme?.backgroundImage?.image?.ref
? blobRefToSrc(
props.record?.theme?.backgroundImage?.image?.ref,
props.pub_creator,
)
: null;
let backgroundImageRepeat =
props.localBgImageRepeat !== undefined
? !!props.localBgImageRepeat
: props.record?.theme?.backgroundImage?.repeat;
let backgroundImageSize =
(props.localBgImageRepeat !== undefined
? props.localBgImageRepeat
: props.record?.theme?.backgroundImage?.width) || 500;
return (
{props.children}
);
}
export function PublicationThemeProvider(props: {
local?: boolean;
children: React.ReactNode;
record?: PubThemeSource;
pub_creator: string;
isStandalone?: boolean;
}) {
let theme = usePubTheme(props.record, props.isStandalone);
let cardBorderHidden = !theme.showPageBackground;
let hasBackgroundImage = !!props.record?.theme?.backgroundImage?.image?.ref;
return (
{props.children}
);
}
export const usePubTheme = (
source?: PubThemeSource,
isStandalone?: boolean,
) => {
const theme = useMemo(() => resolvePublicationTheme(source), [source]);
let bgLeaflet = useColor(theme, "backgroundColor");
let bgPage = useColor(theme, "pageBackground");
// For standalone documents, use the editor default page background (#FFFFFF)
// For publications without explicit pageBackground, use bgLeaflet
if (isStandalone && !theme?.pageBackground) {
bgPage = parseColor(StandalonePageBackground);
} else if (theme && !theme.pageBackground) {
bgPage = bgLeaflet;
}
// For standalone documents, default to showing page background
// For publications, default to hiding it (borderless)
let showPageBackground = isStandalone
? theme?.showPageBackground !== false
: !!theme?.showPageBackground;
let pageWidth = theme?.pageWidth;
let primary = useColor(theme, "primary");
let accent1 = useColor(theme, "accentBackground");
let accent2 = useColor(theme, "accentText");
let highlight1 = useEntity(null, "theme/highlight-1")?.data.value;
let highlight2 = useColorAttribute(null, "theme/highlight-2");
let highlight3 = useColorAttribute(null, "theme/highlight-3");
let headingFontId = theme?.headingFont;
let bodyFontId = theme?.bodyFont;
return {
bgLeaflet,
bgPage,
primary,
accent1,
accent2,
highlight1,
highlight2,
highlight3,
showPageBackground,
pageWidth,
headingFontId,
bodyFontId,
};
};
export const useLocalPubTheme = (
theme: PubLeafletPublication.Record["theme"] | undefined,
showPageBackground?: boolean,
) => {
const pubTheme = usePubTheme({ theme });
const [localOverrides, setTheme] = useState>({});
const mergedTheme = useMemo(() => {
let newTheme = {
...pubTheme,
...localOverrides,
showPageBackground,
};
return {
...newTheme,
};
}, [pubTheme, localOverrides, showPageBackground]);
return {
theme: mergedTheme,
setTheme,
resetChanges: () => setTheme({}),
changes: Object.keys(localOverrides).length > 0,
};
};