diff --git a/lib/loading-indicator.css b/lib/loading-indicator.css
new file mode 100644
index 0000000..62b1602
--- /dev/null
+++ b/lib/loading-indicator.css
@@ -0,0 +1,13 @@
+.loading-indicator {
+ width: 100%;
+ height: 100%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+.loading-indicator svg {
+ width: 64px;
+ height: 64px;
+ fill: lightgray;
+}
diff --git a/lib/loading-indicator.tsx b/lib/loading-indicator.tsx
new file mode 100644
index 0000000..c2ddf10
--- /dev/null
+++ b/lib/loading-indicator.tsx
@@ -0,0 +1,39 @@
+import React from "react";
+
+import "./loading-indicator.css";
+
+/**
+ * Loading indicator taken from:
+ *
+ * https://commons.wikimedia.org/wiki/File:Chromiumthrobber.svg
+ */
+export const LoadingIndicator: React.FC = () => (
+
+);
diff --git a/lib/pages/gallery-page.css b/lib/pages/gallery-page.css
new file mode 100644
index 0000000..420b5a5
--- /dev/null
+++ b/lib/pages/gallery-page.css
@@ -0,0 +1,28 @@
+.gallery-thumbnail {
+ width: 320px;
+ height: 240px;
+}
+
+.gallery-item > a {
+ text-decoration: none;
+}
+
+.gallery-thumbnail.is-empty {
+ background-color: lightgray;
+}
+
+.gallery-thumbnail.is-empty > span {
+ color: darkgray;
+ font-size: 48px;
+}
+
+.gallery-item {
+ display: inline-block;
+ border: 1px solid black;
+ margin-right: 1em;
+ margin-bottom: 1em;
+}
+
+.gallery-item p {
+ margin: 1em;
+}
diff --git a/lib/pages/gallery-page.tsx b/lib/pages/gallery-page.tsx
index df8a853..75f5454 100644
--- a/lib/pages/gallery-page.tsx
+++ b/lib/pages/gallery-page.tsx
@@ -1,7 +1,28 @@
-import React, { useContext, useEffect } from "react";
-import { GalleryComposition, GalleryContext } from "../gallery-context";
+import React, { useContext, useEffect, useRef } from "react";
+import { AutoSizingSvg } from "../auto-sizing-svg";
+import {
+ CreatureContext,
+ CreatureContextType,
+ CreatureSymbol,
+} from "../creature-symbol";
+import {
+ GalleryComposition,
+ GalleryCompositionKind,
+ GalleryContext,
+} from "../gallery-context";
+import { LoadingIndicator } from "../loading-indicator";
import { Page } from "../page";
import { createPageWithStateSearchParams } from "../page-with-shareable-state";
+import { svgScale, SvgTransform } from "../svg-transform";
+import { CreatureDesign } from "./creature-page/core";
+import { deserializeCreatureDesign } from "./creature-page/serialization";
+
+import "./gallery-page.css";
+import {
+ createMandalaAnimationRenderer,
+ MandalaDesign,
+} from "./mandala-page/core";
+import { deserializeMandalaDesign } from "./mandala-page/serialization";
function compositionRemixUrl(comp: GalleryComposition): string {
return (
@@ -10,14 +31,106 @@ function compositionRemixUrl(comp: GalleryComposition): string {
);
}
-const GalleryCompositionView: React.FC = (props) => {
+const THUMBNAIL_CLASS = "gallery-thumbnail canvas";
+
+const THUMBNAIL_SCALE = 0.2;
+
+const ErrorThumbnail: React.FC<{ title?: string }> = ({ title }) => (
+
+ ☹
+
+);
+
+const CreatureThumbnail: React.FC<{ design: CreatureDesign }> = (props) => {
+ const svgRef = useRef(null);
+ const ctx: CreatureContextType = {
+ ...useContext(CreatureContext),
+ ...props.design.compCtx,
+ };
+ const { background } = props.design.compCtx;
+
return (
-
-
- {props.title}
- {" "}
- {props.kind} by {props.ownerName}
-
+
+ );
+};
+
+const MandalaThumbnail: React.FC<{ design: MandalaDesign }> = (props) => {
+ const render = createMandalaAnimationRenderer(props.design, THUMBNAIL_SCALE);
+ const { background } = props.design.baseCompCtx;
+ const svgRef = useRef(null);
+ const canvasRef = useRef(null);
+
+ return (
+
+ );
+};
+
+const THUMBNAILERS: {
+ [key in GalleryCompositionKind]: (gc: GalleryComposition) => JSX.Element;
+} = {
+ creature: (gc) => (
+
+ ),
+ mandala: (gc) => (
+
+ ),
+};
+
+function getThumbnail(gc: GalleryComposition): JSX.Element {
+ let errorTitle: string;
+
+ if (gc.kind in THUMBNAILERS) {
+ try {
+ return THUMBNAILERS[gc.kind](gc);
+ } catch (e) {
+ errorTitle = `Could not deserialize ${gc.kind} "${gc.title}": ${e.message}`;
+ console.error(e);
+ }
+ } else {
+ errorTitle = `Found unknown gallery composition kind "${gc.kind}".`;
+ }
+
+ console.log(errorTitle);
+
+ return ;
+}
+
+const GalleryCompositionView: React.FC = (props) => {
+ const thumbnail = getThumbnail(props);
+ const url = compositionRemixUrl(props);
+
+ return (
+
);
};
@@ -33,10 +146,9 @@ export const GalleryPage: React.FC<{}> = () => {
return (
-
- This gallery is a work in progress! You can publish to it via the
- sidebar on the creature and mandala pages. We don't have thumbnails
- yet, though. It will improve over time.
+
+ You can publish to this gallery via the sidebar on other pages of this
+ site.
{ctx.isLoading ? "Loading\u2026" : "Refresh"}
@@ -44,9 +156,13 @@ export const GalleryPage: React.FC<{}> = () => {
{ctx.error && {ctx.error}
}
- {ctx.compositions.map((comp) => (
-
- ))}
+ {ctx.isLoading ? (
+
+ ) : (
+ ctx.compositions.map((comp) => (
+
+ ))
+ )}
);
diff --git a/lib/pages/mandala-page/core.tsx b/lib/pages/mandala-page/core.tsx
index 2970a71..97f9467 100644
--- a/lib/pages/mandala-page/core.tsx
+++ b/lib/pages/mandala-page/core.tsx
@@ -226,14 +226,17 @@ function isDesignAnimated(design: MandalaDesign): boolean {
return getCirclesFromDesign(design).some((c) => c.animateSymbolRotation);
}
-function createAnimationRenderer({
- baseCompCtx,
- invertCircle2,
- circle1,
- circle2,
- useTwoCircles,
- firstBehind,
-}: MandalaDesign): AnimationRenderer {
+export function createMandalaAnimationRenderer(
+ {
+ baseCompCtx,
+ invertCircle2,
+ circle1,
+ circle2,
+ useTwoCircles,
+ firstBehind,
+ }: MandalaDesign,
+ scale = 0.5
+): AnimationRenderer {
const symbolCtx = noFillIfShowingSpecs(baseCompCtx);
const circle2SymbolCtx = invertCircle2 ? swapColors(symbolCtx) : symbolCtx;
@@ -259,7 +262,7 @@ function createAnimationRenderer({
}
}
- return {circles} ;
+ return {circles} ;
};
}
@@ -336,7 +339,10 @@ export const MandalaPageWithDefaults: React.FC<{
]
);
const isAnimated = isDesignAnimated(design);
- const render = useMemo(() => createAnimationRenderer(design), [design]);
+ const render = useMemo(
+ () => createMandalaAnimationRenderer(design),
+ [design]
+ );
useDebouncedEffect(
250,