feat(lab): Add support for multiple versions in the lab
This commit is contained in:
parent
4db8ab099b
commit
d4833fb6a4
14 changed files with 280 additions and 48 deletions
|
@ -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="/">
|
||||
|
|
|
@ -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}
|
||||
|
|
53
packages/freesewing.lab/components/version-picker.js
Normal file
53
packages/freesewing.lab/components/version-picker.js
Normal 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
|
|
@ -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)
|
||||
|
|
16
packages/freesewing.lab/hooks/useVersion.js
Normal file
16
packages/freesewing.lab/hooks/useVersion.js
Normal 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
|
||||
|
|
@ -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) {
|
||||
|
|
|
@ -54,5 +54,6 @@
|
|||
},
|
||||
"engines": {
|
||||
"node": ">=14.18.1"
|
||||
}
|
||||
},
|
||||
"browserslist": [ "last 2 versions" ]
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
)
|
||||
|
|
|
@ -1,5 +0,0 @@
|
|||
import Template from 'site/page-templates/pattern-list.js'
|
||||
|
||||
const Page = props => <Template sections={['utilities']} />
|
||||
|
||||
export default Page
|
4
packages/freesewing.lab/plugins.json
Normal file
4
packages/freesewing.lab/plugins.json
Normal file
|
@ -0,0 +1,4 @@
|
|||
[
|
||||
"plugin-bundle",
|
||||
"plugin-bust"
|
||||
]
|
21
packages/freesewing.lab/versions.json
Normal file
21
packages/freesewing.lab/versions.json
Normal 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"
|
||||
]
|
||||
|
|
@ -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>
|
||||
)
|
||||
|
||||
|
|
7
packages/freesewing.shared/components/icons/versions.js
Normal file
7
packages/freesewing.shared/components/icons/versions.js
Normal 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
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue