diff --git a/sites/lab/components/layouts/default.mjs b/sites/lab/components/layouts/default.mjs new file mode 100644 index 00000000000..e5b3d010adc --- /dev/null +++ b/sites/lab/components/layouts/default.mjs @@ -0,0 +1,24 @@ +// Components +import { BaseLayout, BaseLayoutLeft, BaseLayoutWide } from 'shared/components/base-layout.mjs' +import { NavLinks, Breadcrumbs, MainSections } from 'shared/components/navigation/sitenav.mjs' + +export const ns = [] + +export const DefaultLayout = ({ children = [], pageTitle = false }) => ( + + + + + + + + {pageTitle && ( +
+ +

{pageTitle}

+
+ )} +
{children}
+
+
+) diff --git a/sites/lab/components/navigation/modal-menu.mjs b/sites/lab/components/navigation/modal-menu.mjs index f009dc5aa05..9261cab4446 100644 --- a/sites/lab/components/navigation/modal-menu.mjs +++ b/sites/lab/components/navigation/modal-menu.mjs @@ -1,33 +1,28 @@ -import { SectionsMenu } from 'site/components/navigation/sections-menu.mjs' -import { useTranslation } from 'next-i18next' -import { ActiveSection, ns as primaryNs } from 'shared/components/navigation/primary.mjs' +// Components +import { SectionsMenu, ns as sectionsNs } from 'shared/components/navigation/sections-menu.mjs' import { ModalWrapper } from 'shared/components/wrappers/modal.mjs' +import { ChoiceLink } from 'shared/components/choice-link.mjs' -export const ns = primaryNs +export const ns = sectionsNs -export const ModalMenu = () => { - const { t } = useTranslation(ns) - - return ( - -
-
-
-

{t('mainSections')}

- -
-
-

{t('currentSection')}

