diff --git a/config/descriptions.yaml b/config/descriptions.yaml
index 94a76afadbf..73e17e0dbd1 100644
--- a/config/descriptions.yaml
+++ b/config/descriptions.yaml
@@ -22,6 +22,7 @@ examples: 'A FreeSewing pattern holding examples for our documentation'
florent: 'A FreeSewing pattern for a flat cap'
florence: 'A FreeSewing pattern for a face mask'
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'
gatsby-remark-jargon: 'A gatsby-transformer-remark sub-plugin for jargon terms'
diff --git a/packages/freesewing.dev/components/header.js b/packages/freesewing.dev/components/header.js
index 54aa8dd9870..ddabd24548f 100644
--- a/packages/freesewing.dev/components/header.js
+++ b/packages/freesewing.dev/components/header.js
@@ -60,8 +60,23 @@ const Header = ({ app, setSearch }) => {
`}
onClick={app.togglePrimaryMenu}>
{app.primaryMenu
- ? <> swipe >
- : <> swipe >
+ ? (
+ <>
+
+
+
+ swipe
+
+ >
+ ) : (
+ <>
+
+
+
+ swipe
+
+ >
+ )
}
@@ -100,7 +115,10 @@ const Header = ({ app, setSearch }) => {
-
+
)
}
diff --git a/packages/freesewing.dev/hooks/useApp.js b/packages/freesewing.dev/hooks/useApp.js
index c9a4f8c5264..57575a32f5b 100644
--- a/packages/freesewing.dev/hooks/useApp.js
+++ b/packages/freesewing.dev/hooks/useApp.js
@@ -6,6 +6,8 @@ import useLocalStorage from 'shared/hooks/useLocalStorage.js'
import prebuildNavigation from 'site/prebuild/navigation.js'
function useApp(full = true) {
+ // No translation for freesewing.dev
+ const language = 'en'
// User color scheme preference
const prefersDarkMode = (typeof window !== 'undefined' && typeof window.matchMedia === 'function')
@@ -15,7 +17,6 @@ function useApp(full = true) {
// Persistent state
const [account, setAccount] = useLocalStorage('account', { username: false })
const [theme, setTheme] = useLocalStorage('theme', prefersDarkMode ? 'dark' : 'light')
- const [language, setLanguage] = useLocalStorage('language', 'en')
// React State
const [primaryMenu, setPrimaryMenu] = useState(false)
@@ -41,9 +42,9 @@ function useApp(full = true) {
return {
// Static vars
site: 'dev',
+ language,
// State
- language,
loading,
navigation,
primaryMenu,
@@ -51,7 +52,6 @@ function useApp(full = true) {
theme,
// State setters
- setLanguage,
setLoading,
setNavigation,
setPrimaryMenu,
@@ -63,6 +63,10 @@ function useApp(full = true) {
// State handlers
togglePrimaryMenu,
+
+ // Dummy translation method
+ t: s => s,
+ i18n: false,
}
}
diff --git a/packages/freesewing.lab/.eslintrc.json b/packages/freesewing.lab/.eslintrc.json
new file mode 100644
index 00000000000..bffb357a712
--- /dev/null
+++ b/packages/freesewing.lab/.eslintrc.json
@@ -0,0 +1,3 @@
+{
+ "extends": "next/core-web-vitals"
+}
diff --git a/packages/freesewing.lab/components/about.js b/packages/freesewing.lab/components/about.js
new file mode 100644
index 00000000000..8c29b068b25
--- /dev/null
+++ b/packages/freesewing.lab/components/about.js
@@ -0,0 +1,25 @@
+import Popout from 'shared/components/popout.js'
+
+const About = () => (
+
+ What to expect at lab.freesewing.org
+
+ The FreeSewing lab is an online environment to road-test our various patterns/designs.
+
+
+ This website runs the bleeding edge of our code base.
+ Some patterns here may be unreleased or at various states of being worked on.
+ As such, this website is intended for FreeSewing contributors or people interested
+ in what happens under the hood.
+
+
+ If you want sewing patterns to actually start making something,
+ please visit freesewing.org , our flagship website for makers.
+
+
+)
+
+export default About
diff --git a/packages/freesewing.lab/components/footer.js b/packages/freesewing.lab/components/footer.js
new file mode 100644
index 00000000000..7d5ee65047c
--- /dev/null
+++ b/packages/freesewing.lab/components/footer.js
@@ -0,0 +1,166 @@
+import NextLink from 'next/link'
+import Logo from 'shared/components/logos/freesewing.js'
+import contributors from 'site/prebuild/allcontributors.js'
+import patrons from 'site/prebuild/patrons.js'
+import OsiLogo from 'shared/components/logos/osi.js'
+import CreativeCommonsLogo from 'shared/components/logos/cc.js'
+import CcByLogo from 'shared/components/logos/cc-by.js'
+
+const Link = ({ href, txt }) => (
+
+ {txt}
+
+)
+const link = "text-secondary font-bold hover:pointer hover:underline px-1"
+
+const social = {
+ Discord: 'https://discord.freesewing.org/',
+ Instagram: 'https://instagram.com/freesewing_org',
+ Facebook: 'https://www.facebook.com/groups/627769821272714/',
+ Github: 'https://github.com/freesewing',
+ Reddit: 'https://www.reddit.com/r/freesewing/',
+ Twitter: 'https://twitter.com/freesewing_org',
+}
+
+const Footer = ({ app }) => (
+
+
+
+
+
+
+
{app.t('whatIsThis')}
+
+
+
+
+
+
Where can I turn for help?
+
+
+ Our Discord server is
+ the best place to ask questions and get help. It's where our community hangs out
+ so you'll get the fastest response and might even make a few new friends along the way.
+
+
+ You can also reach out on Twitter or create an issue on Github if Discord is not your jam.
+
+
+
+
+
Social Media
+
+
+ {Object.keys(social).map(item => )}
+
+
+
+
+
+
FreeSewing
+
+ Come for the sewing patterns
+
+ Stay for the community
+
+
+
+
+ FreeSewing is made by these wonderful contributors
+
+
+ {contributors.map(person => (
+
+
+
+ ))}
+
+
+
+ FreeSewing is supported by these generous patrons
+
+
+ {patrons.map(person => (
+
+
+
+ ))}
+
+
+
+ FreeSewing is hosted by these awesome companies
+
+
+
+
+)
+
+export default Footer
+
diff --git a/packages/freesewing.lab/components/header.js b/packages/freesewing.lab/components/header.js
new file mode 100644
index 00000000000..81723b22e37
--- /dev/null
+++ b/packages/freesewing.lab/components/header.js
@@ -0,0 +1,94 @@
+import { useState, useEffect } from 'react'
+import Logo from 'shared/components/logos/freesewing.js'
+import Link from 'next/link'
+import ThemePicker from 'shared/components/theme-picker.js'
+import LanguagePicker from 'shared/components/language-picker.js'
+import PatternPicker from 'site/components/pattern-picker.js'
+import CloseIcon from 'shared/components/icons/close.js'
+import MenuIcon from 'shared/components/icons/menu.js'
+
+const Right = props => (
+
+
+
+)
+const Left = props => (
+
+
+
+)
+
+const Header = ({ app }) => {
+
+ const [prevScrollPos, setPrevScrollPos] = useState(0)
+ const [show, setShow] = useState(true)
+
+ useEffect(() => {
+ if (typeof window !== 'undefined') {
+ const handleScroll = () => {
+ const curScrollPos = (typeof window !== 'undefined') ? window.pageYOffset : 0
+ if (curScrollPos >= prevScrollPos) {
+ if (show && curScrollPos > 20) setShow(false)
+ }
+ else setShow(true)
+ setPrevScrollPos(curScrollPos)
+ }
+ window.addEventListener('scroll', handleScroll)
+ return () => window.removeEventListener('scroll', handleScroll)
+ }
+ }, [prevScrollPos, show])
+
+
+ return (
+
+
+
+
+ {app.primaryMenu
+ ? <> swipe >
+ : <> swipe >
+ }
+
+
+
+
+
+
+
+
+
+
+
+ )
+}
+
+export default Header
diff --git a/packages/freesewing.lab/components/help-us.js b/packages/freesewing.lab/components/help-us.js
new file mode 100644
index 00000000000..772a9c3c829
--- /dev/null
+++ b/packages/freesewing.lab/components/help-us.js
@@ -0,0 +1,49 @@
+import Popout from 'shared/components/popout.js'
+
+const HelpUs = ({ mdx=false, slug='/' }) => (
+
+ Click here to learn how you can help us improve this page
+ {mdx && (
+
+ Found a mistake?
+ You can edit this page on Github and help us improve our documentation.
+
+ )}
+
+ Does this look ok?
+
+
+ If it looks ok, great! But if not, please let me know about it.
+ Either by
+ reaching out on Discord
+ or feel free to create
+ an issue on Github .
+
+ Why do you ask?
+
+ I recently added a backend endpoint to auto-generate pretty (I hope) Open Graph images.
+ They are those little preview images you see when you paste a link in Discord (for example).
+
+
+ This idea is that it will auto-generate an image, but I am certain there are some edge cases
+ where that will not work.
+ There are hundreds of pages on this website and checking them all one by one is not something
+ I see myself doing. But since you are here on this page, perhaps you could see if the image
+ above looks ok.
+
+ Thank you, I really appreciate your help with this.
+
+
+)
+
+export default HelpUs
+
diff --git a/packages/freesewing.lab/components/pattern-picker.js b/packages/freesewing.lab/components/pattern-picker.js
new file mode 100644
index 00000000000..7d5a85ee970
--- /dev/null
+++ b/packages/freesewing.lab/components/pattern-picker.js
@@ -0,0 +1,43 @@
+import React from 'react'
+import config from 'site/freesewing.config.js'
+import DesignIcon from 'shared/components/icons/design.js'
+import Link from 'next/link'
+
+const PatternPicker = ({ app }) => {
+ const { t } = app
+ return (
+
+
+
+ Patterns
+
+
+ {Object.keys(config.patterns).map(section => (
+
+
+ {t(config.navigation[section].__title)}
+
+ {config.patterns[section].map(pattern => (
+
+
+
+ {pattern}
+
+
+
+ ))}
+
+ ))}
+
+
+ )
+}
+
+export default PatternPicker
diff --git a/packages/freesewing.lab/components/search.js b/packages/freesewing.lab/components/search.js
new file mode 100644
index 00000000000..ad4e17b5767
--- /dev/null
+++ b/packages/freesewing.lab/components/search.js
@@ -0,0 +1 @@
+export default () => null
diff --git a/packages/freesewing.lab/freesewing.config.js b/packages/freesewing.lab/freesewing.config.js
new file mode 100644
index 00000000000..7c913fe76ae
--- /dev/null
+++ b/packages/freesewing.lab/freesewing.config.js
@@ -0,0 +1,103 @@
+const patterns = {
+ accessories: [
+ 'florence',
+ 'hortensia',
+ 'florent',
+ 'holmes',
+ ],
+ 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',
+ 'trayvon',
+ 'ursula',
+ 'wahid',
+ 'walburga',
+ 'waralee',
+ 'yuri',
+ ],
+ utilities: [
+ 'examples',
+ 'legend',
+ 'plugintest',
+ 'rendertest',
+ 'tutorial',
+ ],
+}
+
+const navigation = {
+ accessories: {
+ __title: 'accessoryPatterns',
+ __order: 'accessoryPatterns',
+ __linktitle: 'accessoryPatterns',
+ __slug: 'accessories',
+ },
+ blocks: {
+ __title: 'blockPatterns',
+ __order: 'blockPatterns',
+ __linktitle: 'blockPatterns',
+ __slug: 'blocks',
+ },
+ garments: {
+ __title: 'garmentPatterns',
+ __order: 'garmentPatterns',
+ __linktitle: 'GarmentPatterns',
+ __slug: 'garments',
+ },
+ utilities: {
+ __title: 'utilityPatterns',
+ __order: 'utilityPatterns',
+ __linktitle: 'utilityPatterns',
+ __slug: 'utilities',
+ },
+}
+for (const type in patterns) {
+ for (const design of patterns[type]) {
+ navigation[type][design] = {
+ __title: design,
+ __order: design,
+ __linktitle: design,
+ __slug: `${type}/${design}`
+ }
+ }
+}
+
+
+const config = {
+ monorepo: 'https://github.com/freesewing/freesewing',
+ navigation,
+ patterns,
+}
+
+export default config
+
diff --git a/packages/freesewing.lab/hooks/useApp.js b/packages/freesewing.lab/hooks/useApp.js
new file mode 100644
index 00000000000..332dbe7ee71
--- /dev/null
+++ b/packages/freesewing.lab/hooks/useApp.js
@@ -0,0 +1,102 @@
+import { useState } from 'react'
+import set from 'lodash.set'
+// Stores state in local storage
+import useLocalStorage from 'shared/hooks/useLocalStorage.js'
+// config
+import config from 'site/freesewing.config.js'
+// Languages
+import { strings } from 'pkgs/i18n'
+
+const translateNavigation = (lang, t) => {
+ const newNav = {...config.navigation}
+ for (const key in newNav) {
+ const translated = t(newNav[key].__title, false, lang)
+ newNav[key].__title = translated
+ newNav[key].__linktitle = translated
+ newNav[key].__order = translated
+ }
+
+ return newNav
+}
+
+
+function useApp(full = true) {
+
+ // User color scheme preference
+ const prefersDarkMode = (typeof window !== 'undefined' && typeof window.matchMedia === 'function')
+ ? window.matchMedia(`(prefers-color-scheme: dark`).matches
+ : null
+
+ // Persistent state
+ const [account, setAccount] = useLocalStorage('account', { username: false })
+ const [theme, setTheme] = useLocalStorage('theme', prefersDarkMode ? 'dark' : 'light')
+ const [language, setLanguage] = useLocalStorage('language', 'en')
+
+ // React State
+ const [primaryMenu, setPrimaryMenu] = useState(false)
+ const [navigation, setNavigation] = useState(config.navigation)
+ const [slug, setSlug] = useState('/')
+ const [pattern, setPattern] = useState(false)
+ const [loading, setLoading] = useState(false)
+
+ // State methods
+ const togglePrimaryMenu = () => setPrimaryMenu(!primaryMenu)
+ const changeLanguage = lang => {
+ setLanguage(lang)
+ setNavigation(translateNavigation(lang, t))
+ }
+
+ /*
+ * Translation method
+ */
+ const t = (key, props=false, toLanguage=false) => {
+ if (!toLanguage) toLanguage = language
+ if (!props) { // easy
+ if (strings[toLanguage][key]) return strings[toLanguage][key]
+ // app is the most common prefix, so we allow to skip it
+ if (strings[toLanguage][`app.${key}`]) return strings[toLanguage][`app.${key}`]
+ // Can we fall back to English?
+ if (toLanguage !== 'en') {
+ if (strings.en[key]) return strings.en[key]
+ if (strings.en[`app.${key}`]) return strings.en[`app.${key}`]
+ }
+ }
+ console.log('Missing translation key:', key)
+
+ return key
+ }
+
+ return {
+ // Static vars
+ site: 'lab',
+
+ // State
+ language,
+ loading,
+ navigation,
+ pattern,
+ primaryMenu,
+ slug,
+ theme,
+
+ // State setters
+ setLoading,
+ setNavigation,
+ setPattern,
+ setPrimaryMenu,
+ setSlug,
+ setTheme,
+ startLoading: () => { setLoading(true); setPrimaryMenu(false) }, // Always close menu when navigating
+ stopLoading: () => setLoading(false),
+
+ // State handlers
+ togglePrimaryMenu,
+ changeLanguage,
+
+ // Translation
+ t,
+ }
+}
+
+export default useApp
+
diff --git a/packages/freesewing.lab/next.config.mjs b/packages/freesewing.lab/next.config.mjs
new file mode 100644
index 00000000000..086bd6666a4
--- /dev/null
+++ b/packages/freesewing.lab/next.config.mjs
@@ -0,0 +1,3 @@
+import configBuilder from '../freesewing.shared/config/next.mjs'
+
+export default configBuilder('lab')
diff --git a/packages/freesewing.lab/package.json b/packages/freesewing.lab/package.json
new file mode 100644
index 00000000000..e005bb7806e
--- /dev/null
+++ b/packages/freesewing.lab/package.json
@@ -0,0 +1,54 @@
+{
+ "name": "freesewing.dev",
+ "version": "2.19.9",
+ "private": true,
+ "scripts": {
+ "dev": "next dev -p 3002",
+ "develop": "next dev -p 3002",
+ "prebuild": "SITE=dev node ../freesewing.shared/prebuild/index.mjs",
+ "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"
+ },
+ "dependencies": {
+ "@heroicons/react": "^1.0.5",
+ "@mdx-js/loader": "^2.0.0-rc.2",
+ "@mdx-js/mdx": "^2.0.0-rc.2",
+ "@mdx-js/react": "^2.0.0-rc.2",
+ "@mdx-js/runtime": "next",
+ "@tailwindcss/typography": "^0.5.0",
+ "algoliasearch": "^4.11.0",
+ "daisyui": "^1.16.2",
+ "lodash.get": "^4.4.2",
+ "lodash.orderby": "^4.6.0",
+ "lodash.set": "^4.3.2",
+ "netlify-cli": "^8.4.2",
+ "next": "latest",
+ "react-copy-to-clipboard": "^5.0.4",
+ "react-hotkeys-hook": "^3.4.4",
+ "react-instantsearch-dom": "^6.18.0",
+ "react-markdown": "^7.1.1",
+ "react-swipeable": "^6.2.0",
+ "react-timeago": "^6.2.1",
+ "rehype-highlight": "^5.0.1",
+ "rehype-sanitize": "^5.0.1",
+ "rehype-stringify": "^9.0.2",
+ "remark-copy-linked-files": "https://github.com/joostdecock/remark-copy-linked-files",
+ "remark-gfm": "^3.0.1",
+ "remark-jargon": "^2.19.9"
+ },
+ "devDependencies": {
+ "autoprefixer": "^10.4.0",
+ "eslint-config-next": "12.0.7",
+ "js-yaml": "^4.1.0",
+ "postcss": "^8.4.4",
+ "tailwindcss": "^3.0.1"
+ },
+ "engines": {
+ "node": ">=14.18.1"
+ }
+}
diff --git a/packages/freesewing.lab/page-templates/pattern-list.js b/packages/freesewing.lab/page-templates/pattern-list.js
new file mode 100644
index 00000000000..b2854bc9855
--- /dev/null
+++ b/packages/freesewing.lab/page-templates/pattern-list.js
@@ -0,0 +1,51 @@
+import Page from 'shared/components/wrappers/page.js'
+import useApp from 'site/hooks/useApp.js'
+import Head from 'next/head'
+import Link from 'next/link'
+import config from 'site/freesewing.config.js'
+import About from 'site/components/about.js'
+
+const links = (section, list) => list.map(design => (
+
+
+ {design}
+
+
+))
+
+const ListPage = ({ sections=Object.keys(config.patterns) }) => {
+ const app = useApp()
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {Object.keys(config.navigation).map(section => {
+ if (sections.indexOf(section) !== -1) return (
+
+
{config.navigation[section].__title}
+
+ {links(section, config.patterns[section])}
+
+
+ )
+ else return null
+ })}
+
+
+
+ )
+}
+
+export default ListPage
diff --git a/packages/freesewing.lab/page-templates/workbench.js b/packages/freesewing.lab/page-templates/workbench.js
new file mode 100644
index 00000000000..575eff69926
--- /dev/null
+++ b/packages/freesewing.lab/page-templates/workbench.js
@@ -0,0 +1,16 @@
+import Page from 'shared/components/wrappers/page.js'
+import useApp from 'site/hooks/useApp.js'
+import WorkbenchWrapper from 'shared/components/wrappers/workbench.js'
+
+const WorkbenchPage = ({ pattern }) => {
+ const app = useApp()
+
+ return (
+
+
+
+ )
+}
+
+export default WorkbenchPage
+
diff --git a/packages/freesewing.lab/pages/_app.js b/packages/freesewing.lab/pages/_app.js
new file mode 100644
index 00000000000..8d56d327f58
--- /dev/null
+++ b/packages/freesewing.lab/pages/_app.js
@@ -0,0 +1,5 @@
+import 'shared/styles/globals.css'
+
+const FreeSewingLab = ({ Component, pageProps }) =>
+
+export default FreeSewingLab
diff --git a/packages/freesewing.lab/pages/accessories/index.js b/packages/freesewing.lab/pages/accessories/index.js
new file mode 100644
index 00000000000..341a7ccd6b1
--- /dev/null
+++ b/packages/freesewing.lab/pages/accessories/index.js
@@ -0,0 +1,5 @@
+import Template from 'site/page-templates/pattern-list.js'
+
+const Page = props =>
+
+export default Page
diff --git a/packages/freesewing.lab/pages/blocks/index.js b/packages/freesewing.lab/pages/blocks/index.js
new file mode 100644
index 00000000000..812e3fb9c73
--- /dev/null
+++ b/packages/freesewing.lab/pages/blocks/index.js
@@ -0,0 +1,5 @@
+import Template from 'site/page-templates/pattern-list.js'
+
+const Page = props =>
+
+export default Page
diff --git a/packages/freesewing.lab/pages/garments/aaron.js b/packages/freesewing.lab/pages/garments/aaron.js
new file mode 100644
index 00000000000..bdb2acf619f
--- /dev/null
+++ b/packages/freesewing.lab/pages/garments/aaron.js
@@ -0,0 +1,4 @@
+import pattern from 'pkgs/aaron/src/index.js'
+import PageTemplate from 'site/page-templates/workbench.js'
+
+export default () =>
diff --git a/packages/freesewing.lab/pages/garments/index.js b/packages/freesewing.lab/pages/garments/index.js
new file mode 100644
index 00000000000..98db14d090e
--- /dev/null
+++ b/packages/freesewing.lab/pages/garments/index.js
@@ -0,0 +1,5 @@
+import Template from 'site/page-templates/pattern-list.js'
+
+const Page = props =>
+
+export default Page
diff --git a/packages/freesewing.lab/pages/garments/teagan.js b/packages/freesewing.lab/pages/garments/teagan.js
new file mode 100644
index 00000000000..f7b82e8a8fb
--- /dev/null
+++ b/packages/freesewing.lab/pages/garments/teagan.js
@@ -0,0 +1,8 @@
+import pattern from 'pkgs/teagan/src/index.js'
+import PageTemplate from 'site/page-templates/workbench.js'
+
+const Teagan = function() {
+ return
+}
+
+export default Teagan
diff --git a/packages/freesewing.lab/pages/index.js b/packages/freesewing.lab/pages/index.js
new file mode 100644
index 00000000000..d747c215563
--- /dev/null
+++ b/packages/freesewing.lab/pages/index.js
@@ -0,0 +1,3 @@
+import Page from 'site/page-templates/pattern-list.js'
+
+export default Page
diff --git a/packages/freesewing.lab/pages/utilities/index.js b/packages/freesewing.lab/pages/utilities/index.js
new file mode 100644
index 00000000000..1730faf1952
--- /dev/null
+++ b/packages/freesewing.lab/pages/utilities/index.js
@@ -0,0 +1,5 @@
+import Template from 'site/page-templates/pattern-list.js'
+
+const Page = props =>
+
+export default Page
diff --git a/packages/freesewing.lab/postcss.config.js b/packages/freesewing.lab/postcss.config.js
new file mode 100644
index 00000000000..85bf46214ea
--- /dev/null
+++ b/packages/freesewing.lab/postcss.config.js
@@ -0,0 +1,5 @@
+// Can't seem to make this work as ESM
+const config = require('../freesewing.shared/config/postcss.config.js')
+
+module.exports = config
+
diff --git a/packages/freesewing.lab/public/brands/algolia.svg b/packages/freesewing.lab/public/brands/algolia.svg
new file mode 100644
index 00000000000..ab6ae5ab524
--- /dev/null
+++ b/packages/freesewing.lab/public/brands/algolia.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/packages/freesewing.lab/public/brands/bugsnag.svg b/packages/freesewing.lab/public/brands/bugsnag.svg
new file mode 100644
index 00000000000..8abd9ff4ebf
--- /dev/null
+++ b/packages/freesewing.lab/public/brands/bugsnag.svg
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
diff --git a/packages/freesewing.lab/public/brands/crowdin.svg b/packages/freesewing.lab/public/brands/crowdin.svg
new file mode 100644
index 00000000000..bbfe90540be
--- /dev/null
+++ b/packages/freesewing.lab/public/brands/crowdin.svg
@@ -0,0 +1,61 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/freesewing.lab/public/brands/netlify.svg b/packages/freesewing.lab/public/brands/netlify.svg
new file mode 100644
index 00000000000..8d306be6fb6
--- /dev/null
+++ b/packages/freesewing.lab/public/brands/netlify.svg
@@ -0,0 +1,85 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/freesewing.lab/public/favicon.ico b/packages/freesewing.lab/public/favicon.ico
new file mode 100644
index 00000000000..718d6fea483
Binary files /dev/null and b/packages/freesewing.lab/public/favicon.ico differ
diff --git a/packages/freesewing.lab/public/support.jpg b/packages/freesewing.lab/public/support.jpg
new file mode 100644
index 00000000000..3c476403b0b
Binary files /dev/null and b/packages/freesewing.lab/public/support.jpg differ
diff --git a/packages/freesewing.lab/scripts/algolia.mjs b/packages/freesewing.lab/scripts/algolia.mjs
new file mode 100644
index 00000000000..ac8255068de
--- /dev/null
+++ b/packages/freesewing.lab/scripts/algolia.mjs
@@ -0,0 +1,150 @@
+/*
+ * This will update (replace really) the Algolia index with the
+ * current website contents. Or at least the markdown and Strapi
+ * content
+ *
+ * It expects the following environment vars to be set in a
+ * .env file in the 'packages/freesewing.dev' folder:
+ *
+ * ALGOLIA_APP_ID -> probably MA0Y5A2PF0
+ * ALGOLIA_API_KEY -> Needs permission to index/create/delete
+ * ALGOLIA_INDEX -> Name of the index to index to
+ *
+ */
+import dotenv from 'dotenv'
+import fs from 'fs'
+import path from 'path'
+import algoliasearch from 'algoliasearch'
+import { unified } from 'unified'
+import remarkParser from 'remark-parse'
+import remarkCompiler from 'remark-stringify'
+import remarkFrontmatter from 'remark-frontmatter'
+import remarkFrontmatterExtractor from 'remark-extract-frontmatter'
+import remarkRehype from 'remark-rehype'
+import rehypeSanitize from 'rehype-sanitize'
+import rehypeStringify from 'rehype-stringify'
+import yaml from 'yaml'
+import { getPosts } from '../../freesewing.shared/prebuild/strapi.mjs'
+import { getMdxFileList } from '../../freesewing.shared/prebuild/mdx.mjs'
+dotenv.config()
+
+/*
+ * Initialize Algolia client
+ */
+const client = algoliasearch(process.env.ALGOLIA_APP_ID, process.env.ALGOLIA_API_KEY)
+const index = client.initIndex(process.env.ALGOLIA_INDEX)
+
+/*
+ * Turn a Strapi blog post into an object ready for indexing
+ */
+const transformBlogpost = post => ({
+ objectID: `/blog/${post.slug}`,
+ page: `/blog/${post.slug}`,
+ title: post.title,
+ date: post.date,
+ slug: post.slug,
+ body: post.body,
+ author: post.author,
+ caption: post.caption,
+ type: 'blog',
+})
+
+/*
+ * Turn a Strapi author into an object ready for indexing
+ */
+const transformAuthor = author => ({
+ objectID: `/blog/authors/${author.name}`,
+ page: `/blog/authors/${author.name}`,
+ name: author.name,
+ displayname: author.displayname,
+ about: author.about,
+})
+
+/*
+ * Get and index blog posts and author info from Strapi
+ */
+const indexStrapiContent = async () => {
+
+ // Say hi
+ console.log()
+ console.log(`Indexing Strapi content to Algolia`)
+
+ const authors = {}
+ const rawPosts = await getPosts('blog', 'dev', 'en')
+ // Extract list of authors
+ for (const [slug, post] of Object.entries(rawPosts)) {
+ authors[post.author.slug] = transformAuthor(post.author)
+ rawPosts[slug].author = post.author.slug
+ }
+ // Index posts to Algolia
+ index
+ .saveObjects(Object.values(rawPosts).map(post => transformBlogpost(post)))
+ .then(({ objectIDs }) => console.log(objectIDs))
+ .catch(err => console.log(err))
+ // Index authors to Algolia
+ index
+ .saveObjects(Object.values(authors))
+ .then(({ objectIDs }) => console.log(objectIDs))
+ .catch(err => console.log(err))
+}
+
+/*
+ * Loads markdown from disk and compiles it into HTML for indexing
+ */
+const markdownLoader = async file => {
+
+ const md = await fs.promises.readFile(file, 'utf-8')
+
+ const page = await unified()
+ .use(remarkParser)
+ .use(remarkCompiler)
+ .use(remarkFrontmatter)
+ .use(remarkFrontmatterExtractor, { yaml: yaml.parse })
+ .use(remarkRehype)
+ .use(rehypeSanitize)
+ .use(rehypeStringify)
+ .process(md)
+ const id = file.split('freesewing/markdown/dev').pop().slice(0, -6)
+
+ return {
+ objectID: id,
+ page: id,
+ title: page.data.title,
+ body: page.value,
+ type: 'docs',
+ }
+}
+
+
+
+/*
+ * Get and index markdown content
+ */
+const indexMarkdownContent = async () => {
+
+ // Say hi
+ console.log()
+ console.log(`Indexing Markdown content to Algolia`)
+
+ // Setup MDX root path
+ const mdxRoot = path.resolve('..', '..', 'markdown', 'dev')
+
+ // Get list of filenames
+ const list = await getMdxFileList(mdxRoot, 'en')
+ const pages = []
+ for (const file of list) pages.push(await markdownLoader(file))
+ // Index markdown to Algolia
+ index
+ .saveObjects(pages)
+ .then(({ objectIDs }) => console.log(objectIDs))
+ .catch(err => console.log(err))
+}
+
+const run = async () => {
+ await indexMarkdownContent()
+ await indexStrapiContent()
+ console.log()
+}
+
+run()
+
diff --git a/packages/freesewing.lab/tailwind.config.js b/packages/freesewing.lab/tailwind.config.js
new file mode 100644
index 00000000000..3f664c97b59
--- /dev/null
+++ b/packages/freesewing.lab/tailwind.config.js
@@ -0,0 +1,4 @@
+// Can't seem to make this work as ESM
+const config = require('../freesewing.shared/config/tailwind.config.js')
+
+module.exports = config
diff --git a/packages/freesewing.shared/components/icons/options.js b/packages/freesewing.shared/components/icons/options.js
new file mode 100644
index 00000000000..c349ac2ca09
--- /dev/null
+++ b/packages/freesewing.shared/components/icons/options.js
@@ -0,0 +1,7 @@
+const OptionsIcon = () => (
+
+
+
+)
+
+export default OptionsIcon
diff --git a/packages/freesewing.shared/components/icons/settings.js b/packages/freesewing.shared/components/icons/settings.js
new file mode 100644
index 00000000000..a045de7000e
--- /dev/null
+++ b/packages/freesewing.shared/components/icons/settings.js
@@ -0,0 +1,8 @@
+const SettingsIcon = () => (
+
+
+
+
+)
+
+export default SettingsIcon
diff --git a/packages/freesewing.shared/components/language-picker.js b/packages/freesewing.shared/components/language-picker.js
new file mode 100644
index 00000000000..234349c9bad
--- /dev/null
+++ b/packages/freesewing.shared/components/language-picker.js
@@ -0,0 +1,29 @@
+import themes from 'shared/themes/index.js'
+import LanguageIcon from 'shared/components/icons/i18n.js'
+import { languages } from 'pkgs/i18n'
+
+const LanguagePicker = ({ app }) => {
+ return (
+
+
+
+ {languages[app.language]}
+
+
+ {Object.keys(languages).map(language => (
+
+ app.changeLanguage(language)} className="btn btn-ghost text-base-content hover:bg-base-200">
+ {languages[language]}
+
+
+ ))}
+
+
+ )
+}
+
+export default LanguagePicker
diff --git a/packages/freesewing.shared/components/layouts/default.js b/packages/freesewing.shared/components/layouts/default.js
index 356f714aa15..e4e40a0fe01 100644
--- a/packages/freesewing.shared/components/layouts/default.js
+++ b/packages/freesewing.shared/components/layouts/default.js
@@ -6,6 +6,8 @@ import Link from 'next/link'
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'
@@ -58,9 +60,34 @@ const Breadcrumbs = ({ app, slug=false, title }) => {
)
}
+const asideClasses = `
+ fixed top-0 right-0
+ pt-28
+ sm:pt-8 sm:mt-16
+ pb-4 px-2
+ sm:relative sm:transform-none
+ h-screen w-screen
+ bg-base-100
+ sm:bg-base-50
+ sm:flex
+ sm:sticky
+ overflow-y-scroll
+ z-20
+ bg-base-100 text-base-content
+ sm:bg-neutral sm:bg-opacity-95 sm:text-neutral-content
+ transition-all `
-const DefaultLayout = ({ app, title=false, children=[], search, setSearch}) => {
+const DefaultLayout = ({
+ 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
@@ -73,7 +100,8 @@ const DefaultLayout = ({ app, title=false, children=[], search, setSearch}) => {
router.events?.on('routeChangeStart', startNavigation)
router.events?.on('routeChangeComplete', () => app.stopLoading())
const slug = router.asPath.slice(1)
- const [leftNav, setLeftNav] = useState(false)
+ const [collapsePrimaryNav, setCollapsePrimaryNav] = useState(workbench || false)
+ const [collapseAltMenu, setCollapseAltMenu] = useState(false)
return (
{
-
+
{title && (
<>
@@ -123,8 +157,32 @@ const DefaultLayout = ({ app, title=false, children=[], search, setSearch}) => {
{children}
+ {workbench && AltMenu && (
+
+ )}
- {search && (
+ {!noSearch && search && (
<>
orderBy(obj, ['__order', '__title'], ['asc', 'asc'])
// Component for the collapse toggle
-const Chevron = ({w=8, m=2}) =>
Object.values(order(current))
.filter(entry => (typeof entry === 'object'))
// Shared classes for links
-const linkClasses = `text-lg lg:text-xl
+// Exported for re-use
+export const linkClasses = `text-lg lg:text-xl
py-1 hover:cursor-pointer
text-base-content sm:text-neutral-content
hover:text-secondary
diff --git a/packages/freesewing.shared/components/theme-picker.js b/packages/freesewing.shared/components/theme-picker.js
index 25531867f2f..54ab08ca032 100644
--- a/packages/freesewing.shared/components/theme-picker.js
+++ b/packages/freesewing.shared/components/theme-picker.js
@@ -1,7 +1,7 @@
import themes from 'shared/themes/index.js'
import ThemeIcon from 'shared/components/icons/theme.js'
-const ThemePicker = ({ app, className='' }) => {
+const ThemePicker = ({ app, className }) => {
return (
{
hover:bg-neutral hover:border-neutral-content
`}>
- {app.theme}
- Theme
+ {app.i18n
+ ? app.t(`${app.theme}Theme`)
+ : `${app.theme} Theme`
+ }
-