From 6dfb932669b20d002ace239e1bf8b5f7496d9162 Mon Sep 17 00:00:00 2001 From: Alexandra Goff Date: Thu, 29 Aug 2024 12:02:20 -0700 Subject: [PATCH] feat: embedded views and tours (#127) --- .gitignore | 2 + app/[locale]/embed/[slug]/page.tsx | 80 + app/[locale]/embed/loading.tsx | 8 + app/[locale]/embed/not-found.tsx | 7 + app/[locale]/embed/tour/[id]/layout.tsx | 85 + app/[locale]/embed/tour/[id]/page.tsx | 93 + app/[locale]/iframe/page.tsx | 53 + app/[locale]/iframe/styles.module.css | 16 + app/[locale]/tours/[tour]/tour/layout.tsx | 77 + app/[locale]/tours/[tour]/tour/page.tsx | 53 +- app/[locale]/tours/_styles.scss | 3 - codegen.ts | 27 + components/ShareButtons/CopyUrlButton.jsx | 1 + components/ShareButtons/_styles.scss | 1 + .../molecules/Controls/Container/index.tsx | 22 + .../Controls/Container/styles.module.css | 6 + .../molecules/Controls/Download/index.tsx | 40 + .../Controls/ReturnToTarget/index.tsx | 37 + .../molecules/Controls/ShareImage/index.tsx | 51 + components/molecules/Controls/Zoom/index.tsx | 61 + .../molecules/Controls/Zoom/styles.module.css | 9 + .../ExplorerControls/Zoom/_styles.scss | 23 - .../molecules/ExplorerControls/Zoom/index.jsx | 49 - .../molecules/ExplorerControls/_styles.scss | 7 +- .../molecules/ExplorerControls/index.tsx | 7 +- .../ExplorerControls/styles.module.css | 5 + components/molecules/FOVScale/index.tsx | 102 + .../molecules/FOVScale/styles.module.css | 18 + components/molecules/FOVSize/index.tsx | 90 + .../molecules/FOVSize/styles.module.css | 21 + components/molecules/NavigationList/index.tsx | 30 +- .../NavigationList/styles.module.css | 4 + components/molecules/PoiDescription/index.tsx | 4 +- .../organisms/AladinTourGuide/index.tsx | 97 +- .../AladinTourGuide/styles.module.css | 4 - components/organisms/CopyLink/index.tsx | 19 + .../organisms/CopyLink/styles.module.css | 23 + components/organisms/Embedded/index.tsx | 64 + .../organisms/Embedded/styles.module.css | 5 + components/organisms/Explorer/index.tsx | 6 +- components/primitives/AladinCanvas/index.tsx | 12 +- components/primitives/AladinCanvas/styles.css | 6 + .../AladinOverlay/styles.module.css | 2 + components/primitives/Button/_styles.scss | 2 +- components/primitives/IconButton/index.tsx | 35 + .../primitives/IconButton/styles.module.css | 27 + components/primitives/IconToggle/index.tsx | 13 +- .../primitives/IconToggle/styles.module.css | 18 +- components/primitives/Stack/index.tsx | 32 + components/primitives/Stack/styles.module.css | 13 + components/templates/Aladin/index.tsx | 33 +- components/templates/Aladin/styles.module.css | 13 +- components/tours/_styles.scss | 2 +- fixtures/defaultSurveyView.ts | 13 + gql/fragment-masking.ts | 87 + gql/gql.ts | 67 + gql/graphql.ts | 9198 +++++++++++++++++ gql/index.ts | 2 + lib/api/fragments/survey.js | 2 +- lib/api/survey.ts | 61 + lib/api/urql.ts | 58 + lib/i18n/localeStrings/en/translation.json | 6 + lib/i18n/site.ts | 5 + lib/utilities.ts | 74 + package.json | 19 +- types/aladin/events.d.ts | 3 +- types/aladin/index.d.ts | 30 +- types/css.d.ts | 6 + types/next.ts | 5 + types/share.d.ts | 6 + yarn.lock | 2439 ++++- 71 files changed, 13125 insertions(+), 474 deletions(-) create mode 100644 app/[locale]/embed/[slug]/page.tsx create mode 100644 app/[locale]/embed/loading.tsx create mode 100644 app/[locale]/embed/not-found.tsx create mode 100644 app/[locale]/embed/tour/[id]/layout.tsx create mode 100644 app/[locale]/embed/tour/[id]/page.tsx create mode 100644 app/[locale]/iframe/page.tsx create mode 100644 app/[locale]/iframe/styles.module.css create mode 100644 app/[locale]/tours/[tour]/tour/layout.tsx create mode 100644 codegen.ts create mode 100644 components/molecules/Controls/Container/index.tsx create mode 100644 components/molecules/Controls/Container/styles.module.css create mode 100644 components/molecules/Controls/Download/index.tsx create mode 100644 components/molecules/Controls/ReturnToTarget/index.tsx create mode 100644 components/molecules/Controls/ShareImage/index.tsx create mode 100644 components/molecules/Controls/Zoom/index.tsx create mode 100644 components/molecules/Controls/Zoom/styles.module.css delete mode 100644 components/molecules/ExplorerControls/Zoom/_styles.scss delete mode 100644 components/molecules/ExplorerControls/Zoom/index.jsx create mode 100644 components/molecules/ExplorerControls/styles.module.css create mode 100644 components/molecules/FOVScale/index.tsx create mode 100644 components/molecules/FOVScale/styles.module.css create mode 100644 components/molecules/FOVSize/index.tsx create mode 100644 components/molecules/FOVSize/styles.module.css create mode 100644 components/organisms/CopyLink/index.tsx create mode 100644 components/organisms/CopyLink/styles.module.css create mode 100644 components/organisms/Embedded/index.tsx create mode 100644 components/organisms/Embedded/styles.module.css create mode 100644 components/primitives/IconButton/index.tsx create mode 100644 components/primitives/IconButton/styles.module.css create mode 100644 components/primitives/Stack/index.tsx create mode 100644 components/primitives/Stack/styles.module.css create mode 100644 fixtures/defaultSurveyView.ts create mode 100644 gql/fragment-masking.ts create mode 100644 gql/gql.ts create mode 100644 gql/graphql.ts create mode 100644 gql/index.ts create mode 100644 lib/api/survey.ts create mode 100644 lib/api/urql.ts create mode 100644 lib/i18n/site.ts create mode 100644 lib/utilities.ts create mode 100644 types/css.d.ts create mode 100644 types/next.ts create mode 100644 types/share.d.ts diff --git a/.gitignore b/.gitignore index b3ea322..f42663a 100644 --- a/.gitignore +++ b/.gitignore @@ -36,3 +36,5 @@ yarn-error.log* # Environment config .env + +certificates \ No newline at end of file diff --git a/app/[locale]/embed/[slug]/page.tsx b/app/[locale]/embed/[slug]/page.tsx new file mode 100644 index 0000000..4692b68 --- /dev/null +++ b/app/[locale]/embed/[slug]/page.tsx @@ -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 | 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 = 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 = ``; + + return ( + } + > + + + ); +}; + +export default EmbeddedPage; diff --git a/app/[locale]/embed/loading.tsx b/app/[locale]/embed/loading.tsx new file mode 100644 index 0000000..d7d33ed --- /dev/null +++ b/app/[locale]/embed/loading.tsx @@ -0,0 +1,8 @@ +import LoadingSpinner from "@/components/primitives/LoadingSpinner"; +import { FunctionComponent } from "react"; + +const EmbedLoading: FunctionComponent = () => { + return ; +}; + +export default EmbedLoading; diff --git a/app/[locale]/embed/not-found.tsx b/app/[locale]/embed/not-found.tsx new file mode 100644 index 0000000..ff2c954 --- /dev/null +++ b/app/[locale]/embed/not-found.tsx @@ -0,0 +1,7 @@ +import { FunctionComponent } from "react"; + +const EmbedNotFound: FunctionComponent = () => { + return

Not found

; +}; + +export default EmbedNotFound; diff --git a/app/[locale]/embed/tour/[id]/layout.tsx b/app/[locale]/embed/tour/[id]/layout.tsx new file mode 100644 index 0000000..43f4a17 --- /dev/null +++ b/app/[locale]/embed/tour/[id]/layout.tsx @@ -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 +> = 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 ( + + {children} + + ); +}; + +export default EmbeddedTourLayout; diff --git a/app/[locale]/embed/tour/[id]/page.tsx b/app/[locale]/embed/tour/[id]/page.tsx new file mode 100644 index 0000000..4dd4d14 --- /dev/null +++ b/app/[locale]/embed/tour/[id]/page.tsx @@ -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 +> = 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 ( + } + /> + ); +}; + +export default EmbeddedTour; diff --git a/app/[locale]/iframe/page.tsx b/app/[locale]/iframe/page.tsx new file mode 100644 index 0000000..9731dde --- /dev/null +++ b/app/[locale]/iframe/page.tsx @@ -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 ( +
+

Breaking News Out of Rubin Observatory

+

New Image Stuns World

+ +
+

+ Imagine me as a news article but with a Skyviewer image embedded in + the middle. What would that look like? Can you imagine it? +

+

+ 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?” +

+