diff --git a/.github/workflows/dependabot.yml b/.github/dependabot.yml similarity index 100% rename from .github/workflows/dependabot.yml rename to .github/dependabot.yml diff --git a/.github/workflows/dependabot-automerge.yml b/.github/workflows/dependabot-automerge.yml index 4869cd9..0c30cf5 100644 --- a/.github/workflows/dependabot-automerge.yml +++ b/.github/workflows/dependabot-automerge.yml @@ -11,7 +11,7 @@ jobs: steps: - name: Dependabot metadata id: metadata - uses: dependabot/fetch-metadata@v1.1.1 + uses: dependabot/fetch-metadata@v2.1.0 with: github-token: "${{ secrets.GITHUB_TOKEN }}" - name: Enable auto-merge for Dependabot PRs diff --git a/gatsby-node.ts b/gatsby-node.ts index 834286a..ef0f6b0 100644 --- a/gatsby-node.ts +++ b/gatsby-node.ts @@ -1,7 +1,10 @@ import TidalData from "./data/tides.json"; import path from "path"; -import { TidesJson_PDFObject } from "./src/types"; -import { CreatePagesArgs } from "gatsby"; +import { TidesJson_PDFObject, TidesJson_ScheduleObject } from "./src/types"; +import { BuildArgs, CreatePagesArgs } from "gatsby"; +import fs from "fs"; +import ical, { ICalEventBusyStatus, ICalEventClass } from "ical-generator"; +import { DateTime } from "luxon"; export const createPages = async function ({ actions, @@ -9,10 +12,70 @@ export const createPages = async function ({ }: CreatePagesArgs) { TidalData.pdfs.forEach((pdf: TidesJson_PDFObject) => { actions.createPage({ - path: "tide-tables/" + pdf.url, + path: "/tide-tables/" + pdf.url, component: path.resolve(`./src/components/templates/TideTablePage.tsx`), context: { pdf }, defer: false, }); + actions.createRedirect({ + fromPath: "/tide-tables/" + pdf.url.replace("/", "-"), + toPath: "/tide-tables/" + pdf.url, + }); + actions.createRedirect({ + fromPath: "/tide-tables/" + pdf.url.replace("/", "-") + ".pdf", + toPath: "/tide-tables/" + pdf.url + ".pdf", + }); + }); + + // Legacy page + actions.createRedirect({ + fromPath: "/historical-tables", + toPath: "/tide-tables", }); }; + +export const onPostBootstrap = function ({ reporter }: BuildArgs) { + reporter.info(`Generating iCal file for tide times`); + const cal = ical(); + cal.timezone("Europe/London"); + cal.name("Porthmadog Tide Times"); + cal.description( + "Tide times for Porthmadog, Borth-y-gest, Morfa Bychan and Black Rock Sands from Port-Tides.com" + ); + const today = new Date(); + today.setHours(0, 0, 0, 0); + const nextYear = new Date(today); + nextYear.setDate(today.getDate() + 365); + TidalData.schedule + .filter((tideDay: TidesJson_ScheduleObject) => { + let date = new Date(tideDay.date); + return date >= today && date <= nextYear; + }) + .forEach((day: TidesJson_ScheduleObject) => + day.groups.forEach((tide) => { + cal.createEvent({ + start: DateTime.fromSQL(day.date + " " + tide.time).toJSDate(), + end: DateTime.fromSQL(day.date + " " + tide.time) + .plus({ minutes: 30 }) + .toJSDate(), + summary: `High Tide Porthmadog - ${tide.height}m`, + description: { + plain: "Powered by port-tides.com", + html: `More details at port-tides.com`, + }, + // Commented out to reduce file size + /*location: { + title: "Porthmadog", + address: "Harbwr Porthmadog, LL49 9AY, UK", + }, + busystatus: ICalEventBusyStatus.FREE, + class: ICalEventClass.PUBLIC, + url: "https://port-tides.com/tide-tables",*/ + }); + }) + ); + fs.writeFileSync("public/porthmadog-tides.ical", cal.toString()); + reporter.success( + `Generated iCal file for tide times at public/porthmadog-tides.ical` + ); +}; diff --git a/package-lock.json b/package-lock.json index bfed7ad..e0bd6b4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,6 +17,7 @@ "gatsby-plugin-sitemap": "^6.13.1", "gatsby-source-build-date": "^1.0.1", "gatsby-source-filesystem": "^5.13.1", + "ical-generator": "^7.1.0", "luxon": "^3.4.4", "react": "^18.3.1", "react-dom": "^18.3.1", @@ -4370,7 +4371,7 @@ "version": "3.4.2", "resolved": "https://registry.npmjs.org/@types/luxon/-/luxon-3.4.2.tgz", "integrity": "sha512-TifLZlFudklWlMBfhubvgqTXRzLDI5pCbGa4P8a3wPyUQSW+1xQ5eDsreP9DWHX3tjq1ke96uYG/nwundroWcA==", - "dev": true + "devOptional": true }, "node_modules/@types/minimatch": { "version": "5.1.2", @@ -10886,6 +10887,56 @@ "node": ">=10.17.0" } }, + "node_modules/ical-generator": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/ical-generator/-/ical-generator-7.1.0.tgz", + "integrity": "sha512-mv7wW35+YHbaFDQGgFPSnn+SOiN805UvQL8VaMye6F6AgTQCDlT8dc0XIndCHgp22KvOdJ0po795QHoPIAq+kA==", + "dependencies": { + "uuid-random": "^1.3.2" + }, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "@touch4it/ical-timezones": ">=1.6.0", + "@types/luxon": ">= 1.26.0", + "@types/mocha": ">= 8.2.1", + "dayjs": ">= 1.10.0", + "luxon": ">= 1.26.0", + "moment": ">= 2.29.0", + "moment-timezone": ">= 0.5.33", + "rrule": ">= 2.6.8" + }, + "peerDependenciesMeta": { + "@touch4it/ical-timezones": { + "optional": true + }, + "@types/luxon": { + "optional": true + }, + "@types/mocha": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "dayjs": { + "optional": true + }, + "luxon": { + "optional": true + }, + "moment": { + "optional": true + }, + "moment-timezone": { + "optional": true + }, + "rrule": { + "optional": true + } + } + }, "node_modules/iconv-lite": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", @@ -17054,6 +17105,11 @@ "uuid": "dist/bin/uuid" } }, + "node_modules/uuid-random": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/uuid-random/-/uuid-random-1.3.2.tgz", + "integrity": "sha512-UOzej0Le/UgkbWEO8flm+0y+G+ljUon1QWTEZOq1rnMAsxo2+SckbiZdKzAHHlVh6gJqI1TjC/xwgR50MuCrBQ==" + }, "node_modules/v8-compile-cache": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.4.0.tgz", diff --git a/package.json b/package.json index 815eecd..458f653 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,7 @@ "gatsby-plugin-sitemap": "^6.13.1", "gatsby-source-build-date": "^1.0.1", "gatsby-source-filesystem": "^5.13.1", + "ical-generator": "^7.1.0", "luxon": "^3.4.4", "react": "^18.3.1", "react-dom": "^18.3.1", diff --git a/src/pages/ical.tsx b/src/pages/ical.tsx new file mode 100644 index 0000000..95cba32 --- /dev/null +++ b/src/pages/ical.tsx @@ -0,0 +1,171 @@ +import { Accordion, Button, Code, CopyButton, Text, rem } from "@mantine/core"; +import { IconCopy, IconCopyCheck, IconHome } from "@tabler/icons-react"; +import type { HeadFC, PageProps } from "gatsby"; +import { Link } from "gatsby"; +import * as React from "react"; +import TidalData from "../../data/tides.json"; +import { SEO } from "../components/SEO"; +import Layout from "../components/navigation/Layout"; +import { TideTablesMonthList } from "../components/tideTables/TideTablesMonthList"; +import { TidesJson_PDFObject } from "../types"; +import { useLocation } from "@reach/router"; + +const Page: React.FC = () => { + const { origin } = useLocation(); + const iCalUrl = origin + "/porthmadog-tides.ical"; + return ( + + + + } + > + + + + + Google Calendar + + + + +
    +
  1. + Copy the URL {iCalUrl} + + {({ copied, copy }) => ( + + )} + +
  2. +
  3. + + Open Google Calendar + + , click the + above My calendars and choose{" "} + From URL. +
  4. +
  5. + Paste the URL into the field named URL of calendar{" "} + that appears and click + Add calendar. +
  6. +
  7. + After a few seconds, it should appear in your calendar. If it + does not, try reloading Google Calendar. +
  8. +
+
+
+
+ + + + Apple Calendar + + + + +
    +
  1. + Copy the URL {iCalUrl} + + {({ copied, copy }) => ( + + )} + +
  2. +
  3. + In Apple Calendar, click the File menu and choose{" "} + New Calendar Subscription…. +
  4. +
  5. + Paste the URL into the dialog that appears and click{" "} + Subscribe. +
  6. +
  7. + A window with settings of the calendar subscription will + appear. Set Auto-refresh to Every hour to + keep your calendar up to date and click OK. +
  8. +
+
+
+
+ + + + Outlook + + + + +
    +
  1. + Copy the URL {iCalUrl} + + {({ copied, copy }) => ( + + )} + +
  2. +
  3. + In Outlook, click Open Calendar and choose{" "} + From Internet…. +
  4. +
  5. + Paste the URL into the Outlook dialog that appears and click{" "} + OK. +
  6. +
  7. + After a few seconds, Outlook will ask if the internet calendar + should be added. Click Yes. +
  8. +
+
+
+
+
+
+ ); +}; +export default Page; + +export const Head: HeadFC = () => ; diff --git a/src/pages/index.tsx b/src/pages/index.tsx index 4dfe86f..a353514 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -7,9 +7,9 @@ import { Stack, Text, Title, - useMatches + useMatches, } from "@mantine/core"; -import { IconArrowRight, IconTable } from "@tabler/icons-react"; +import { IconArrowRight, IconCalendar, IconTable } from "@tabler/icons-react"; import { Link, type HeadFC, type PageProps } from "gatsby"; import { DateTime } from "luxon"; import * as React from "react"; @@ -123,11 +123,18 @@ const Page: React.FC = () => { Monthly Tide Tables - - - + + + + + + + +