1
0
Fork 0

feat(lab): Add support for multiple versions in the lab

This commit is contained in:
Joost De Cock 2022-03-26 18:06:02 +01:00
parent 4db8ab099b
commit d4833fb6a4
14 changed files with 280 additions and 48 deletions

View file

@ -4,6 +4,7 @@ import Link from 'next/link'
import ThemePicker from 'shared/components/theme-picker.js'
import LocalePicker from 'shared/components/locale-picker.js'
import PatternPicker from 'site/components/pattern-picker.js'
import VersionPicker from 'site/components/version-picker.js'
import CloseIcon from 'shared/components/icons/close.js'
import MenuIcon from 'shared/components/icons/menu.js'
@ -67,6 +68,7 @@ const Header = ({ app }) => {
</button>
<div className="hidden sm:flex flex-row items-center">
<PatternPicker app={app} />
<VersionPicker app={app} />
</div>
<div className="hidden md:flex md:flex-row gap-2">
<Link href="/">

View file

@ -2,9 +2,12 @@ import React from 'react'
import DesignIcon from 'shared/components/icons/design.js'
import Link from 'next/link'
import { useTranslation } from 'next-i18next'
import useVersion from 'site/hooks/useVersion.js'
import { formatVersionUri } from './version-picker.js'
const PatternPicker = ({ app }) => {
const { t } = useTranslation(['common'])
const version = useVersion()
return (
<div className="dropdown">
@ -27,7 +30,7 @@ const PatternPicker = ({ app }) => {
</li>
{app.patterns[section].map(pattern => (
<li key={pattern}>
<Link href={`/${section}/${pattern}`}>
<Link href={formatVersionUri(version, pattern)}>
<button className="btn btn-ghost">
<span className="text-base-content">
{pattern}

View file

@ -0,0 +1,53 @@
import React from 'react'
import VersionsIcon from 'shared/components/icons/versions.js'
import Link from 'next/link'
import { useTranslation } from 'next-i18next'
import versions from 'site/versions.json'
import useVersion from 'site/hooks/useVersion.js'
export const defaultVersion = 'next'
export const formatVersionTitle = version => (!version || version === defaultVersion)
? `${defaultVersion} version`
: `Version ${version}`
export const formatVersionUri = (version=false, design=false) => {
if (!version && !design) return '/'
if (!version && design) return `/${design}`
if (version && !design) return `/v/${version}`
return `/v/${version}/${design}`
}
const PatternPicker = ({ app }) => {
const { t } = useTranslation(['common'])
const version = useVersion()
return (
<div className="dropdown">
<div tabIndex="0" className={`
m-0 btn btn-neutral flex flex-row gap-2
sm:btn-ghost
hover:bg-neutral hover:border-neutral-content
`}>
<VersionsIcon />
<span>{formatVersionTitle(version)}</span>
</div>
<ul tabIndex="0" className="p-2 shadow menu dropdown-content bg-base-100 rounded-box w-52 overflow-y-scroll navdrop">
{[defaultVersion, ...versions].map(v => (
<li key={v}>
<Link href={formatVersionUri(v)}>
<button className="btn btn-ghost">
<span className="text-base-content captialize">
{formatVersionTitle(v)}
</span>
</button>
</Link>
</li>
))}
</ul>
</div>
)
}
export default PatternPicker

View file

@ -6,9 +6,10 @@ import patterns from 'site/patterns.json'
// Locale and translation
import { useTranslation } from 'next-i18next'
import { capitalize } from 'shared/utils'
import useVersion from 'site/hooks/useVersion.js'
// Initial navigation
const initialNavigation = (t) => {
const initialNavigation = (t, version) => {
const base = {
accessories: {
__title: t('accessoryPatterns'),
@ -41,7 +42,7 @@ const initialNavigation = (t) => {
__title: capitalize(design),
__order: design,
__linktitle: capitalize(design),
__slug: `${type}/${design}`
__slug: `v/${version}/${design}`
}
}
}
@ -51,6 +52,9 @@ const initialNavigation = (t) => {
function useApp(full = true) {
// Version
const version = useVersion()
// Locale (aka language)
const { t } = useTranslation(['app'])
@ -65,7 +69,7 @@ function useApp(full = true) {
// React State
const [primaryMenu, setPrimaryMenu] = useState(false)
const [navigation, setNavigation] = useState(initialNavigation(t))
const [navigation, setNavigation] = useState(initialNavigation(t, version))
const [slug, setSlug] = useState('/')
const [pattern, setPattern] = useState(false)
const [loading, setLoading] = useState(false)

View file

@ -0,0 +1,16 @@
import { useRouter } from 'next/router'
export const defaultVersion = 'next'
const useVersion = () => {
const { pathname } = useRouter()
const chunks = pathname.split('/')
const version = (chunks[1] === 'v')
? chunks[2]
: defaultVersion
return version
}
export default useVersion

View file

@ -26,7 +26,7 @@ const config = {
// Aliases
config.resolve.alias.shared = path.resolve('../freesewing.shared/')
config.resolve.alias.site = path.resolve(`.`)
//config.resolve.alias.markdown = path.resolve(`../../markdown/${site}/`)
config.resolve.alias.lib = path.resolve(`./lib`)
config.resolve.alias.pkgs = path.resolve(`../`)
// This forces webpack to load the code from source, rather than compiled bundle
for (const pkg of pkgs) {

View file

@ -54,5 +54,6 @@
},
"engines": {
"node": ">=14.18.1"
}
},
"browserslist": [ "last 2 versions" ]
}

View file

@ -4,22 +4,23 @@ import Head from 'next/head'
import Link from 'next/link'
import About from 'site/components/about.js'
import { useTranslation } from 'next-i18next'
import { defaultVersion, formatVersionTitle, formatVersionUri } from 'site/components/version-picker.js'
const links = (section, list) => list.map(design => (
const links = (section, list, version) => list.map(design => (
<li key={design} className="">
<Link href={`/${section}/${design}`}>
<Link href={formatVersionUri(version, design)}>
<a className="text-secondary text-xl capitalize">{design}</a>
</Link>
</li>
))
const PatternListPageTemplate = ({ sections=false }) => {
const PatternListPageTemplate = ({ sections=false, version=false }) => {
const app = useApp()
const { t } = useTranslation(['app'])
if (sections === false) sections = Object.keys(app.patterns)
return (
<Page app={app} title="FreeSewing Lab" noSearch>
<Page app={app} title={`FreeSewing Lab: ${formatVersionTitle(version)}`}>
<Head>
<meta property="og:title" content="lab.FreeSewing.dev" key="title" />
<meta property="og:type" content="article" key='type' />
@ -39,7 +40,7 @@ const PatternListPageTemplate = ({ sections=false }) => {
<div key={section}>
<h2>{t(app.navigation[section].__title)}</h2>
<ul className="flex flex-row flex-wrap gap-2">
{links(section, app.patterns[section])}
{links(section, app.patterns[section], version)}
</ul>
</div>
)

View file

@ -1,5 +0,0 @@
import Template from 'site/page-templates/pattern-list.js'
const Page = props => <Template sections={['utilities']} />
export default Page

View file

@ -0,0 +1,4 @@
[
"plugin-bundle",
"plugin-bust"
]

View file

@ -0,0 +1,21 @@
[
"2.20.7",
"2.20.6",
"2.20.5",
"2.20.4",
"2.20.3",
"2.20.2",
"2.20.1",
"2.20.0",
"2.19.9",
"2.19.8",
"2.19.7",
"2.19.6",
"2.19.5",
"2.19.4",
"2.19.3",
"2.19.2",
"2.19.1",
"2.19.0"
]

View file

@ -1,6 +1,6 @@
const Design = () => (
<svg xmlns="http://www.w3.org/2000/svg" className="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="m11.975 2.9104c-1.5285 0-2.7845 1.2563-2.7845 2.7848 0 0.7494 0.30048 1.4389 0.78637 1.9394a0.79437 0.79437 0 0 0 0.0084 0.00839c0.38087 0.38087 0.74541 0.62517 0.94538 0.82483 0.19998 0.19966 0.25013 0.2645 0.25013 0.51907v0.65964l-9.1217 5.2665c-0.28478 0.16442-0.83603 0.46612-1.3165 0.9611-0.48047 0.49498-0.92451 1.3399-0.66684 2.2585 0.22026 0.78524 0.7746 1.3486 1.3416 1.5878 0.56697 0.23928 1.0982 0.23415 1.4685 0.23415h18.041c0.37033 0 0.90158 0.0051 1.4686-0.23415 0.56697-0.23928 1.1215-0.80261 1.3418-1.5878 0.25767-0.91859-0.18662-1.7636-0.66709-2.2585-0.48046-0.49498-1.0315-0.79669-1.3162-0.9611l-8.9844-5.1873v-0.73889c0-0.70372-0.35623-1.2837-0.71653-1.6435-0.35778-0.3572-0.70316-0.58503-0.93768-0.81789-0.20864-0.21601-0.33607-0.50298-0.33607-0.83033 0-0.67 0.52595-1.1962 1.1959-1.1962 0.67001 0 1.1962 0.5262 1.1962 1.1962a0.79429 0.79429 0 0 0 0.79434 0.79427 0.79429 0.79429 0 0 0 0.79427-0.79427c0-1.5285-1.2563-2.7848-2.7848-2.7848zm-0.06859 8.2927 8.9919 5.1914c0.28947 0.16712 0.69347 0.41336 0.94393 0.67138 0.25046 0.25803 0.31301 0.3714 0.24754 0.60483-0.10289 0.36677-0.19003 0.40213-0.35969 0.47373-0.16967 0.07161-0.47013 0.09952-0.80336 0.09952h-18.041c-0.33323 0-0.6337-0.02792-0.80336-0.09952-0.16967-0.07161-0.25675-0.10696-0.35963-0.47373-0.06548-0.23342-0.00303-0.3468 0.24748-0.60483 0.25046-0.25803 0.65471-0.50426 0.94418-0.67138z" />
<svg xmlns="http://www.w3.org/2000/svg" className="h-6 w-6" fill="currentColor" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={0.75} d="m11.975 2.9104c-1.5285 0-2.7845 1.2563-2.7845 2.7848 0 0.7494 0.30048 1.4389 0.78637 1.9394a0.79437 0.79437 0 0 0 0.0084 0.00839c0.38087 0.38087 0.74541 0.62517 0.94538 0.82483 0.19998 0.19966 0.25013 0.2645 0.25013 0.51907v0.65964l-9.1217 5.2665c-0.28478 0.16442-0.83603 0.46612-1.3165 0.9611-0.48047 0.49498-0.92451 1.3399-0.66684 2.2585 0.22026 0.78524 0.7746 1.3486 1.3416 1.5878 0.56697 0.23928 1.0982 0.23415 1.4685 0.23415h18.041c0.37033 0 0.90158 0.0051 1.4686-0.23415 0.56697-0.23928 1.1215-0.80261 1.3418-1.5878 0.25767-0.91859-0.18662-1.7636-0.66709-2.2585-0.48046-0.49498-1.0315-0.79669-1.3162-0.9611l-8.9844-5.1873v-0.73889c0-0.70372-0.35623-1.2837-0.71653-1.6435-0.35778-0.3572-0.70316-0.58503-0.93768-0.81789-0.20864-0.21601-0.33607-0.50298-0.33607-0.83033 0-0.67 0.52595-1.1962 1.1959-1.1962 0.67001 0 1.1962 0.5262 1.1962 1.1962a0.79429 0.79429 0 0 0 0.79434 0.79427 0.79429 0.79429 0 0 0 0.79427-0.79427c0-1.5285-1.2563-2.7848-2.7848-2.7848zm-0.06859 8.2927 8.9919 5.1914c0.28947 0.16712 0.69347 0.41336 0.94393 0.67138 0.25046 0.25803 0.31301 0.3714 0.24754 0.60483-0.10289 0.36677-0.19003 0.40213-0.35969 0.47373-0.16967 0.07161-0.47013 0.09952-0.80336 0.09952h-18.041c-0.33323 0-0.6337-0.02792-0.80336-0.09952-0.16967-0.07161-0.25675-0.10696-0.35963-0.47373-0.06548-0.23342-0.00303-0.3468 0.24748-0.60483 0.25046-0.25803 0.65471-0.50426 0.94418-0.67138z" />
</svg>
)

View file

@ -0,0 +1,7 @@
const VersionsIcon = props => (
<svg xmlns="http://www.w3.org/2000/svg" className="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
<path strokeLinecap="round" strokeLinejoin="round" d="M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10" />
</svg>
)
export default VersionsIcon

View file

@ -1,17 +1,12 @@
import fs from 'fs'
import fs_ from 'fs'
import path from 'path'
import axios from 'axios'
const loadPatterns = () => {
const patterns = fs.readFileSync(
path.resolve('..', 'freesewing.lab', 'patterns.json'),
'utf-8'
)
const fs = fs_.promises
return JSON.parse(patterns)
}
const pageTemplate = design => `
/* This page was auto-generated by the prebuild script
const header = `/*
*
* This page was auto-generated by the prebuild script
* Any changes you make to it will be lost on the next (pre)build.
*
* If you want to make changes, update the pageTemplate in:
@ -19,11 +14,27 @@ const pageTemplate = design => `
* packages/freesewing.shared/prebuild/lab.mjs
*
*/
`
const loadFromUnpkg = (design, version) => {
const start = Date.now()
return axios
.get(`https://unpkg.com/@freesewing/${design}@${version}/dist/index.mjs`)
.then(res => {
if (res.data) {
console.log(`Downloaded @freesewing/${design}@${version} in ${Date.now() - start}ms`)
return res.data
}
return false
})
.catch(err => false)
}
const pageTemplate = design => `${header}
import pattern from 'pkgs/${design}/src/index.js'
import { serverSideTranslations } from 'next-i18next/serverSideTranslations'
import PageTemplate from 'site/page-templates/workbench.js'
const Page = () => <PageTemplate pattern={pattern} />
const Page = (props) => <PageTemplate {...props} pattern={pattern} version="next"/>
export default Page
export async function getStaticProps({ locale }) {
@ -35,26 +46,140 @@ export async function getStaticProps({ locale }) {
}
`
/*
* Main method that does what needs doing
*/
export const prebuildLab = async (site) => {
const patterns = loadPatterns()
for (const section in patterns) {
console.log(`Generating pages for ${section} patterns`)
for (const design of patterns[section]) {
console.log(` - ${design}`)
const page = pageTemplate(design)
fs.writeFileSync(
path.resolve('..', `freesewing.lab`, 'pages', section, `${design}.js`),
page
)
fs.writeFileSync(
path.resolve('..', `freesewing.lab`, 'pages', `${design}.js`),
page
)
const versionedPageTemplate = (design, version) => `${header}
import pattern from 'lib/${version}/${design}.mjs'
import { serverSideTranslations } from 'next-i18next/serverSideTranslations'
import PageTemplate from 'site/page-templates/workbench.js'
const Page = (props) => <PageTemplate {...props} pattern={pattern} version="${version}"/>
export default Page
export async function getStaticProps({ locale }) {
return {
props: {
...(await serverSideTranslations(locale)),
}
}
}
`
const versionOverviewPage = (version) => `${header}
import { serverSideTranslations } from 'next-i18next/serverSideTranslations'
import Template from 'site/page-templates/pattern-list.js'
const Page = props => <Template {...props} version="${version}" />
export default Page
export async function getStaticProps({ locale }) {
return {
props: {
...(await serverSideTranslations(locale)),
}
}
}
`
/*
* Main method that does what needs doing
*/
const l = 'prebuild'
export const prebuildLab = async (site) => {
const promises = []
const allVersions = {}
// Load config
const versions = JSON.parse(await fs.readFile(
path.resolve('..', 'freesewing.lab', 'versions.json'),
'utf-8'
))
const patterns = JSON.parse(await fs.readFile(
path.resolve('..', 'freesewing.lab', 'patterns.json'),
'utf-8'
))
const plugins = JSON.parse(await fs.readFile(
path.resolve('..', 'freesewing.lab', 'plugins.json'),
'utf-8'
))
for (const section in patterns) {
// Iterate over sections
console.log(`Generating pages for ${section} patterns`)
for (const design of patterns[section]) {
// Generate pattern pages for next
console.log(` - ${design}`)
const page = pageTemplate(design)
await fs.mkdir(path.resolve('..', `freesewing.lab`, 'pages', 'v', 'next'), { recursive: true })
promises.push(
fs.writeFile(
path.resolve('..', `freesewing.lab`, 'pages', `${design}.js`),
page
),
fs.writeFile(
path.resolve('..', `freesewing.lab`, 'pages', section, `${design}.js`),
page
),
fs.writeFile(
path.resolve('..', `freesewing.lab`, 'pages', 'v', 'next', `${design}.js`),
page
)
)
// Download published versions from unpkg
allVersions[design] = []
for (const version of versions) {
// Assume that if the file is on disk, it's good to go (caching)
const file = path.resolve('..', `freesewing.lab`, 'lib', design, `${version}.js`)
let cached
try {
await fs.access(file)
cached = true
}
catch(err) {
cached = false
}
if (!cached) {
await fs.mkdir(path.resolve('..', `freesewing.lab`, 'lib', version), { recursive: true })
await fs.mkdir(path.resolve('..', `freesewing.lab`, 'pages', 'v', version), { recursive: true })
const code = (await loadFromUnpkg(design, version))
if (code) {
promises.push(
fs.writeFile(
path.resolve('..', `freesewing.lab`, 'lib', version, `${design}.mjs`),
code
),
fs.writeFile(
path.resolve('..', `freesewing.lab`, 'pages', 'v', version, `${design}.js`),
versionedPageTemplate(design, version)
),
)
allVersions[design].push(version)
} else console.log(`No ${version} for ${design}`)
}
}
}
}
// Also add version overview pages
for (const version of versions) {
// Assume that if the file is on disk, it's good to go (caching)
const page = path.resolve('..', `freesewing.lab`, 'pages', 'v', version, 'index.js')
let cached
try {
await fs.access(page)
cached = true
}
catch(err) {
cached = false
}
// Create page if it's not there already
if (!cached) promises.push(fs.writeFile(page, versionOverviewPage(version)))
}
await Promise.all(promises)
}