Skip to content

Commit

Permalink
feat: embedded views and tours (#127)
Browse files Browse the repository at this point in the history
  • Loading branch information
alexgoff authored Aug 29, 2024
1 parent 5aec92a commit 6dfb932
Show file tree
Hide file tree
Showing 71 changed files with 13,125 additions and 474 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,5 @@ yarn-error.log*

# Environment config
.env

certificates
80 changes: 80 additions & 0 deletions app/[locale]/embed/[slug]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import { FunctionComponent } from "react";
import { graphql } from "@/gql";
import { RootParams } from "../../layout";
import AladinTemplate from "@/components/templates/Aladin";
import EmbeddedExplorer from "@/components/organisms/Embedded";
import getSurveyImage from "@/lib/api/survey";
import { queryAPI } from "@/lib/api/urql";
import { siteFromLocale } from "@/lib/i18n/site";
import { notFound } from "next/navigation";
import CopyText from "@/components/organisms/CopyLink";
import { headers } from "next/headers";

type EmbedParams = {
slug: string;
};

export interface EmbeddedProps {
params: RootParams & EmbedParams;
searchParams?: Record<string, string | Array<string> | undefined>;
}

const Query = graphql(`
query EmbeddedPageQuery($site: [String], $slug: [String]) {
embeddedViewsEntries(slug: $slug, site: $site) {
... on embeddedViews_default_Entry {
title
fov
fovMax
fovMin
target
...EmbeddedExplorer
}
}
}
`);

const EmbeddedPage: FunctionComponent<EmbeddedProps> = async ({
params: { locale, slug },
}) => {
const site = siteFromLocale(locale);
const survey = await getSurveyImage();
const { data } = await queryAPI({
query: Query,
variables: {
site: [site],
slug: [slug],
},
});

if (!data || !data.embeddedViewsEntries || !survey) {
notFound();
}

const { embeddedViewsEntries } = data;
const view = embeddedViewsEntries[0];

if (!view) {
notFound();
}

const { fov, fovMin, fovMax, target, title } = view;
const { imgFormat, path } = survey;

const link = `${headers().get("host")}/${locale}/embed/${slug}`;
const textToCopy = `<iframe src="${link}" title=${title}></iframe>`;

return (
<AladinTemplate
fovRange={[fovMin, fovMax]}
hipsConfig={{ id: path, options: { imgFormat } }}
options={{ fov, target: target || undefined }}
embedded
footer={<CopyText {...{ textToCopy }} />}
>
<EmbeddedExplorer data={view} />
</AladinTemplate>
);
};

export default EmbeddedPage;
8 changes: 8 additions & 0 deletions app/[locale]/embed/loading.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import LoadingSpinner from "@/components/primitives/LoadingSpinner";
import { FunctionComponent } from "react";

const EmbedLoading: FunctionComponent = () => {
return <LoadingSpinner />;
};

export default EmbedLoading;
7 changes: 7 additions & 0 deletions app/[locale]/embed/not-found.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { FunctionComponent } from "react";

const EmbedNotFound: FunctionComponent = () => {
return <h1>Not found</h1>;
};

export default EmbedNotFound;
85 changes: 85 additions & 0 deletions app/[locale]/embed/tour/[id]/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import { FunctionComponent, PropsWithChildren } from "react";
import { notFound } from "next/navigation";
import { graphql } from "@/gql";
import { AstroObjectsSectionEntryUnion } from "@/gql/graphql";
import { siteFromLocale } from "@/lib/i18n/site";
import { queryAPI } from "@/lib/api/urql";
import getSurveyImage from "@/lib/api/survey";
import { RootParams } from "@/app/[locale]/layout";
import AladinTemplate from "@/components/templates/Aladin";

type TourParams = {
id: string;
};

export interface TourProps {
params: TourParams & RootParams;
}

const Query = graphql(`
query EmbeddedTourLayout($site: [String], $id: [QueryArgument]) {
toursEntries(id: $id, site: $site) {
... on tours_tour_Entry {
tourPois {
... on tourPois_tourPoi_BlockType {
fov
astroObject {
... on astroObjects_astroObject_Entry {
ra
dec
}
}
}
}
}
}
}
`);

const EmbeddedTourLayout: FunctionComponent<
PropsWithChildren<TourProps>
> = async ({ params: { locale, id }, children }) => {
const site = siteFromLocale(locale);
const survey = await getSurveyImage();
const { data } = await queryAPI({
query: Query,
variables: {
site: [site],
id: [id],
},
});

if (!data || !data.toursEntries) {
notFound();
}

const { toursEntries } = data;
const tour = toursEntries[0];

if (!survey || !tour) {
notFound();
}

const { path, imgFormat, fovRange } = survey;
const { tourPois } = tour;

if (!tourPois || !tourPois[0]) {
notFound();
}

const { fov, astroObject } = tourPois[0];
const { ra, dec } = astroObject[0] as AstroObjectsSectionEntryUnion;

return (
<AladinTemplate
fovRange={fovRange}
hipsConfig={{ id: path, options: { imgFormat } }}
options={{ fov, target: `${ra} ${dec}` }}
embedded
>
{children}
</AladinTemplate>
);
};

export default EmbeddedTourLayout;
93 changes: 93 additions & 0 deletions app/[locale]/embed/tour/[id]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import { FunctionComponent } from "react";
import { notFound } from "next/navigation";
import { graphql } from "@/gql";
import { siteFromLocale } from "@/lib/i18n/site";
import { queryAPI } from "@/lib/api/urql";
import AladinTourGuide from "@/components/organisms/AladinTourGuide";
import { TourProps } from "./layout";
import NavigationList from "@/components/molecules/NavigationList";
import { useTranslation } from "@/lib/i18n";
import { PropsWithSearchParams } from "@/types/next";

const Query = graphql(`
query EmbeddedTourQuery($site: [String], $id: [QueryArgument]) {
toursEntries(id: $id, site: $site) {
... on tours_tour_Entry {
... on tours_tour_Entry {
id
slug
title
tourPois {
... on tourPois_tourPoi_BlockType {
id
description
fov
astroObject {
... on astroObjects_astroObject_Entry {
title
astroObjectId
ra
dec
characteristics
}
}
}
}
}
}
}
}
`);

const EmbeddedTour: FunctionComponent<
PropsWithSearchParams<TourProps>
> = async ({ params: { locale, id }, searchParams = {} }) => {
const { t } = await useTranslation(locale, "translation");
const { poi = "1", returnTo } = searchParams;
const poiIndex = parseInt(Array.isArray(poi) ? poi[0] : poi) - 1;
const returnUrl = Array.isArray(returnTo)
? returnTo.join(",")
: `${returnTo}`;

const site = siteFromLocale(locale);
const { data } = await queryAPI({
query: Query,
variables: {
site: [site],
id: [id],
},
});

if (!data || !data?.toursEntries || !data?.toursEntries[0]) {
notFound();
}

const { title, tourPois } = data.toursEntries[0];

const isFirst = poiIndex === 0;
const isLast = poiIndex === data.toursEntries[0].tourPois.length - 1;

const backLink = isFirst
? { url: returnUrl, text: t("navigation.cta.exit") }
: {
url: `/embed/tour/${id}?poi=${poiIndex}&returnTo=${returnUrl}`,
text: t("navigation.cta.back"),
};
const nextLink = isLast
? { url: returnUrl, text: t("navigation.cta.exit") }
: {
url: `/embed/tour/${id}?poi=${poiIndex + 2}&returnTo=${returnUrl}`,
text: t("navigation.cta.next"),
};

return (
<AladinTourGuide
{...{ poiIndex }}
tourPois={tourPois as any}
title={title || undefined}
controls={<NavigationList links={[backLink, nextLink]} />}
/>
);
};

export default EmbeddedTour;
53 changes: 53 additions & 0 deletions app/[locale]/iframe/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { FunctionComponent } from "react";
import styles from "./styles.module.css";
import Buttonish from "@rubin-epo/epo-react-lib/Buttonish";

const IframePage: FunctionComponent = () => {
return (
<article className={styles["page-container"]}>
<h1>Breaking News Out of Rubin Observatory</h1>
<h3>New Image Stuns World</h3>
<time>August 19, 2024</time>
<section className={styles["flow-content"]}>
<p>
Imagine me as a news article but with a Skyviewer image embedded in
the middle. What would that look like? Can you imagine it?
</p>
<p>
Alice was beginning to get very tired of sitting by her sister on the
bank, and of having nothing to do: once or twice she had peeped into
the book her sister was reading, but it had no pictures or
conversations in it, “and what is the use of a book,” thought Alice
“without pictures or conversations?”
</p>
<iframe
src="/embed/test-embedded-view"
className={styles["embedded-frame"]}
/>
<Buttonish url={"/"} text="Visit the Skyviewer" isBlock />
<p>
So she was considering in her own mind (as well as she could, for the
hot day made her feel very sleepy and stupid), whether the pleasure of
making a daisy-chain would be worth the trouble of getting up and
picking the daisies, when suddenly a White Rabbit with pink eyes ran
close by her.
</p>
<p>
There was nothing so very remarkable in that; nor did Alice think it
so very much out of the way to hear the Rabbit say to itself, “Oh
dear! Oh dear! I shall be late!” (when she thought it over afterwards,
it occurred to her that she ought to have wondered at this, but at the
time it all seemed quite natural); but when the Rabbit actually took a
watch out of its waistcoat-pocket, and looked at it, and then hurried
on, Alice started to her feet, for it flashed across her mind that she
had never before seen a rabbit with either a waistcoat-pocket, or a
watch to take out of it, and burning with curiosity, she ran across
the field after it, and fortunately was just in time to see it pop
down a large rabbit-hole under the hedge.
</p>
</section>
</article>
);
};

export default IframePage;
16 changes: 16 additions & 0 deletions app/[locale]/iframe/styles.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
.page-container {
width: clamp(320px, 90%, 1000px);
margin: 0 auto;
}

.flow-content {
> * + * {
margin-block-start: 1em;
}
}

.embedded-frame {
border: none;
width: 100%;
height: 30em;
}
Loading

0 comments on commit 6dfb932

Please sign in to comment.