chore: Adapt sites to recent changes
77
.gitignore
vendored
|
@ -3,10 +3,12 @@
|
|||
# .env
|
||||
.env
|
||||
|
||||
# Specifics
|
||||
export
|
||||
packages/plugin-theme/src/bundle.css.js
|
||||
packages/plugin-theme/css
|
||||
# Plugins
|
||||
plugins/plugin-theme/src/bundle.css.js
|
||||
plugins/plugin-theme/css
|
||||
|
||||
|
||||
# Components package build artifacts
|
||||
packages/components/Blockquote
|
||||
packages/components/Draft
|
||||
packages/components/DraftConfigurator
|
||||
|
@ -27,8 +29,12 @@ packages/components/withGist
|
|||
packages/components/withLanguage
|
||||
packages/components/withStorage
|
||||
packages/components/Workbench
|
||||
|
||||
# Code coverage reports
|
||||
packages/core/coverage
|
||||
packages/workbench
|
||||
packages/*/report
|
||||
|
||||
# Utils package build artifacts
|
||||
packages/utils/backend
|
||||
packages/utils/camelCase
|
||||
packages/utils/capitalize
|
||||
|
@ -54,60 +60,48 @@ packages/utils/tiler
|
|||
packages/utils/validateEmail
|
||||
packages/utils/validateTld
|
||||
|
||||
packages/*/example/yarn.lock
|
||||
packages/*/report
|
||||
# Any NPM or Yarn lock files
|
||||
designs/*/package-lock.json
|
||||
designs/*/yarn.lock
|
||||
packages/*/package-lock.json
|
||||
packages/*/yarn.lock
|
||||
packages/strapi/.cache
|
||||
plugins/*/package-lock.json
|
||||
plugins/*/yarn.lock
|
||||
|
||||
packages/freesewing.dev/public/locales/en/*.json
|
||||
# NPM lock files for sites (we use yarn)
|
||||
sites/*/package-lock.json
|
||||
|
||||
packages/freesewing.org/public/locales/en/*.json
|
||||
packages/freesewing.org/public/locales/es/*.json
|
||||
packages/freesewing.org/public/locales/de/*.json
|
||||
packages/freesewing.org/public/locales/fr/*.json
|
||||
packages/freesewing.org/public/locales/nl/*.json
|
||||
# Strapi cache
|
||||
sites/strapi/.cache
|
||||
|
||||
packages/freesewing.lab/public/locales/en/*.json
|
||||
packages/freesewing.lab/public/locales/es/*.json
|
||||
packages/freesewing.lab/public/locales/de/*.json
|
||||
packages/freesewing.lab/public/locales/fr/*.json
|
||||
packages/freesewing.lab/public/locales/nl/*.json
|
||||
# Sites prebuild artifacts
|
||||
sites/*/public/locales/*/*.json
|
||||
sites/*/public/feeds/*
|
||||
|
||||
packages/freesewing.org/public/feeds/*
|
||||
packages/freesewing.dev/public/feeds/*
|
||||
# Lab auto-generated pages
|
||||
sites/lab/lib
|
||||
sites/lab/pages
|
||||
|
||||
packages/freesewing.lab/lib
|
||||
packages/freesewing.lab/pages
|
||||
|
||||
# dependencies
|
||||
# Node dependencies
|
||||
node_modules
|
||||
|
||||
# build
|
||||
# Build folders
|
||||
dist
|
||||
build
|
||||
export
|
||||
|
||||
#prebuild
|
||||
# Prebuild files
|
||||
prebuild/*.json
|
||||
|
||||
# yarn
|
||||
# Yarn
|
||||
.yarn
|
||||
.yarnrc
|
||||
|
||||
# NextJS
|
||||
.next
|
||||
packages/freesewing.dev/out
|
||||
packages/freesewing.lab/out
|
||||
packages/freesewing.org/out
|
||||
packages/freesewing.dev/public/mdx
|
||||
packages/freesewing.lab/public/mdx
|
||||
packages/freesewing.org/public/mdx
|
||||
packages/freesewing.dev/prebuild
|
||||
packages/freesewing.lab/prebuild
|
||||
packages/freesewing.org/prebuild
|
||||
|
||||
# Mock registry (verdaccio)
|
||||
.registry
|
||||
sites/*/out
|
||||
sites/*/public/mdx
|
||||
sites/*/prebuild
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
|
@ -127,9 +121,6 @@ packages/i18n/sort.sh
|
|||
scripts/taskrunner.sh
|
||||
scripts/verdaccio.sh
|
||||
|
||||
# Local Netlify folder
|
||||
.netlify
|
||||
|
||||
# Don't ignore the specific Points.dist() docs folder
|
||||
!markdown/dev/reference/api/point/dist
|
||||
|
||||
|
|
906
CHANGELOG.md
|
@ -45,3 +45,5 @@ export const degreeMeasurements = ['shoulderSlope']
|
|||
|
||||
/* Helper method to determine whether a measurement uses degrees */
|
||||
export const isDegreeMeasurement = (measie) => degreeMeasurements.indexOf(measie) !== -1
|
||||
|
||||
|
|
@ -1,12 +1,24 @@
|
|||
{
|
||||
"aaron": "A FreeSewing pattern for a A-shirt or tank top",
|
||||
"albert": "A FreeSewing pattern for an apron",
|
||||
"bee": "A FreeSewing pattern for a bikini top",
|
||||
"bella": "A FreeSewing pattern for a womenswear bodice block",
|
||||
"accessories": {
|
||||
"benjamin": "A FreeSewing pattern for a bow tie",
|
||||
"florence": "A FreeSewing pattern for a face mask",
|
||||
"florent": "A FreeSewing pattern for a flat cap",
|
||||
"hi": "A FreeSewing pattern for a shark plush toy",
|
||||
"holmes": "A FreeSewing pattern for a Sherlock Holmes hat",
|
||||
"hortensia": "A FreeSewing pattern for a handbag",
|
||||
"trayvon": "A FreeSewing pattern for a tie"
|
||||
},
|
||||
"blocks": {
|
||||
"bella": "A FreeSewing pattern for a womenswear bodice block",
|
||||
"bent": "A FreeSewing pattern for a menswear body block with a two-part sleeve",
|
||||
"breanna": "A FreeSewing pattern for a basic body block for womenswear",
|
||||
"brian": "A FreeSewing pattern for a basic body block for menswear",
|
||||
"titan": "A FreeSewing pattern for a unisex trouser block"
|
||||
},
|
||||
"garments": {
|
||||
"aaron": "A FreeSewing pattern for a A-shirt or tank top",
|
||||
"albert": "A FreeSewing pattern for an apron",
|
||||
"bee": "A FreeSewing pattern for a bikini top",
|
||||
"bruce": "A FreeSewing pattern for boxer briefs",
|
||||
"carlita": "A FreeSewing pattern for Sherlock Holmes cosplay; Or just a nice long coat",
|
||||
"carlton": "A FreeSewing pattern for Sherlock Holmes cosplay; Or just a nice long coat",
|
||||
|
@ -14,22 +26,13 @@
|
|||
"charlie": "A FreeSewing pattern for chino trousers",
|
||||
"cornelius": "A FreeSewing pattern for cycling breeches, based on the Keystone drafting system",
|
||||
"diana": "A FreeSewing pattern for a top with a draped neck",
|
||||
"examples": "A FreeSewing pattern holding examples for our documentation",
|
||||
"florence": "A FreeSewing pattern for a face mask",
|
||||
"florent": "A FreeSewing pattern for a flat cap",
|
||||
"hi": "A FreeSewing pattern for a shark plush toy",
|
||||
"holmes": "A FreeSewing pattern for a Sherlock Holmes hat",
|
||||
"hortensia": "A FreeSewing pattern for a handbag",
|
||||
"huey": "A FreeSewing pattern for a zip-up hoodie",
|
||||
"hugo": "A FreeSewing pattern for a hooded jumper with raglan sleeves",
|
||||
"jaeger": "A FreeSewing pattern for a sport coat style jacket",
|
||||
"legend": "A FreeSewing pattern to document pattern notation",
|
||||
"lucy": "A FreeSewing pattern for a historical tie-on pocket",
|
||||
"lunetius": "A FreeSewing pattern for a lacerna, a historical Roman cloak",
|
||||
"paco": "A FreeSewing pattern for summer pants",
|
||||
"penelope": "A FreeSewing pattern for a pencil skirt",
|
||||
"plugintest": "A FreeSewing pattern to test (y)our plugins",
|
||||
"rendertest": "A FreeSewing pattern to test (y)our render engine our CSS",
|
||||
"sandy": "A FreeSewing pattern for a circle skirt",
|
||||
"shin": "A FreeSewing pattern for swim trunks",
|
||||
"simon": "A FreeSewing pattern for a button down shirt",
|
||||
|
@ -39,13 +42,18 @@
|
|||
"teagan": "A FreeSewing pattern for a T-shirt",
|
||||
"theo": "A FreeSewing pattern for classic trousers",
|
||||
"tiberius": "A FreeSewing pattern for a tunica, a historical Roman tunic",
|
||||
"titan": "A FreeSewing pattern for a unisex trouser block",
|
||||
"trayvon": "A FreeSewing pattern for a tie",
|
||||
"tutorial": "A FreeSewing pattern for a baby bib that's used in our tutorial",
|
||||
"unice": "A FreeSewing pattern for a basic, highly-customizable underwear pattern",
|
||||
"ursula": "A FreeSewing pattern for a basic, highly-customizable underwear pattern",
|
||||
"wahid": "A FreeSewing pattern for a classic fitted waistcoat",
|
||||
"walburga": "A FreeSewing pattern for a wappenrock (tabard/surcoat), a historical European/medieval (ish) garment",
|
||||
"waralee": "A FreeSewing pattern for wrap pants",
|
||||
"yuri": "A FreeSewing pattern for a fancy zipless sweater based on the Huey hoodie"
|
||||
},
|
||||
"utilities": {
|
||||
"examples": "A FreeSewing pattern holding examples for our documentation",
|
||||
"legend": "A FreeSewing pattern to document pattern notation",
|
||||
"plugintest": "A FreeSewing pattern to test (y)our plugins",
|
||||
"rendertest": "A FreeSewing pattern to test (y)our render engine our CSS",
|
||||
"tutorial": "A FreeSewing pattern for a baby bib that's used in our tutorial"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import designs from './designs.json'
|
||||
import designsByType from './designs.json'
|
||||
import packages from './packages.json'
|
||||
import plugins from './plugins.json'
|
||||
import sites from './sites.json'
|
||||
|
@ -8,8 +8,15 @@ const unpack = (obj, folder) => Object.fromEntries(
|
|||
Object.keys(obj).map(name => [name, { name, folder, description: obj[name], type: folder.slice(0, -1) } ])
|
||||
)
|
||||
|
||||
const designs = {
|
||||
...designsByType.accessories,
|
||||
...designsByType.blocks,
|
||||
...designsByType.garments,
|
||||
...designsByType.utilities,
|
||||
}
|
||||
|
||||
// Re-Export imported JSON
|
||||
export { designs, packages, plugins, sites }
|
||||
export { designs, designsByType, packages, plugins, sites }
|
||||
|
||||
// All software
|
||||
export const software = {
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
{
|
||||
"backend": "FreeSewing backend",
|
||||
"freesewing.dev": "FreeSewing website with documentation for contributors & developers",
|
||||
"freesewing.lab": "FreeSewing website to test various patterns",
|
||||
"freesewing.org": "FreeSewing website",
|
||||
"freesewing.shared": "Shared code and React components for different websites",
|
||||
"dev": "FreeSewing website with documentation for contributors & developers",
|
||||
"lab": "FreeSewing website to test various patterns",
|
||||
"org": "FreeSewing website",
|
||||
"shared": "Shared code and React components for different websites",
|
||||
"strapi": "Freesewing's Strapi instance",
|
||||
"svgtopdf": "FreeSewing on-demand tiler"
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@ import glob from 'glob'
|
|||
import yaml from 'js-yaml'
|
||||
import chalk from 'chalk'
|
||||
import mustache from 'mustache'
|
||||
import { capitalize } from '../sites/freesewing.shared/utils.mjs'
|
||||
import { capitalize } from '../sites/shared/utils.mjs'
|
||||
import conf from '../lerna.json'
|
||||
const { version } = conf
|
||||
import {
|
||||
|
|
|
@ -4,8 +4,6 @@ const config = {
|
|||
index: 'freesewing.dev',
|
||||
key: '589c7a7e4d9c95a4f12868581259bf3a', // Search-only API key
|
||||
},
|
||||
strapi: 'https://posts.freesewing.org',
|
||||
monorepo: 'https://github.com/freesewing/freesewing'
|
||||
}
|
||||
|
||||
export default config
|
37
sites/dev/components/layouts/docs.js
Normal file
|
@ -0,0 +1,37 @@
|
|||
import { useRouter } from 'next/router'
|
||||
import Link from 'next/link'
|
||||
// Shared components
|
||||
import Aside from 'shared/components/navigation/aside'
|
||||
import ThemePicker from 'shared/components/theme-picker'
|
||||
import Breadcrumbs from 'shared/components/breadcrumbs.js'
|
||||
import { getCrumbs } from 'shared/utils'
|
||||
|
||||
const DefaultLayout = ({ app, title=false, crumbs=false, children=[] }) => {
|
||||
const router = useRouter()
|
||||
const slug = router.asPath.slice(1)
|
||||
const breadcrumbs = crumbs
|
||||
? crumbs
|
||||
: getCrumbs(app, slug, title)
|
||||
|
||||
return (
|
||||
<div className="m-auto flex flex-row justify-center">
|
||||
<Aside app={app} slug={slug} before={<ThemePicker app={app} className="block sm:hidden"/>}/>
|
||||
<section className="py-28 md:py-36 max-w-7xl px-6 xl:pl-8 2xl:pl-16">
|
||||
<div>
|
||||
{title && (
|
||||
<>
|
||||
<Breadcrumbs title={title} crumbs={breadcrumbs} />
|
||||
{title
|
||||
? <h1>{title}</h1>
|
||||
: <h1>{app.getTitle(slug)}</h1>
|
||||
}
|
||||
</>
|
||||
)}
|
||||
{children}
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default DefaultLayout
|
|
@ -5,7 +5,7 @@ import { useRouter } from 'next/router'
|
|||
import algoliasearch from 'algoliasearch/lite';
|
||||
import { useHotkeys } from 'react-hotkeys-hook'
|
||||
import { InstantSearch, connectHits, connectHighlight, connectSearchBox } from 'react-instantsearch-dom'
|
||||
import config from 'site/freesewing.config.js'
|
||||
import config from 'site/algolia.config.mjs'
|
||||
|
||||
const searchClient = algoliasearch(config.algolia.app, config.algolia.key)
|
||||
|
3
sites/dev/i18n.config.mjs
Normal file
|
@ -0,0 +1,3 @@
|
|||
import i18n from '../shared/config/i18n.config.mjs'
|
||||
|
||||
export default i18n()
|
|
@ -1,4 +1,4 @@
|
|||
import configBuilder from '../freesewing.shared/config/next.mjs'
|
||||
import configBuilder from '../shared/config/next.mjs'
|
||||
import i18nConfig from './next-i18next.config.js'
|
||||
|
||||
const config = configBuilder('dev')
|
|
@ -3,16 +3,14 @@
|
|||
"version": "2.21.0-rc.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev -p 3002",
|
||||
"dev": "node --experimental-json-modules ./node_modules/.bin/next dev -p 8000",
|
||||
"develop": "next dev -p 3002",
|
||||
"prebuild": "SITE=dev node ../freesewing.shared/prebuild/index.mjs",
|
||||
"prebuild": "SITE=dev node --experimental-json-modules ../shared/prebuild/index.mjs",
|
||||
"build": "next build",
|
||||
"export": "yarn prebuild && next build && next export && node scripts/algolia.mjs",
|
||||
"start": "next start -p 3002",
|
||||
"lint": "next lint",
|
||||
"testdeploy": "next build && next export && netlify-cli deploy",
|
||||
"deploy": "next build && next export && netlify-cli deploy --prod",
|
||||
"serve": "pm2 start npm --name 'freesewing.dev' -- run start"
|
||||
"serve": "pm2 start npm --name 'dev' -- run start"
|
||||
},
|
||||
"dependencies": {
|
||||
"@heroicons/react": "^1.0.5",
|
107
sites/dev/pages/blog/index.js
Normal file
|
@ -0,0 +1,107 @@
|
|||
import Page from 'site/components/wrappers/page.js'
|
||||
import useApp from 'site/hooks/useApp.js'
|
||||
import Link from 'next/link'
|
||||
import TimeAgo from 'react-timeago'
|
||||
import { strapiHost } from 'shared/config/freesewing.mjs'
|
||||
import { strapiImage } from 'shared/utils'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
import { serverSideTranslations } from 'next-i18next/serverSideTranslations'
|
||||
|
||||
const strapi = "https://posts.freesewing.org"
|
||||
const textShadow = {
|
||||
style: {
|
||||
textShadow: "1px 1px 1px #000000, -1px -1px 1px #000000, 1px -1px 1px #000000, -1px 1px 1px #000000, 2px 2px 1px #000000"
|
||||
}
|
||||
}
|
||||
|
||||
const Preview = ({ app, post }) => (
|
||||
<div className="shadow rounded-lg">
|
||||
<Link href={`/blog/${post.slug}`}>
|
||||
<a title={post.title} className="hover:underline">
|
||||
<div className="bg-base-100 w-full aspect-video shadow flex flex-column items-end rounded-lg" style={{
|
||||
backgroundImage: `url(${strapi}${post.image.sizes.medium.url})`,
|
||||
backgroundSize: 'cover',
|
||||
}}>
|
||||
<div className="grow"></div>
|
||||
<div className="text-right mb-3 lg:mb-8">
|
||||
<div className={`
|
||||
bg-neutral text-neutral-content bg-opacity-40 text-right
|
||||
px-4 py-1
|
||||
lg:px-8 lg:py-4
|
||||
|
||||
`}>
|
||||
<h5 className={`
|
||||
text-neutral-content
|
||||
text-xl font-bold
|
||||
md:text-2xl md:font-normal
|
||||
xl:text-3xl
|
||||
`} {...textShadow}
|
||||
>
|
||||
{post.title}
|
||||
</h5>
|
||||
<p className={`
|
||||
hidden md:block
|
||||
m-0 p-1 -mt-2
|
||||
text-neutral-content
|
||||
leading-normal text-sm font-normal
|
||||
opacity-70
|
||||
`}{ ...textShadow}>
|
||||
<TimeAgo date={post.date} /> by <strong>{post.author}</strong>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</Link>
|
||||
</div>
|
||||
)
|
||||
|
||||
const BlogIndexPage = (props) => {
|
||||
const app = useApp()
|
||||
const { t } = useTranslation()
|
||||
|
||||
return (
|
||||
<Page app={app} title={t('blog')} slug='blog'>
|
||||
<div className="grid grid-cols-1 gap-4 lg:grid-cols-2 max-w-7xl">
|
||||
{props.posts.map(post => <Preview app={app} post={post} key={post.slug}/>)
|
||||
}
|
||||
</div>
|
||||
</Page>
|
||||
)
|
||||
}
|
||||
|
||||
export default BlogIndexPage
|
||||
|
||||
/*
|
||||
* getStaticProps() is used to fetch data at build-time.
|
||||
*
|
||||
* On this page, it is loading the blog content from strapi.
|
||||
*
|
||||
* This, in combination with getStaticPaths() below means this
|
||||
* page will be used to render/generate all blog content.
|
||||
*
|
||||
* To learn more, see: https://nextjs.org/docs/basic-features/data-fetching
|
||||
*/
|
||||
export async function getStaticProps({ params, locale }) {
|
||||
|
||||
const posts = await fetch(
|
||||
`${strapiHost}/blogposts?_locale=en&_sort=date:DESC&dev_eq=true`
|
||||
)
|
||||
.then(response => response.json())
|
||||
.then(data => data)
|
||||
.catch(err => console.log(err))
|
||||
|
||||
return {
|
||||
props: {
|
||||
posts: posts.map(post => ({
|
||||
slug: post.slug,
|
||||
title: post.title,
|
||||
date: post.date,
|
||||
author: post.author.displayname,
|
||||
image: strapiImage(post.image, ['medium']),
|
||||
})),
|
||||
...(await serverSideTranslations(locale)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
5
sites/dev/postcss.config.js
Normal file
|
@ -0,0 +1,5 @@
|
|||
// Can't seem to make this work as ESM
|
||||
const config = require('../shared/config/postcss.config.js')
|
||||
|
||||
module.exports = config
|
||||
|
Before Width: | Height: | Size: 7.7 KiB After Width: | Height: | Size: 7.7 KiB |
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 9.2 KiB After Width: | Height: | Size: 9.2 KiB |
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 22 KiB |
Before Width: | Height: | Size: 5.2 KiB After Width: | Height: | Size: 5.2 KiB |
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 25 KiB |
Before Width: | Height: | Size: 118 KiB After Width: | Height: | Size: 118 KiB |
Before Width: | Height: | Size: 313 KiB After Width: | Height: | Size: 313 KiB |
4
sites/dev/tailwind.config.js
Normal file
|
@ -0,0 +1,4 @@
|
|||
// Can't seem to make this work as ESM
|
||||
const config = require('../shared/config/tailwind.config.js')
|
||||
|
||||
module.exports = config
|
|
@ -1,78 +0,0 @@
|
|||
import React from 'react'
|
||||
import { useRouter } from 'next/router'
|
||||
import Link from 'next/link'
|
||||
// Shared components
|
||||
import Logo from 'shared/components/logos/freesewing.js'
|
||||
import Aside from 'shared/components/navigation/aside'
|
||||
import get from 'lodash.get'
|
||||
|
||||
const PageTitle = ({ app, slug, title }) => {
|
||||
if (title) return <h1>{title}</h1>
|
||||
if (slug) return <h1>{get(app.navigation, slug.split('/')).__title}</h1>
|
||||
|
||||
return <h1>FIXME: This page has no title</h1>
|
||||
}
|
||||
|
||||
const Breadcrumbs = ({ app, slug=false, title }) => {
|
||||
if (!slug) return null
|
||||
const crumbs = []
|
||||
const chunks = slug.split('/')
|
||||
for (const i in chunks) {
|
||||
const j = parseInt(i)+parseInt(1)
|
||||
const page = get(app.navigation, chunks.slice(0,j))
|
||||
if (page) crumbs.push([page.__linktitle, '/'+chunks.slice(0,j).join('/'), (j < chunks.length)])
|
||||
}
|
||||
|
||||
return (
|
||||
<ul className="flex flex-row flex-wrap gap-2 font-bold">
|
||||
<li>
|
||||
<Link href="/">
|
||||
<a title="To the homepage" className="text-base-content">
|
||||
<Logo size={24} fill="currentColor" stroke={false}/>
|
||||
</a>
|
||||
</Link>
|
||||
</li>
|
||||
{crumbs.map(crumb => (
|
||||
<React.Fragment key={crumb[1]}>
|
||||
<li className="text-base-content">»</li>
|
||||
<li>
|
||||
{crumb[2]
|
||||
? (
|
||||
<Link href={crumb[1]}>
|
||||
<a title={crumb[0]} className="text-secondary hover:text-secondary-focus">
|
||||
{crumb[0]}
|
||||
</a>
|
||||
</Link>
|
||||
)
|
||||
: <span className="text-base-content">{crumb[0]}</span>
|
||||
}
|
||||
</li>
|
||||
</React.Fragment>
|
||||
))}
|
||||
</ul>
|
||||
)
|
||||
}
|
||||
|
||||
const DefaultLayout = ({ app, title=false, children=[] }) => {
|
||||
const router = useRouter()
|
||||
const slug = router.asPath.slice(1)
|
||||
|
||||
return (
|
||||
<div className="m-auto flex flex-row justify-center">
|
||||
<Aside app={app} slug={slug} />
|
||||
<section className="py-28 md:py-36">
|
||||
<div>
|
||||
{title && (
|
||||
<div className="px-8 xl:pl-8 2xl:pl-16">
|
||||
<Breadcrumbs app={app} slug={slug} title={title} />
|
||||
<PageTitle app={app} slug={slug} title={title} />
|
||||
</div>
|
||||
)}
|
||||
{children}
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default DefaultLayout
|
|
@ -1,115 +0,0 @@
|
|||
import React from 'react'
|
||||
import { useState } from 'react'
|
||||
import { useRouter } from 'next/router'
|
||||
import Link from 'next/link'
|
||||
// Shared components
|
||||
import Logo from 'shared/components/logos/freesewing.js'
|
||||
import PrimaryNavigation from 'shared/components/navigation/primary'
|
||||
import get from 'lodash.get'
|
||||
import Right from 'shared/components/icons/right.js'
|
||||
import Left from 'shared/components/icons/left.js'
|
||||
// Site components
|
||||
import Header from 'site/components/header'
|
||||
import Footer from 'site/components/footer'
|
||||
import Search from 'site/components/search'
|
||||
|
||||
const PageTitle = ({ app, slug, title }) => {
|
||||
if (title) return <h1>{title}</h1>
|
||||
if (slug) return <h1>{get(app.navigation, slug.split('/')).__title}</h1>
|
||||
|
||||
return <h1>FIXME: This page has no title</h1>
|
||||
}
|
||||
|
||||
const Breadcrumbs = ({ app, slug=false, title }) => {
|
||||
if (!slug) return null
|
||||
const crumbs = []
|
||||
const chunks = slug.split('/')
|
||||
for (const i in chunks) {
|
||||
const j = parseInt(i)+parseInt(1)
|
||||
const page = get(app.navigation, chunks.slice(0,j))
|
||||
if (page) crumbs.push([page.__linktitle, '/'+chunks.slice(0,j).join('/'), (j < chunks.length)])
|
||||
}
|
||||
|
||||
return (
|
||||
<ul className="flex flex-row flex-wrap gap-2 font-bold">
|
||||
<li>
|
||||
<Link href="/">
|
||||
<a title="To the homepage" className="text-base-content">
|
||||
<Logo size={24} fill="currentColor" stroke={false}/>
|
||||
</a>
|
||||
</Link>
|
||||
</li>
|
||||
{crumbs.map(crumb => (
|
||||
<React.Fragment key={crumb[1]}>
|
||||
<li className="text-base-content">»</li>
|
||||
<li>
|
||||
{crumb[2]
|
||||
? (
|
||||
<Link href={crumb[1]}>
|
||||
<a title={crumb[0]} className="text-secondary hover:text-secondary-focus">
|
||||
{crumb[0]}
|
||||
</a>
|
||||
</Link>
|
||||
)
|
||||
: <span className="text-base-content">{crumb[0]}</span>
|
||||
}
|
||||
</li>
|
||||
</React.Fragment>
|
||||
))}
|
||||
</ul>
|
||||
)
|
||||
}
|
||||
|
||||
const LayoutWrapper = ({
|
||||
app,
|
||||
title=false,
|
||||
children=[],
|
||||
search,
|
||||
setSearch,
|
||||
noSearch=false,
|
||||
workbench=false,
|
||||
AltMenu=null,
|
||||
}) => {
|
||||
const startNavigation = () => {
|
||||
app.startLoading()
|
||||
// Force close of menu on mobile if it is open
|
||||
if (app.primaryNavigation) app.setPrimaryNavigation(false)
|
||||
// Force close of search modal if it is open
|
||||
if (search) setSearch(false)
|
||||
}
|
||||
|
||||
const router = useRouter()
|
||||
router.events?.on('routeChangeStart', startNavigation)
|
||||
router.events?.on('routeChangeComplete', () => app.stopLoading())
|
||||
const slug = router.asPath.slice(1)
|
||||
const [collapsePrimaryNav, setCollapsePrimaryNav] = useState(workbench || false)
|
||||
const [collapseAltMenu, setCollapseAltMenu] = useState(false)
|
||||
|
||||
return (
|
||||
<div className={`
|
||||
flex flex-col justify-between
|
||||
min-h-screen
|
||||
bg-base-100
|
||||
`}>
|
||||
<Header app={app} setSearch={setSearch} />
|
||||
<main className="grow">{children}</main>
|
||||
{!noSearch && search && (
|
||||
<>
|
||||
<div className={`
|
||||
fixed w-full max-h-screen bg-base-100 top-0 z-30 pt-0 pb-16 px-8
|
||||
md:rounded-lg md:top-24
|
||||
md:max-w-xl md:m-auto md:inset-x-12
|
||||
md:max-w-2xl
|
||||
lg:max-w-4xl
|
||||
`}>
|
||||
<Search app={app} search={search} setSearch={setSearch}/>
|
||||
</div>
|
||||
<div className="fixed top-0 left-0 w-full min-h-screen bg-neutral z-20 bg-opacity-70"></div>
|
||||
</>
|
||||
)}
|
||||
<Footer app={app} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default LayoutWrapper
|
|
@ -1,3 +0,0 @@
|
|||
import i18n from '../freesewing.shared/config/i18n.config.mjs'
|
||||
|
||||
export default i18n()
|
|
@ -1,88 +0,0 @@
|
|||
import Page from 'shared/components/wrappers/page.js'
|
||||
import useApp from 'site/hooks/useApp.js'
|
||||
import Link from 'next/link'
|
||||
import { posts } from 'site/prebuild/strapi.blog.en.js'
|
||||
import orderBy from 'lodash.orderby'
|
||||
import TimeAgo from 'react-timeago'
|
||||
import Head from 'next/head'
|
||||
import HelpUs from 'site/components/help-us.js'
|
||||
import { serverSideTranslations } from 'next-i18next/serverSideTranslations'
|
||||
|
||||
const strapi = "https://posts.freesewing.org"
|
||||
|
||||
const Preview = ({ app, post }) => (
|
||||
<div className="theme-gradient p-1 hover:p-0 hover:mb-1 hover:pointer transition-all">
|
||||
<Link href={`/blog/${post.slug}`}>
|
||||
<a title={post.title} className="hover:underline">
|
||||
<div className="bg-base-100 w-full aspect-video shadow flex flex-column items-end" style={{
|
||||
backgroundImage: `url(${strapi}${post.img})`,
|
||||
backgroundSize: 'cover',
|
||||
}}>
|
||||
<div className="grow"></div>
|
||||
<div className="text-right">
|
||||
<div className={`
|
||||
bg-neutral text-neutral-content
|
||||
bg-opacity-80
|
||||
px-4 text-right
|
||||
`}>
|
||||
<h5 className={`
|
||||
text-neutral-content
|
||||
text-xl font-normal
|
||||
md:text-2xl md:font-light
|
||||
`}>
|
||||
{post.title}
|
||||
</h5>
|
||||
<p className={`
|
||||
m-0 p-1 -mt-2
|
||||
text-neutral-content
|
||||
opacity-50
|
||||
leading-normal text-sm font-normal
|
||||
`}>
|
||||
<TimeAgo date={post.date} /> by {post.author}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</Link>
|
||||
</div>
|
||||
)
|
||||
|
||||
const BlogIndexPage = (props) => {
|
||||
const app = useApp()
|
||||
|
||||
return (
|
||||
<Page app={app} title='FreeSewing Development Blog' slug='blog'>
|
||||
<Head>
|
||||
<meta property="og:title" content="FreeSewing Developers Blog" key="title" />
|
||||
<meta property="og:type" content="article" key='type' />
|
||||
<meta property="og:description" content="Content for developers and contributors alike. Strictly no sewing stuff" key='type' />
|
||||
<meta property="og:article:author" content='Joost De Cock' key='author' />
|
||||
<meta property="og:image" content="https://canary.backend.freesewing.org/og-img/en/dev/blog" key='image' />
|
||||
<meta property="og:image:type" content="image/png" />
|
||||
<meta property="og:image:width" content="1200" />
|
||||
<meta property="og:image:height" content="630" />
|
||||
<meta property="og:url" content="https://freesewing.dev/blog" key='url' />
|
||||
<meta property="og:locale" content="en_US" key='locale' />
|
||||
<meta property="og:site_name" content="freesewing.dev" key='site' />
|
||||
</Head>
|
||||
<div className="grid grid-cols-1 gap-4 lg:grid-cols-2">
|
||||
{Object.values(orderBy(posts, ['date'], ['desc']))
|
||||
.map(post => <Preview app={app} post={post} key={post.slug}/>)
|
||||
}
|
||||
</div>
|
||||
<HelpUs slug='/blog' />
|
||||
</Page>
|
||||
)
|
||||
}
|
||||
|
||||
export default BlogIndexPage
|
||||
|
||||
export async function getStaticProps({ locale }) {
|
||||
return {
|
||||
props: {
|
||||
...(await serverSideTranslations('en')),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
import i18n from '../freesewing.shared/config/i18n.config.mjs'
|
||||
|
||||
export default i18n()
|
|
@ -1,46 +0,0 @@
|
|||
import path from 'path'
|
||||
import { readdirSync } from 'fs'
|
||||
import i18nConfig from './next-i18next.config.js'
|
||||
|
||||
const getDirectories = source =>
|
||||
readdirSync(source, { withFileTypes: true })
|
||||
.filter(dirent => dirent.isDirectory())
|
||||
.map(dirent => dirent.name)
|
||||
|
||||
const pkgs = getDirectories(path.resolve(`../`))
|
||||
|
||||
const config = {
|
||||
experimental: {
|
||||
externalDir: true,
|
||||
},
|
||||
i18n: i18nConfig.i18n,
|
||||
pageExtensions: [ 'js' ],
|
||||
webpack: (config, options) => {
|
||||
|
||||
// YAML support
|
||||
config.module.rules.push({
|
||||
test: /\.ya?ml$/,
|
||||
type: 'json',
|
||||
use: 'yaml-loader'
|
||||
})
|
||||
|
||||
// Suppress warnings about importing version from package.json
|
||||
// We'll deal with it in v3 of FreeSewing
|
||||
config.ignoreWarnings = [
|
||||
/only default export is available soon/
|
||||
]
|
||||
|
||||
// Aliases
|
||||
config.resolve.alias.shared = path.resolve('../freesewing.shared/')
|
||||
config.resolve.alias.site = path.resolve(`.`)
|
||||
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) {
|
||||
config.resolve.alias[`@freesewing/${pkg}$`] = path.resolve(`../${pkg}/src/index.js`)
|
||||
}
|
||||
|
||||
return config
|
||||
}
|
||||
}
|
||||
export default config
|
|
@ -1,5 +0,0 @@
|
|||
// Can't seem to make this work as ESM
|
||||
const config = require('../freesewing.shared/config/postcss.config.js')
|
||||
|
||||
module.exports = config
|
||||
|
|
@ -1 +0,0 @@
|
|||
../../../freesewing.dev/public/img/splash.jpg
|
|
@ -1,4 +0,0 @@
|
|||
// Can't seem to make this work as ESM
|
||||
const config = require('../freesewing.shared/config/tailwind.config.js')
|
||||
|
||||
module.exports = config
|
|
@ -1,5 +0,0 @@
|
|||
// Can't seem to make this work as ESM
|
||||
const config = require('../freesewing.shared/config/postcss.config.js')
|
||||
|
||||
module.exports = config
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
// Can't seem to make this work as ESM
|
||||
const config = require('../freesewing.shared/config/tailwind.config.js')
|
||||
|
||||
module.exports = config
|
|
@ -1,60 +0,0 @@
|
|||
{
|
||||
"accessories": [
|
||||
"florence",
|
||||
"florent",
|
||||
"hi",
|
||||
"holmes",
|
||||
"hortensia",
|
||||
"lucy",
|
||||
"trayvon"
|
||||
],
|
||||
"blocks": [
|
||||
"bella",
|
||||
"bent",
|
||||
"brian",
|
||||
"titan"
|
||||
],
|
||||
"garments": [
|
||||
"aaron",
|
||||
"albert",
|
||||
"bee",
|
||||
"benjamin",
|
||||
"breanna",
|
||||
"bruce",
|
||||
"carlita",
|
||||
"carlton",
|
||||
"cathrin",
|
||||
"charlie",
|
||||
"cornelius",
|
||||
"diana",
|
||||
"huey",
|
||||
"hugo",
|
||||
"jaeger",
|
||||
"lunetius",
|
||||
"paco",
|
||||
"penelope",
|
||||
"sandy",
|
||||
"shin",
|
||||
"simon",
|
||||
"simone",
|
||||
"sven",
|
||||
"tamiko",
|
||||
"teagan",
|
||||
"theo",
|
||||
"tiberius",
|
||||
"unice",
|
||||
"ursula",
|
||||
"wahid",
|
||||
"walburga",
|
||||
"waralee",
|
||||
"yuri"
|
||||
],
|
||||
"utilities": [
|
||||
"examples",
|
||||
"legend",
|
||||
"plugintest",
|
||||
"rendertest",
|
||||
"tutorial"
|
||||
]
|
||||
}
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
export const strapiHost = 'https://posts.freesewing.org'
|
||||
|
||||
export const monorepo = 'https://github.com/freesewing/freesewing'
|
||||
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
const pitches = [
|
||||
'This website does not track you',
|
||||
"Come in, we're open",
|
||||
'Made-to-measure sewing patterns',
|
||||
]
|
||||
|
||||
export default pitches
|
|
@ -1,11 +1,11 @@
|
|||
import { useState } from 'react'
|
||||
// Stores state in local storage
|
||||
import useLocalStorage from 'shared/hooks/useLocalStorage.js'
|
||||
// Patterns
|
||||
import patterns from 'shared/config/designs.json'
|
||||
// Designs
|
||||
import { designsByType } from 'config/software/index.mjs'
|
||||
// Locale and translation
|
||||
import { useTranslation } from 'next-i18next'
|
||||
import { capitalize } from 'shared/utils'
|
||||
import { capitalize } from 'shared/utils.mjs'
|
||||
import { formatVersionUri } from '../components/version-picker.js'
|
||||
import useVersion from 'site/hooks/useVersion.js'
|
||||
import useTheme from 'shared/hooks/useTheme'
|
||||
|
@ -14,44 +14,46 @@ import useTheme from 'shared/hooks/useTheme'
|
|||
const initialNavigation = (t, version) => {
|
||||
const base = {
|
||||
accessories: {
|
||||
__title: t('accessoryPatterns'),
|
||||
__order: t('accessoryPatterns'),
|
||||
__linktitle: t('accessoryPatterns'),
|
||||
__title: t('accessoryDesigns'),
|
||||
__slug: 'accessories',
|
||||
},
|
||||
blocks: {
|
||||
__title: t('blockPatterns'),
|
||||
__order: t('blockPatterns'),
|
||||
__linktitle: t('blockPatterns'),
|
||||
__title: t('blockDesigns'),
|
||||
__slug: 'blocks',
|
||||
},
|
||||
garments: {
|
||||
__title: t('garmentPatterns'),
|
||||
__order: t('garmentPatterns'),
|
||||
__linktitle: t('garmentPatterns'),
|
||||
__title: t('garmentDesigns'),
|
||||
__slug: t('garments'),
|
||||
},
|
||||
utilities: {
|
||||
__title: t('utilityPatterns'),
|
||||
__order: t('utilityPatterns'),
|
||||
__linktitle: t('utilityPatterns'),
|
||||
__title: t('utilityDesigns'),
|
||||
__slug: 'utilities',
|
||||
},
|
||||
}
|
||||
for (const type in patterns) {
|
||||
for (const design of patterns[type]) {
|
||||
for (const type in designsByType) {
|
||||
for (const design in designsByType[type]) {
|
||||
base[type][design] = {
|
||||
__title: capitalize(design),
|
||||
__order: design,
|
||||
__linktitle: capitalize(design),
|
||||
__slug: formatVersionUri(version,design)
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const key in base) {
|
||||
base[key].__order = base[key].__title
|
||||
base[key].__linktitle = base[key].__title
|
||||
}
|
||||
|
||||
return base
|
||||
}
|
||||
|
||||
const designs = {}
|
||||
for (const type in designsByType) {
|
||||
designs[type] = []
|
||||
for (const design in designsByType[type]) {
|
||||
designs[type].push(design)
|
||||
}
|
||||
}
|
||||
|
||||
function useApp(full = true) {
|
||||
|
||||
// Version
|
||||
|
@ -68,7 +70,7 @@ function useApp(full = true) {
|
|||
const [primaryMenu, setPrimaryMenu] = useState(false)
|
||||
const [navigation, setNavigation] = useState(initialNavigation(t, version))
|
||||
const [slug, setSlug] = useState('/')
|
||||
const [pattern, setPattern] = useState(false)
|
||||
const [design, setDesign] = useState(false)
|
||||
const [loading, setLoading] = useState(false)
|
||||
|
||||
// State methods
|
||||
|
@ -77,12 +79,12 @@ function useApp(full = true) {
|
|||
return {
|
||||
// Static vars
|
||||
site: 'lab',
|
||||
patterns,
|
||||
designs,
|
||||
|
||||
// State
|
||||
loading,
|
||||
navigation,
|
||||
pattern,
|
||||
design,
|
||||
primaryMenu,
|
||||
slug,
|
||||
theme,
|
||||
|
@ -90,7 +92,7 @@ function useApp(full = true) {
|
|||
// State setters
|
||||
setLoading,
|
||||
setNavigation,
|
||||
setPattern,
|
||||
setDesign,
|
||||
setPrimaryMenu,
|
||||
setSlug,
|
||||
setTheme,
|
3
sites/lab/i18n.config.mjs
Normal file
|
@ -0,0 +1,3 @@
|
|||
import i18n from '../shared/config/i18n.config.mjs'
|
||||
|
||||
export default i18n()
|
59
sites/lab/next.config.mjs
Normal file
|
@ -0,0 +1,59 @@
|
|||
import path from 'path'
|
||||
import { readdirSync } from 'fs'
|
||||
import i18nConfig from './next-i18next.config.js'
|
||||
import { designs, plugins, packages } from '../../config/software/index.mjs'
|
||||
|
||||
const getDirectories = source =>
|
||||
readdirSync(source, { withFileTypes: true })
|
||||
.filter(dirent => dirent.isDirectory())
|
||||
.map(dirent => dirent.name)
|
||||
|
||||
const pkgs = getDirectories(path.resolve(`../`))
|
||||
|
||||
const config = {
|
||||
experimental: {
|
||||
externalDir: true,
|
||||
},
|
||||
i18n: i18nConfig.i18n,
|
||||
pageExtensions: [ 'js' ],
|
||||
webpack: (config, options) => {
|
||||
|
||||
// YAML support
|
||||
config.module.rules.push({
|
||||
test: /\.ya?ml$/,
|
||||
type: 'json',
|
||||
use: 'yaml-loader'
|
||||
})
|
||||
|
||||
// Suppress warnings about importing version from package.json
|
||||
// We'll deal with it in v3 of FreeSewing
|
||||
config.ignoreWarnings = [
|
||||
/only default export is available soon/
|
||||
]
|
||||
|
||||
// Aliases
|
||||
config.resolve.alias.shared = path.resolve('../shared/')
|
||||
config.resolve.alias.site = path.resolve('.')
|
||||
config.resolve.alias.lib = path.resolve('./lib')
|
||||
config.resolve.alias.config = path.resolve('../../config/')
|
||||
config.resolve.alias.designs = path.resolve('../../designs/')
|
||||
config.resolve.alias.plugins = path.resolve('../../plugins/')
|
||||
config.resolve.alias.pkgs = path.resolve('../../packages/')
|
||||
|
||||
// Load designs from source, rather than compiled package
|
||||
for (const design in designs) {
|
||||
config.resolve.alias[`@freesewing/${design}$`] = path.resolve(`../../designs/${design}/src/index.js`)
|
||||
}
|
||||
// Load plugins from source, rather than compiled package
|
||||
for (const plugin in plugins) {
|
||||
config.resolve.alias[`@freesewing/${plugin}$`] = path.resolve(`../../plugins/${plugin}/src/index.js`)
|
||||
}
|
||||
// Load these from source, rather than compiled package
|
||||
for (const pkg of ['core', 'config-helpers', 'i18n', 'models']) {
|
||||
config.resolve.alias[`@freesewing/${pkg}$`] = path.resolve(`../../packages/${pkg}/src/index.js`)
|
||||
}
|
||||
|
||||
return config
|
||||
}
|
||||
}
|
||||
export default config
|
|
@ -3,17 +3,15 @@
|
|||
"version": "2.21.0-rc.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev -p 3002",
|
||||
"develop": "next dev -p 3002",
|
||||
"prebuild": "cd ../utils && npm run build && cd - && SITE=lab node ../freesewing.shared/prebuild/index.mjs",
|
||||
"dev": "node --experimental-json-modules ./node_modules/.bin/next dev -p 8000",
|
||||
"develop": "next dev -p 8000",
|
||||
"prebuild": "SITE=lab node --experimental-json-modules ../shared/prebuild/index.mjs",
|
||||
"cibuild": "yarn prebuild && next build",
|
||||
"build": "next build",
|
||||
"export": "yarn prebuild && next build && next export",
|
||||
"start": "next start -p 3002",
|
||||
"lint": "next lint",
|
||||
"testdeploy": "next build && next export && netlify-cli deploy",
|
||||
"deploy": "next build && next export && netlify-cli deploy --prod",
|
||||
"serve": "pm2 start npm --name 'freesewing.dev' -- run start"
|
||||
"serve": "pm2 start npm --name 'dev' -- run start"
|
||||
},
|
||||
"dependencies": {
|
||||
"@heroicons/react": "^1.0.5",
|
|
@ -4,14 +4,14 @@ import WorkbenchWrapper from 'shared/components/wrappers/workbench.js'
|
|||
import { useRouter } from 'next/router'
|
||||
import Layout from 'site/components/layouts/lab'
|
||||
|
||||
const WorkbenchPage = ({ pattern }) => {
|
||||
const WorkbenchPage = ({ design }) => {
|
||||
const app = useApp()
|
||||
const router = useRouter()
|
||||
const { preload, from } = router.query
|
||||
|
||||
return (
|
||||
<Page app={app}>
|
||||
<WorkbenchWrapper {...{ app, pattern, preload, from }} layout={Layout} />
|
||||
<WorkbenchWrapper {...{ app, design, preload, from }} layout={Layout} />
|
||||
</Page>
|
||||
)
|
||||
}
|