diff --git a/gatsby-node.js b/gatsby-node.js index c606781b3..640ebcbf0 100644 --- a/gatsby-node.js +++ b/gatsby-node.js @@ -1,268 +1,57 @@ -const slugify = require('slugify') -var minimatch = require('minimatch') const path = require('path') -const { spawn } = require('child_process') -const onCreateNode = require('./gatsby/transform-nodes') -const createResolvers = require('./gatsby/create-resolvers') -/* -Run Scripts After Build -================================================================================ -*/ -// exports.onCreateDevServer = async ({ reporter }) => { -// const genTypes = await spawn('yarn', ['run', 'gen-types'], { -// stdio: 'inherit', -// shell: true, -// }) -// genTypes.on('exit', (code) => { -// if (code === 0) { -// reporter.success( -// 'graphql-codegen: types generated from gatsby graphql endpoint', -// ) -// } else { -// reporter.error(`graphql-codegen: exited with code ${code}`) -// } -// }) -// } +const schema = require('./gatsby/customize-schema') +const transformers = require('./gatsby/transform-nodes') +const resolvers = require('./gatsby/create-resolvers') +const pages = require('./gatsby/create-pages') /* -Transform Nodes +Customize the GraqphQL Schema ================================================================================ */ -exports.onCreateNode = onCreateNode +exports.createSchemaCustomization = (gatsbyUtils) => { + schema.defineTeamTypes(gatsbyUtils) +} /* -Customize the GraqphQL Schema +Transform Nodes ================================================================================ */ -exports.createSchemaCustomization = ({ actions }) => { - const { createTypes } = actions - - const typeDefs = ` - type DATeamTenure implements Node { - role: DATeamRole - start: Date - end: Date - isActive: Boolean - } - ` - - createTypes(typeDefs) +exports.onCreateNode = (gatsbyUtils) => { + transformers.createRegionsFromMarkdown(gatsbyUtils) + transformers.createSubregionsFromMarkdown(gatsbyUtils) + transformers.createTeamRolesFromMarkdown(gatsbyUtils) + transformers.createTeamMembersFromMarkdown(gatsbyUtils) + transformers.createLineItemsFromJson(gatsbyUtils) } /* Create Resolvers for Looking Up Related Nodes ================================================================================ */ -exports.createResolvers = createResolvers +exports.createResolvers = (gatsbyUtils) => { + resolvers.resolveRegionFields(gatsbyUtils) + resolvers.resolveSubregionFields(gatsbyUtils) + resolvers.resolveTeamMemberFields(gatsbyUtils) +} /* Create Dynamic Pages ================================================================================ */ -exports.createPages = async ({ graphql, actions, reporter }) => { - const { createPage } = actions - - /* - Site Metadata - ------------------------------------------------------------ - */ - const metadataResult = await graphql(` - { - site { - siteMetadata { - domain - } - pathPrefix - } - } - `) - - if (metadataResult.errors) { - reporter.panicOnBuild(` - Error while running GraphQL query to get - the siteMetadata set in gatsby-config.js. - `) - return - } - - /* - Regions & Subregions - ------------------------------------------------------------ - */ - const regionsQuery = await graphql(` - query RegionsQuery { - regions: allDaRegion { - nodes { - name - map { - gatsbyImageData - } - overview - governmentResponse - newsUpdates { - title - visibleCount - updates { - title - content - date - pinned - } - } - stayInformed { - title - links { - label - url - description - } - } - subregions { - name - } - } - } - } - `) - - regionsQuery.data.regions.nodes.forEach((region) => { - const regionSlug = slugify(region.name, { - lower: true, - strict: true, - }) - - console.info(`creating region page at /routes/${regionSlug}`) - - createPage({ - path: `/regions/${regionSlug}`, - component: path.resolve(`./src/templates/RegionPage.tsx`), - context: { - region: region, - }, - }) - }) - - const subregionsQuery = await graphql(` - query SubregionsQuery { - subregions: allDaSubregion { - nodes { - name - map { - gatsbyImageData - } - overview - newsUpdates { - title - visibleCount - updates { - title - content - date - pinned - } - } - region { - name - } - } - } - } - `) - - subregionsQuery.data.subregions.nodes.forEach((subregion) => { - const regionSlug = slugify(subregion.region.name, { - lower: true, - strict: true, - }) - - const subregionSlug = slugify(subregion.name, { - lower: true, - strict: true, - }) - - console.info( - `creating subregion page at /regions/${regionSlug}/${subregionSlug}`, - ) - - createPage({ - path: `/regions/${regionSlug}/${subregionSlug}`, - component: path.resolve(`./src/templates/SubregionPage.tsx`), - context: { - subregion: subregion, - }, - }) - }) - - /* - Routes - ------------------------------------------------------------ - For each page in the content/routes directory, we create a page using its path. - */ - const routesQuery = await graphql(` - query RoutePagesQuery { - allFile(filter: { relativeDirectory: { eq: "pages/routes" } }) { - nodes { - id - childMarkdownRemark { - frontmatter { - pagePath - routeOrigin - routeDestination - introduction - mapUrl - aidRequestFormUrl - images { - deliverySection - reservationSection - groupsSection - storageSection - palletsSection - } - costs { - currency - standardPaletteCost - overflowPricing - halfPaletteCost - } - deadlines { - submissionsDeadline - confirmationDate - stagingBegins - stagingEnds - shipmentDeparture - } - frontlineGroups { - logo - name - } - } - } - relativeDirectory - } - } - } - `) - - routesQuery.data.allFile.nodes.forEach((route) => { - if (route.childMarkdownRemark?.frontmatter) { - console.info( - `creating route page at /routes/${route.childMarkdownRemark.frontmatter.pagePath}`, - ) - createPage({ - path: `/routes/${route.childMarkdownRemark.frontmatter.pagePath}`, - component: path.resolve(`./src/templates/RoutePage.tsx`), - context: { - pageFields: route.childMarkdownRemark.frontmatter, - }, - }) - } - }) +exports.createPages = async (gatsbyUtils) => { + await pages.createRegionPages(gatsbyUtils) + await pages.createSubregionPages(gatsbyUtils) + await pages.createRoutePages(gatsbyUtils) } +/* +Config +================================================================================ +*/ // https://reactjs.org/blog/2020/09/22/introducing-the-new-jsx-transform.html#manual-babel-setup // This should really be handled by Gatsby but here we are -exports.onCreateBabelConfig = ({ actions }) => { - actions.setBabelPlugin({ +exports.onCreateBabelConfig = ({ actions: { setBabelPlugin } }) => { + setBabelPlugin({ name: '@babel/plugin-transform-react-jsx', options: { runtime: 'automatic', diff --git a/gatsby/create-pages.js b/gatsby/create-pages.js new file mode 100644 index 000000000..4aeb7dcec --- /dev/null +++ b/gatsby/create-pages.js @@ -0,0 +1,186 @@ +const slugify = require('slugify') +const path = require('path') + +module.exports = { + /* + Region Pages + ================================================================================ + */ + createRegionPages: async ({ graphql, actions: { createPage } }) => { + const regionsQuery = await graphql(` + query RegionsQuery { + regions: allDaRegion { + nodes { + name + map { + gatsbyImageData + } + overview + governmentResponse + newsUpdates { + title + visibleCount + updates { + title + content + date + pinned + } + } + stayInformed { + title + links { + label + url + description + } + } + subregions { + name + } + } + } + } + `) + + regionsQuery.data.regions.nodes.forEach((region) => { + const regionSlug = slugify(region.name, { + lower: true, + strict: true, + }) + + console.info(`creating region page at /routes/${regionSlug}`) + + createPage({ + path: `/regions/${regionSlug}`, + component: path.resolve(`./src/templates/RegionPage.tsx`), + context: { + region: region, + }, + }) + }) + }, + + /* + Subregion Pages + ================================================================================ + */ + createSubregionPages: async ({ graphql, actions: { createPage } }) => { + const subregionsQuery = await graphql(` + query SubregionsQuery { + subregions: allDaSubregion { + nodes { + name + map { + gatsbyImageData + } + overview + newsUpdates { + title + visibleCount + updates { + title + content + date + pinned + } + } + region { + name + } + } + } + } + `) + + subregionsQuery.data.subregions.nodes.forEach((subregion) => { + const regionSlug = slugify(subregion.region.name, { + lower: true, + strict: true, + }) + + const subregionSlug = slugify(subregion.name, { + lower: true, + strict: true, + }) + + console.info( + `creating subregion page at /regions/${regionSlug}/${subregionSlug}`, + ) + + createPage({ + path: `/regions/${regionSlug}/${subregionSlug}`, + component: path.resolve(`./src/templates/SubregionPage.tsx`), + context: { + subregion: subregion, + }, + }) + }) + }, + + /* + Route Pages + ================================================================================ + */ + createRoutePages: async ({ graphql, actions: { createPage } }) => { + const routesQuery = await graphql(` + query RoutePagesQuery { + allFile(filter: { relativeDirectory: { eq: "pages/routes" } }) { + nodes { + id + childMarkdownRemark { + frontmatter { + pagePath + routeOrigin + routeDestination + introduction + mapUrl + aidRequestFormUrl + images { + deliverySection + reservationSection + groupsSection + storageSection + palletsSection + } + costs { + currency + standardPaletteCost + overflowPricing + halfPaletteCost + } + deadlines { + submissionsDeadline + confirmationDate + stagingBegins + stagingEnds + shipmentDeparture + } + frontlineGroups { + logo + name + } + } + } + relativeDirectory + } + } + } + `) + + routesQuery.data.allFile.nodes.forEach((route) => { + if (route.childMarkdownRemark?.frontmatter) { + console.info( + `creating route page at /routes/${route.childMarkdownRemark.frontmatter.pagePath}`, + ) + createPage({ + path: `/routes/${route.childMarkdownRemark.frontmatter.pagePath}`, + component: path.resolve(`./src/templates/RoutePage.tsx`), + context: { + pageFields: route.childMarkdownRemark.frontmatter, + }, + }) + } + }) + }, +} diff --git a/gatsby/create-resolvers.js b/gatsby/create-resolvers.js index a94f8485e..96af3e4ee 100644 --- a/gatsby/create-resolvers.js +++ b/gatsby/create-resolvers.js @@ -1,102 +1,113 @@ -module.exports = createResolvers = ({ createResolvers, getNode }) => { - const resolvers = { - /* - Region - ------------------------------------------------------------ - */ - DARegion: { - subregions: { - type: ['DASubregion'], - resolve: async (source, args, context, info) => { - const { entries: subregions } = await context.nodeModel.findAll({ - query: { - filter: { - fileRelativePath: { in: source.subregionFileRelativePaths }, +module.exports = { + /* + Regions + ================================================================================ + */ + resolveRegionFields: ({ createResolvers, getNode }) => { + createResolvers({ + DARegion: { + subregions: { + type: ['DASubregion'], + resolve: async (source, args, context, info) => { + const { entries: subregions } = await context.nodeModel.findAll({ + query: { + filter: { + fileRelativePath: { in: source.subregionFileRelativePaths }, + }, }, - }, - type: 'DASubregion', - }) - return subregions + type: 'DASubregion', + }) + return subregions + }, }, - }, - map: imageSharpResolver(getNode, 'mapFileRelativePath'), - }, + map: imageSharpResolver(getNode, 'mapFileRelativePath'), + }, + }) + }, - /* - Subregion - ------------------------------------------------------------ - */ - DASubregion: { - region: { - type: 'DARegion', - resolve: async (source, args, context, info) => { - const region = await context.nodeModel.findOne({ - query: { - filter: { - subregionFileRelativePaths: { eq: source.fileRelativePath }, + /* + Subregions + ================================================================================ + */ + resolveSubregionFields: ({ createResolvers, getNode }) => { + createResolvers({ + DASubregion: { + region: { + type: 'DARegion', + resolve: async (source, args, context, info) => { + const region = await context.nodeModel.findOne({ + query: { + filter: { + subregionFileRelativePaths: { eq: source.fileRelativePath }, + }, }, - }, - type: 'DARegion', - }) - return region + type: 'DARegion', + }) + return region + }, }, - }, - map: imageSharpResolver(getNode, 'mapFileRelativePath'), - }, + map: imageSharpResolver(getNode, 'mapFileRelativePath'), + }, + }) + }, - /* - Team Member - ------------------------------------------------------------ - TODO: Consider creating the DATeamTenure nodes in transform-nodes and - using Gatsby's @link directive in createSchemaCustomization to - automatically link them to the DATeamMember. + /* + Team Members + ================================================================================ + TODO: Consider creating the DATeamTenure nodes in transform-nodes and + using Gatsby's @link directive in createSchemaCustomization to + automatically link them to the DATeamMember. - This technique may be applicable to all our resolvers. + This technique may be applicable to all our resolvers. - https://www.gatsbyjs.com/docs/reference/graphql-data-layer/schema-customization/#foreign-key-fields - */ - DATeamMember: { - roles: { - type: ['DATeamTenure'], - resolve: async (source, args, context, info) => { - const roleFileRelativePaths = source.roleData.map((role) => { - return role.fileRelativePath - }) + https://www.gatsbyjs.com/docs/reference/graphql-data-layer/schema-customization/#foreign-key-fields + */ + resolveTeamMemberFields: ({ createResolvers, getNode }) => { + createResolvers({ + DATeamMember: { + roles: { + type: ['DATeamTenure'], + resolve: async (source, args, context, info) => { + const roleFileRelativePaths = source.roleData.map((role) => { + return role.fileRelativePath + }) - const results = await context.nodeModel.findAll({ - query: { - filter: { - fileRelativePath: { in: roleFileRelativePaths }, + const results = await context.nodeModel.findAll({ + query: { + filter: { + fileRelativePath: { in: roleFileRelativePaths }, + }, }, - }, - type: 'DATeamRole', - }) - const entries = Array.from(results.entries) - - const roles = source.roleData.map((role) => { - const entry = entries.find((entry) => { - return entry.fileRelativePath === role.fileRelativePath + type: 'DATeamRole', }) + const entries = Array.from(results.entries) - return { - role: entry, - start: role.start, - end: role.end, - isActive: role.isActive, - } - }) + const roles = source.roleData.map((role) => { + const entry = entries.find((entry) => { + return entry.fileRelativePath === role.fileRelativePath + }) - return roles - }, - }, + return { + role: entry, + start: role.start, + end: role.end, + isActive: role.isActive, + } + }) - profilePhoto: imageSharpResolver(getNode, 'profilePhotoFileRelativePath'), - }, - } + return roles + }, + }, - createResolvers(resolvers) + profilePhoto: imageSharpResolver( + getNode, + 'profilePhotoFileRelativePath', + ), + }, + }) + }, } /* diff --git a/gatsby/customize-schema.js b/gatsby/customize-schema.js new file mode 100644 index 000000000..ecf70ea04 --- /dev/null +++ b/gatsby/customize-schema.js @@ -0,0 +1,14 @@ +module.exports = { + defineTeamTypes: ({ actions: { createTypes } }) => { + const typeDefs = ` + type DATeamTenure implements Node { + role: DATeamRole + start: Date + end: Date + isActive: Boolean + } + ` + + createTypes(typeDefs) + }, +} diff --git a/gatsby/transform-nodes.js b/gatsby/transform-nodes.js index b4f3d2228..ecb543477 100644 --- a/gatsby/transform-nodes.js +++ b/gatsby/transform-nodes.js @@ -1,33 +1,24 @@ var minimatch = require('minimatch') const path = require('path') -module.exports = onCreateNode = ({ - node, - actions, - createNodeId, - createContentDigest, - getNode, -}) => { - const { createNode, createNodeField } = actions - +module.exports = { /* - Forestry Data + Regions ================================================================================ */ - if ( - node.internal.type === 'MarkdownRemark' && - node.fileAbsolutePath && - minimatch(node.fileAbsolutePath, '**/content/**/*.md') - ) { - const fm = node.frontmatter - - /* - Regions - ------------------------------------------------------------ - */ + createRegionsFromMarkdown: ({ + node, + actions: { createNode }, + createNodeId, + createContentDigest, + getNode, + }) => { if ( + node.internal.type === 'MarkdownRemark' && + node.fileAbsolutePath && minimatch(node.fileAbsolutePath, '**/content/pages/regions/*/index.md') ) { + const fm = node.frontmatter const fileRelativePath = path.join( 'content', getNode(node.parent).relativePath, @@ -55,13 +46,26 @@ module.exports = onCreateNode = ({ contentDigest: createContentDigest(fm), }, }) - } else if ( - /* - Subregions - ------------------------------------------------------------ - */ + } + }, + + /* + Subregions + ================================================================================ + */ + createSubregionsFromMarkdown: ({ + node, + actions: { createNode }, + createNodeId, + createContentDigest, + getNode, + }) => { + if ( + node.internal.type === 'MarkdownRemark' && + node.fileAbsolutePath && minimatch(node.fileAbsolutePath, '**/content/pages/regions/*/!(index).md') ) { + const fm = node.frontmatter const fileRelativePath = path.join( 'content', getNode(node.parent).relativePath, @@ -87,13 +91,26 @@ module.exports = onCreateNode = ({ contentDigest: createContentDigest(fm), }, }) - } else if ( - /* - Team Roles - ------------------------------------------------------------ - */ + } + }, + + /* + Team Roles + ================================================================================ + */ + createTeamRolesFromMarkdown: ({ + node, + actions: { createNode }, + createNodeId, + createContentDigest, + getNode, + }) => { + if ( + node.internal.type === 'MarkdownRemark' && + node.fileAbsolutePath && minimatch(node.fileAbsolutePath, '**/content/blocks/roles/*.md') ) { + const fm = node.frontmatter const fileRelativePath = path.join( 'content', getNode(node.parent).relativePath, @@ -122,13 +139,26 @@ module.exports = onCreateNode = ({ contentDigest: createContentDigest(fm), }, }) - } else if ( - /* - Team Members - ------------------------------------------------------------ - */ + } + }, + + /* + Team Members + ================================================================================ + */ + createTeamMembersFromMarkdown: ({ + node, + actions: { createNode }, + createNodeId, + createContentDigest, + getNode, + }) => { + if ( + node.internal.type === 'MarkdownRemark' && + node.fileAbsolutePath && minimatch(node.fileAbsolutePath, '**/content/blocks/members/*.md') ) { + const fm = node.frontmatter const fileRelativePath = path.join( 'content', getNode(node.parent).relativePath, @@ -166,63 +196,63 @@ module.exports = onCreateNode = ({ contentDigest: createContentDigest(fm), }, }) - } else { - // do nothing for other markdown remark types } + }, - /* - Json Data + /* + Line Items ================================================================================ */ + createLineItemsFromJson: ({ + node, + actions: { createNode }, + createNodeId, + createContentDigest, + }) => { + if (node.internal.type === 'CombinedManifestsJson') { + const rawValue = node['$ Total'] + const value = parseFloat(rawValue?.replaceAll(/[\$,]/g, '')) + const count = parseInt(node['Count']?.replaceAll(/[\,|\.]/g, '')) + if (value && !isNaN(value) && count && !isNaN(count)) { + const rawShipment = node['Shipment #'] + const shipmentComponents = rawShipment.match( + /^(\d{2})-(\d{3})-([A-Z]{3})-([A-Z]{3})$/, + ) + const shipment = + shipmentComponents !== null + ? { + year: shipmentComponents[1], + number: shipmentComponents[2], + origin: shipmentComponents[3], + destination: shipmentComponents[4], + } + : undefined + const item = { + category: node['Category'], + item: node['Item'], + ageGender: node['Age / Gender'], + sizeStyle: node['Size / Style'], + } + createNode({ + // Node Data + value, + count, + rawShipment, + shipment, + item, - /* - Line Items - ------------------------------------------------------------ - */ - } else if (node.internal.type === 'CombinedManifestsJson') { - const rawValue = node['$ Total'] - const value = parseFloat(rawValue?.replaceAll(/[\$,]/g, '')) - const count = parseInt(node['Count']?.replaceAll(/[\,|\.]/g, '')) - if (value && !isNaN(value) && count && !isNaN(count)) { - const rawShipment = node['Shipment #'] - const shipmentComponents = rawShipment.match( - /^(\d{2})-(\d{3})-([A-Z]{3})-([A-Z]{3})$/, - ) - const shipment = - shipmentComponents !== null - ? { - year: shipmentComponents[1], - number: shipmentComponents[2], - origin: shipmentComponents[3], - destination: shipmentComponents[4], - } - : undefined - const item = { - category: node['Category'], - item: node['Item'], - ageGender: node['Age / Gender'], - sizeStyle: node['Size / Style'], + // Gatsby Fields + id: createNodeId(`DA LineItem - ${node.id}`), + parent: node.id, + children: [], + internal: { + type: 'DaLineItem', + contentDigest: createContentDigest(`${value} ${shipment}`), + }, + }) + } else { + // console.warn(`Line Item missing value, raw value: ${rawValue}`) } - createNode({ - // Node Data - value, - count, - rawShipment, - shipment, - item, - - // Gatsby Fields - id: createNodeId(`DA LineItem - ${node.id}`), - parent: node.id, - children: [], - internal: { - type: 'DaLineItem', - contentDigest: createContentDigest(`${value} ${shipment}`), - }, - }) - } else { - console.warn(`Line Item missing value, raw value: ${rawValue}`) } - // Other Pages - } -} + }, +} // module.exports