- -
+export const ModalMenu = () => ( + +
+
+
+ + + The sitemap lists all pages on this website. It can give you a good idea of what you can + find here. +
- - ) -} +
+
+) diff --git a/sites/lab/components/navigation/sections-menu.mjs b/sites/lab/components/navigation/sections-menu.mjs deleted file mode 100644 index 5472803a021..00000000000 --- a/sites/lab/components/navigation/sections-menu.mjs +++ /dev/null @@ -1,46 +0,0 @@ -import { useContext } from 'react' -import Link from 'next/link' -import { icons, ns as sectionsNs } from 'shared/components/navigation/primary.mjs' -import { useTranslation } from 'next-i18next' -import orderBy from 'lodash.orderby' -import { colors } from 'shared/components/header.mjs' -import { NavigationContext } from 'shared/context/navigation-context.mjs' -import { HelpIcon } from 'shared/components/icons.mjs' - -export const ns = sectionsNs - -export const SectionsMenu = () => { - const { t } = useTranslation(ns) - const { sections = false } = useContext(NavigationContext) - if (!sections) return null - - // Ensure each page as an `o` key so we can put them in order - const sortableSections = sections.map((s) => ({ ...s, o: s.o ? s.o : s.t })) - const output = [] - let i = 1 - for (const page of orderBy(sortableSections, ['o', 't'])) { - const item = ( - -
-
-

{page.t}

- {icons[page.s] ? icons[page.s]('w-10 h-10') : } -
-
- {t(page.s + 'About')} -
-
- - ) - i++ - output.push(item) - } - - return
{output}
-} diff --git a/sites/lab/prebuild.mjs b/sites/lab/prebuild.mjs new file mode 100644 index 00000000000..b43b205f9a5 --- /dev/null +++ b/sites/lab/prebuild.mjs @@ -0,0 +1,27 @@ +import { prebuildRunner } from '../shared/prebuild/runner.mjs' + +/* + * This handles the prebuild step for FreeSewing.dev + * It runs via an NPM run script, so in a pure NodeJS context + * + * See the org or dev site for an example with inline-comments + */ +prebuildRunner({ + site: 'lab', + prebuild: { + // Always prebuild + designs: true, + i18n: true, + navigation: true, + // Prebuild in production + favicon: 'productionOnly', + ogImages: 'productionOnly', + // Never prebuild + docs: false, + contributors: false, + crowdin: false, + git: false, + patrons: false, + posts: false, + }, +}) diff --git a/sites/shared/prebuild/navigation.mjs b/sites/shared/prebuild/navigation.mjs index b54b6379cbb..12fb003a926 100644 --- a/sites/shared/prebuild/navigation.mjs +++ b/sites/shared/prebuild/navigation.mjs @@ -1,13 +1,15 @@ +import allLanguages from '../../../config/languages.json' assert { type: 'json' } import path from 'path' import fs from 'fs' import set from 'lodash.set' import orderBy from 'lodash.orderby' import { extendSiteNav as dev } from './sitenav-dev.mjs' import { extendSiteNav as org } from './sitenav-org.mjs' +import { extendSiteNav as lab } from './sitenav-lab.mjs' import { pageHasChildren } from '../utils.mjs' import { header } from './shared.mjs' -const extendNav = { dev, org } +const extendNav = { dev, org, lab } /* * A method to recursively add the ordered slugs to the LUT @@ -47,7 +49,7 @@ export const orderedSlugLut = (nav) => { * Main method that does what needs doing */ export const prebuildNavigation = async (store) => { - const { docs, site, posts = false } = store + const { site, docs = false, posts = false } = store /* * Since this is written to disk and loaded as JSON, we minimize * the data to load by using the following 1-character keys: @@ -60,23 +62,24 @@ export const prebuildNavigation = async (store) => { const all = { sitenav: '', } - const locales = [] - for (const lang in docs) { - locales.push(lang) + const locales = docs ? Object.keys(docs) : allLanguages + for (const lang of locales) { sitenav[lang] = {} - // Handle docs - for (const slug of Object.keys(docs[lang]).sort()) { - const page = docs[lang][slug] - const val = { - t: page.t, - s: slug, + // Handle docs if there are any + if (docs[lang]) { + for (const slug of Object.keys(docs[lang]).sort()) { + const page = docs[lang][slug] + const val = { + t: page.t, + s: slug, + } + if (page.o) val.o = page.o + set(sitenav, [lang, ...slug.split('/')], val) } - if (page.o) val.o = page.o - set(sitenav, [lang, ...slug.split('/')], val) } - // Handle posts + // Handle posts if there are any if (posts) { for (const type in posts) { for (const [slug, post] of Object.entries(posts[type].posts[lang])) { @@ -118,6 +121,9 @@ export const prebuildNavigation = async (store) => { `${header}${all.sitenav}export const siteNav = { ${locales.join(',')} }` ) + // In the lab, there will be no navigation set in the store + if (!store.navigation) store.navigation = {} + // Update the store store.navigation.sitenav = sitenav diff --git a/sites/shared/prebuild/sitenav-lab.mjs b/sites/shared/prebuild/sitenav-lab.mjs new file mode 100644 index 00000000000..ed325b73789 --- /dev/null +++ b/sites/shared/prebuild/sitenav-lab.mjs @@ -0,0 +1,205 @@ +import { freeSewingConfig as conf } from '../config/freesewing.config.mjs' +import { designs, tags } from '../config/designs.mjs' +// Translation via i18next directly +import i18next from 'i18next' +// Actual translations for various languages +// EN +import accountEn from '../../lab/public/locales/en/sections.json' assert { type: 'json' } +import designsEn from '../../lab/public/locales/en/sections.json' assert { type: 'json' } +import sectionsEn from '../../lab/public/locales/en/sections.json' assert { type: 'json' } +import tagsEn from '../../lab/public/locales/en/sections.json' assert { type: 'json' } +// DE +import accountDe from '../../lab/public/locales/de/sections.json' assert { type: 'json' } +import designsDe from '../../lab/public/locales/de/sections.json' assert { type: 'json' } +import sectionsDe from '../../lab/public/locales/de/sections.json' assert { type: 'json' } +import tagsDe from '../../lab/public/locales/de/sections.json' assert { type: 'json' } +// ES +import accountEs from '../../lab/public/locales/es/sections.json' assert { type: 'json' } +import designsEs from '../../lab/public/locales/es/sections.json' assert { type: 'json' } +import sectionsEs from '../../lab/public/locales/es/sections.json' assert { type: 'json' } +import tagsEs from '../../lab/public/locales/es/sections.json' assert { type: 'json' } +// FR +import accountFr from '../../lab/public/locales/fr/sections.json' assert { type: 'json' } +import designsFr from '../../lab/public/locales/fr/sections.json' assert { type: 'json' } +import sectionsFr from '../../lab/public/locales/fr/sections.json' assert { type: 'json' } +import tagsFr from '../../lab/public/locales/fr/sections.json' assert { type: 'json' } +// NL +import accountNl from '../../lab/public/locales/nl/sections.json' assert { type: 'json' } +import designsNl from '../../lab/public/locales/nl/sections.json' assert { type: 'json' } +import sectionsNl from '../../lab/public/locales/nl/sections.json' assert { type: 'json' } +import tagsNl from '../../lab/public/locales/nl/sections.json' assert { type: 'json' } +// UK +import accountUk from '../../lab/public/locales/uk/sections.json' assert { type: 'json' } +import designsUk from '../../lab/public/locales/uk/sections.json' assert { type: 'json' } +import sectionsUk from '../../lab/public/locales/uk/sections.json' assert { type: 'json' } +import tagsUk from '../../lab/public/locales/uk/sections.json' assert { type: 'json' } + +/* + * Construct an object we can load the translations from + */ +const translations = { + en: { + account: accountEn, + design: designsEn, + sections: sectionsEn, + tags: tagsEn, + }, + de: { + account: accountDe, + design: designsDe, + sections: sectionsDe, + tags: tagsDe, + }, + es: { + account: accountEs, + design: designsEs, + sections: sectionsEs, + tags: tagsEs, + }, + fr: { + account: accountFr, + design: designsFr, + sections: sectionsFr, + tags: tagsFr, + }, + nl: { + account: accountNl, + design: designsNl, + sections: sectionsNl, + tags: tagsNl, + }, + uk: { + account: accountUk, + design: designsUk, + sections: sectionsUk, + tags: tagsUk, + }, +} + +/* Remember Mc_Shifton: + * Note: Set 'm' to truthy to show this as a main section in the side-navigation (optional) + * Note: Set 'c' to set the control level to hide things from users (optional) + * Note: Set 's' to the slug (optional insofar as it's not a real page (a spacer for the header)) + * Note: Set '_' to never show the page in the site navigation (like the tags pages) + * Note: Set 'h' to indicate this is a top-level page that should be hidden from the side-nav (like search) + * Note: Set 'i' when something should be included as top-level in the collapse side-navigation (optional) + * Note: Set 'f' to add the page to the footer + * Note: Set 't' to the title + * Note: Set 'o' to set the order (optional) + * Note: Set 'n' to mark this as a noisy entry that should always be closed unless active (like blog) + */ +export const extendSiteNav = (pages, lang) => { + const resources = {} + resources[lang] = translations[lang] + i18next.init({ + lng: lang, + resources, + }) + const { t } = i18next + + const addThese = { + designs: { + m: 1, + s: 'designs', + t: t('sections:designs'), + n: 1, + tags: { + _: 1, + s: 'designs/tags', + t: t('design:tags'), + o: 'aaa', + }, + }, + patterns: { + m: 1, + s: 'patterns', + t: t('sections:patterns'), + }, + sets: { + m: 1, + s: 'sets', + t: t('sections:sets'), + }, + docs: { + m: 1, + s: 'docs', + t: t('sections:docs'), + }, + code: { + m: 1, + s: 'code', + t: t('sections:code'), + }, + account: { + m: 1, + s: 'account', + t: t('sections:account'), + n: 1, + reload: { + s: `account/reload`, + t: t(`account:reload`), + }, + }, + // Top-level pages that are not in the sections menu + new: { + m: 1, + s: 'new', + h: 1, + t: t('sections:new'), + pattern: { + t: t('patternNew'), + s: 'new/pattern', + o: 10, + }, + }, + profile: { + s: 'profile', + h: 1, + t: t('yourProfile'), + }, + sitemap: { + s: 'sitemap', + h: 1, + t: t('sitemap'), + }, + } + + for (const section in conf.account.fields) { + for (const [field, controlScore] of Object.entries(conf.account.fields[section])) { + addThese.account[field] = { + s: `account/${field}`, + t: t(`account:${field}`), + c: controlScore, + } + } + } + + for (const design in designs) { + // addThese.designs[design] = { + // t: t(`designs:${design}.t`), + // s: `designs/${design}`, + // } + addThese.new.pattern[design] = { + s: `new/${design}`, + t: t(`account:generateANewThing`, { thing: t(`designs:${design}.t`) }), + } + } + + for (const tag of tags) { + addThese.designs.tags[tag] = { + s: `designs/tags/${tag}`, + t: t(`tags:${tag}`), + } + } + + // Set order on main sections + addThese.designs.o = 10 + addThese.patterns.o = 20 + addThese.sets.o = 30 + addThese.docs.o = 40 + addThese.code.o = 50 + addThese.account.o = 80 + addThese.new.o = 90 + + return { ...pages, ...addThese } +}