Merge branch 'develop' into i18n3
This commit is contained in:
commit
fe61e85fa9
101 changed files with 3291 additions and 2445 deletions
|
@ -27,6 +27,7 @@ module.exports = {
|
||||||
extends: 'eslint:recommended',
|
extends: 'eslint:recommended',
|
||||||
env: {
|
env: {
|
||||||
es2021: true,
|
es2021: true,
|
||||||
|
node: true,
|
||||||
},
|
},
|
||||||
// Required when using experimental EcmaScript features
|
// Required when using experimental EcmaScript features
|
||||||
parser: '@babel/eslint-parser',
|
parser: '@babel/eslint-parser',
|
||||||
|
|
|
@ -62,7 +62,7 @@ core:
|
||||||
'lodash.unset': &_unset '4.5.2'
|
'lodash.unset': &_unset '4.5.2'
|
||||||
'lodash.clonedeep': '^4.5.0'
|
'lodash.clonedeep': '^4.5.0'
|
||||||
dev:
|
dev:
|
||||||
'eslint': &eslint '8.42.0'
|
'eslint': &eslint '8.43.0'
|
||||||
'nyc': '15.1.0'
|
'nyc': '15.1.0'
|
||||||
'mocha': *mocha
|
'mocha': *mocha
|
||||||
'chai': *chai
|
'chai': *chai
|
||||||
|
@ -192,12 +192,12 @@ yuri:
|
||||||
|
|
||||||
backend:
|
backend:
|
||||||
_:
|
_:
|
||||||
'@aws-sdk/client-sesv2': '3.352.0'
|
'@aws-sdk/client-sesv2': '3.354.0'
|
||||||
'@prisma/client': &prisma '4.15.0'
|
'@prisma/client': &prisma '4.16.1'
|
||||||
'bcryptjs': '2.4.3'
|
'bcryptjs': '2.4.3'
|
||||||
'cors': '2.8.5'
|
'cors': '2.8.5'
|
||||||
'crypto': '1.0.1'
|
'crypto': '1.0.1'
|
||||||
'dotenv': '16.1.4'
|
'dotenv': '16.3.1'
|
||||||
'express': '4.18.2'
|
'express': '4.18.2'
|
||||||
'js-yaml': *jsyaml
|
'js-yaml': *jsyaml
|
||||||
'lodash.get': *_get
|
'lodash.get': *_get
|
||||||
|
@ -208,12 +208,12 @@ backend:
|
||||||
'passport-jwt': '4.0.1'
|
'passport-jwt': '4.0.1'
|
||||||
'pino': '8.14.1'
|
'pino': '8.14.1'
|
||||||
'qrcode': '1.5.3'
|
'qrcode': '1.5.3'
|
||||||
'swagger-ui-dist': '4.19.0'
|
'swagger-ui-dist': '5.1.0'
|
||||||
'swagger-ui-express': '4.6.3'
|
'swagger-ui-express': '4.6.3'
|
||||||
dev:
|
dev:
|
||||||
'chai': *chai
|
'chai': *chai
|
||||||
'chai-http': '4.4.0'
|
'chai-http': '4.4.0'
|
||||||
'esbuild': '0.18.2'
|
'esbuild': '0.18.8'
|
||||||
'mocha': *mocha
|
'mocha': *mocha
|
||||||
'mocha-steps': '1.3.0'
|
'mocha-steps': '1.3.0'
|
||||||
'nodemon': '2.0.22'
|
'nodemon': '2.0.22'
|
||||||
|
@ -225,10 +225,10 @@ dev:
|
||||||
'@mdx-js/mdx': *mdx
|
'@mdx-js/mdx': *mdx
|
||||||
'@mdx-js/react': *mdx
|
'@mdx-js/react': *mdx
|
||||||
'@mdx-js/runtime': &mdxRuntime '2.0.0-next.9'
|
'@mdx-js/runtime': &mdxRuntime '2.0.0-next.9'
|
||||||
'@next/bundle-analyzer': &next '13.4.6'
|
'@next/bundle-analyzer': &next '13.4.7'
|
||||||
'@tailwindcss/typography': &tailwindTypography '0.5.9'
|
'@tailwindcss/typography': &tailwindTypography '0.5.9'
|
||||||
'algoliasearch': '4.17.2'
|
'algoliasearch': '4.18.0'
|
||||||
'daisyui': &daisyui '3.1.0'
|
'daisyui': &daisyui '3.1.1'
|
||||||
'lodash.get': *_get
|
'lodash.get': *_get
|
||||||
'lodash.orderby': &_orderby '4.6.0'
|
'lodash.orderby': &_orderby '4.6.0'
|
||||||
'lodash.set': *_set
|
'lodash.set': *_set
|
||||||
|
@ -238,7 +238,7 @@ dev:
|
||||||
'react-dom': *react
|
'react-dom': *react
|
||||||
'react-hotkeys-hook': &reactHotkeysHook '4.4.0'
|
'react-hotkeys-hook': &reactHotkeysHook '4.4.0'
|
||||||
'react-instantsearch-dom': &reactInstantsearchDom '6.40.0'
|
'react-instantsearch-dom': &reactInstantsearchDom '6.40.0'
|
||||||
'react-instantsearch-hooks-web': '6.44.2'
|
'react-instantsearch-hooks-web': '6.45.0'
|
||||||
'react-markdown': &reactMarkdown '8.0.7'
|
'react-markdown': &reactMarkdown '8.0.7'
|
||||||
'react-swipeable': &reactSwipeable '7.0.1'
|
'react-swipeable': &reactSwipeable '7.0.1'
|
||||||
'react-timeago': &reactTimeago '7.1.0'
|
'react-timeago': &reactTimeago '7.1.0'
|
||||||
|
@ -276,7 +276,7 @@ lab:
|
||||||
'@mdx-js/react': *mdx
|
'@mdx-js/react': *mdx
|
||||||
'@mdx-js/runtime': *mdxRuntime
|
'@mdx-js/runtime': *mdxRuntime
|
||||||
'@tailwindcss/typography': *tailwindTypography
|
'@tailwindcss/typography': *tailwindTypography
|
||||||
'algoliasearch': &algoliasearch '4.17.2'
|
'algoliasearch': &algoliasearch '4.18.0'
|
||||||
'd3-dispatch': '3.0.1'
|
'd3-dispatch': '3.0.1'
|
||||||
'd3-drag': '3.0.0'
|
'd3-drag': '3.0.0'
|
||||||
'd3-selection': '3.0.0'
|
'd3-selection': '3.0.0'
|
||||||
|
@ -286,7 +286,7 @@ lab:
|
||||||
'lodash.orderby': *_orderby
|
'lodash.orderby': *_orderby
|
||||||
'lodash.set': *_set
|
'lodash.set': *_set
|
||||||
'next': *next
|
'next': *next
|
||||||
'next-i18next': &nextI18next '13.3.0'
|
'next-i18next': &nextI18next '14.0.0'
|
||||||
'react': *react
|
'react': *react
|
||||||
'react-copy-to-clipboard': *reactCopyToClipboard
|
'react-copy-to-clipboard': *reactCopyToClipboard
|
||||||
'react-hotkeys-hook': *reactHotkeysHook
|
'react-hotkeys-hook': *reactHotkeysHook
|
||||||
|
@ -314,15 +314,20 @@ org:
|
||||||
'@mdx-js/mdx': *mdx
|
'@mdx-js/mdx': *mdx
|
||||||
'@mdx-js/react': *mdx
|
'@mdx-js/react': *mdx
|
||||||
'@mdx-js/runtime': *mdxRuntime
|
'@mdx-js/runtime': *mdxRuntime
|
||||||
|
'@portabletext/react': '^1.0.6'
|
||||||
|
'@sanity/client': '^6.1.2'
|
||||||
'@tailwindcss/typography': *tailwindTypography
|
'@tailwindcss/typography': *tailwindTypography
|
||||||
'algoliasearch': *algoliasearch
|
'algoliasearch': *algoliasearch
|
||||||
'react-copy-to-clipboard': 5.1.0
|
'react-copy-to-clipboard': 5.1.0
|
||||||
'daisyui': *daisyui
|
'daisyui': *daisyui
|
||||||
|
'jotai': &jotai '2.1.1'
|
||||||
|
'jotai-location': &jotai-location '0.5.1'
|
||||||
'lodash.get': *_get
|
'lodash.get': *_get
|
||||||
'lodash.orderby': *_orderby
|
'lodash.orderby': *_orderby
|
||||||
'lodash.set': *_set
|
'lodash.set': *_set
|
||||||
'luxon': '3.3.0'
|
'luxon': '3.3.0'
|
||||||
'next': *next
|
'next': *next
|
||||||
|
'next-sanity': '^4.3.3'
|
||||||
'react-dropzone': '14.2.3'
|
'react-dropzone': '14.2.3'
|
||||||
'react-hotkeys-hook': *reactHotkeysHook
|
'react-hotkeys-hook': *reactHotkeysHook
|
||||||
'react-instantsearch-dom': *reactInstantsearchDom
|
'react-instantsearch-dom': *reactInstantsearchDom
|
||||||
|
@ -344,8 +349,8 @@ org:
|
||||||
|
|
||||||
sanity:
|
sanity:
|
||||||
_:
|
_:
|
||||||
'@sanity/vision': &sanity '3.12.0'
|
'@sanity/vision': &sanity '3.12.2'
|
||||||
'easymde': '2.16.0'
|
'easymde': '2.18.0'
|
||||||
'react': *react
|
'react': *react
|
||||||
'react-dom': *react
|
'react-dom': *react
|
||||||
'react-is': *react
|
'react-is': *react
|
||||||
|
@ -357,12 +362,12 @@ sanity:
|
||||||
'eslint': *eslint
|
'eslint': *eslint
|
||||||
'prettier': '2.8.8'
|
'prettier': '2.8.8'
|
||||||
'typescript': '5.1.3'
|
'typescript': '5.1.3'
|
||||||
'@sanity/cli': '3.12.1'
|
'@sanity/cli': '3.12.2'
|
||||||
|
|
||||||
shared:
|
shared:
|
||||||
_:
|
_:
|
||||||
'@headlessui/react': *headlessUiReact
|
'@headlessui/react': *headlessUiReact
|
||||||
'@next/mdx': '13.4.6'
|
'@next/mdx': '13.4.7'
|
||||||
'@resvg/resvg-js': '2.4.1'
|
'@resvg/resvg-js': '2.4.1'
|
||||||
'@tailwindcss/typography': *tailwindTypography
|
'@tailwindcss/typography': *tailwindTypography
|
||||||
'Buffer': '0.0.0'
|
'Buffer': '0.0.0'
|
||||||
|
@ -375,8 +380,8 @@ shared:
|
||||||
'front-matter': '4.0.2'
|
'front-matter': '4.0.2'
|
||||||
'highlight.js': '11.8.0'
|
'highlight.js': '11.8.0'
|
||||||
'github-slugger': '2.0.0'
|
'github-slugger': '2.0.0'
|
||||||
'jotai': '2.1.1'
|
'jotai': *jotai
|
||||||
'jotai-location': '0.5.1'
|
'jotai-location': *jotai-location
|
||||||
'lodash.clonedeep': '4.5.0'
|
'lodash.clonedeep': '4.5.0'
|
||||||
'lodash.orderby': *_orderby
|
'lodash.orderby': *_orderby
|
||||||
'lodash.unset': *_unset
|
'lodash.unset': *_unset
|
||||||
|
@ -398,8 +403,8 @@ shared:
|
||||||
'remark-smartypants': '2.0.0'
|
'remark-smartypants': '2.0.0'
|
||||||
'sharp': '0.32.1'
|
'sharp': '0.32.1'
|
||||||
'svg-to-pdfkit': 'https://github.com/eriese/SVG-to-PDFKit'
|
'svg-to-pdfkit': 'https://github.com/eriese/SVG-to-PDFKit'
|
||||||
'tlds': '1.239.0'
|
'tlds': '1.240.0'
|
||||||
'to-vfile': '7.2.4'
|
'to-vfile': '8.0.0'
|
||||||
'unist-util-visit': *unist-util-visit
|
'unist-util-visit': *unist-util-visit
|
||||||
'use-persisted-state': *use-persisted-state
|
'use-persisted-state': *use-persisted-state
|
||||||
'web-worker': '1.2.0'
|
'web-worker': '1.2.0'
|
||||||
|
|
|
@ -5,6 +5,7 @@ import { dimensions } from './shared.mjs'
|
||||||
export const front = {
|
export const front = {
|
||||||
from: base,
|
from: base,
|
||||||
name: 'aaron.front',
|
name: 'aaron.front',
|
||||||
|
measurements: ['hips'],
|
||||||
options: {
|
options: {
|
||||||
brianFitCollar: false,
|
brianFitCollar: false,
|
||||||
brianFitSleeve: false,
|
brianFitSleeve: false,
|
||||||
|
|
|
@ -8,6 +8,7 @@ export const cup = {
|
||||||
inherited: true,
|
inherited: true,
|
||||||
},
|
},
|
||||||
after: neckTie,
|
after: neckTie,
|
||||||
|
measurements: ['bustPointToUnderbust'],
|
||||||
options: {
|
options: {
|
||||||
topDepth: { pct: 54, min: 50, max: 80, menu: 'fit' },
|
topDepth: { pct: 54, min: 50, max: 80, menu: 'fit' },
|
||||||
bottomCupDepth: { pct: 8, min: 0, max: 20, menu: 'fit' },
|
bottomCupDepth: { pct: 8, min: 0, max: 20, menu: 'fit' },
|
||||||
|
|
|
@ -483,7 +483,7 @@ export const front = {
|
||||||
name: 'carlton.front',
|
name: 'carlton.front',
|
||||||
from: bentFront,
|
from: bentFront,
|
||||||
hide: hidePresets.HIDE_TREE,
|
hide: hidePresets.HIDE_TREE,
|
||||||
measurements: ['waist', 'waistToFloor', 'waistToSeat'],
|
measurements: ['waist', 'waistToFloor', 'waistToSeat', 'seat'],
|
||||||
options: {
|
options: {
|
||||||
chestEase: { pct: 10, min: 5, max: 20, menu: 'fit' },
|
chestEase: { pct: 10, min: 5, max: 20, menu: 'fit' },
|
||||||
buttonSpacingHorizontal: { pct: 43.5, min: 15, max: 60, menu: 'style' },
|
buttonSpacingHorizontal: { pct: 43.5, min: 15, max: 60, menu: 'style' },
|
||||||
|
|
|
@ -415,7 +415,7 @@ function simoneFbaFront({
|
||||||
export const fbaFront = {
|
export const fbaFront = {
|
||||||
name: 'simone.fbaFront',
|
name: 'simone.fbaFront',
|
||||||
from: front,
|
from: front,
|
||||||
measurements: ['highBust'],
|
measurements: ['highBust', 'bustSpan', 'hpsToBust'],
|
||||||
hide: {
|
hide: {
|
||||||
self: true,
|
self: true,
|
||||||
from: true,
|
from: true,
|
||||||
|
|
38
markdown/dev/reference/api/snippet/rotate/en.md
Normal file
38
markdown/dev/reference/api/snippet/rotate/en.md
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
---
|
||||||
|
title: Snippet.rotate()
|
||||||
|
---
|
||||||
|
|
||||||
|
The `Snippet.rotate()` method allows you to scale a snippet. Under the hood, it
|
||||||
|
sets the `data-rotate` property.
|
||||||
|
|
||||||
|
## Signature
|
||||||
|
|
||||||
|
```js
|
||||||
|
Snippet snippet.rotate(rotation, overwrite=true)
|
||||||
|
```
|
||||||
|
|
||||||
|
<Tip compact>This method is chainable as it returns the `Snippet` object</Tip>
|
||||||
|
|
||||||
|
## Example
|
||||||
|
|
||||||
|
<Example caption="An example of the Snippet.rotate() method">
|
||||||
|
```js
|
||||||
|
({ Point, Path, paths, Snippet, snippets, part }) => {
|
||||||
|
|
||||||
|
for (const i of [0,1,2,3,4,5,6]) {
|
||||||
|
snippets[`demo${i}`] = new Snippet(
|
||||||
|
"logo",
|
||||||
|
new Point(60*i, 0)
|
||||||
|
).rotate(60 * i)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prevent clipping
|
||||||
|
paths.diag = new Path()
|
||||||
|
.move(new Point(-30,-50))
|
||||||
|
.move(new Point(400,50))
|
||||||
|
|
||||||
|
return part
|
||||||
|
}
|
||||||
|
```
|
||||||
|
</Example>
|
||||||
|
|
38
markdown/dev/reference/api/snippet/scale/en.md
Normal file
38
markdown/dev/reference/api/snippet/scale/en.md
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
---
|
||||||
|
title: Snippet.scale()
|
||||||
|
---
|
||||||
|
|
||||||
|
The `Snippet.scale()` method allows you to scale a snippet. Under the hood, it
|
||||||
|
sets the `data-scale` property.
|
||||||
|
|
||||||
|
## Signature
|
||||||
|
|
||||||
|
```js
|
||||||
|
Snippet snippet.scale(scale, overwrite=true)
|
||||||
|
```
|
||||||
|
|
||||||
|
<Tip compact>This method is chainable as it returns the `Snippet` object</Tip>
|
||||||
|
|
||||||
|
## Example
|
||||||
|
|
||||||
|
<Example caption="An example of the Snippet.clone() method">
|
||||||
|
```js
|
||||||
|
({ Point, Path, paths, Snippet, snippets, part }) => {
|
||||||
|
|
||||||
|
for (const i of [1,2,3,4,5,6]) {
|
||||||
|
snippets[`demo${i}`] = new Snippet(
|
||||||
|
"logo",
|
||||||
|
new Point(30*i, 0)
|
||||||
|
).scale(i/10)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prevent clipping
|
||||||
|
paths.diag = new Path()
|
||||||
|
.move(new Point(0,-30))
|
||||||
|
.move(new Point(200,20))
|
||||||
|
|
||||||
|
return part
|
||||||
|
}
|
||||||
|
```
|
||||||
|
</Example>
|
||||||
|
|
|
@ -2,4 +2,4 @@
|
||||||
title: Waist to hips
|
title: Waist to hips
|
||||||
---
|
---
|
||||||
|
|
||||||
The **waist to hips** measurement is measured from your waist down to the top of your hip bone (where your trousers sit). Measure it at the side of your body.
|
The **waist to hips** measurement is measured from your waist down to the top of your hip bone. Measure it at the side of your body.
|
||||||
|
|
|
@ -88,7 +88,7 @@
|
||||||
"handlebars": "^4.7.7",
|
"handlebars": "^4.7.7",
|
||||||
"husky": "^8.0.1",
|
"husky": "^8.0.1",
|
||||||
"js-yaml": "^4.0.0",
|
"js-yaml": "^4.0.0",
|
||||||
"lerna": "^6.0.0",
|
"lerna": "^7.0.2",
|
||||||
"lint-staged": "^13.0.3",
|
"lint-staged": "^13.0.3",
|
||||||
"mocha": "^10.0.0",
|
"mocha": "^10.0.0",
|
||||||
"mustache": "^4.0.1",
|
"mustache": "^4.0.1",
|
||||||
|
@ -116,7 +116,7 @@
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"autoprefixer": "^10.4.0",
|
"autoprefixer": "^10.4.0",
|
||||||
"c8": "^7.12.0",
|
"c8": "^8.0.0",
|
||||||
"handlebars": "^4.7.7",
|
"handlebars": "^4.7.7",
|
||||||
"jsonfile": "^6.1.0",
|
"jsonfile": "^6.1.0",
|
||||||
"postcss": "^8.4.5",
|
"postcss": "^8.4.5",
|
||||||
|
|
|
@ -59,7 +59,7 @@
|
||||||
"lodash.clonedeep": "^4.5.0"
|
"lodash.clonedeep": "^4.5.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"eslint": "8.42.0",
|
"eslint": "8.43.0",
|
||||||
"nyc": "15.1.0",
|
"nyc": "15.1.0",
|
||||||
"mocha": "10.2.0",
|
"mocha": "10.2.0",
|
||||||
"chai": "4.3.7",
|
"chai": "4.3.7",
|
||||||
|
|
|
@ -176,7 +176,7 @@ Part.prototype.shorthand = function () {
|
||||||
get: function (measurements, name) {
|
get: function (measurements, name) {
|
||||||
if (typeof measurements[name] === 'undefined')
|
if (typeof measurements[name] === 'undefined')
|
||||||
self.context.store.log.warning(
|
self.context.store.log.warning(
|
||||||
`Tried to access \`measurements.${name}\` but it is \`undefined\``
|
`${self.name} tried to access \`measurements.${name}\` but it is \`undefined\``
|
||||||
)
|
)
|
||||||
return Reflect.get(...arguments)
|
return Reflect.get(...arguments)
|
||||||
},
|
},
|
||||||
|
|
|
@ -52,6 +52,30 @@ Snippet.prototype.clone = function () {
|
||||||
return clone
|
return clone
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper method to scale a snippet
|
||||||
|
*
|
||||||
|
* @param {number} scale - The scale to set
|
||||||
|
* @param {bool} overwrite - Whether to overwrite the existing scale or not (default is true)
|
||||||
|
*
|
||||||
|
* @return {Snippet} this - The snippet instance
|
||||||
|
*/
|
||||||
|
Snippet.prototype.scale = function (scale, overwrite = true) {
|
||||||
|
return this.attr('data-scale', scale, overwrite)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper method to rotate a snippet
|
||||||
|
*
|
||||||
|
* @param {number} rotation - The rotation to set
|
||||||
|
* @param {bool} overwrite - Whether to overwrite the existing rotation or not (default is true)
|
||||||
|
*
|
||||||
|
* @return {Snippet} this - The snippet instance
|
||||||
|
*/
|
||||||
|
Snippet.prototype.rotate = function (rotation, overwrite = true) {
|
||||||
|
return this.attr('data-rotate', rotation, overwrite)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a snippet as an object suitable for inclusion in renderprops
|
* Returns a snippet as an object suitable for inclusion in renderprops
|
||||||
*
|
*
|
||||||
|
|
|
@ -27,6 +27,16 @@ describe('Snippet', () => {
|
||||||
expect(s.attributes.get('class')).to.equal('less')
|
expect(s.attributes.get('class')).to.equal('less')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('Should scale a snippet', () => {
|
||||||
|
let s = new Snippet('test', new Point(12, -34)).scale(0.1234)
|
||||||
|
expect(s.attributes.get('data-scale')).to.equal('0.1234')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Should rotate a snippet', () => {
|
||||||
|
let s = new Snippet('test', new Point(12, -34)).rotate(123)
|
||||||
|
expect(s.attributes.get('data-rotate')).to.equal('123')
|
||||||
|
})
|
||||||
|
|
||||||
it('Should get a snippet via the snippets proxy', () => {
|
it('Should get a snippet via the snippets proxy', () => {
|
||||||
let result
|
let result
|
||||||
const part = {
|
const part = {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import chai from 'chai'
|
import chai from 'chai'
|
||||||
import * as all from './dist/index.mjs'
|
import * as all from '../src/index.mjs'
|
||||||
|
|
||||||
const expect = chai.expect
|
const expect = chai.expect
|
||||||
const { measurements, sizes } = all
|
const { measurements, sizes } = all
|
||||||
|
|
|
@ -86,12 +86,14 @@ const PaperlessDefs = ({ units = 'metric', stacks }) =>
|
||||||
<MetricPaperlessDefs stacks={stacks} />
|
<MetricPaperlessDefs stacks={stacks} />
|
||||||
)
|
)
|
||||||
|
|
||||||
export const Defs = (props) =>
|
export const Defs = (props) => {
|
||||||
props.svg ? (
|
console.log(props.svg.defs)
|
||||||
|
return props.svg ? (
|
||||||
<defs>
|
<defs>
|
||||||
{props.svg.defs.forSvg ? sanitize(props.svg.defs.forSvg) : null}
|
{props.svg.defs.list ? sanitize(Object.values(props.svg.defs.list).join('')) : null}
|
||||||
{props.settings[0].paperless ? (
|
{props.settings[0].paperless ? (
|
||||||
<PaperlessDefs units={props.settings[0].units} stacks={props.stacks} />
|
<PaperlessDefs units={props.settings[0].units} stacks={props.stacks} />
|
||||||
) : null}
|
) : null}
|
||||||
</defs>
|
</defs>
|
||||||
) : null
|
) : null
|
||||||
|
}
|
||||||
|
|
|
@ -28,12 +28,12 @@
|
||||||
},
|
},
|
||||||
"peerDependencies": {},
|
"peerDependencies": {},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@aws-sdk/client-sesv2": "3.352.0",
|
"@aws-sdk/client-sesv2": "3.354.0",
|
||||||
"@prisma/client": "4.15.0",
|
"@prisma/client": "4.16.1",
|
||||||
"bcryptjs": "2.4.3",
|
"bcryptjs": "2.4.3",
|
||||||
"cors": "2.8.5",
|
"cors": "2.8.5",
|
||||||
"crypto": "1.0.1",
|
"crypto": "1.0.1",
|
||||||
"dotenv": "16.1.4",
|
"dotenv": "16.3.1",
|
||||||
"express": "4.18.2",
|
"express": "4.18.2",
|
||||||
"js-yaml": "4.1.0",
|
"js-yaml": "4.1.0",
|
||||||
"lodash.get": "4.4.2",
|
"lodash.get": "4.4.2",
|
||||||
|
@ -44,17 +44,17 @@
|
||||||
"passport-jwt": "4.0.1",
|
"passport-jwt": "4.0.1",
|
||||||
"pino": "8.14.1",
|
"pino": "8.14.1",
|
||||||
"qrcode": "1.5.3",
|
"qrcode": "1.5.3",
|
||||||
"swagger-ui-dist": "4.19.0",
|
"swagger-ui-dist": "5.1.0",
|
||||||
"swagger-ui-express": "4.6.3"
|
"swagger-ui-express": "4.6.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"chai": "4.3.7",
|
"chai": "4.3.7",
|
||||||
"chai-http": "4.4.0",
|
"chai-http": "4.4.0",
|
||||||
"esbuild": "0.18.2",
|
"esbuild": "0.18.8",
|
||||||
"mocha": "10.2.0",
|
"mocha": "10.2.0",
|
||||||
"mocha-steps": "1.3.0",
|
"mocha-steps": "1.3.0",
|
||||||
"nodemon": "2.0.22",
|
"nodemon": "2.0.22",
|
||||||
"prisma": "4.15.0"
|
"prisma": "4.16.1"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=16.0.0",
|
"node": ">=16.0.0",
|
||||||
|
|
|
@ -1,9 +1,8 @@
|
||||||
// Hooks
|
// Hooks
|
||||||
import { useState, useEffect, useContext } from 'react'
|
import { useContext } from 'react'
|
||||||
import { useTranslation } from 'next-i18next'
|
import { useTranslation } from 'next-i18next'
|
||||||
// Context
|
// Context
|
||||||
import { ModalContext } from 'shared/context/modal-context.mjs'
|
import { ModalContext } from 'shared/context/modal-context.mjs'
|
||||||
import { LoadingContext } from 'shared/context/loading-context.mjs'
|
|
||||||
// Components
|
// Components
|
||||||
import {
|
import {
|
||||||
I18nIcon,
|
I18nIcon,
|
||||||
|
@ -17,7 +16,7 @@ import {
|
||||||
FreeSewingIcon,
|
FreeSewingIcon,
|
||||||
HeartIcon,
|
HeartIcon,
|
||||||
} from 'shared/components/icons.mjs'
|
} from 'shared/components/icons.mjs'
|
||||||
import { Ribbon } from 'shared/components/ribbon.mjs'
|
import { HeaderWrapper } from 'shared/components/wrappers/header.mjs'
|
||||||
import { ModalThemePicker, ns as themeNs } from 'shared/components/modal/theme-picker.mjs'
|
import { ModalThemePicker, ns as themeNs } from 'shared/components/modal/theme-picker.mjs'
|
||||||
import { ModalMenu } from 'site/components/navigation/modal-menu.mjs'
|
import { ModalMenu } from 'site/components/navigation/modal-menu.mjs'
|
||||||
|
|
||||||
|
@ -100,38 +99,11 @@ const NavIcons = ({ setModal }) => {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Header = () => {
|
export const Header = (props) => {
|
||||||
const { setModal } = useContext(ModalContext) || {}
|
const { setModal } = useContext(ModalContext) || {}
|
||||||
const { loading } = useContext(LoadingContext)
|
|
||||||
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 (
|
return (
|
||||||
<header
|
<HeaderWrapper {...props}>
|
||||||
className={`
|
|
||||||
fixed bottom-0 lg:bottom-auto lg:top-0 left-0
|
|
||||||
bg-neutral
|
|
||||||
w-full
|
|
||||||
z-30
|
|
||||||
transition-transform
|
|
||||||
${show || loading ? '' : 'fixed bottom-0 lg:top-0 left-0 translate-y-36 lg:-translate-y-36'}
|
|
||||||
drop-shadow-xl
|
|
||||||
`}
|
|
||||||
>
|
|
||||||
<div className="m-auto md:px-8">
|
<div className="m-auto md:px-8">
|
||||||
<div className="p-0 flex flex-row gap-2 justify-between text-neutral-content items-center">
|
<div className="p-0 flex flex-row gap-2 justify-between text-neutral-content items-center">
|
||||||
{/* Non-mobile content */}
|
{/* Non-mobile content */}
|
||||||
|
@ -145,7 +117,6 @@ export const Header = () => {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Ribbon />
|
</HeaderWrapper>
|
||||||
</header>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,7 @@ export const DocsLayout = ({ children = [], pageTitle = false }) => {
|
||||||
const { title, crumbs } = useContext(NavigationContext)
|
const { title, crumbs } = useContext(NavigationContext)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="grid grid-cols-4 m-auto justify-center place-items-stretch lg:mt-16">
|
<div className="grid grid-cols-4 m-auto justify-center place-items-stretch">
|
||||||
<AsideNavigation />
|
<AsideNavigation />
|
||||||
<section className="col-span-4 lg:col-span-3 py-8 lg:py-16 px-4 lg:pl-8 bg-base-50">
|
<section className="col-span-4 lg:col-span-3 py-8 lg:py-16 px-4 lg:pl-8 bg-base-50">
|
||||||
{title && (
|
{title && (
|
||||||
|
|
|
@ -4,6 +4,8 @@ import { siteConfig } from 'site/site.config.mjs'
|
||||||
import Link from 'next/link'
|
import Link from 'next/link'
|
||||||
import { ClearIcon } from 'shared/components/icons.mjs'
|
import { ClearIcon } from 'shared/components/icons.mjs'
|
||||||
|
|
||||||
|
export const ns = ['search']
|
||||||
|
|
||||||
const searchClient = algoliasearch(siteConfig.algolia.app, siteConfig.algolia.key)
|
const searchClient = algoliasearch(siteConfig.algolia.app, siteConfig.algolia.key)
|
||||||
|
|
||||||
const Hit = (props) => (
|
const Hit = (props) => (
|
||||||
|
|
|
@ -1,26 +0,0 @@
|
||||||
import Head from 'next/head'
|
|
||||||
import { Header, ns as headerNs } from 'site/components/header/index.mjs'
|
|
||||||
import { Footer, ns as footerNs } from 'shared/components/footer/index.mjs'
|
|
||||||
|
|
||||||
export const ns = [...new Set([...headerNs, ...footerNs])]
|
|
||||||
|
|
||||||
export const LayoutWrapper = ({ children = [], header = false }) => {
|
|
||||||
const ChosenHeader = header ? header : Header
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className={`
|
|
||||||
flex flex-col justify-between
|
|
||||||
min-h-screen
|
|
||||||
bg-base-100
|
|
||||||
`}
|
|
||||||
>
|
|
||||||
<Head>
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
||||||
</Head>
|
|
||||||
<ChosenHeader />
|
|
||||||
<main className="grow">{children}</main>
|
|
||||||
<Footer />
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
|
@ -33,20 +33,20 @@
|
||||||
"@mdx-js/mdx": "2.3.0",
|
"@mdx-js/mdx": "2.3.0",
|
||||||
"@mdx-js/react": "2.3.0",
|
"@mdx-js/react": "2.3.0",
|
||||||
"@mdx-js/runtime": "2.0.0-next.9",
|
"@mdx-js/runtime": "2.0.0-next.9",
|
||||||
"@next/bundle-analyzer": "13.4.6",
|
"@next/bundle-analyzer": "13.4.7",
|
||||||
"@tailwindcss/typography": "0.5.9",
|
"@tailwindcss/typography": "0.5.9",
|
||||||
"algoliasearch": "4.17.2",
|
"algoliasearch": "4.18.0",
|
||||||
"daisyui": "3.1.0",
|
"daisyui": "3.1.1",
|
||||||
"lodash.get": "4.4.2",
|
"lodash.get": "4.4.2",
|
||||||
"lodash.orderby": "4.6.0",
|
"lodash.orderby": "4.6.0",
|
||||||
"lodash.set": "4.3.2",
|
"lodash.set": "4.3.2",
|
||||||
"next": "13.4.6",
|
"next": "13.4.7",
|
||||||
"react": "18.2.0",
|
"react": "18.2.0",
|
||||||
"react-copy-to-clipboard": "5.1.0",
|
"react-copy-to-clipboard": "5.1.0",
|
||||||
"react-dom": "18.2.0",
|
"react-dom": "18.2.0",
|
||||||
"react-hotkeys-hook": "4.4.0",
|
"react-hotkeys-hook": "4.4.0",
|
||||||
"react-instantsearch-dom": "6.40.0",
|
"react-instantsearch-dom": "6.40.0",
|
||||||
"react-instantsearch-hooks-web": "6.44.2",
|
"react-instantsearch-hooks-web": "6.45.0",
|
||||||
"react-markdown": "8.0.7",
|
"react-markdown": "8.0.7",
|
||||||
"react-swipeable": "7.0.1",
|
"react-swipeable": "7.0.1",
|
||||||
"react-timeago": "7.1.0",
|
"react-timeago": "7.1.0",
|
||||||
|
@ -61,7 +61,7 @@
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@playwright/test": "^1.32.3",
|
"@playwright/test": "^1.32.3",
|
||||||
"autoprefixer": "10.4.14",
|
"autoprefixer": "10.4.14",
|
||||||
"eslint-config-next": "13.4.6",
|
"eslint-config-next": "13.4.7",
|
||||||
"js-yaml": "4.1.0",
|
"js-yaml": "4.1.0",
|
||||||
"postcss": "8.4.24",
|
"postcss": "8.4.24",
|
||||||
"playwright": "^1.32.3",
|
"playwright": "^1.32.3",
|
||||||
|
|
|
@ -1,9 +1,8 @@
|
||||||
// Hooks
|
// Hooks
|
||||||
import { useState, useEffect, useContext } from 'react'
|
import { useContext } from 'react'
|
||||||
import { useTranslation } from 'next-i18next'
|
import { useTranslation } from 'next-i18next'
|
||||||
// Context
|
// Context
|
||||||
import { ModalContext } from 'shared/context/modal-context.mjs'
|
import { ModalContext } from 'shared/context/modal-context.mjs'
|
||||||
import { LoadingContext } from 'shared/context/loading-context.mjs'
|
|
||||||
// Components
|
// Components
|
||||||
import {
|
import {
|
||||||
DesignIcon,
|
DesignIcon,
|
||||||
|
@ -12,12 +11,12 @@ import {
|
||||||
UserIcon,
|
UserIcon,
|
||||||
ThemeIcon,
|
ThemeIcon,
|
||||||
I18nIcon,
|
I18nIcon,
|
||||||
MeasureIcon,
|
MeasieIcon,
|
||||||
PageIcon,
|
PageIcon,
|
||||||
GitHubIcon,
|
GitHubIcon,
|
||||||
PlusIcon,
|
PlusIcon,
|
||||||
} from 'shared/components/icons.mjs'
|
} from 'shared/components/icons.mjs'
|
||||||
import { Ribbon } from 'shared/components/ribbon.mjs'
|
import { HeaderWrapper } from 'shared/components/wrappers/header.mjs'
|
||||||
import { ModalThemePicker, ns as themeNs } from 'shared/components/modal/theme-picker.mjs'
|
import { ModalThemePicker, ns as themeNs } from 'shared/components/modal/theme-picker.mjs'
|
||||||
import { ModalLocalePicker, ns as localeNs } from 'shared/components/modal/locale-picker.mjs'
|
import { ModalLocalePicker, ns as localeNs } from 'shared/components/modal/locale-picker.mjs'
|
||||||
import { ModalMenu } from 'site/components/navigation/modal-menu.mjs'
|
import { ModalMenu } from 'site/components/navigation/modal-menu.mjs'
|
||||||
|
@ -53,7 +52,7 @@ const NavIcons = ({ setModal }) => {
|
||||||
color={colors[3]}
|
color={colors[3]}
|
||||||
extraClasses="hidden lg:flex"
|
extraClasses="hidden lg:flex"
|
||||||
>
|
>
|
||||||
<MeasureIcon className={iconSize} />
|
<MeasieIcon className={iconSize} />
|
||||||
</NavButton>
|
</NavButton>
|
||||||
<NavSpacer />
|
<NavSpacer />
|
||||||
<NavButton
|
<NavButton
|
||||||
|
@ -93,42 +92,15 @@ const NavIcons = ({ setModal }) => {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Header = ({ setSearch }) => {
|
export const Header = ({ setSearch, show }) => {
|
||||||
const { setModal } = useContext(ModalContext)
|
const { setModal } = useContext(ModalContext)
|
||||||
const { loading } = useContext(LoadingContext)
|
|
||||||
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 (
|
return (
|
||||||
<header
|
<HeaderWrapper {...{ setSearch, show }}>
|
||||||
className={`
|
|
||||||
fixed bottom-0 lg:bottom-auto lg:top-0 left-0
|
|
||||||
bg-neutral
|
|
||||||
w-full
|
|
||||||
z-30
|
|
||||||
transition-transform
|
|
||||||
${show || loading ? '' : 'fixed bottom-0 lg:top-0 left-0 translate-y-36 lg:-translate-y-36'}
|
|
||||||
drop-shadow-xl
|
|
||||||
`}
|
|
||||||
>
|
|
||||||
<div className="m-auto md:px-8">
|
<div className="m-auto md:px-8">
|
||||||
<div className="p-0 flex flex-row gap-2 justify-between text-neutral-content items-center">
|
<div className="p-0 flex flex-row gap-2 justify-between text-neutral-content items-center">
|
||||||
{/* Non-mobile content */}
|
{/* Non-mobile content */}
|
||||||
<div className="hidden lg:flex lg:px-2 flex-row items-center justify-center w-full">
|
<div className="hidden lg:flex lg:flex-row lg:justify-between items-center xl:justify-center w-full">
|
||||||
<NavIcons setModal={setModal} setSearch={setSearch} />
|
<NavIcons setModal={setModal} setSearch={setSearch} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -138,7 +110,6 @@ export const Header = ({ setSearch }) => {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Ribbon />
|
</HeaderWrapper>
|
||||||
</header>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,9 +10,9 @@ export const DocsLayout = ({ children = [], pageTitle = false }) => {
|
||||||
const { title, crumbs } = useContext(NavigationContext)
|
const { title, crumbs } = useContext(NavigationContext)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="grid grid-cols-4 m-auto justify-center place-items-stretch lg:mt-16">
|
<div className="grid grid-cols-4 m-auto justify-center place-items-stretch">
|
||||||
<AsideNavigation />
|
<AsideNavigation />
|
||||||
<section className="col-span-4 lg:col-span-3 py-24 px-4 lg:pl-8 bg-base-50">
|
<section className="col-span-4 lg:col-span-3 py-8 lg:py-24 px-4 lg:pl-8 bg-base-50">
|
||||||
{title && (
|
{title && (
|
||||||
<div className="xl:pl-4">
|
<div className="xl:pl-4">
|
||||||
<Breadcrumbs crumbs={crumbs} title={pageTitle ? pageTitle : title} />
|
<Breadcrumbs crumbs={crumbs} title={pageTitle ? pageTitle : title} />
|
||||||
|
|
|
@ -14,7 +14,7 @@ export const BeforeNav = ({ app }) => (
|
||||||
)
|
)
|
||||||
|
|
||||||
export const LabLayout = ({ app, AltMenu, children = [] }) => (
|
export const LabLayout = ({ app, AltMenu, children = [] }) => (
|
||||||
<div className="py-24 lg:py-36 flex flex-row">
|
<div className="pb-24 flex flex-row">
|
||||||
<div className="w-full xl:w-3/4 px-8">{children}</div>
|
<div className="w-full xl:w-3/4 px-8">{children}</div>
|
||||||
<aside
|
<aside
|
||||||
className={`
|
className={`
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
export const ns = []
|
export const ns = []
|
||||||
|
|
||||||
export const WorkbenchLayout = (props) => (
|
export const WorkbenchLayout = (props) => (
|
||||||
<section id="fs-workbench" className="m-0 lg:mt-24 p-0">
|
<section id="fs-workbench" className="m-0 p-0">
|
||||||
{props.children}
|
{props.children}
|
||||||
</section>
|
</section>
|
||||||
)
|
)
|
||||||
|
|
|
@ -1 +1,2 @@
|
||||||
|
export const ns = []
|
||||||
export const Search = () => null
|
export const Search = () => null
|
||||||
|
|
|
@ -1,26 +0,0 @@
|
||||||
import Head from 'next/head'
|
|
||||||
import { Header, ns as headerNs } from 'site/components/header/index.mjs'
|
|
||||||
import { Footer, ns as footerNs } from 'shared/components/footer/index.mjs'
|
|
||||||
|
|
||||||
export const ns = [...new Set([...headerNs, ...footerNs])]
|
|
||||||
|
|
||||||
export const LayoutWrapper = ({ children = [], header = false }) => {
|
|
||||||
const ChosenHeader = header ? header : Header
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className={`
|
|
||||||
flex flex-col justify-between
|
|
||||||
min-h-screen
|
|
||||||
bg-base-100
|
|
||||||
`}
|
|
||||||
>
|
|
||||||
<Head>
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
||||||
</Head>
|
|
||||||
<ChosenHeader />
|
|
||||||
<main className="grow">{children}</main>
|
|
||||||
<Footer />
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
|
@ -34,17 +34,17 @@
|
||||||
"@mdx-js/react": "2.3.0",
|
"@mdx-js/react": "2.3.0",
|
||||||
"@mdx-js/runtime": "2.0.0-next.9",
|
"@mdx-js/runtime": "2.0.0-next.9",
|
||||||
"@tailwindcss/typography": "0.5.9",
|
"@tailwindcss/typography": "0.5.9",
|
||||||
"algoliasearch": "4.17.2",
|
"algoliasearch": "4.18.0",
|
||||||
"d3-dispatch": "3.0.1",
|
"d3-dispatch": "3.0.1",
|
||||||
"d3-drag": "3.0.0",
|
"d3-drag": "3.0.0",
|
||||||
"d3-selection": "3.0.0",
|
"d3-selection": "3.0.0",
|
||||||
"daisyui": "3.1.0",
|
"daisyui": "3.1.1",
|
||||||
"i18next": "22.5.1",
|
"i18next": "22.5.1",
|
||||||
"lodash.get": "4.4.2",
|
"lodash.get": "4.4.2",
|
||||||
"lodash.orderby": "4.6.0",
|
"lodash.orderby": "4.6.0",
|
||||||
"lodash.set": "4.3.2",
|
"lodash.set": "4.3.2",
|
||||||
"next": "13.4.6",
|
"next": "13.4.7",
|
||||||
"next-i18next": "13.3.0",
|
"next-i18next": "14.0.0",
|
||||||
"react": "18.2.0",
|
"react": "18.2.0",
|
||||||
"react-copy-to-clipboard": "5.1.0",
|
"react-copy-to-clipboard": "5.1.0",
|
||||||
"react-hotkeys-hook": "4.4.0",
|
"react-hotkeys-hook": "4.4.0",
|
||||||
|
@ -65,7 +65,7 @@
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@playwright/test": "^1.32.3",
|
"@playwright/test": "^1.32.3",
|
||||||
"autoprefixer": "10.4.14",
|
"autoprefixer": "10.4.14",
|
||||||
"eslint-config-next": "13.4.6",
|
"eslint-config-next": "13.4.7",
|
||||||
"js-yaml": "4.1.0",
|
"js-yaml": "4.1.0",
|
||||||
"postcss": "8.4.24",
|
"postcss": "8.4.24",
|
||||||
"playwright": "^1.32.3",
|
"playwright": "^1.32.3",
|
||||||
|
|
|
@ -5,10 +5,10 @@ import { useTranslation } from 'next-i18next'
|
||||||
// Components
|
// Components
|
||||||
import Head from 'next/head'
|
import Head from 'next/head'
|
||||||
import { PageWrapper, ns as pageNs } from 'shared/components/wrappers/page.mjs'
|
import { PageWrapper, ns as pageNs } from 'shared/components/wrappers/page.mjs'
|
||||||
import { Popout } from 'shared/components/popout.mjs'
|
import { Popout, ns as popoutNs } from 'shared/components/popout.mjs'
|
||||||
import { WebLink } from 'shared/components/web-link.mjs'
|
import { WebLink } from 'shared/components/web-link.mjs'
|
||||||
|
|
||||||
const ns = ['lab', ...pageNs]
|
const ns = ['lab', ...pageNs, ...popoutNs]
|
||||||
/*
|
/*
|
||||||
* Each page MUST be wrapped in the PageWrapper component.
|
* Each page MUST be wrapped in the PageWrapper component.
|
||||||
* You also MUST spread props.page into this wrapper component
|
* You also MUST spread props.page into this wrapper component
|
||||||
|
|
95
sites/lab/pages/new/pattern/[design]/[type]/[id].mjs
Normal file
95
sites/lab/pages/new/pattern/[design]/[type]/[id].mjs
Normal file
|
@ -0,0 +1,95 @@
|
||||||
|
// Hooks
|
||||||
|
import { useEffect, useState } from 'react'
|
||||||
|
import { useAccount } from 'shared/hooks/use-account.mjs'
|
||||||
|
import { useBackend } from 'shared/hooks/use-backend.mjs'
|
||||||
|
import { useDesign } from 'shared/hooks/use-design.mjs'
|
||||||
|
// Dependencies
|
||||||
|
import { serverSideTranslations } from 'next-i18next/serverSideTranslations'
|
||||||
|
import { nsMerge } from 'shared/utils.mjs'
|
||||||
|
// Components
|
||||||
|
import { PageWrapper, ns as pageNs } from 'shared/components/wrappers/page.mjs'
|
||||||
|
import { Workbench, ns as wbNs } from 'shared/components/workbench/index.mjs'
|
||||||
|
import { WorkbenchLayout } from 'site/components/layouts/workbench.mjs'
|
||||||
|
import { DynamicOrgDocs as DynamicDocs } from 'site/components/dynamic-org-docs.mjs'
|
||||||
|
import { VagueError, ns as errorNs } from 'shared/components/errors/vague.mjs'
|
||||||
|
|
||||||
|
// Translation namespaces used on this page
|
||||||
|
const namespaces = nsMerge(errorNs, wbNs, pageNs)
|
||||||
|
|
||||||
|
const loadMeasurements = async ({ type, id, backend }) => {
|
||||||
|
const method = {
|
||||||
|
set: backend.getSet,
|
||||||
|
cset: backend.getCuratedSet,
|
||||||
|
}
|
||||||
|
const key = {
|
||||||
|
set: 'set',
|
||||||
|
cset: 'curatedSet',
|
||||||
|
}
|
||||||
|
if (!type || !method[type]) return false
|
||||||
|
|
||||||
|
const result = await method[type](id)
|
||||||
|
if (result.success) return result.data[key[type]]
|
||||||
|
else return false
|
||||||
|
}
|
||||||
|
|
||||||
|
const NewDesignFromSetPage = ({ page, id, design, type }) => {
|
||||||
|
const { token } = useAccount()
|
||||||
|
const backend = useBackend(token)
|
||||||
|
const [set, setSet] = useState(false)
|
||||||
|
const [error, setError] = useState(false)
|
||||||
|
const Design = useDesign(design)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// Guard against loops as the backend object is recreated on each render
|
||||||
|
const getSet = async () => {
|
||||||
|
const data = await loadMeasurements({ type, id, backend })
|
||||||
|
if (data) setSet(data)
|
||||||
|
else setError(true)
|
||||||
|
}
|
||||||
|
if (set === false) getSet()
|
||||||
|
else if (set?.id && set.id !== id) getSet()
|
||||||
|
}, [id, type, backend, set])
|
||||||
|
|
||||||
|
// Short-circuit errors
|
||||||
|
if (error)
|
||||||
|
return (
|
||||||
|
<PageWrapper {...page} title={false}>
|
||||||
|
<div className="max-w-lg flex flex-col items-center m-auto justify-center text-center">
|
||||||
|
<VagueError />
|
||||||
|
</div>
|
||||||
|
</PageWrapper>
|
||||||
|
)
|
||||||
|
|
||||||
|
const baseSettings = set?.measies ? { measurements: set.measies } : null
|
||||||
|
|
||||||
|
return (
|
||||||
|
<PageWrapper {...page} title={design} layout={WorkbenchLayout}>
|
||||||
|
<Workbench {...{ design, Design, set, DynamicDocs, baseSettings }} />
|
||||||
|
</PageWrapper>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default NewDesignFromSetPage
|
||||||
|
|
||||||
|
export async function getStaticProps({ locale, params }) {
|
||||||
|
return {
|
||||||
|
props: {
|
||||||
|
...(await serverSideTranslations(locale, [`o_${params.design}`, ...namespaces])),
|
||||||
|
id: Number(params.id),
|
||||||
|
design: params.design,
|
||||||
|
type: params.type,
|
||||||
|
page: {
|
||||||
|
locale,
|
||||||
|
path: ['new', 'pattern', params.design, 'set', params.id],
|
||||||
|
title: '',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getStaticPaths() {
|
||||||
|
return {
|
||||||
|
paths: [],
|
||||||
|
fallback: true,
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,9 +1,8 @@
|
||||||
// Hooks
|
// Hooks
|
||||||
import { useState, useEffect, useContext } from 'react'
|
import { useContext } from 'react'
|
||||||
import { useTranslation } from 'next-i18next'
|
import { useTranslation } from 'next-i18next'
|
||||||
// Context
|
// Context
|
||||||
import { ModalContext } from 'shared/context/modal-context.mjs'
|
import { ModalContext } from 'shared/context/modal-context.mjs'
|
||||||
import { LoadingContext } from 'shared/context/loading-context.mjs'
|
|
||||||
// Components
|
// Components
|
||||||
import {
|
import {
|
||||||
DesignIcon,
|
DesignIcon,
|
||||||
|
@ -18,7 +17,7 @@ import {
|
||||||
PageIcon,
|
PageIcon,
|
||||||
PlusIcon,
|
PlusIcon,
|
||||||
} from 'shared/components/icons.mjs'
|
} from 'shared/components/icons.mjs'
|
||||||
import { Ribbon } from 'shared/components/ribbon.mjs'
|
import { HeaderWrapper } from 'shared/components/wrappers/header.mjs'
|
||||||
import { ModalThemePicker, ns as themeNs } from 'shared/components/modal/theme-picker.mjs'
|
import { ModalThemePicker, ns as themeNs } from 'shared/components/modal/theme-picker.mjs'
|
||||||
import { ModalLocalePicker, ns as localeNs } from 'shared/components/modal/locale-picker.mjs'
|
import { ModalLocalePicker, ns as localeNs } from 'shared/components/modal/locale-picker.mjs'
|
||||||
import { ModalMenu } from 'site/components/navigation/modal-menu.mjs'
|
import { ModalMenu } from 'site/components/navigation/modal-menu.mjs'
|
||||||
|
@ -101,38 +100,10 @@ const NavIcons = ({ setModal, setSearch }) => {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Header = ({ setSearch }) => {
|
export const Header = ({ setSearch, show }) => {
|
||||||
const { setModal } = useContext(ModalContext)
|
const { setModal } = useContext(ModalContext)
|
||||||
const { loading } = useContext(LoadingContext)
|
|
||||||
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 (
|
return (
|
||||||
<header
|
<HeaderWrapper setSearch={setSearch} show={show}>
|
||||||
className={`
|
|
||||||
fixed bottom-0 lg:bottom-auto lg:top-0 left-0
|
|
||||||
bg-neutral
|
|
||||||
w-full
|
|
||||||
z-30
|
|
||||||
transition-transform
|
|
||||||
${show || loading ? '' : 'fixed bottom-0 lg:top-0 left-0 translate-y-36 lg:-translate-y-36'}
|
|
||||||
drop-shadow-xl
|
|
||||||
`}
|
|
||||||
>
|
|
||||||
<div className="m-auto md:px-8">
|
<div className="m-auto md:px-8">
|
||||||
<div className="p-0 flex flex-row gap-2 justify-between text-neutral-content items-center">
|
<div className="p-0 flex flex-row gap-2 justify-between text-neutral-content items-center">
|
||||||
{/* Non-mobile content */}
|
{/* Non-mobile content */}
|
||||||
|
@ -146,7 +117,6 @@ export const Header = ({ setSearch }) => {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Ribbon />
|
</HeaderWrapper>
|
||||||
</header>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,7 @@ export const DocsLayout = ({ children = [], pageTitle = false }) => {
|
||||||
const { crumbs } = useContext(NavigationContext)
|
const { crumbs } = useContext(NavigationContext)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="grid grid-cols-4 mx-auto justify-center place-items-stretch lg:mt-16">
|
<div className="grid grid-cols-4 mx-auto justify-center place-items-stretch">
|
||||||
<AsideNavigation />
|
<AsideNavigation />
|
||||||
<section className="col-span-4 lg:col-span-3 py-8 lg:py-24 px-4 lg:pl-8 bg-base-50">
|
<section className="col-span-4 lg:col-span-3 py-8 lg:py-24 px-4 lg:pl-8 bg-base-50">
|
||||||
{pageTitle && (
|
{pageTitle && (
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
export const ns = []
|
export const ns = []
|
||||||
|
|
||||||
export const WorkbenchLayout = (props) => (
|
export const WorkbenchLayout = (props) => (
|
||||||
<section id="fs-workbench" className="m-0 lg:mt-24 p-0">
|
<section id="fs-workbench" className="m-0 p-0">
|
||||||
{props.children}
|
{props.children}
|
||||||
</section>
|
</section>
|
||||||
)
|
)
|
||||||
|
|
43
sites/org/components/sanity/author.mjs
Normal file
43
sites/org/components/sanity/author.mjs
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
import { SanityMdxWrapper } from './mdx-wrapper.mjs'
|
||||||
|
import { useTranslation } from 'next-i18next'
|
||||||
|
|
||||||
|
export const Author = ({ author = {} }) => {
|
||||||
|
const { t } = useTranslation(['posts'])
|
||||||
|
return (
|
||||||
|
<div id="author" className="flex flex-col lg:flex-row m-auto p-2 items-center">
|
||||||
|
<div className="theme-gradient w-40 h-40 p-2 rounded-full aspect-square hidden lg:block">
|
||||||
|
<div
|
||||||
|
className={`
|
||||||
|
w-lg bg-cover bg-center rounded-full aspect-square
|
||||||
|
hidden lg:block
|
||||||
|
`}
|
||||||
|
style={{ backgroundImage: `url(${author.image})` }}
|
||||||
|
></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="theme-gradient p-2 rounded-full aspect-square w-40 h-40 lg:hidden m-auto">
|
||||||
|
<img
|
||||||
|
className={`block w-full h-full mx-auto rounded-full`}
|
||||||
|
src={author.image}
|
||||||
|
alt={author.displayname}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className={`
|
||||||
|
text-center p-2 px-4 rounded-r-lg bg-opacity-50
|
||||||
|
lg:text-left
|
||||||
|
`}
|
||||||
|
>
|
||||||
|
<p
|
||||||
|
className="text-xl"
|
||||||
|
dangerouslySetInnerHTML={{
|
||||||
|
__html: t('xMadeThis', { x: author.displayname }),
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<div className="prose mdx">
|
||||||
|
<SanityMdxWrapper MDX={author.about} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
30
sites/org/components/sanity/mdx-wrapper.mjs
Normal file
30
sites/org/components/sanity/mdx-wrapper.mjs
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
import { compile, run } from '@mdx-js/mdx'
|
||||||
|
import * as runtime from 'react/jsx-runtime' // Production.
|
||||||
|
import { useState, useEffect } from 'react'
|
||||||
|
|
||||||
|
import { PlainMdxWrapper } from 'shared/components/wrappers/mdx.mjs'
|
||||||
|
|
||||||
|
export const useEvaledMdx = (mdxStr = '') => {
|
||||||
|
const [mdxModule, setMdxModule] = useState(false)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const runEffect = async () => {
|
||||||
|
const code = await compile(mdxStr, {
|
||||||
|
outputFormat: 'function-body',
|
||||||
|
development: false,
|
||||||
|
})
|
||||||
|
const evaled = await run(code, runtime)
|
||||||
|
setMdxModule(() => evaled.default)
|
||||||
|
}
|
||||||
|
runEffect()
|
||||||
|
}, [mdxStr])
|
||||||
|
|
||||||
|
return mdxModule
|
||||||
|
}
|
||||||
|
|
||||||
|
export const MdxEvalWrapper = ({ MDX = false, components = {}, site = 'org' }) => {
|
||||||
|
const evaled = useEvaledMdx(MDX)
|
||||||
|
return <PlainMdxWrapper {...{ MDX: evaled, components, site }} />
|
||||||
|
}
|
||||||
|
|
||||||
|
export const SanityMdxWrapper = MdxEvalWrapper
|
80
sites/org/components/sanity/page-wrapper.mjs
Normal file
80
sites/org/components/sanity/page-wrapper.mjs
Normal file
|
@ -0,0 +1,80 @@
|
||||||
|
import Head from 'next/head'
|
||||||
|
import { PageLink } from 'shared/components/page-link.mjs'
|
||||||
|
import { Lightbox } from 'shared/components/lightbox.mjs'
|
||||||
|
import { ImageWrapper } from 'shared/components/wrappers/img.mjs'
|
||||||
|
import { PageWrapper, ns as pageNs } from 'shared/components/wrappers/page.mjs'
|
||||||
|
import { Author } from './author.mjs'
|
||||||
|
import { TimeAgo } from 'shared/components/wrappers/mdx.mjs'
|
||||||
|
import { SanityMdxWrapper } from './mdx-wrapper.mjs'
|
||||||
|
import { useTranslation } from 'next-i18next'
|
||||||
|
|
||||||
|
export const ns = ['common', 'posts', ...pageNs]
|
||||||
|
|
||||||
|
export const SanityPageWrapper = ({
|
||||||
|
post = {},
|
||||||
|
author = {},
|
||||||
|
page = {},
|
||||||
|
namespaces = ['common'],
|
||||||
|
}) => {
|
||||||
|
const { t } = useTranslation(namespaces)
|
||||||
|
return (
|
||||||
|
<PageWrapper title={post.title} {...page}>
|
||||||
|
<Head>
|
||||||
|
<meta property="og:type" content="article" key="type" />
|
||||||
|
<meta property="og:description" content={post.intro || post.title} key="description" />
|
||||||
|
<meta property="og:article:author" content={author.displayname} key="author" />
|
||||||
|
<meta property="og:url" content={`https://freesewing.org/blog/${post.slug}`} key="url" />
|
||||||
|
<meta
|
||||||
|
property="og:image"
|
||||||
|
content={`https://canary.backend.freesewing.org/og-img/en/dev/blog/${post.slug}`}
|
||||||
|
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:locale" content="en_US" key="locale" />
|
||||||
|
<meta property="og:site_name" content="freesewing.org" key="site" />
|
||||||
|
</Head>
|
||||||
|
<article className="mb-12 px-8 max-w-7xl">
|
||||||
|
<div className="flex flex-row justify-between text-sm mb-1 mt-2">
|
||||||
|
<div>
|
||||||
|
<TimeAgo date={post.date} t={t} /> [{post.date}]
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
{post.designs?.map((design) => (
|
||||||
|
<PageLink
|
||||||
|
href={`/showcase/designs/${design}`}
|
||||||
|
txt={design}
|
||||||
|
key={design}
|
||||||
|
className="px-2 capitalize"
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
By{' '}
|
||||||
|
<a href="#maker" className="text-secondary hover:text-secondary-focus">
|
||||||
|
{author.displayname || 'FIXME: No displayname'}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<figure>
|
||||||
|
<Lightbox>
|
||||||
|
<ImageWrapper>
|
||||||
|
<img src={post.image} alt={post.caption} className="shadow m-auto" />
|
||||||
|
</ImageWrapper>
|
||||||
|
<figcaption
|
||||||
|
className="text-center mb-8 prose m-auto"
|
||||||
|
dangerouslySetInnerHTML={{ __html: post.caption }}
|
||||||
|
/>
|
||||||
|
</Lightbox>
|
||||||
|
</figure>
|
||||||
|
<div className="strapi prose lg:prose-lg mb-12 m-auto">
|
||||||
|
<SanityMdxWrapper MDX={post.body} />
|
||||||
|
</div>
|
||||||
|
<div className="max-w-prose text-lg lg:text-xl">
|
||||||
|
<Author author={author} />
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
</PageWrapper>
|
||||||
|
)
|
||||||
|
}
|
44
sites/org/components/sanity/utils.mjs
Normal file
44
sites/org/components/sanity/utils.mjs
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
import { createClient } from 'next-sanity'
|
||||||
|
import { siteConfig } from 'site/site.config.mjs'
|
||||||
|
|
||||||
|
let sanityClient
|
||||||
|
const cache = {}
|
||||||
|
export const sanityLoader = async ({ query, language, type, slug, order, filters = '' }) => {
|
||||||
|
sanityClient =
|
||||||
|
sanityClient ||
|
||||||
|
createClient({
|
||||||
|
projectId: 'hl5bw8cj',
|
||||||
|
dataset: 'site-content',
|
||||||
|
apiVersion: '2023-06-17',
|
||||||
|
// token: process.env.SANITY_TOKEN,
|
||||||
|
useCdn: false,
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!query) {
|
||||||
|
query = `*[_type == "${type}${language}"`
|
||||||
|
if (slug) query += ` && slug.current == "${slug}"`
|
||||||
|
query += ']'
|
||||||
|
}
|
||||||
|
|
||||||
|
if (order) {
|
||||||
|
query += ` | order(${order})`
|
||||||
|
}
|
||||||
|
|
||||||
|
query += filters
|
||||||
|
|
||||||
|
if (cache[query]) return cache[query]
|
||||||
|
|
||||||
|
const result = await sanityClient.fetch(query)
|
||||||
|
cache[query] = result
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
export const sanityImage = (image, dataset = 'site-content') => {
|
||||||
|
const [, assetName, origSize, format] = image.asset._ref.split('-')
|
||||||
|
return `https://cdn.sanity.io/images/${siteConfig.sanity.project}/${dataset}/${assetName}-${origSize}.${format}`
|
||||||
|
}
|
||||||
|
|
||||||
|
export const sanitySiteImage = (image) => sanityImage(image, 'site-content')
|
||||||
|
export const sanityUserImage = (image) => sanityImage(image, 'user-content')
|
||||||
|
|
||||||
|
export const numPerPage = 12
|
|
@ -6,6 +6,18 @@ import { jargon } from './jargon.mjs'
|
||||||
|
|
||||||
let config = configBuilder({ site: 'org', jargon })
|
let config = configBuilder({ site: 'org', jargon })
|
||||||
config.i18n = i18nConfig.i18n
|
config.i18n = i18nConfig.i18n
|
||||||
|
config.rewrites = async () => {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
source: '/blog',
|
||||||
|
destination: '/blog/page/1',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
source: '/showcase',
|
||||||
|
destination: '/showcase/page/1',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
// Say hi
|
// Say hi
|
||||||
console.log(banner + '\n')
|
console.log(banner + '\n')
|
||||||
|
|
|
@ -34,15 +34,20 @@
|
||||||
"@mdx-js/mdx": "2.3.0",
|
"@mdx-js/mdx": "2.3.0",
|
||||||
"@mdx-js/react": "2.3.0",
|
"@mdx-js/react": "2.3.0",
|
||||||
"@mdx-js/runtime": "2.0.0-next.9",
|
"@mdx-js/runtime": "2.0.0-next.9",
|
||||||
|
"@portabletext/react": "^1.0.6",
|
||||||
|
"@sanity/client": "^6.1.2",
|
||||||
"@tailwindcss/typography": "0.5.9",
|
"@tailwindcss/typography": "0.5.9",
|
||||||
"algoliasearch": "4.17.2",
|
"algoliasearch": "4.18.0",
|
||||||
"react-copy-to-clipboard": "5.1.0",
|
"react-copy-to-clipboard": "5.1.0",
|
||||||
"daisyui": "3.1.0",
|
"daisyui": "3.1.1",
|
||||||
|
"jotai": "2.1.1",
|
||||||
|
"jotai-location": "0.5.1",
|
||||||
"lodash.get": "4.4.2",
|
"lodash.get": "4.4.2",
|
||||||
"lodash.orderby": "4.6.0",
|
"lodash.orderby": "4.6.0",
|
||||||
"lodash.set": "4.3.2",
|
"lodash.set": "4.3.2",
|
||||||
"luxon": "3.3.0",
|
"luxon": "3.3.0",
|
||||||
"next": "13.4.6",
|
"next": "13.4.7",
|
||||||
|
"next-sanity": "^4.3.3",
|
||||||
"react-dropzone": "14.2.3",
|
"react-dropzone": "14.2.3",
|
||||||
"react-hotkeys-hook": "4.4.0",
|
"react-hotkeys-hook": "4.4.0",
|
||||||
"react-instantsearch-dom": "6.40.0",
|
"react-instantsearch-dom": "6.40.0",
|
||||||
|
@ -64,7 +69,7 @@
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@playwright/test": "^1.32.3",
|
"@playwright/test": "^1.32.3",
|
||||||
"autoprefixer": "10.4.14",
|
"autoprefixer": "10.4.14",
|
||||||
"eslint-config-next": "13.4.6",
|
"eslint-config-next": "13.4.7",
|
||||||
"js-yaml": "4.1.0",
|
"js-yaml": "4.1.0",
|
||||||
"postcss": "8.4.24",
|
"postcss": "8.4.24",
|
||||||
"playwright": "^1.32.3",
|
"playwright": "^1.32.3",
|
||||||
|
|
67
sites/org/pages/blog/[slug].mjs
Normal file
67
sites/org/pages/blog/[slug].mjs
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
import { SanityPageWrapper, ns as sanityNs } from 'site/components/sanity/page-wrapper.mjs'
|
||||||
|
import { serverSideTranslations } from 'next-i18next/serverSideTranslations'
|
||||||
|
import { sanityLoader, sanityImage } from 'site/components/sanity/utils.mjs'
|
||||||
|
|
||||||
|
const namespaces = [...sanityNs]
|
||||||
|
|
||||||
|
const BlogPostPage = (props) => {
|
||||||
|
return <SanityPageWrapper {...props} namespaces={namespaces} />
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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 { slug } = params
|
||||||
|
const post = await sanityLoader({ type: 'blog', language: locale, slug })
|
||||||
|
.then((data) => data[0])
|
||||||
|
.catch((err) => console.log(err))
|
||||||
|
|
||||||
|
return {
|
||||||
|
props: {
|
||||||
|
post: {
|
||||||
|
slug,
|
||||||
|
body: post.body,
|
||||||
|
title: post.title,
|
||||||
|
date: post.date,
|
||||||
|
caption: post.caption,
|
||||||
|
image: sanityImage(post.image),
|
||||||
|
},
|
||||||
|
// FIXME load the author separately
|
||||||
|
author: {
|
||||||
|
displayname: post.author,
|
||||||
|
// slug: post.author.slug,
|
||||||
|
// about: post.author.about,
|
||||||
|
// image: strapiImage(post.author.picture, ['small']),
|
||||||
|
// about: post.author.about,
|
||||||
|
},
|
||||||
|
...(await serverSideTranslations(locale, namespaces)),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getStaticPaths = async () => {
|
||||||
|
const paths = await sanityLoader({ language: 'en', type: 'blog' })
|
||||||
|
.then((data) => data.map((post) => `/blog/${post.slug.current}`))
|
||||||
|
.catch((err) => console.log(err))
|
||||||
|
|
||||||
|
return {
|
||||||
|
paths: [
|
||||||
|
...paths,
|
||||||
|
...paths.map((p) => `/de${p}`),
|
||||||
|
...paths.map((p) => `/es${p}`),
|
||||||
|
...paths.map((p) => `/fr${p}`),
|
||||||
|
...paths.map((p) => `/nl${p}`),
|
||||||
|
],
|
||||||
|
fallback: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default BlogPostPage
|
|
@ -1,36 +0,0 @@
|
||||||
// Dependencies
|
|
||||||
import { serverSideTranslations } from 'next-i18next/serverSideTranslations'
|
|
||||||
// Components
|
|
||||||
import { PageWrapper, ns as pageNs } from 'shared/components/wrappers/page.mjs'
|
|
||||||
import { V3Wip } from 'shared/components/v3-wip.mjs'
|
|
||||||
|
|
||||||
// Translation namespaces used on this page
|
|
||||||
const namespaces = [...new Set(['designs', ...pageNs])]
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Each page MUST be wrapped in the PageWrapper component.
|
|
||||||
* You also MUST spread props.page into this wrapper component
|
|
||||||
* when path and locale come from static props (as here)
|
|
||||||
* or set them manually.
|
|
||||||
*/
|
|
||||||
const BlogIndexPage = ({ page }) => (
|
|
||||||
<PageWrapper {...page}>
|
|
||||||
<div className="max-w-2xl">
|
|
||||||
<V3Wip />
|
|
||||||
</div>
|
|
||||||
</PageWrapper>
|
|
||||||
)
|
|
||||||
|
|
||||||
export default BlogIndexPage
|
|
||||||
|
|
||||||
export async function getStaticProps({ locale }) {
|
|
||||||
return {
|
|
||||||
props: {
|
|
||||||
...(await serverSideTranslations(locale, namespaces)),
|
|
||||||
page: {
|
|
||||||
locale,
|
|
||||||
path: ['blog'],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
131
sites/org/pages/blog/page/[page].mjs
Normal file
131
sites/org/pages/blog/page/[page].mjs
Normal file
|
@ -0,0 +1,131 @@
|
||||||
|
// Dependencies
|
||||||
|
import { serverSideTranslations } from 'next-i18next/serverSideTranslations'
|
||||||
|
import { sanitySiteImage, numPerPage, sanityLoader } from 'site/components/sanity/utils.mjs'
|
||||||
|
// Hooks
|
||||||
|
import { useTranslation } from 'next-i18next'
|
||||||
|
// Components
|
||||||
|
import Link from 'next/link'
|
||||||
|
import { TimeAgo } from 'shared/components/wrappers/mdx.mjs'
|
||||||
|
import { PageWrapper, ns as pageNs } from 'shared/components/wrappers/page.mjs'
|
||||||
|
import { Pagination } from 'shared/components/navigation/pagination.mjs'
|
||||||
|
import { siteConfig } from 'site/site.config.mjs'
|
||||||
|
|
||||||
|
// Translation namespaces used on this page
|
||||||
|
const namespaces = [...new Set(['designs', ...pageNs])]
|
||||||
|
|
||||||
|
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 = ({ post, t }) => (
|
||||||
|
<div className="shadow rounded-lg">
|
||||||
|
<Link href={`/blog/${post.slug.current}`} className="hover:underline">
|
||||||
|
<div
|
||||||
|
className="bg-base-100 w-full h-full overflow-hidden shadow flex flex-column items-center rounded-lg"
|
||||||
|
style={{
|
||||||
|
backgroundImage: `url(${sanitySiteImage(post.image) + '?fit=clip&w=400'})`,
|
||||||
|
backgroundSize: 'cover',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className="text-right my-2 w-full">
|
||||||
|
<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} t={t} /> by <strong>{post.author}</strong>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
/*
|
||||||
|
* Each page MUST be wrapped in the PageWrapper component.
|
||||||
|
* You also MUST spread props.page into this wrapper component
|
||||||
|
* when path and locale come from static props (as here)
|
||||||
|
* or set them manually.
|
||||||
|
*/
|
||||||
|
const BlogIndexPage = ({ posts, page, current, total }) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<PageWrapper {...page}>
|
||||||
|
<div className="grid grid-cols-1 gap-4 lg:grid-cols-2 xl:grid-cols-3 max-w-7xl lg:pr-4 xl:pr-6">
|
||||||
|
{posts.map((post) => (
|
||||||
|
<Preview post={post} t={t} key={post.slug.current} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<Pagination {...{ current, total }} />
|
||||||
|
</PageWrapper>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default BlogIndexPage
|
||||||
|
|
||||||
|
export async function getStaticProps({ locale, params }) {
|
||||||
|
const allPosts = await sanityLoader({
|
||||||
|
language: locale,
|
||||||
|
type: 'blog',
|
||||||
|
order: 'date desc',
|
||||||
|
filters: '{_id, date, slug, title, author, image}',
|
||||||
|
})
|
||||||
|
const pageNum = parseInt(params.page)
|
||||||
|
|
||||||
|
return {
|
||||||
|
props: {
|
||||||
|
posts: allPosts.slice(numPerPage * (pageNum - 1), numPerPage * pageNum),
|
||||||
|
current: pageNum,
|
||||||
|
total: allPosts.length,
|
||||||
|
...(await serverSideTranslations(locale, namespaces)),
|
||||||
|
page: {
|
||||||
|
locale,
|
||||||
|
// title: 'Freesewing Blog',
|
||||||
|
path: ['blog', 'page', params.page],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getStaticPaths = async () => {
|
||||||
|
const numPosts = await sanityLoader({ query: `count(*[_type == "blogen"])` })
|
||||||
|
const numPages = Math.ceil(numPosts / numPerPage)
|
||||||
|
const paths = []
|
||||||
|
for (let i = 0; i < numPages; i++) {
|
||||||
|
const pathName = `/blog/page/${i + 1}`
|
||||||
|
siteConfig.languages.forEach((l) => paths.push(`${l.length ? '/' : ''}${l}${pathName}`))
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
paths,
|
||||||
|
fallback: false,
|
||||||
|
}
|
||||||
|
}
|
50
sites/org/pages/new/[design].mjs
Normal file
50
sites/org/pages/new/[design].mjs
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
// Hooks
|
||||||
|
import { useDesign, collection } from 'shared/hooks/use-design.mjs'
|
||||||
|
// Dependencies
|
||||||
|
import { serverSideTranslations } from 'next-i18next/serverSideTranslations'
|
||||||
|
import { nsMerge } from 'shared/utils.mjs'
|
||||||
|
// Components
|
||||||
|
import { PageWrapper, ns as pageNs } from 'shared/components/wrappers/page.mjs'
|
||||||
|
import { Workbench, ns as wbNs } from 'shared/components/workbench/new.mjs'
|
||||||
|
import { WorkbenchLayout } from 'site/components/layouts/workbench.mjs'
|
||||||
|
import { DynamicOrgDocs as DynamicDocs } from 'site/components/dynamic-org-docs.mjs'
|
||||||
|
|
||||||
|
// Translation namespaces used on this page
|
||||||
|
const namespaces = nsMerge(wbNs, pageNs)
|
||||||
|
|
||||||
|
const NewDesignPage = ({ page, design }) => {
|
||||||
|
const Design = useDesign(design)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<PageWrapper {...page} title={design} layout={WorkbenchLayout}>
|
||||||
|
<Workbench {...{ design, Design, DynamicDocs }} />
|
||||||
|
</PageWrapper>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default NewDesignPage
|
||||||
|
|
||||||
|
export async function getStaticProps({ locale, params }) {
|
||||||
|
return {
|
||||||
|
props: {
|
||||||
|
...(await serverSideTranslations(locale, [`o_${params.design}`, ...namespaces])),
|
||||||
|
design: params.design,
|
||||||
|
page: {
|
||||||
|
locale,
|
||||||
|
path: ['new', params.design],
|
||||||
|
title: '',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* getStaticPaths() is used to specify for which routes (think URLs)
|
||||||
|
* this page should be used to generate the result.
|
||||||
|
*/
|
||||||
|
export async function getStaticPaths() {
|
||||||
|
return {
|
||||||
|
paths: [...collection.map((design) => `/new/${design}`)],
|
||||||
|
fallback: 'blocking',
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,10 +2,12 @@
|
||||||
import { serverSideTranslations } from 'next-i18next/serverSideTranslations'
|
import { serverSideTranslations } from 'next-i18next/serverSideTranslations'
|
||||||
// Components
|
// Components
|
||||||
import { PageWrapper, ns as pageNs } from 'shared/components/wrappers/page.mjs'
|
import { PageWrapper, ns as pageNs } from 'shared/components/wrappers/page.mjs'
|
||||||
import { V3Wip } from 'shared/components/v3-wip.mjs'
|
import { ns as authNs } from 'shared/components/wrappers/auth/index.mjs'
|
||||||
|
import { ns as setsNs } from 'shared/components/account/sets.mjs'
|
||||||
|
import { DesignPicker, ns as designNs } from 'shared/components/designs/design-picker.mjs'
|
||||||
|
|
||||||
// Translation namespaces used on this page
|
// Translation namespaces used on this page
|
||||||
const namespaces = [...new Set(['showcase', ...pageNs])]
|
const namespaces = [...new Set([...designNs, ...setsNs, ...authNs, ...pageNs])]
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Each page MUST be wrapped in the PageWrapper component.
|
* Each page MUST be wrapped in the PageWrapper component.
|
||||||
|
@ -13,15 +15,13 @@ const namespaces = [...new Set(['showcase', ...pageNs])]
|
||||||
* when path and locale come from static props (as here)
|
* when path and locale come from static props (as here)
|
||||||
* or set them manually.
|
* or set them manually.
|
||||||
*/
|
*/
|
||||||
const DesignsPage = ({ page }) => (
|
const NewSetPage = ({ page }) => (
|
||||||
<PageWrapper {...page}>
|
<PageWrapper {...page}>
|
||||||
<div className="max-w-2xl">
|
<DesignPicker />
|
||||||
<V3Wip />
|
|
||||||
</div>
|
|
||||||
</PageWrapper>
|
</PageWrapper>
|
||||||
)
|
)
|
||||||
|
|
||||||
export default DesignsPage
|
export default NewSetPage
|
||||||
|
|
||||||
export async function getStaticProps({ locale }) {
|
export async function getStaticProps({ locale }) {
|
||||||
return {
|
return {
|
||||||
|
@ -29,7 +29,7 @@ export async function getStaticProps({ locale }) {
|
||||||
...(await serverSideTranslations(locale, namespaces)),
|
...(await serverSideTranslations(locale, namespaces)),
|
||||||
page: {
|
page: {
|
||||||
locale,
|
locale,
|
||||||
path: ['showcase'],
|
path: ['new', 'pattern'],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
71
sites/org/pages/showcase/[slug].mjs
Normal file
71
sites/org/pages/showcase/[slug].mjs
Normal file
|
@ -0,0 +1,71 @@
|
||||||
|
import { SanityPageWrapper, ns as sanityNs } from 'site/components/sanity/page-wrapper.mjs'
|
||||||
|
import { serverSideTranslations } from 'next-i18next/serverSideTranslations'
|
||||||
|
import { sanityLoader, sanityImage } from 'site/components/sanity/utils.mjs'
|
||||||
|
|
||||||
|
const namespaces = [...sanityNs]
|
||||||
|
|
||||||
|
const ShowcasePage = (props) => {
|
||||||
|
return <SanityPageWrapper {...props} namespaces={namespaces} />
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* getStaticProps() is used to fetch data at build-time.
|
||||||
|
*
|
||||||
|
* On this page, it is loading the showcase content from strapi.
|
||||||
|
*
|
||||||
|
* This, in combination with getStaticPaths() below means this
|
||||||
|
* page will be used to render/generate all showcase content.
|
||||||
|
*
|
||||||
|
* To learn more, see: https://nextjs.org/docs/basic-features/data-fetching
|
||||||
|
*/
|
||||||
|
export async function getStaticProps({ params, locale }) {
|
||||||
|
const { slug } = params
|
||||||
|
const post = await sanityLoader({ type: 'showcase', language: locale, slug })
|
||||||
|
.then((data) => data[0])
|
||||||
|
.catch((err) => console.log(err))
|
||||||
|
|
||||||
|
const designs = [post.design1 || null]
|
||||||
|
if (post.design2 && post.design2.length > 2) designs.push(post.design2)
|
||||||
|
if (post.design3 && post.design3.length > 2) designs.push(post.design3)
|
||||||
|
|
||||||
|
return {
|
||||||
|
props: {
|
||||||
|
post: {
|
||||||
|
slug,
|
||||||
|
body: post.body,
|
||||||
|
title: post.title,
|
||||||
|
date: post.date,
|
||||||
|
caption: post.caption,
|
||||||
|
image: sanityImage(post.image[0]),
|
||||||
|
designs,
|
||||||
|
},
|
||||||
|
// FIXME load the author separately
|
||||||
|
author: {
|
||||||
|
displayname: post.maker,
|
||||||
|
// slug: post.maker.slug,
|
||||||
|
// image: strapiImage(post.maker.picture, ['small']),
|
||||||
|
// ...(await mdxCompiler(post.maker.about)),
|
||||||
|
},
|
||||||
|
...(await serverSideTranslations(locale, namespaces)),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getStaticPaths = async () => {
|
||||||
|
const paths = await sanityLoader({ language: 'en', type: 'showcase' })
|
||||||
|
.then((data) => data.map((post) => `/showcase/${post.slug.current}`))
|
||||||
|
.catch((err) => console.log(err))
|
||||||
|
|
||||||
|
return {
|
||||||
|
paths: [
|
||||||
|
...paths,
|
||||||
|
...paths.map((p) => `/de${p}`),
|
||||||
|
...paths.map((p) => `/es${p}`),
|
||||||
|
...paths.map((p) => `/fr${p}`),
|
||||||
|
...paths.map((p) => `/nl${p}`),
|
||||||
|
],
|
||||||
|
fallback: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ShowcasePage
|
122
sites/org/pages/showcase/page/[page].mjs
Normal file
122
sites/org/pages/showcase/page/[page].mjs
Normal file
|
@ -0,0 +1,122 @@
|
||||||
|
// Dependencies
|
||||||
|
import { serverSideTranslations } from 'next-i18next/serverSideTranslations'
|
||||||
|
import { sanitySiteImage, numPerPage, sanityLoader } from 'site/components/sanity/utils.mjs'
|
||||||
|
// Hooks
|
||||||
|
import { useTranslation } from 'next-i18next'
|
||||||
|
// Components
|
||||||
|
import { PageWrapper, ns as pageNs } from 'shared/components/wrappers/page.mjs'
|
||||||
|
import Link from 'next/link'
|
||||||
|
import { Pagination } from 'shared/components/navigation/pagination.mjs'
|
||||||
|
import { siteConfig } from 'site/site.config.mjs'
|
||||||
|
|
||||||
|
// Translation namespaces used on this page
|
||||||
|
const namespaces = [...new Set(['common', 'designs', ...pageNs])]
|
||||||
|
|
||||||
|
export const PreviewTile = ({ img, slug, title }) => (
|
||||||
|
<Link href={`/showcase/${slug}`} className="text-center">
|
||||||
|
<span
|
||||||
|
style={{ backgroundImage: `url(${img})`, backgroundSize: 'cover' }}
|
||||||
|
className={`
|
||||||
|
rounded-full inline-block border-base-100
|
||||||
|
w-40 h-40
|
||||||
|
md:w-56 md:h-56
|
||||||
|
`}
|
||||||
|
></span>
|
||||||
|
<p>{title}</p>
|
||||||
|
</Link>
|
||||||
|
)
|
||||||
|
|
||||||
|
// const DesignPosts = ({ design, posts }) => {
|
||||||
|
// const { t } = useTranslation(['patterns'])
|
||||||
|
// return (
|
||||||
|
// <div className='py-2'>
|
||||||
|
// <h2>
|
||||||
|
// <Link href={`/showcase/designs/${design}`}>
|
||||||
|
// <a className="hover:text-secondary-focus hover:underline">{t(`${design}.t`)}</a>
|
||||||
|
// </Link>
|
||||||
|
// </h2>
|
||||||
|
|
||||||
|
// </div>
|
||||||
|
// )
|
||||||
|
// }
|
||||||
|
|
||||||
|
const Posts = ({ posts }) => {
|
||||||
|
const previews = []
|
||||||
|
posts.forEach((post) => {
|
||||||
|
// for (const design of post.designs) {
|
||||||
|
// if (typeof designs[design] === 'undefined') designs[design] = []
|
||||||
|
// designs[design].push(post)
|
||||||
|
// }
|
||||||
|
|
||||||
|
previews.push(
|
||||||
|
<PreviewTile
|
||||||
|
img={sanitySiteImage(post.image[0]) + '?fit=clip&w=400'}
|
||||||
|
slug={post.slug.current}
|
||||||
|
title={post.title}
|
||||||
|
key={post.slug.current}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="grid grid-cols-1 gap-4 xl:gap-8 lg:grid-cols-2 xl:grid-cols-3 lg:pr-4 xl:pr-8">
|
||||||
|
{previews}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const ShowcaseIndexPage = ({ posts, page, current, total }) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
|
||||||
|
// const designKeys = useMemo(() => Object.keys(designs).sort(), [designs])
|
||||||
|
return (
|
||||||
|
<PageWrapper title={t('showcase')} {...page}>
|
||||||
|
<div className="text-center">
|
||||||
|
<Posts locale={page.locale} posts={posts} />
|
||||||
|
<Pagination {...{ current, total }} />
|
||||||
|
</div>
|
||||||
|
</PageWrapper>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ShowcaseIndexPage
|
||||||
|
|
||||||
|
export async function getStaticProps({ locale, params }) {
|
||||||
|
const allPosts = await sanityLoader({
|
||||||
|
language: locale,
|
||||||
|
type: 'showcase',
|
||||||
|
order: 'date desc',
|
||||||
|
filters: '{_id, date, slug, title, maker, image}',
|
||||||
|
})
|
||||||
|
const pageNum = parseInt(params.page)
|
||||||
|
|
||||||
|
return {
|
||||||
|
props: {
|
||||||
|
// designs,
|
||||||
|
posts: allPosts.slice(numPerPage * (pageNum - 1), numPerPage * pageNum),
|
||||||
|
current: pageNum,
|
||||||
|
total: allPosts.length,
|
||||||
|
...(await serverSideTranslations(locale, namespaces)),
|
||||||
|
page: {
|
||||||
|
locale,
|
||||||
|
// title: 'Freesewing Blog',
|
||||||
|
path: ['showcase', 'page', params.page],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getStaticPaths = async () => {
|
||||||
|
const numPosts = await sanityLoader({ query: `count(*[_type == "showcaseen"])` })
|
||||||
|
const numPages = Math.ceil(numPosts / numPerPage)
|
||||||
|
const paths = []
|
||||||
|
for (let i = 0; i < numPages; i++) {
|
||||||
|
const pathName = `/showcase/page/${i + 1}`
|
||||||
|
siteConfig.languages.forEach((l) => paths.push(`${l.length ? '/' : ''}${l}${pathName}`))
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
paths,
|
||||||
|
fallback: false,
|
||||||
|
}
|
||||||
|
}
|
|
@ -7,6 +7,9 @@ export const siteConfig = {
|
||||||
bugsnag: {
|
bugsnag: {
|
||||||
key: '1b3a900d6ebbfd071975e39b534e1ff5',
|
key: '1b3a900d6ebbfd071975e39b534e1ff5',
|
||||||
},
|
},
|
||||||
|
sanity: {
|
||||||
|
project: process.env.SANITY_PROJECT || 'hl5bw8cj',
|
||||||
|
},
|
||||||
languages: ['en', 'es', 'de', 'fr', 'nl'],
|
languages: ['en', 'es', 'de', 'fr', 'nl'],
|
||||||
site: 'FreeSewing.org',
|
site: 'FreeSewing.org',
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,21 +20,21 @@
|
||||||
},
|
},
|
||||||
"peerDependencies": {},
|
"peerDependencies": {},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@sanity/vision": "3.12.0",
|
"@sanity/vision": "3.12.2",
|
||||||
"easymde": "2.16.0",
|
"easymde": "2.18.0",
|
||||||
"react": "18.2.0",
|
"react": "18.2.0",
|
||||||
"react-dom": "18.2.0",
|
"react-dom": "18.2.0",
|
||||||
"react-is": "18.2.0",
|
"react-is": "18.2.0",
|
||||||
"sanity": "3.12.0",
|
"sanity": "3.12.2",
|
||||||
"styled-components": "5.3.11",
|
"styled-components": "5.3.11",
|
||||||
"sanity-plugin-markdown": "4.1.0"
|
"sanity-plugin-markdown": "4.1.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@sanity/eslint-config-studio": "2.0.1",
|
"@sanity/eslint-config-studio": "2.0.1",
|
||||||
"eslint": "8.42.0",
|
"eslint": "8.43.0",
|
||||||
"prettier": "2.8.8",
|
"prettier": "2.8.8",
|
||||||
"typescript": "5.1.3",
|
"typescript": "5.1.3",
|
||||||
"@sanity/cli": "3.12.1"
|
"@sanity/cli": "3.12.2"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=16.0.0",
|
"node": ">=16.0.0",
|
||||||
|
|
|
@ -65,7 +65,7 @@ const transformBlogPost = async (p, lang) => {
|
||||||
const asIs = ['title', 'linktitle', 'caption', 'body']
|
const asIs = ['title', 'linktitle', 'caption', 'body']
|
||||||
|
|
||||||
const post = {
|
const post = {
|
||||||
_id: `${lang}.blog.${p.slug}`,
|
_id: `${lang}--blog--${p.slug}`,
|
||||||
_type: `blog${lang}`,
|
_type: `blog${lang}`,
|
||||||
}
|
}
|
||||||
for (const field of asIs) post[field] = p[field]
|
for (const field of asIs) post[field] = p[field]
|
||||||
|
@ -106,7 +106,7 @@ const transformShowcasePost = async (p, lang) => {
|
||||||
const asIs = ['title', 'caption', 'body']
|
const asIs = ['title', 'caption', 'body']
|
||||||
|
|
||||||
const post = {
|
const post = {
|
||||||
_id: `${lang}.showcase.${p.slug}`,
|
_id: `${lang}--showcase--${p.slug}`,
|
||||||
_type: `showcase${lang}`,
|
_type: `showcase${lang}`,
|
||||||
}
|
}
|
||||||
for (const field of asIs) post[field] = p[field]
|
for (const field of asIs) post[field] = p[field]
|
||||||
|
@ -149,7 +149,7 @@ const transformNewsletterPost = async (p) => {
|
||||||
const asIs = ['title', 'body']
|
const asIs = ['title', 'body']
|
||||||
|
|
||||||
const post = {
|
const post = {
|
||||||
_id: `newsletter.${p.slug}`,
|
_id: `newsletter--${p.slug}`,
|
||||||
_type: 'newsletter',
|
_type: 'newsletter',
|
||||||
}
|
}
|
||||||
for (const field of asIs) post[field] = p[field]
|
for (const field of asIs) post[field] = p[field]
|
||||||
|
|
|
@ -2,8 +2,7 @@
|
||||||
import { useState, useEffect, useContext, useCallback } from 'react'
|
import { useState, useEffect, useContext, useCallback } from 'react'
|
||||||
import { useTranslation } from 'next-i18next'
|
import { useTranslation } from 'next-i18next'
|
||||||
import orderBy from 'lodash.orderby'
|
import orderBy from 'lodash.orderby'
|
||||||
import { measurements, isDegreeMeasurement } from 'config/measurements.mjs'
|
import { measurements } from 'config/measurements.mjs'
|
||||||
import { measurementAsMm, formatMm } from 'shared/utils.mjs'
|
|
||||||
import { measurements as designMeasurements } from 'shared/prebuild/data/design-measurements.mjs'
|
import { measurements as designMeasurements } from 'shared/prebuild/data/design-measurements.mjs'
|
||||||
import { freeSewingConfig as conf } from 'shared/config/freesewing.config.mjs'
|
import { freeSewingConfig as conf } from 'shared/config/freesewing.config.mjs'
|
||||||
// Hooks
|
// Hooks
|
||||||
|
@ -23,7 +22,6 @@ import { ModalDesignPicker } from 'shared/components/modal/design-picker.mjs'
|
||||||
import {
|
import {
|
||||||
FilterIcon,
|
FilterIcon,
|
||||||
ClearIcon,
|
ClearIcon,
|
||||||
PlusIcon,
|
|
||||||
OkIcon,
|
OkIcon,
|
||||||
NoIcon,
|
NoIcon,
|
||||||
TrashIcon,
|
TrashIcon,
|
||||||
|
@ -34,6 +32,7 @@ import Markdown from 'react-markdown'
|
||||||
import { Tab } from './bio.mjs'
|
import { Tab } from './bio.mjs'
|
||||||
import Timeago from 'react-timeago'
|
import Timeago from 'react-timeago'
|
||||||
import { Spinner } from 'shared/components/spinner.mjs'
|
import { Spinner } from 'shared/components/spinner.mjs'
|
||||||
|
import { MeasieRow } from 'shared/components/sets/measie-input.mjs'
|
||||||
|
|
||||||
export const ns = ['account', 'patterns', 'toast']
|
export const ns = ['account', 'patterns', 'toast']
|
||||||
|
|
||||||
|
@ -140,214 +139,6 @@ export const EditRow = (props) => (
|
||||||
</Collapse>
|
</Collapse>
|
||||||
)
|
)
|
||||||
|
|
||||||
const Mval = ({ m, val = false, imperial = false, className = '' }) =>
|
|
||||||
val ? (
|
|
||||||
isDegreeMeasurement(m) ? (
|
|
||||||
<span>{val}°</span>
|
|
||||||
) : (
|
|
||||||
<span
|
|
||||||
dangerouslySetInnerHTML={{ __html: formatMm(val, imperial ? 'imperial' : 'metric') }}
|
|
||||||
className={className}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
) : null
|
|
||||||
|
|
||||||
export const MeasieRow = (props) => {
|
|
||||||
const { t, m, mset } = props
|
|
||||||
|
|
||||||
const isSet = typeof mset.measies?.[m] === 'undefined' ? false : true
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Collapse
|
|
||||||
color="secondary"
|
|
||||||
openTitle={t(m)}
|
|
||||||
title={
|
|
||||||
<>
|
|
||||||
<div className="grow text-left md:text-right block md:inline font-bold pr-4">{t(m)}</div>
|
|
||||||
{isSet ? (
|
|
||||||
<Mval m={m} val={mset.measies[m]} imperial={mset.imperial} className="w-1/3" />
|
|
||||||
) : (
|
|
||||||
<div className="w-1/3" />
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
}
|
|
||||||
toggle={isSet ? <EditIcon /> : <PlusIcon />}
|
|
||||||
toggleClasses={`btn ${isSet ? 'btn-secondary' : 'btn-neutral bg-opacity-50'}`}
|
|
||||||
>
|
|
||||||
<MeasieInput {...props} />
|
|
||||||
</Collapse>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const MeasieInput = ({ t, m, mset, startLoading, stopLoading, backend, refresh, toast }) => {
|
|
||||||
const isDegree = isDegreeMeasurement(m)
|
|
||||||
const factor = isDegree ? 1 : mset.imperial ? 25.4 : 10
|
|
||||||
|
|
||||||
const isValValid = (val) =>
|
|
||||||
typeof val === 'undefined' || val === '' ? null : val != false && !isNaN(val)
|
|
||||||
const isValid = (newVal) => (typeof newVal === 'undefined' ? isValValid(val) : isValValid(newVal))
|
|
||||||
|
|
||||||
const [val, setVal] = useState(mset.measies?.[m] / factor || '')
|
|
||||||
const [valid, setValid] = useState(isValid(mset.measies?.[m] / factor || ''))
|
|
||||||
|
|
||||||
// Update onChange
|
|
||||||
const update = (evt) => {
|
|
||||||
setVal(evt.target.value)
|
|
||||||
|
|
||||||
let useVal = isDegree
|
|
||||||
? evt.target.value
|
|
||||||
: measurementAsMm(evt.target.value, mset.imperial ? 'imperial' : 'metric')
|
|
||||||
setValid(isValid(useVal))
|
|
||||||
}
|
|
||||||
|
|
||||||
const save = async () => {
|
|
||||||
// FIXME
|
|
||||||
startLoading()
|
|
||||||
const measies = {}
|
|
||||||
measies[m] = val * factor
|
|
||||||
const result = await backend.updateSet(mset.id, { measies })
|
|
||||||
if (result.success) {
|
|
||||||
refresh()
|
|
||||||
toast.for.settingsSaved()
|
|
||||||
} else toast.for.backendError()
|
|
||||||
stopLoading()
|
|
||||||
}
|
|
||||||
|
|
||||||
const fraction = (i, base) => update({ target: { value: Math.floor(val) + i / base } })
|
|
||||||
|
|
||||||
if (!m) return null
|
|
||||||
|
|
||||||
const fractionClasses =
|
|
||||||
'h-3 border-2 border-solid border-base-100 hover:border-secondary bg-secondary rounded bg-opacity-50 hover:bg-opacity-100'
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="form-control mb-2 flex flex-row flexwrap gap-2">
|
|
||||||
<div className="flex flex-col items-center">
|
|
||||||
<label className="input-group w-full">
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
className={`
|
|
||||||
input input-bordered text-base-content border-r-0 w-full
|
|
||||||
${valid === false && 'input-error'}
|
|
||||||
${valid === true && 'input-success'}
|
|
||||||
`}
|
|
||||||
value={val}
|
|
||||||
onChange={update}
|
|
||||||
/>
|
|
||||||
{mset.imperial ? (
|
|
||||||
<span
|
|
||||||
className={`bg-transparent border-y
|
|
||||||
${valid === false && 'border-error text-neutral-content'}
|
|
||||||
${valid === true && 'border-success text-neutral-content'}
|
|
||||||
${valid === null && 'border-base-200 text-base-content'}
|
|
||||||
`}
|
|
||||||
>
|
|
||||||
<Mval
|
|
||||||
imperial={true}
|
|
||||||
val={val * 25.4}
|
|
||||||
m={m}
|
|
||||||
className="text-base-content bg-transparent text-success text-xs font-bold p-0"
|
|
||||||
/>
|
|
||||||
</span>
|
|
||||||
) : null}
|
|
||||||
<span
|
|
||||||
role="img"
|
|
||||||
className={`bg-transparent border-y
|
|
||||||
${valid === false && 'border-error text-neutral-content'}
|
|
||||||
${valid === true && 'border-success text-neutral-content'}
|
|
||||||
${valid === null && 'border-base-200 text-base-content'}
|
|
||||||
`}
|
|
||||||
>
|
|
||||||
{valid === true && '👍'}
|
|
||||||
{valid === false && '🤔'}
|
|
||||||
</span>
|
|
||||||
<span
|
|
||||||
className={`
|
|
||||||
${valid === false && 'bg-error text-neutral-content'}
|
|
||||||
${valid === true && 'bg-success text-neutral-content'}
|
|
||||||
${valid === null && 'bg-base-200 text-base-content'}
|
|
||||||
`}
|
|
||||||
>
|
|
||||||
{isDegree ? '° ' : mset.imperial ? 'in' : 'cm'}
|
|
||||||
</span>
|
|
||||||
</label>
|
|
||||||
{mset.imperial ? (
|
|
||||||
<div className="w-full mt-2">
|
|
||||||
<div className="flex flex-row items-center">
|
|
||||||
<span className="text-xs inline-block w-8 text-right pr-2">
|
|
||||||
<sup>1</sup>/<sub>2</sub>
|
|
||||||
</span>
|
|
||||||
<button
|
|
||||||
className={`w-[50%] ${fractionClasses}`}
|
|
||||||
title={`1/2"`}
|
|
||||||
onClick={() => fraction(1, 2)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="flex flex-row">
|
|
||||||
<span className="text-xs inline-block w-8 text-right pr-2">
|
|
||||||
<sup>1</sup>/<sub>4</sub>
|
|
||||||
</span>
|
|
||||||
{[1, 2, 3].map((i) => (
|
|
||||||
<button
|
|
||||||
key={i}
|
|
||||||
className={`w-[25%] ${fractionClasses}`}
|
|
||||||
title={`${i}1/4"`}
|
|
||||||
onClick={() => fraction(i, 4)}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
<div className="flex flex-row">
|
|
||||||
<span className="text-xs inline-block w-8 text-right pr-2">
|
|
||||||
<sup>1</sup>/<sub>8</sub>
|
|
||||||
</span>
|
|
||||||
{[1, 2, 3, 4, 5, 6, 7].map((i) => (
|
|
||||||
<button
|
|
||||||
key={i}
|
|
||||||
className={`w-[12.5%] ${fractionClasses}`}
|
|
||||||
title={`${i}1/8"`}
|
|
||||||
onClick={() => fraction(i, 8)}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
<div className="flex flex-row mt-1">
|
|
||||||
<span className="text-xs inline-block w-8 text-right pr-2">
|
|
||||||
<sup>1</sup>/<sub>16</sub>
|
|
||||||
</span>
|
|
||||||
{[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15].map((i) => (
|
|
||||||
<button
|
|
||||||
key={i}
|
|
||||||
className={`w-[6.25%] ${fractionClasses}`}
|
|
||||||
title={`${i}1/16"`}
|
|
||||||
onClick={() => fraction(i, 16)}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
<div className="flex flex-row mt-1">
|
|
||||||
<span className="text-xs inline-block w-8 text-right pr-2">
|
|
||||||
<sup>1</sup>/<sub>32</sub>
|
|
||||||
</span>
|
|
||||||
{[
|
|
||||||
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23,
|
|
||||||
24, 25, 26, 27, 28, 29, 30, 31,
|
|
||||||
].map((i) => (
|
|
||||||
<button
|
|
||||||
key={i}
|
|
||||||
className={`w-[3.125%] ${fractionClasses}`}
|
|
||||||
title={`${i}/32"`}
|
|
||||||
onClick={() => fraction(i, 32)}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
) : null}
|
|
||||||
</div>
|
|
||||||
<button className="btn btn-secondary w-24" onClick={save} disabled={!valid}>
|
|
||||||
{t('save')}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const EditImg = ({ t, mset, account, backend, startLoading, stopLoading, toast, refresh }) => {
|
const EditImg = ({ t, mset, account, backend, startLoading, stopLoading, toast, refresh }) => {
|
||||||
const [img, setImg] = useState(mset.img)
|
const [img, setImg] = useState(mset.img)
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
import { CloseIcon } from 'shared/components/icons.mjs'
|
import { DownIcon } from 'shared/components/icons.mjs'
|
||||||
import Link from 'next/link'
|
import Link from 'next/link'
|
||||||
|
|
||||||
const OpenTitleButton = ({
|
const OpenTitleButton = ({
|
||||||
|
@ -17,12 +17,11 @@ const OpenTitleButton = ({
|
||||||
bg-${color} text-${color}-content px-4 py-1 text-lg font-medium`}
|
bg-${color} text-${color}-content px-4 py-1 text-lg font-medium`}
|
||||||
onClick={toggle}
|
onClick={toggle}
|
||||||
>
|
>
|
||||||
{title}
|
{<DownIcon className="rotate-180 w-6 h-6 mr-4" />}
|
||||||
|
{!bottom && title}
|
||||||
<div className="flex flex-row items-center gap-2 z-5">
|
<div className="flex flex-row items-center gap-2 z-5">
|
||||||
{openButtons}
|
{openButtons}
|
||||||
<button className="btn btn-ghost btn-xs px-0" onClick={toggle}>
|
<button className="btn btn-ghost btn-xs px-0" onClick={toggle}></button>
|
||||||
<CloseIcon stroke={3} />
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
@ -68,8 +67,7 @@ export const Collapse = ({
|
||||||
grow flex flex-row gap-4 py-1 px-4 items-center justify-start hover:cursor-pointer hover:bg-${color} hover:bg-opacity-20`}
|
grow flex flex-row gap-4 py-1 px-4 items-center justify-start hover:cursor-pointer hover:bg-${color} hover:bg-opacity-20`}
|
||||||
onClick={onClick ? onClick : () => setOpen(true)}
|
onClick={onClick ? onClick : () => setOpen(true)}
|
||||||
>
|
>
|
||||||
{title}
|
<DownIcon /> {title}
|
||||||
</div>
|
|
||||||
{toggle ? (
|
{toggle ? (
|
||||||
<button onClick={() => setOpen(true)} className={toggleClasses}>
|
<button onClick={() => setOpen(true)} className={toggleClasses}>
|
||||||
{toggle}
|
{toggle}
|
||||||
|
@ -78,6 +76,7 @@ export const Collapse = ({
|
||||||
buttons
|
buttons
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,7 @@ import { DesignTag } from 'shared/components/designs/tag.mjs'
|
||||||
|
|
||||||
export const ns = ['design', 'designs', 'tags']
|
export const ns = ['design', 'designs', 'tags']
|
||||||
|
|
||||||
const defaultLink = (design) => `/new/pattern/${design}`
|
const defaultLink = (design) => `/new/${design}`
|
||||||
|
|
||||||
export const Design = ({ name, hrefBuilder = false }) => {
|
export const Design = ({ name, hrefBuilder = false }) => {
|
||||||
const { t } = useTranslation(ns)
|
const { t } = useTranslation(ns)
|
||||||
|
|
|
@ -184,7 +184,16 @@ export const DocsIcon = (props) => (
|
||||||
<path d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
|
<path d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
|
||||||
</IconWrapper>
|
</IconWrapper>
|
||||||
)
|
)
|
||||||
|
export const DoubleLeftIcon = (props) => (
|
||||||
|
<IconWrapper {...props}>
|
||||||
|
<path d="M11 19l-7-7 7-7 M19 19l-7-7 7-7" />
|
||||||
|
</IconWrapper>
|
||||||
|
)
|
||||||
|
export const DoubleRightIcon = (props) => (
|
||||||
|
<IconWrapper {...props}>
|
||||||
|
<path d="M5 5l7 7-7 7 M13 5l7 7-7 7" />
|
||||||
|
</IconWrapper>
|
||||||
|
)
|
||||||
export const DownIcon = (props) => (
|
export const DownIcon = (props) => (
|
||||||
<IconWrapper {...props}>
|
<IconWrapper {...props}>
|
||||||
<path
|
<path
|
||||||
|
@ -264,11 +273,7 @@ export const HeartIcon = (props) => (
|
||||||
|
|
||||||
export const HelpIcon = (props) => (
|
export const HelpIcon = (props) => (
|
||||||
<IconWrapper {...props}>
|
<IconWrapper {...props}>
|
||||||
<path
|
<path d="M9.879 7.519c1.171-1.025 3.071-1.025 4.242 0 1.172 1.025 1.172 2.687 0 3.712-.203.179-.43.326-.67.442-.745.361-1.45.999-1.45 1.827v.75M21 12a9 9 0 11-18 0 9 9 0 0118 0zm-9 5.25h.008v.008H12v-.008z" />
|
||||||
d="m 20.608816,6.2251433 q 0,1.5957853 -0.473995,2.8281739 -0.458196,1.2165888 -1.311388,2.1171808 -0.853192,0.900591 -2.053981,1.611585 -1.184989,0.710993 -2.685975,1.295588 v 3.602366 H 8.521928 v -5.32455 q 1.1217896,-0.300198 2.022381,-0.616195 0.916392,-0.315997 1.911783,-1.02699 0.932191,-0.631994 1.453586,-1.4693866 0.537195,-0.8373922 0.537195,-1.8959824 0,-1.5799854 -1.02699,-2.2435793 Q 12.408692,4.42396 10.560109,4.42396 9.4225197,4.42396 7.9847329,4.9137554 6.5627461,5.4035509 5.377757,6.1777438 H 4.7457629 V 1.3587883 Q 5.7569535,0.93219225 7.8583341,0.47399649 9.9597147,8.6799264e-7 12.124295,8.6799264e-7 q 3.902564,0 6.193542,1.72218403200736 2.290979,1.7221841 2.290979,4.5029584 z M 14.525872,23.999979 H 8.1427315 v -4.171161 h 6.3831405 z"
|
|
||||||
stroke="none"
|
|
||||||
fill="currentColor"
|
|
||||||
/>
|
|
||||||
</IconWrapper>
|
</IconWrapper>
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -67,15 +67,7 @@ const ShowPattern = ({ renderProps, logs, mode = 'normal' }) => {
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
||||||
if (mode === 'xray')
|
return mode === 'xray' ? <PatternXray {...{ renderProps }} /> : <Pattern {...{ renderProps }} />
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<p>xray</p>
|
|
||||||
<PatternXray {...{ renderProps }} />
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
|
|
||||||
return <Pattern {...{ renderProps }} />
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wrapper component dealing with the tabs and code view
|
// Wrapper component dealing with the tabs and code view
|
||||||
|
|
|
@ -6,20 +6,18 @@ export const AsideNavigation = ({ mobileOnly = false, before = [], after = [] })
|
||||||
<aside
|
<aside
|
||||||
className={`
|
className={`
|
||||||
hidden lg:block
|
hidden lg:block
|
||||||
fixed top-0 right-0 h-screen
|
min-h-screen
|
||||||
overflow-y-auto z-20
|
z-20
|
||||||
bg-base-100 text-base-content
|
bg-base-100 text-base-content
|
||||||
px-0 pb-20 pt-8 shrink-0
|
px-0 pb-20 shrink-0 pt-8
|
||||||
|
|
||||||
lg:w-auto
|
lg:w-auto
|
||||||
lg:sticky lg:relative lg:transform-none
|
|
||||||
lg:justify-center
|
lg:justify-center
|
||||||
lg:bg-base-300 lg:bg-opacity-10
|
lg:bg-base-300 lg:bg-opacity-10
|
||||||
lg:pt-16
|
|
||||||
${mobileOnly ? 'block lg:hidden w-full ' : ''}
|
${mobileOnly ? 'block lg:hidden w-full ' : ''}
|
||||||
`}
|
`}
|
||||||
>
|
>
|
||||||
<div className="w-screen lg:w-auto">
|
<div className="w-screen lg:w-auto lg:sticky lg:top-28 max-h-screen overflow-y-auto">
|
||||||
{before}
|
{before}
|
||||||
<MainSections />
|
<MainSections />
|
||||||
<div className="mt-4 pt-4">
|
<div className="mt-4 pt-4">
|
||||||
|
|
79
sites/shared/components/navigation/pagination.mjs
Normal file
79
sites/shared/components/navigation/pagination.mjs
Normal file
|
@ -0,0 +1,79 @@
|
||||||
|
import Link from 'next/link'
|
||||||
|
import { useTranslation } from 'next-i18next'
|
||||||
|
import { LeftIcon, DoubleLeftIcon, RightIcon, DoubleRightIcon } from 'shared/components/icons.mjs'
|
||||||
|
|
||||||
|
const pageButtonClasses = 'btn btn-primary btn-ghost text-base leading-none'
|
||||||
|
|
||||||
|
const PageButton = ({ pageNum, label, title, hrefBuilder, visible = true }) => (
|
||||||
|
<Link
|
||||||
|
className={`${pageButtonClasses} ${visible ? 'join-item' : 'invisible'}`}
|
||||||
|
href={hrefBuilder(pageNum)}
|
||||||
|
title={title || label}
|
||||||
|
>
|
||||||
|
{label}
|
||||||
|
</Link>
|
||||||
|
)
|
||||||
|
|
||||||
|
const defaultHrefBuilder = (pageNum) => `${pageNum}`
|
||||||
|
|
||||||
|
export const Pagination = ({ current, total, hrefBuilder = defaultHrefBuilder }) => {
|
||||||
|
const { t } = useTranslation('common')
|
||||||
|
const prevButtons = []
|
||||||
|
const nextButtons = []
|
||||||
|
|
||||||
|
const buttonProps = { hrefBuilder }
|
||||||
|
for (let i = 1; i < 4; i++) {
|
||||||
|
const isEnd = i === 3
|
||||||
|
|
||||||
|
prevButtons.unshift(
|
||||||
|
<PageButton
|
||||||
|
key={`prev-${i}`}
|
||||||
|
{...{
|
||||||
|
pageNum: isEnd ? 1 : current - i,
|
||||||
|
label: isEnd ? <DoubleLeftIcon /> : current - i,
|
||||||
|
visible: current > i,
|
||||||
|
...buttonProps,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
|
||||||
|
nextButtons.push(
|
||||||
|
<PageButton
|
||||||
|
key={`next-${i}`}
|
||||||
|
{...{
|
||||||
|
pageNum: isEnd ? total : current + i,
|
||||||
|
label: isEnd ? <DoubleRightIcon /> : current + i,
|
||||||
|
visible: current < total + 1 - i,
|
||||||
|
...buttonProps,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<div className="flex justify-evenly items-center mt-8">
|
||||||
|
<PageButton
|
||||||
|
{...{
|
||||||
|
pageNum: current - 1,
|
||||||
|
label: <LeftIcon />,
|
||||||
|
title: t('previous'),
|
||||||
|
...buttonProps,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<div className="flex items-center">
|
||||||
|
{prevButtons}
|
||||||
|
<span className={`text-primary text-xl mx-4`} disabled>
|
||||||
|
{current}
|
||||||
|
</span>
|
||||||
|
{nextButtons}
|
||||||
|
</div>
|
||||||
|
<PageButton
|
||||||
|
{...{
|
||||||
|
pageNum: current + 1,
|
||||||
|
label: <RightIcon />,
|
||||||
|
title: t('next'),
|
||||||
|
...buttonProps,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
|
@ -1,56 +0,0 @@
|
||||||
import { ChoiceLink } from 'shared/components/choice-link.mjs'
|
|
||||||
import { OkIcon, NoIcon, WarningIcon } from 'shared/components/icons.mjs'
|
|
||||||
import { useTranslation } from 'next-i18next'
|
|
||||||
import { capitalize } from 'shared/utils.mjs'
|
|
||||||
|
|
||||||
export const ns = ['sets']
|
|
||||||
|
|
||||||
const Title = ({ set, language }) => (
|
|
||||||
<div className="flex flex-row items-center gap-2">
|
|
||||||
<img
|
|
||||||
alt="img"
|
|
||||||
src={set.img}
|
|
||||||
className="shadow mask mask-squircle bg-neutral aspect-square w-12 h-12"
|
|
||||||
/>
|
|
||||||
<span>{set[`name${capitalize(language)}`]}</span>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
|
|
||||||
export const CuratedSetLacksMeasies = ({ set, design, t, language }) => (
|
|
||||||
<ChoiceLink
|
|
||||||
icon={<NoIcon className="w-10 h-10 text-error" />}
|
|
||||||
title={<Title set={set} language={language} />}
|
|
||||||
href={`/new/pattern/${design}/sets/${set.id}`}
|
|
||||||
>
|
|
||||||
<div className="flex flex-row gap-2 items-center">
|
|
||||||
<WarningIcon className="w-6 h-6 shrink-0 text-error" />
|
|
||||||
<span>{t('setLacksMeasiesForDesign', { design: t(`designs:${design}.t`) })}</span>
|
|
||||||
</div>
|
|
||||||
</ChoiceLink>
|
|
||||||
)
|
|
||||||
|
|
||||||
export const CuratedSetSummary = ({ set, language, href }) => (
|
|
||||||
<ChoiceLink
|
|
||||||
title={<Title set={set} language={language} />}
|
|
||||||
icon={<OkIcon className="w-10 h-10 text-success" stroke={3} />}
|
|
||||||
href={href}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
|
|
||||||
export const CuratedSetCandidate = ({ set, design, requiredMeasies = [], href }) => {
|
|
||||||
const { t, i18n } = useTranslation(['sets'])
|
|
||||||
const { language } = i18n
|
|
||||||
|
|
||||||
const setProps = { set, design, t, language, href }
|
|
||||||
|
|
||||||
// Quick check for required measurements
|
|
||||||
if (!set.measies || Object.keys(set.measies).length < requiredMeasies.length)
|
|
||||||
return <CuratedSetLacksMeasies {...setProps} />
|
|
||||||
|
|
||||||
// Proper check for required measurements
|
|
||||||
for (const m of requiredMeasies) {
|
|
||||||
if (!Object.keys(set.measies).includes(m)) return <CuratedSetLacksMeasies {...setProps} />
|
|
||||||
}
|
|
||||||
|
|
||||||
return <CuratedSetSummary {...setProps} />
|
|
||||||
}
|
|
212
sites/shared/components/sets/measie-input.mjs
Normal file
212
sites/shared/components/sets/measie-input.mjs
Normal file
|
@ -0,0 +1,212 @@
|
||||||
|
import { isDegreeMeasurement } from 'config/measurements.mjs'
|
||||||
|
import { measurementAsMm, formatMm } from 'shared/utils.mjs'
|
||||||
|
import { Collapse } from 'shared/components/collapse.mjs'
|
||||||
|
import { PlusIcon, EditIcon } from 'shared/components/icons.mjs'
|
||||||
|
import { useState } from 'react'
|
||||||
|
export const ns = ['account']
|
||||||
|
|
||||||
|
const Mval = ({ m, val = false, imperial = false, className = '' }) =>
|
||||||
|
val ? (
|
||||||
|
isDegreeMeasurement(m) ? (
|
||||||
|
<span className={className}>{val}°</span>
|
||||||
|
) : (
|
||||||
|
<span
|
||||||
|
dangerouslySetInnerHTML={{ __html: formatMm(val, imperial ? 'imperial' : 'metric') }}
|
||||||
|
className={className}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
) : null
|
||||||
|
|
||||||
|
const heightClasses = {
|
||||||
|
2: 'h-12',
|
||||||
|
4: 'h-10',
|
||||||
|
8: 'h-8',
|
||||||
|
16: 'h-6',
|
||||||
|
32: 'h-4',
|
||||||
|
}
|
||||||
|
|
||||||
|
const fractionClasses =
|
||||||
|
'w-full border-2 border-solid border-base-100 hover:border-secondary bg-secondary rounded bg-opacity-50 hover:bg-opacity-100'
|
||||||
|
|
||||||
|
const FractionButtons = ({ t, fraction }) => (
|
||||||
|
<div className="flex flex-row mt-1 content-center items-center justify-around">
|
||||||
|
<span className="text-xs inline-block pr-2">{t('fractions')}</span>
|
||||||
|
<div className="grow max-w-2xl flex items-baseline">
|
||||||
|
{Array.from({ length: 31 }, (_null, i) => {
|
||||||
|
let denom = 32
|
||||||
|
let num = i + 1
|
||||||
|
|
||||||
|
for (let n = 4; n > 0; n--) {
|
||||||
|
const fac = Math.pow(2, n)
|
||||||
|
if (num % fac === 0) {
|
||||||
|
denom = 32 / fac
|
||||||
|
num = num / fac
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<span className="group w-[3.125%] relative" key={i}>
|
||||||
|
<button
|
||||||
|
className={`${heightClasses[denom]} ${fractionClasses}`}
|
||||||
|
title={`${num}/${denom}″`}
|
||||||
|
onClick={() => fraction(num, denom)}
|
||||||
|
/>
|
||||||
|
<span className="group-hover:visible invisible text-xs text-center absolute left-0 -bottom-6">{`${num}/${denom}"`}</span>
|
||||||
|
</span>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
|
||||||
|
export const MeasieRow = (props) => {
|
||||||
|
const { t, m, mset } = props
|
||||||
|
|
||||||
|
const isSet = typeof mset.measies?.[m] !== 'undefined'
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Collapse
|
||||||
|
color="secondary"
|
||||||
|
openTitle={t(m)}
|
||||||
|
title={
|
||||||
|
<>
|
||||||
|
<div className="grow text-left md:text-right block md:inline font-bold pr-4">{t(m)}</div>
|
||||||
|
{isSet ? (
|
||||||
|
<Mval m={m} val={mset.measies[m]} imperial={mset.imperial} className="w-1/3" />
|
||||||
|
) : (
|
||||||
|
<div className="w-1/3" />
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
toggle={isSet ? <EditIcon /> : <PlusIcon />}
|
||||||
|
toggleClasses={`btn ${isSet ? 'btn-secondary' : 'btn-neutral bg-opacity-50'}`}
|
||||||
|
>
|
||||||
|
<MeasieInput {...props} />
|
||||||
|
</Collapse>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const MeasieInput = ({
|
||||||
|
t,
|
||||||
|
m,
|
||||||
|
mset,
|
||||||
|
backend,
|
||||||
|
refresh,
|
||||||
|
toast,
|
||||||
|
children,
|
||||||
|
onUpdate,
|
||||||
|
startLoading = () => null,
|
||||||
|
stopLoading = () => null,
|
||||||
|
}) => {
|
||||||
|
const isDegree = isDegreeMeasurement(m)
|
||||||
|
const factor = isDegree ? 1 : mset.imperial ? 25.4 : 10
|
||||||
|
|
||||||
|
const isValValid = (val) =>
|
||||||
|
typeof val === 'undefined' || val === '' ? null : val != false && !isNaN(val)
|
||||||
|
const isValid = (newVal) => (typeof newVal === 'undefined' ? isValValid(val) : isValValid(newVal))
|
||||||
|
|
||||||
|
const [val, setVal] = useState(mset.measies?.[m] / factor || '')
|
||||||
|
const [valid, setValid] = useState(isValid(mset.measies?.[m] / factor || ''))
|
||||||
|
|
||||||
|
// Update onChange
|
||||||
|
const update = (evt) => {
|
||||||
|
setVal(evt.target.value)
|
||||||
|
|
||||||
|
const useVal = isDegree
|
||||||
|
? evt.target.value
|
||||||
|
: measurementAsMm(evt.target.value, mset.imperial ? 'imperial' : 'metric')
|
||||||
|
const validUpdate = isValid(useVal)
|
||||||
|
setValid(validUpdate)
|
||||||
|
|
||||||
|
if (validUpdate && typeof onUpdate === 'function') {
|
||||||
|
onUpdate(m, useVal)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const save = async () => {
|
||||||
|
// FIXME
|
||||||
|
startLoading()
|
||||||
|
const measies = {}
|
||||||
|
measies[m] = val * factor
|
||||||
|
const result = await backend.updateSet(mset.id, { measies })
|
||||||
|
if (result.success) {
|
||||||
|
refresh()
|
||||||
|
toast.for.settingsSaved()
|
||||||
|
} else toast.for.backendError()
|
||||||
|
stopLoading()
|
||||||
|
}
|
||||||
|
|
||||||
|
const fraction = (i, base) => update({ target: { value: Math.floor(val) + i / base } })
|
||||||
|
|
||||||
|
if (!m) return null
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="form-control mb-2 ">
|
||||||
|
<div className="flex items-center gap-4 flex-wrap mx-auto">
|
||||||
|
<label className="shrink-0 grow max-w-full">
|
||||||
|
{children}
|
||||||
|
<span className="input-group">
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
step={mset.imperial && !isDegree ? 0.03125 : 0.1}
|
||||||
|
className={`
|
||||||
|
input input-bordered text-base-content border-r-0 w-full
|
||||||
|
${valid === false && 'input-error'}
|
||||||
|
${valid === true && 'input-success'}
|
||||||
|
`}
|
||||||
|
value={val}
|
||||||
|
onChange={update}
|
||||||
|
/>
|
||||||
|
{mset.imperial ? (
|
||||||
|
<span
|
||||||
|
className={`bg-transparent border-y w-20
|
||||||
|
${valid === false && 'border-error text-neutral-content'}
|
||||||
|
${valid === true && 'border-success text-neutral-content'}
|
||||||
|
${valid === null && 'border-base-200 text-base-content'}
|
||||||
|
`}
|
||||||
|
>
|
||||||
|
<Mval
|
||||||
|
imperial={true}
|
||||||
|
val={val * 25.4}
|
||||||
|
m={m}
|
||||||
|
className="text-base-content bg-transparent text-success text-xs font-bold p-0"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
) : null}
|
||||||
|
<span
|
||||||
|
role="img"
|
||||||
|
className={`bg-transparent border-y
|
||||||
|
${valid === false && 'border-error text-neutral-content'}
|
||||||
|
${valid === true && 'border-success text-neutral-content'}
|
||||||
|
${valid === null && 'border-base-200 text-base-content'}
|
||||||
|
`}
|
||||||
|
>
|
||||||
|
{valid === true && '👍'}
|
||||||
|
{valid === false && '🤔'}
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
className={`w-14 text-center
|
||||||
|
${valid === false && 'bg-error text-neutral-content'}
|
||||||
|
${valid === true && 'bg-success text-neutral-content'}
|
||||||
|
${valid === null && 'bg-base-200 text-base-content'}
|
||||||
|
`}
|
||||||
|
>
|
||||||
|
{isDegree ? '°' : mset.imperial ? 'in' : 'cm'}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
{mset.imperial && (
|
||||||
|
<div className="grow my-2 sm:min-w-[22rem]">
|
||||||
|
{!isDegree && <FractionButtons {...{ t, fraction }} />}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
{backend && (
|
||||||
|
<button className="btn btn-secondary w-24" onClick={save} disabled={!valid}>
|
||||||
|
{t('save')}
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
|
@ -1,54 +1,59 @@
|
||||||
|
import { ChoiceButton } from 'shared/components/choice-button.mjs'
|
||||||
import { ChoiceLink } from 'shared/components/choice-link.mjs'
|
import { ChoiceLink } from 'shared/components/choice-link.mjs'
|
||||||
import { OkIcon, NoIcon, WarningIcon } from 'shared/components/icons.mjs'
|
import { OkIcon, NoIcon, WarningIcon } from 'shared/components/icons.mjs'
|
||||||
import { useTranslation } from 'next-i18next'
|
import { useTranslation } from 'next-i18next'
|
||||||
|
import { capitalize, hasRequiredMeasurements } from 'shared/utils.mjs'
|
||||||
|
import Image from 'next/image'
|
||||||
|
|
||||||
export const ns = ['sets']
|
export const ns = ['sets']
|
||||||
|
|
||||||
const Title = ({ set }) => (
|
const Title = ({ set, language }) => (
|
||||||
<div className="flex flex-row items-center gap-2">
|
<div className="flex flex-row items-center gap-2">
|
||||||
<img
|
<Image
|
||||||
alt="img"
|
alt="img"
|
||||||
src={set.img}
|
src={set.img || ''}
|
||||||
|
width={100}
|
||||||
|
height={100}
|
||||||
className="shadow mask mask-squircle bg-neutral aspect-square w-12 h-12"
|
className="shadow mask mask-squircle bg-neutral aspect-square w-12 h-12"
|
||||||
/>
|
/>
|
||||||
<span>{set.name}</span>
|
<span>{set[`name${language ? capitalize(language) : ''}`]}</span>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
||||||
export const SetLacksMeasies = ({ set, design, t }) => (
|
export const SetSummary = ({ set, href, clickHandler, language, hasMeasies, t, design }) => {
|
||||||
<ChoiceLink
|
const inner = hasMeasies ? null : (
|
||||||
icon={<NoIcon className="w-10 h-10 text-error" />}
|
|
||||||
title={<Title set={set} />}
|
|
||||||
href={`/sets/${set.id}`}
|
|
||||||
>
|
|
||||||
<div className="flex flex-row gap-2 items-center">
|
<div className="flex flex-row gap-2 items-center">
|
||||||
<WarningIcon className="w-6 h-6 shrink-0 text-error" />
|
<WarningIcon className="w-6 h-6 shrink-0 text-error" />
|
||||||
<span>{t('setLacksMeasiesForDesign', { design: t(`designs:${design}.t`) })}</span>
|
<span>{t('setLacksMeasiesForDesign', { design: t(`designs:${design}.t`) })}</span>
|
||||||
</div>
|
</div>
|
||||||
</ChoiceLink>
|
)
|
||||||
)
|
const wrapProps = {
|
||||||
|
icon: hasMeasies ? (
|
||||||
export const SetSummary = ({ set, design, t }) => (
|
<OkIcon className="w-10 h-10 text-success" />
|
||||||
<ChoiceLink
|
) : (
|
||||||
title={<Title set={set} />}
|
<NoIcon className="w-10 h-10 text-error" />
|
||||||
icon={<OkIcon className="w-10 h-10 text-success" stroke={3} />}
|
),
|
||||||
href={`/new/pattern/${design}/set/${set.id}`}
|
title: <Title set={set} language={language} />,
|
||||||
></ChoiceLink>
|
|
||||||
)
|
|
||||||
|
|
||||||
export const SetCandidate = ({ set, design, requiredMeasies = [] }) => {
|
|
||||||
const { t } = useTranslation(['sets'])
|
|
||||||
|
|
||||||
const setProps = { set, design, t }
|
|
||||||
|
|
||||||
// Quick check for required measurements
|
|
||||||
if (!set.measies || Object.keys(set.measies).length < requiredMeasies.length)
|
|
||||||
return <SetLacksMeasies {...setProps} />
|
|
||||||
|
|
||||||
// Proper check for required measurements
|
|
||||||
for (const m of requiredMeasies) {
|
|
||||||
if (!Object.keys(set.measies).includes(m)) return <SetLacksMeasies {...setProps} />
|
|
||||||
}
|
}
|
||||||
|
if (clickHandler) wrapProps.onClick = () => clickHandler(set)
|
||||||
|
else if (href) wrapProps.href = href
|
||||||
|
|
||||||
|
const Component = clickHandler ? ChoiceButton : ChoiceLink
|
||||||
|
|
||||||
|
return <Component {...wrapProps}>{inner}</Component>
|
||||||
|
}
|
||||||
|
|
||||||
|
export const SetCandidate = ({
|
||||||
|
set,
|
||||||
|
design,
|
||||||
|
requiredMeasies = [],
|
||||||
|
href,
|
||||||
|
clickHandler,
|
||||||
|
language,
|
||||||
|
}) => {
|
||||||
|
const { t } = useTranslation(['sets', design])
|
||||||
|
const [hasMeasies, missingMeasies] = hasRequiredMeasurements(requiredMeasies, set.measies, true)
|
||||||
|
const setProps = { set, design, t, href, clickHandler, hasMeasies, language }
|
||||||
|
|
||||||
return <SetSummary {...setProps} />
|
return <SetSummary {...setProps} />
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,24 +10,22 @@ import { useAccount } from 'shared/hooks/use-account.mjs'
|
||||||
import { useBackend } from 'shared/hooks/use-backend.mjs'
|
import { useBackend } from 'shared/hooks/use-backend.mjs'
|
||||||
// Components
|
// Components
|
||||||
import { SetCandidate, ns as setNs } from 'shared/components/sets/set-candidate.mjs'
|
import { SetCandidate, ns as setNs } from 'shared/components/sets/set-candidate.mjs'
|
||||||
import { CuratedSetCandidate } from 'shared/components/sets/curated-set-candidate.mjs'
|
|
||||||
import { PopoutWrapper } from 'shared/components/wrappers/popout.mjs'
|
import { PopoutWrapper } from 'shared/components/wrappers/popout.mjs'
|
||||||
import { Tag } from 'shared/components/tag.mjs'
|
import { Tag } from 'shared/components/tag.mjs'
|
||||||
import { FilterIcon } from 'shared/components/icons.mjs'
|
import { FilterIcon } from 'shared/components/icons.mjs'
|
||||||
|
|
||||||
export const ns = setNs
|
export const ns = setNs
|
||||||
|
|
||||||
export const CuratedSetPicker = ({ design, language }) => {
|
export const CuratedSetPicker = ({ design, language, href, clickHandler }) => {
|
||||||
// Hooks
|
// Hooks
|
||||||
const { token } = useAccount()
|
const { token } = useAccount()
|
||||||
const backend = useBackend(token)
|
const backend = useBackend(token)
|
||||||
const { t } = useTranslation('sets')
|
const { t, i18n } = useTranslation('sets')
|
||||||
|
|
||||||
// State
|
// State
|
||||||
const [curatedSets, setCuratedSets] = useState([])
|
const [curatedSets, setCuratedSets] = useState([])
|
||||||
const [filter, setFilter] = useState([])
|
const [filter, setFilter] = useState([])
|
||||||
const [tags, setTags] = useState([])
|
const [tags, setTags] = useState([])
|
||||||
const [reload, setReload] = useState(0)
|
|
||||||
|
|
||||||
// Effects
|
// Effects
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -45,7 +43,7 @@ export const CuratedSetPicker = ({ design, language }) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
getCuratedSets()
|
getCuratedSets()
|
||||||
}, [reload])
|
}, [backend, language])
|
||||||
|
|
||||||
const addFilter = (tag) => {
|
const addFilter = (tag) => {
|
||||||
const newFilter = [...filter, tag]
|
const newFilter = [...filter, tag]
|
||||||
|
@ -108,11 +106,9 @@ export const CuratedSetPicker = ({ design, language }) => {
|
||||||
<div className="flex flex-row flex-wrap gap-2">
|
<div className="flex flex-row flex-wrap gap-2">
|
||||||
{orderBy(list, ['name'], ['asc']).map((set) => (
|
{orderBy(list, ['name'], ['asc']).map((set) => (
|
||||||
<div className="w-full lg:w-96" key={set.id}>
|
<div className="w-full lg:w-96" key={set.id}>
|
||||||
<CuratedSetCandidate
|
<SetCandidate
|
||||||
href={`/new/pattern/${design}/cset/${set.id}`}
|
|
||||||
set={set}
|
|
||||||
requiredMeasies={measurements[design]}
|
requiredMeasies={measurements[design]}
|
||||||
design={design}
|
{...{ set, design, href, clickHandler, language: i18n.language }}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
|
@ -121,14 +117,13 @@ export const CuratedSetPicker = ({ design, language }) => {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const UserSetPicker = ({ design, t, language }) => {
|
export const UserSetPicker = ({ design, t, href, clickHandler }) => {
|
||||||
// Hooks
|
// Hooks
|
||||||
const { token } = useAccount()
|
const { token } = useAccount()
|
||||||
const backend = useBackend(token)
|
const backend = useBackend(token)
|
||||||
|
|
||||||
// State
|
// State
|
||||||
const [sets, setSets] = useState({})
|
const [sets, setSets] = useState({})
|
||||||
const [list, setList] = useState([])
|
|
||||||
|
|
||||||
// Effects
|
// Effects
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -141,14 +136,10 @@ export const UserSetPicker = ({ design, t, language }) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
getSets()
|
getSets()
|
||||||
}, [])
|
})
|
||||||
|
|
||||||
// Need to sort designs by their translated title
|
|
||||||
const translated = {}
|
|
||||||
for (const d of list) translated[t(`${d}.t`)] = d
|
|
||||||
|
|
||||||
return Object.keys(sets).length < 1 ? (
|
return Object.keys(sets).length < 1 ? (
|
||||||
<PopoutWrapper tip>
|
<PopoutWrapper tip noP>
|
||||||
<h5>{t('patternForWhichSet')}</h5>
|
<h5>{t('patternForWhichSet')}</h5>
|
||||||
<p>{t('fsmtm')}</p>
|
<p>{t('fsmtm')}</p>
|
||||||
</PopoutWrapper>
|
</PopoutWrapper>
|
||||||
|
@ -160,7 +151,10 @@ export const UserSetPicker = ({ design, t, language }) => {
|
||||||
<div className="flex flex-row flex-wrap gap-2">
|
<div className="flex flex-row flex-wrap gap-2">
|
||||||
{orderBy(sets, ['name'], ['asc']).map((set) => (
|
{orderBy(sets, ['name'], ['asc']).map((set) => (
|
||||||
<div className="w-full lg:w-96" key={set.id}>
|
<div className="w-full lg:w-96" key={set.id}>
|
||||||
<SetCandidate set={set} requiredMeasies={measurements[design]} design={design} />
|
<SetCandidate
|
||||||
|
requiredMeasies={measurements[design]}
|
||||||
|
{...{ set, design, href, clickHandler }}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
@ -174,25 +168,27 @@ export const UserSetPicker = ({ design, t, language }) => {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const BookmarkedSetPicker = ({ design, t }) => (
|
export const BookmarkedSetPicker = ({ t }) => (
|
||||||
<>
|
<>
|
||||||
<h3>{t('bookmarkedSets')}</h3>
|
<h3>{t('bookmarkedSets')}</h3>
|
||||||
<PopoutWrapper fixme>Implement bookmarked set picker (also implement bookmarks)</PopoutWrapper>
|
<PopoutWrapper fixme>Implement bookmarked set picker (also implement bookmarks)</PopoutWrapper>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
|
|
||||||
export const SetPicker = ({ design }) => {
|
export const SetPicker = ({ design, href = false, clickHandler = false }) => {
|
||||||
const { t, i18n } = useTranslation('sets')
|
const { t, i18n } = useTranslation('sets')
|
||||||
const { language } = i18n
|
const { language } = i18n
|
||||||
|
|
||||||
const pickerProps = { design, t, language }
|
const pickerProps = { design, t, language, href, clickHandler }
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<h2>{t('chooseSet')}</h2>
|
<h2>{t('chooseSet')}</h2>
|
||||||
<UserSetPicker {...pickerProps} />
|
<UserSetPicker {...pickerProps} />
|
||||||
<BookmarkedSetPicker {...pickerProps} />
|
|
||||||
<CuratedSetPicker {...pickerProps} />
|
<CuratedSetPicker {...pickerProps} />
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//<BookmarkedSetPicker {...pickerProps} />
|
||||||
|
//<CuratedSetPicker {...pickerProps} />
|
||||||
|
|
|
@ -59,7 +59,10 @@ const NavIcons = ({ setView, setDense, dense, view }) => {
|
||||||
extraClasses="text-success bg-neutral hover:bg-success hover:text-neutral"
|
extraClasses="text-success bg-neutral hover:bg-success hover:text-neutral"
|
||||||
>
|
>
|
||||||
{dense ? (
|
{dense ? (
|
||||||
<RightIcon className={`${iconSize} animate-bounce-right`} stroke={4} />
|
<RightIcon
|
||||||
|
className={`${iconSize} group-hover:animate-[bounceright_1s_infinite] animate-[bounceright_1s_5]`}
|
||||||
|
stroke={4}
|
||||||
|
/>
|
||||||
) : (
|
) : (
|
||||||
<LeftIcon className={`${iconSize} animate-bounce-right`} stroke={4} />
|
<LeftIcon className={`${iconSize} animate-bounce-right`} stroke={4} />
|
||||||
)}
|
)}
|
||||||
|
@ -143,19 +146,23 @@ const NavIcons = ({ setView, setDense, dense, view }) => {
|
||||||
|
|
||||||
export const WorkbenchHeader = ({ view, setView }) => {
|
export const WorkbenchHeader = ({ view, setView }) => {
|
||||||
const [dense, setDense] = useState(true)
|
const [dense, setDense] = useState(true)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<header
|
<header
|
||||||
className={`
|
className={`
|
||||||
hidden lg:block
|
h-full w-64 min-h-screen pt-4
|
||||||
bg-neutral
|
bg-neutral
|
||||||
w-64 min-h-screen pt-4
|
|
||||||
transition-all
|
transition-all
|
||||||
drop-shadow-xl
|
drop-shadow-xl
|
||||||
${dense ? '-ml-52' : 'ml-0'}
|
${dense ? '-ml-52' : 'ml-0'}
|
||||||
|
group
|
||||||
`}
|
`}
|
||||||
>
|
>
|
||||||
<div className="hidden lg:flex lg:flex-col lg:justify-between items-center w-full">
|
<div
|
||||||
|
className={`
|
||||||
|
flex flex-col
|
||||||
|
items-center w-full sticky top-4 lg:top-28`}
|
||||||
|
>
|
||||||
<NavIcons {...{ setView, setDense, dense, view }} />
|
<NavIcons {...{ setView, setDense, dense, view }} />
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
|
@ -11,7 +11,7 @@ import {
|
||||||
} from 'shared/components/icons.mjs'
|
} from 'shared/components/icons.mjs'
|
||||||
|
|
||||||
export const defaultSamm = (units, inMm = true) => {
|
export const defaultSamm = (units, inMm = true) => {
|
||||||
const dflt = units === 'metric' ? 1 : 0.5
|
const dflt = units === 'imperial' ? 0.5 : 1
|
||||||
return inMm ? measurementAsMm(dflt, units) : dflt
|
return inMm ? measurementAsMm(dflt, units) : dflt
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -39,9 +39,8 @@ export const loadSettingsConfig = ({
|
||||||
? {
|
? {
|
||||||
control: 2, // Show when control > 1
|
control: 2, // Show when control > 1
|
||||||
min: 0,
|
min: 0,
|
||||||
max: units === 'metric' ? 2.5 : 2,
|
max: units === 'imperial' ? 2 : 2.5,
|
||||||
dflt: defaultSamm(units, false),
|
dflt: defaultSamm(units),
|
||||||
step: units === 'metric' ? 0.1 : 0.125,
|
|
||||||
icon: SaIcon,
|
icon: SaIcon,
|
||||||
}
|
}
|
||||||
: false,
|
: false,
|
||||||
|
@ -126,8 +125,7 @@ export const loadSettingsConfig = ({
|
||||||
control: 4, // Show when control > 3
|
control: 4, // Show when control > 3
|
||||||
min: 0,
|
min: 0,
|
||||||
max: 2.5,
|
max: 2.5,
|
||||||
dflt: units === 'metric' ? 0.2 : 0.125,
|
dflt: measurementAsMm(units === 'imperial' ? 0.125 : 0.2, units),
|
||||||
step: units === 'metric' ? 0.1 : 0.125,
|
|
||||||
icon: MarginIcon,
|
icon: MarginIcon,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
//Dependencies
|
//Dependencies
|
||||||
import { loadSettingsConfig, defaultSamm } from './config.mjs'
|
import { loadSettingsConfig, defaultSamm } from './config.mjs'
|
||||||
// Components
|
// Components
|
||||||
import { SettingsIcon } from 'shared/components/icons.mjs'
|
import { SettingsIcon, TrashIcon } from 'shared/components/icons.mjs'
|
||||||
import { WorkbenchMenu } from '../shared/index.mjs'
|
import { WorkbenchMenu } from '../shared/index.mjs'
|
||||||
import { MenuItem } from '../shared/menu-item.mjs'
|
import { MenuItem } from '../shared/menu-item.mjs'
|
||||||
// input components and event handlers
|
// input components and event handlers
|
||||||
|
@ -9,6 +9,8 @@ import { inputs, handlers } from './inputs.mjs'
|
||||||
// values
|
// values
|
||||||
import { values } from './values.mjs'
|
import { values } from './values.mjs'
|
||||||
|
|
||||||
|
import { useTranslation } from 'next-i18next'
|
||||||
|
|
||||||
export const ns = ['core-settings', 'modal']
|
export const ns = ['core-settings', 'modal']
|
||||||
|
|
||||||
/** A wrapper for {@see MenuItem} to handle core settings-specific business */
|
/** A wrapper for {@see MenuItem} to handle core settings-specific business */
|
||||||
|
@ -41,6 +43,21 @@ const CoreSetting = ({ name, config, control, updateFunc, current, passProps, ..
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const ClearAllButton = ({ setSettings, compact = false }) => {
|
||||||
|
const { t } = useTranslation('core-settings')
|
||||||
|
return (
|
||||||
|
<div className={`${compact ? '' : 'text-center mt-8'}`}>
|
||||||
|
<button
|
||||||
|
className={`justify-self-center btn btn-error btn-outline ${compact ? 'btn-sm' : ''}`}
|
||||||
|
onClick={() => setSettings({})}
|
||||||
|
>
|
||||||
|
<TrashIcon />
|
||||||
|
{t('clearSettings')}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The core settings menu
|
* The core settings menu
|
||||||
* @param {Object} options.update settings and ui update functions
|
* @param {Object} options.update settings and ui update functions
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
import { measurementAsMm } from 'shared/utils.mjs'
|
|
||||||
import { ListInput, SliderInput, BoolInput, MmInput } from '../shared/inputs.mjs'
|
import { ListInput, SliderInput, BoolInput, MmInput } from '../shared/inputs.mjs'
|
||||||
|
|
||||||
/** an input for the 'only' setting. toggles individual parts*/
|
/** an input for the 'only' setting. toggles individual parts*/
|
||||||
|
@ -45,10 +44,10 @@ export const handlers = {
|
||||||
updateFunc(path, newParts)
|
updateFunc(path, newParts)
|
||||||
},
|
},
|
||||||
samm:
|
samm:
|
||||||
({ updateFunc, config, units }) =>
|
({ updateFunc, config }) =>
|
||||||
(_path, newCurrent) => {
|
(_path, newCurrent) => {
|
||||||
// convert to millimeters if there's a value
|
// convert to millimeters if there's a value
|
||||||
newCurrent = newCurrent === undefined ? measurementAsMm(config.dflt, units) : newCurrent
|
newCurrent = newCurrent === undefined ? config.dflt : newCurrent
|
||||||
// update both values to match
|
// update both values to match
|
||||||
updateFunc([
|
updateFunc([
|
||||||
[['samm'], newCurrent],
|
[['samm'], newCurrent],
|
||||||
|
|
|
@ -85,9 +85,10 @@ export const ListToggle = ({ config, changed, updateFunc, name }) => {
|
||||||
return (
|
return (
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
className={`toggle toggle-sm ${changed ? 'toggle-accent' : 'toggle-secondary'}`}
|
className={`toggle ${changed ? 'toggle-accent' : 'toggle-secondary'}`}
|
||||||
checked={checked}
|
checked={checked}
|
||||||
onChange={doToggle}
|
onChange={doToggle}
|
||||||
|
onClick={(evt) => evt.stopPropagation()}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -220,7 +221,7 @@ export const SliderInput = ({
|
||||||
}
|
}
|
||||||
|
|
||||||
/** A {@see SliderInput} to handle percentage values */
|
/** A {@see SliderInput} to handle percentage values */
|
||||||
export const PctInput = ({ current, changed, updateFunc, ...rest }) => {
|
export const PctInput = ({ current, changed, updateFunc, config, ...rest }) => {
|
||||||
const factor = 100
|
const factor = 100
|
||||||
let pctCurrent = changed ? current * factor : current
|
let pctCurrent = changed ? current * factor : current
|
||||||
const pctUpdateFunc = useCallback(
|
const pctUpdateFunc = useCallback(
|
||||||
|
@ -232,6 +233,7 @@ export const PctInput = ({ current, changed, updateFunc, ...rest }) => {
|
||||||
<SliderInput
|
<SliderInput
|
||||||
{...{
|
{...{
|
||||||
...rest,
|
...rest,
|
||||||
|
config: { ...config, dflt: config.dflt * factor },
|
||||||
current: pctCurrent,
|
current: pctCurrent,
|
||||||
updateFunc: pctUpdateFunc,
|
updateFunc: pctUpdateFunc,
|
||||||
suffix: '%',
|
suffix: '%',
|
||||||
|
@ -257,13 +259,17 @@ export const MmInput = (props) => {
|
||||||
)
|
)
|
||||||
|
|
||||||
// add a default step that's appropriate to the unit. can be overwritten by config
|
// add a default step that's appropriate to the unit. can be overwritten by config
|
||||||
const defaultStep = units === 'metric' ? 0.1 : 0.125
|
const defaultStep = units === 'imperial' ? 0.125 : 0.1
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SliderInput
|
<SliderInput
|
||||||
{...{
|
{...{
|
||||||
...props,
|
...props,
|
||||||
config: { step: defaultStep, ...config },
|
config: {
|
||||||
|
step: defaultStep,
|
||||||
|
...config,
|
||||||
|
dflt: measurementAsUnits(config.dflt, units),
|
||||||
|
},
|
||||||
current: current === undefined ? undefined : measurementAsUnits(current, units),
|
current: current === undefined ? undefined : measurementAsUnits(current, units),
|
||||||
updateFunc: mmUpdateFunc,
|
updateFunc: mmUpdateFunc,
|
||||||
valFormatter: (val) => (units === 'imperial' ? formatFraction128(val, null) : val),
|
valFormatter: (val) => (units === 'imperial' ? formatFraction128(val, null) : val),
|
||||||
|
|
|
@ -95,7 +95,7 @@ export const MenuItem = ({
|
||||||
if (loadDocs)
|
if (loadDocs)
|
||||||
openButtons.push(
|
openButtons.push(
|
||||||
<button className={openButtonClass} key="help" onClick={(evt) => loadDocs(evt, name)}>
|
<button className={openButtonClass} key="help" onClick={(evt) => loadDocs(evt, name)}>
|
||||||
<HelpIcon className="w-4 h-4" />
|
<HelpIcon className="w-6 h-6" />
|
||||||
</button>
|
</button>
|
||||||
)
|
)
|
||||||
if (allowOverride)
|
if (allowOverride)
|
||||||
|
@ -111,10 +111,10 @@ export const MenuItem = ({
|
||||||
<EditIcon className={`w-6 h-6 ${override ? 'bg-base-100 text-accent rounded' : ''}`} />
|
<EditIcon className={`w-6 h-6 ${override ? 'bg-base-100 text-accent rounded' : ''}`} />
|
||||||
</button>
|
</button>
|
||||||
)
|
)
|
||||||
if (changed && !allowToggle) {
|
const ResetButton = ({ open, disabled = false }) => (
|
||||||
const ResetButton = ({ open }) => (
|
|
||||||
<button
|
<button
|
||||||
className={open ? openButtonClass : 'btn btn-accent'}
|
className={`${open ? openButtonClass : 'btn btn-accent'} disabled:bg-opacity-0`}
|
||||||
|
disabled={disabled}
|
||||||
onClick={(evt) => {
|
onClick={(evt) => {
|
||||||
evt.stopPropagation()
|
evt.stopPropagation()
|
||||||
updateFunc([name])
|
updateFunc([name])
|
||||||
|
@ -123,12 +123,15 @@ export const MenuItem = ({
|
||||||
<ClearIcon />
|
<ClearIcon />
|
||||||
</button>
|
</button>
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if (changed && !allowToggle) {
|
||||||
buttons.push(<ResetButton key="clear" />)
|
buttons.push(<ResetButton key="clear" />)
|
||||||
openButtons.push(<ResetButton open key="clear" />)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (allowToggle) {
|
if (allowToggle) {
|
||||||
buttons.push(<ListToggle key="toggle" {...{ config, changed, updateFunc, name }} />)
|
buttons.push(<ListToggle key="toggle" {...{ config, changed, updateFunc, name }} />)
|
||||||
|
} else {
|
||||||
|
openButtons.push(<ResetButton open disabled={!changed} key="clear" />)
|
||||||
}
|
}
|
||||||
|
|
||||||
// props to pass to the ItemTitle
|
// props to pass to the ItemTitle
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { formatMm, formatFraction128 } from 'shared/utils.mjs'
|
import { formatMm } from 'shared/utils.mjs'
|
||||||
|
|
||||||
/*********************************************************************************************************
|
/*********************************************************************************************************
|
||||||
* This file contains the base components to be used for displaying values in menu titles in the workbench
|
* This file contains the base components to be used for displaying values in menu titles in the workbench
|
||||||
|
@ -54,11 +54,7 @@ export const MmValue = ({ current, config, units, changed }) => (
|
||||||
<HighlightedValue changed={changed}>
|
<HighlightedValue changed={changed}>
|
||||||
<span
|
<span
|
||||||
dangerouslySetInnerHTML={{
|
dangerouslySetInnerHTML={{
|
||||||
__html: changed
|
__html: formatMm(changed ? current : config.dflt, units),
|
||||||
? formatMm(current, units)
|
|
||||||
: units === 'imperial'
|
|
||||||
? formatFraction128(config.dflt)
|
|
||||||
: `${config.dflt}cm`,
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</HighlightedValue>
|
</HighlightedValue>
|
||||||
|
|
|
@ -2,12 +2,13 @@
|
||||||
import { useEffect, useState } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
import { useTranslation } from 'next-i18next'
|
import { useTranslation } from 'next-i18next'
|
||||||
import { useView } from 'shared/hooks/use-view.mjs'
|
import { useView } from 'shared/hooks/use-view.mjs'
|
||||||
|
import { usePatternSettings } from 'shared/hooks/use-pattern-settings.mjs'
|
||||||
import { useAccount } from 'shared/hooks/use-account.mjs'
|
import { useAccount } from 'shared/hooks/use-account.mjs'
|
||||||
import { useControlState } from 'shared/components/account/control.mjs'
|
import { useControlState } from 'shared/components/account/control.mjs'
|
||||||
// Dependencies
|
// Dependencies
|
||||||
import { pluginTheme } from '@freesewing/plugin-theme'
|
import { pluginTheme } from '@freesewing/plugin-theme'
|
||||||
import { pluginI18n } from '@freesewing/plugin-i18n'
|
import { pluginI18n } from '@freesewing/plugin-i18n'
|
||||||
import { objUpdate } from 'shared/utils.mjs'
|
import { objUpdate, hasRequiredMeasurements } from 'shared/utils.mjs'
|
||||||
// Components
|
// Components
|
||||||
import { WorkbenchHeader } from './header.mjs'
|
import { WorkbenchHeader } from './header.mjs'
|
||||||
import { ErrorView } from 'shared/components/error/view.mjs'
|
import { ErrorView } from 'shared/components/error/view.mjs'
|
||||||
|
@ -23,6 +24,7 @@ import { ExportView, ns as exportNs } from 'shared/components/workbench/views/ex
|
||||||
import { LogView, ns as logNs } from 'shared/components/workbench/views/logs/index.mjs'
|
import { LogView, ns as logNs } from 'shared/components/workbench/views/logs/index.mjs'
|
||||||
import { InspectView, ns as inspectNs } from 'shared/components/workbench/views/inspect/index.mjs'
|
import { InspectView, ns as inspectNs } from 'shared/components/workbench/views/inspect/index.mjs'
|
||||||
import { MeasiesView, ns as measiesNs } from 'shared/components/workbench/views/measies/index.mjs'
|
import { MeasiesView, ns as measiesNs } from 'shared/components/workbench/views/measies/index.mjs'
|
||||||
|
|
||||||
export const ns = [
|
export const ns = [
|
||||||
'account',
|
'account',
|
||||||
'workbench',
|
'workbench',
|
||||||
|
@ -56,7 +58,7 @@ const views = {
|
||||||
|
|
||||||
const draftViews = ['draft', 'inspect']
|
const draftViews = ['draft', 'inspect']
|
||||||
|
|
||||||
export const Workbench = ({ design, Design, baseSettings, DynamicDocs, from }) => {
|
export const Workbench = ({ design, Design, DynamicDocs }) => {
|
||||||
// Hooks
|
// Hooks
|
||||||
const { t, i18n } = useTranslation(ns)
|
const { t, i18n } = useTranslation(ns)
|
||||||
const { language } = i18n
|
const { language } = i18n
|
||||||
|
@ -65,15 +67,27 @@ export const Workbench = ({ design, Design, baseSettings, DynamicDocs, from }) =
|
||||||
|
|
||||||
// State
|
// State
|
||||||
const [view, setView] = useView()
|
const [view, setView] = useView()
|
||||||
const [settings, setSettings] = useState({ ...baseSettings, embed: true })
|
const [settings, setSettings] = usePatternSettings()
|
||||||
const [ui, setUi] = useState(defaultUi)
|
const [ui, setUi] = useState(defaultUi)
|
||||||
const [error, setError] = useState(false)
|
const [error, setError] = useState(false)
|
||||||
|
const [mounted, setMounted] = useState(false)
|
||||||
|
const [missingMeasurements, setMissingMeasurements] = useState(false)
|
||||||
|
|
||||||
|
// set mounted on mount
|
||||||
|
useEffect(() => setMounted(true), [setMounted])
|
||||||
|
|
||||||
// Effect
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// Force re-render when baseSettings changes. Required when they are loaded async.
|
// protect against loops
|
||||||
setSettings({ ...baseSettings, embed: true })
|
if (!mounted) return
|
||||||
}, [baseSettings])
|
|
||||||
|
const [ok, missing] = hasRequiredMeasurements(Design, settings.measurements)
|
||||||
|
if (ok) setMissingMeasurements(false)
|
||||||
|
// Force the measurements view if we have missing measurements
|
||||||
|
else {
|
||||||
|
setMissingMeasurements(missing)
|
||||||
|
if (view !== 'measies') setView('measies')
|
||||||
|
}
|
||||||
|
}, [Design, settings.measurements, mounted, view, setView])
|
||||||
|
|
||||||
// Helper methods for settings/ui updates
|
// Helper methods for settings/ui updates
|
||||||
const update = {
|
const update = {
|
||||||
|
@ -104,7 +118,7 @@ export const Workbench = ({ design, Design, baseSettings, DynamicDocs, from }) =
|
||||||
}
|
}
|
||||||
|
|
||||||
// Don't bother without a Design
|
// Don't bother without a Design
|
||||||
if (!Design || !baseSettings) return <ModalSpinner />
|
if (!Design) return <ModalSpinner />
|
||||||
|
|
||||||
// Short-circuit errors early
|
// Short-circuit errors early
|
||||||
if (error)
|
if (error)
|
||||||
|
@ -123,6 +137,7 @@ export const Workbench = ({ design, Design, baseSettings, DynamicDocs, from }) =
|
||||||
setView,
|
setView,
|
||||||
update,
|
update,
|
||||||
settings,
|
settings,
|
||||||
|
setSettings,
|
||||||
ui,
|
ui,
|
||||||
language,
|
language,
|
||||||
DynamicDocs,
|
DynamicDocs,
|
||||||
|
@ -133,7 +148,7 @@ export const Workbench = ({ design, Design, baseSettings, DynamicDocs, from }) =
|
||||||
switch (view) {
|
switch (view) {
|
||||||
// Save view
|
// Save view
|
||||||
case 'save':
|
case 'save':
|
||||||
viewContent = <SaveView {...viewProps} from={from} />
|
viewContent = <SaveView {...viewProps} />
|
||||||
break
|
break
|
||||||
case 'export':
|
case 'export':
|
||||||
viewContent = <ExportView {...viewProps} />
|
viewContent = <ExportView {...viewProps} />
|
||||||
|
@ -141,10 +156,14 @@ export const Workbench = ({ design, Design, baseSettings, DynamicDocs, from }) =
|
||||||
case 'edit':
|
case 'edit':
|
||||||
viewContent = <EditView {...viewProps} setSettings={setSettings} />
|
viewContent = <EditView {...viewProps} setSettings={setSettings} />
|
||||||
break
|
break
|
||||||
|
case 'measies':
|
||||||
|
viewContent = <MeasiesView {...viewProps} {...{ missingMeasurements }} />
|
||||||
|
break
|
||||||
default: {
|
default: {
|
||||||
const layout = ui.layouts?.[view] || settings.layout || true
|
const layout = ui.layouts?.[view] || settings.layout || true
|
||||||
// Generate the pattern here so we can pass it down to both the view and the options menu
|
// Generate the pattern here so we can pass it down to both the view and the options menu
|
||||||
const pattern = settings.measurements !== undefined && new Design({ layout, ...settings })
|
const pattern =
|
||||||
|
settings.measurements !== undefined && new Design({ layout, embed: true, ...settings })
|
||||||
|
|
||||||
// Return early if the pattern is not initialized yet
|
// Return early if the pattern is not initialized yet
|
||||||
if (typeof pattern.getConfig !== 'function') return null
|
if (typeof pattern.getConfig !== 'function') return null
|
|
@ -1,8 +1,9 @@
|
||||||
// Dependencies
|
// Dependencies
|
||||||
import { forwardRef } from 'react'
|
import { forwardRef, useContext } from 'react'
|
||||||
// Hooks
|
// Hooks
|
||||||
import { useTranslation } from 'next-i18next'
|
import { useTranslation } from 'next-i18next'
|
||||||
// Context
|
// Context
|
||||||
|
import { PanZoomContext } from 'shared/components/workbench/pattern/pan-zoom-context.mjs'
|
||||||
// Components
|
// Components
|
||||||
import { SizeMe } from 'react-sizeme'
|
import { SizeMe } from 'react-sizeme'
|
||||||
import { TransformWrapper, TransformComponent } from 'react-zoom-pan-pinch'
|
import { TransformWrapper, TransformComponent } from 'react-zoom-pan-pinch'
|
||||||
|
@ -34,8 +35,7 @@ export const PanZoomPattern = forwardRef((props, ref) => {
|
||||||
const { t } = useTranslation(ns)
|
const { t } = useTranslation(ns)
|
||||||
|
|
||||||
const { renderProps = false, components = {} } = props
|
const { renderProps = false, components = {} } = props
|
||||||
|
const { onTransformed, setZoomFunctions } = useContext(PanZoomContext)
|
||||||
if (!renderProps) return null
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SizeMe refreshRate={64}>
|
<SizeMe refreshRate={64}>
|
||||||
|
@ -44,10 +44,13 @@ export const PanZoomPattern = forwardRef((props, ref) => {
|
||||||
minScale={0.1}
|
minScale={0.1}
|
||||||
centerZoomedOut={true}
|
centerZoomedOut={true}
|
||||||
wheel={{ activationKeys: ['Control'] }}
|
wheel={{ activationKeys: ['Control'] }}
|
||||||
|
doubleClick={{ mode: 'reset' }}
|
||||||
|
onTransformed={onTransformed}
|
||||||
|
onInit={setZoomFunctions}
|
||||||
>
|
>
|
||||||
<TransformComponent>
|
<TransformComponent>
|
||||||
<div style={{ width: size.width + 'px' }} className="max-h-screen">
|
<div style={{ width: size.width + 'px' }} className="max-h-screen">
|
||||||
<Pattern {...{ t, components, renderProps }} ref={ref} />
|
{props.children || <Pattern {...{ t, components, renderProps }} ref={ref} />}
|
||||||
</div>
|
</div>
|
||||||
</TransformComponent>
|
</TransformComponent>
|
||||||
</TransformWrapper>
|
</TransformWrapper>
|
||||||
|
|
|
@ -0,0 +1,46 @@
|
||||||
|
import React, { useState, useMemo, useCallback } from 'react'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A context for managing zoom state of a {@see PanZoomPattern}
|
||||||
|
* Allows transform handlers to be available in components outside of the TransformWrapper without a bunch of prop drilling
|
||||||
|
* */
|
||||||
|
export const PanZoomContext = React.createContext({})
|
||||||
|
|
||||||
|
/** Provider for the {@see PanZoomContext} */
|
||||||
|
export const PanZoomContextProvider = ({ children }) => {
|
||||||
|
const [zoomed, setZoomed] = useState(false)
|
||||||
|
const [zoomFunctions, _setZoomFunctions] = useState(false)
|
||||||
|
|
||||||
|
const setZoomFunctions = useCallback(
|
||||||
|
(zoomInstance) => {
|
||||||
|
const reset = () => {
|
||||||
|
setZoomed(false)
|
||||||
|
zoomInstance.resetTransform()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (zoomInstance) {
|
||||||
|
const { zoomIn, zoomOut, resetTransform } = zoomInstance
|
||||||
|
_setZoomFunctions({ zoomIn, zoomOut, resetTransform, reset })
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[_setZoomFunctions, setZoomed]
|
||||||
|
)
|
||||||
|
|
||||||
|
const onTransformed = useCallback(
|
||||||
|
(_ref, state) => {
|
||||||
|
setZoomed(state.scale !== 1)
|
||||||
|
},
|
||||||
|
[setZoomed]
|
||||||
|
)
|
||||||
|
|
||||||
|
const value = useMemo(() => {
|
||||||
|
return {
|
||||||
|
zoomed,
|
||||||
|
zoomFunctions,
|
||||||
|
setZoomFunctions,
|
||||||
|
onTransformed,
|
||||||
|
}
|
||||||
|
}, [zoomed, zoomFunctions, setZoomFunctions, onTransformed])
|
||||||
|
|
||||||
|
return <PanZoomContext.Provider value={value}>{children}</PanZoomContext.Provider>
|
||||||
|
}
|
|
@ -38,6 +38,7 @@ export const CutView = ({
|
||||||
design,
|
design,
|
||||||
patternConfig,
|
patternConfig,
|
||||||
settings,
|
settings,
|
||||||
|
setSettings,
|
||||||
ui,
|
ui,
|
||||||
update,
|
update,
|
||||||
language,
|
language,
|
||||||
|
@ -59,7 +60,8 @@ export const CutView = ({
|
||||||
)
|
)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!materialList.includes(materialSettings.activeMaterial)) setActiveMaterial(materialList[0])
|
if (materialList.length && !materialList.includes(materialSettings.activeMaterial))
|
||||||
|
setActiveMaterial(materialList[0])
|
||||||
}, [materialSettings, materialList, setActiveMaterial])
|
}, [materialSettings, materialList, setActiveMaterial])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -112,6 +114,7 @@ export const CutView = ({
|
||||||
account,
|
account,
|
||||||
DynamicDocs,
|
DynamicDocs,
|
||||||
materialSettings,
|
materialSettings,
|
||||||
|
setSettings,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -5,6 +5,7 @@ import {
|
||||||
} from 'shared/components/workbench/menus/design-options/index.mjs'
|
} from 'shared/components/workbench/menus/design-options/index.mjs'
|
||||||
import {
|
import {
|
||||||
CoreSettings,
|
CoreSettings,
|
||||||
|
ClearAllButton,
|
||||||
ns as coreMenuNs,
|
ns as coreMenuNs,
|
||||||
} from 'shared/components/workbench/menus/core-settings/index.mjs'
|
} from 'shared/components/workbench/menus/core-settings/index.mjs'
|
||||||
import { CutSettings, ns as cutNs } from './settings.mjs'
|
import { CutSettings, ns as cutNs } from './settings.mjs'
|
||||||
|
@ -41,6 +42,7 @@ export const CutMenu = ({
|
||||||
account,
|
account,
|
||||||
DynamicDocs,
|
DynamicDocs,
|
||||||
materialSettings,
|
materialSettings,
|
||||||
|
setSettings,
|
||||||
}) => {
|
}) => {
|
||||||
const control = account.control
|
const control = account.control
|
||||||
const menuProps = {
|
const menuProps = {
|
||||||
|
@ -57,8 +59,9 @@ export const CutMenu = ({
|
||||||
<nav className="grow mb-12">
|
<nav className="grow mb-12">
|
||||||
<CutActions update={update} ui={ui} materialSettings={materialSettings} />
|
<CutActions update={update} ui={ui} materialSettings={materialSettings} />
|
||||||
<CutSettings {...menuProps} ui={ui} materialSettings={materialSettings} />
|
<CutSettings {...menuProps} ui={ui} materialSettings={materialSettings} />
|
||||||
<DesignOptions {...menuProps} />
|
<DesignOptions {...menuProps} isFirst={false} />
|
||||||
<CoreSettings {...menuProps} />
|
<CoreSettings {...menuProps} />
|
||||||
|
<ClearAllButton setSettings={setSettings} />
|
||||||
</nav>
|
</nav>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -64,6 +64,7 @@ export const CutSettings = ({ update, settings, account, materialSettings }) =>
|
||||||
ns,
|
ns,
|
||||||
passProps,
|
passProps,
|
||||||
updateFunc,
|
updateFunc,
|
||||||
|
isFirst: true,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,88 +1,16 @@
|
||||||
import { PanZoomPattern as ShowPattern } from 'shared/components/workbench/pan-zoom-pattern.mjs'
|
import { PanZoomPattern as ShowPattern } from 'shared/components/workbench/pan-zoom-pattern.mjs'
|
||||||
import { DraftMenu, ns as menuNs } from './menu.mjs'
|
import { DraftMenu, ns as menuNs } from './menu.mjs'
|
||||||
import {
|
import { ViewHeader, ns as headerNs } from 'shared/components/workbench/views/view-header.mjs'
|
||||||
PaperlessIcon,
|
import { PanZoomContextProvider } from 'shared/components/workbench/pattern/pan-zoom-context.mjs'
|
||||||
SaIcon,
|
|
||||||
RocketIcon,
|
|
||||||
BulletIcon,
|
|
||||||
UnitsIcon,
|
|
||||||
DetailIcon,
|
|
||||||
} from 'shared/components/icons.mjs'
|
|
||||||
|
|
||||||
export const ns = menuNs
|
export const ns = [menuNs, ...headerNs]
|
||||||
|
|
||||||
const IconButton = ({ Icon, onClick, dflt = true }) => (
|
|
||||||
<button
|
|
||||||
onClick={onClick}
|
|
||||||
className={`text-${dflt ? 'neutral-content' : 'accent'} hover:text-secondary-focus`}
|
|
||||||
>
|
|
||||||
<Icon />
|
|
||||||
</button>
|
|
||||||
)
|
|
||||||
|
|
||||||
const Spacer = () => <span className="opacity-50">|</span>
|
|
||||||
|
|
||||||
export const DraftViewHeader = ({ update, settings, ui, control }) => {
|
|
||||||
return (
|
|
||||||
<div className="flex flex-row gap-4 py-4 mt-2 pt-4 w-full bg-neutral text-neutral-content items-center justify-center">
|
|
||||||
<div className="flex flex-row items-center gap-4">
|
|
||||||
<IconButton
|
|
||||||
Icon={SaIcon}
|
|
||||||
dflt={settings.sabool ? false : true}
|
|
||||||
onClick={() => update.toggleSa()}
|
|
||||||
/>
|
|
||||||
<IconButton
|
|
||||||
Icon={PaperlessIcon}
|
|
||||||
dflt={settings.paperless ? false : true}
|
|
||||||
onClick={() => update.settings(['paperless'], !settings.paperless)}
|
|
||||||
/>
|
|
||||||
<IconButton
|
|
||||||
Icon={DetailIcon}
|
|
||||||
dflt={settings.complete}
|
|
||||||
onClick={() =>
|
|
||||||
update.settings(
|
|
||||||
['complete'],
|
|
||||||
typeof settings.complete === 'undefined' ? 0 : settings.complete ? 0 : 1
|
|
||||||
)
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<IconButton
|
|
||||||
Icon={
|
|
||||||
settings.units !== 'imperial'
|
|
||||||
? UnitsIcon
|
|
||||||
: ({ className }) => <UnitsIcon className={`${className} rotate-180 w-6 h-6`} />
|
|
||||||
}
|
|
||||||
dflt={settings.units !== 'imperial'}
|
|
||||||
onClick={() =>
|
|
||||||
update.settings(['units'], settings.units === 'imperial' ? 'metric' : 'imperial')
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<Spacer />
|
|
||||||
<div className="flex flex-row items-center">
|
|
||||||
{[1, 2, 3, 4, 5].map((score) => (
|
|
||||||
<button onClick={() => update.setControl(score)} className="text-primary" key={score}>
|
|
||||||
<BulletIcon fill={control >= score ? true : false} />
|
|
||||||
</button>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
<Spacer />
|
|
||||||
<div className="flex flex-row items-center gap-4">
|
|
||||||
<IconButton
|
|
||||||
Icon={RocketIcon}
|
|
||||||
dflt={ui.renderer !== 'svg'}
|
|
||||||
onClick={() => update.ui(['renderer'], ui.renderer === 'react' ? 'svg' : 'react')}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export const DraftView = ({
|
export const DraftView = ({
|
||||||
design,
|
design,
|
||||||
pattern,
|
pattern,
|
||||||
patternConfig,
|
patternConfig,
|
||||||
settings,
|
settings,
|
||||||
|
setSettings,
|
||||||
ui,
|
ui,
|
||||||
update,
|
update,
|
||||||
language,
|
language,
|
||||||
|
@ -96,7 +24,11 @@ export const DraftView = ({
|
||||||
if (ui.renderer === 'svg') {
|
if (ui.renderer === 'svg') {
|
||||||
try {
|
try {
|
||||||
const __html = pattern.render()
|
const __html = pattern.render()
|
||||||
output = <div dangerouslySetInnerHTML={{ __html }} />
|
output = (
|
||||||
|
<ShowPattern>
|
||||||
|
<div className="w-full h-full" dangerouslySetInnerHTML={{ __html }} />
|
||||||
|
</ShowPattern>
|
||||||
|
)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.log(err)
|
console.log(err)
|
||||||
}
|
}
|
||||||
|
@ -106,13 +38,16 @@ export const DraftView = ({
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<PanZoomContextProvider>
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
<DraftViewHeader
|
<ViewHeader
|
||||||
{...{
|
{...{
|
||||||
settings,
|
settings,
|
||||||
|
setSettings,
|
||||||
ui,
|
ui,
|
||||||
update,
|
update,
|
||||||
control: account.control,
|
control: account.control,
|
||||||
|
setSettings,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<div className="flex flex-row">
|
<div className="flex flex-row">
|
||||||
|
@ -123,6 +58,7 @@ export const DraftView = ({
|
||||||
design,
|
design,
|
||||||
pattern,
|
pattern,
|
||||||
patternConfig,
|
patternConfig,
|
||||||
|
setSettings,
|
||||||
settings,
|
settings,
|
||||||
ui,
|
ui,
|
||||||
update,
|
update,
|
||||||
|
@ -137,5 +73,6 @@ export const DraftView = ({
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</PanZoomContextProvider>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@ import {
|
||||||
} from 'shared/components/workbench/menus/design-options/index.mjs'
|
} from 'shared/components/workbench/menus/design-options/index.mjs'
|
||||||
import {
|
import {
|
||||||
CoreSettings,
|
CoreSettings,
|
||||||
|
ClearAllButton,
|
||||||
ns as coreMenuNs,
|
ns as coreMenuNs,
|
||||||
} from 'shared/components/workbench/menus/core-settings/index.mjs'
|
} from 'shared/components/workbench/menus/core-settings/index.mjs'
|
||||||
import { UiSettings, ns as uiNs } from 'shared/components/workbench/menus/ui-settings/index.mjs'
|
import { UiSettings, ns as uiNs } from 'shared/components/workbench/menus/ui-settings/index.mjs'
|
||||||
|
@ -13,6 +14,7 @@ export const ns = [...coreMenuNs, ...designMenuNs, ...uiNs]
|
||||||
export const DraftMenu = ({
|
export const DraftMenu = ({
|
||||||
design,
|
design,
|
||||||
patternConfig,
|
patternConfig,
|
||||||
|
setSettings,
|
||||||
settings,
|
settings,
|
||||||
ui,
|
ui,
|
||||||
update,
|
update,
|
||||||
|
@ -39,6 +41,7 @@ export const DraftMenu = ({
|
||||||
<DesignOptions {...menuProps} />
|
<DesignOptions {...menuProps} />
|
||||||
<CoreSettings {...menuProps} />
|
<CoreSettings {...menuProps} />
|
||||||
<UiSettings {...menuProps} {...{ ui, view, setView }} />
|
<UiSettings {...menuProps} {...{ ui, view, setView }} />
|
||||||
|
<ClearAllButton setSettings={setSettings} />
|
||||||
</nav>
|
</nav>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,16 +1,18 @@
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
import { PanZoomPattern as ShowPattern } from 'shared/components/workbench/pan-zoom-pattern.mjs'
|
|
||||||
import { InspectorPattern } from './inspector/pattern.mjs'
|
import { InspectorPattern } from './inspector/pattern.mjs'
|
||||||
import { DraftMenu, ns as menuNs } from './menu.mjs'
|
import { DraftMenu, ns as menuNs } from './menu.mjs'
|
||||||
import { objUpdate } from 'shared/utils.mjs'
|
import { objUpdate } from 'shared/utils.mjs'
|
||||||
|
import { ViewHeader } from '../view-header.mjs'
|
||||||
|
import { PanZoomContextProvider } from 'shared/components/workbench/pattern/pan-zoom-context.mjs'
|
||||||
|
|
||||||
export const ns = menuNs
|
export const ns = menuNs
|
||||||
|
|
||||||
export const DraftView = ({
|
export const InspectView = ({
|
||||||
design,
|
design,
|
||||||
pattern,
|
pattern,
|
||||||
patternConfig,
|
patternConfig,
|
||||||
settings,
|
settings,
|
||||||
|
setSettings,
|
||||||
ui,
|
ui,
|
||||||
update,
|
update,
|
||||||
language,
|
language,
|
||||||
|
@ -62,14 +64,21 @@ export const DraftView = ({
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
renderProps = pattern.getRenderProps()
|
renderProps = pattern.getRenderProps()
|
||||||
output = ui.inspect ? (
|
output = <InspectorPattern {...{ renderProps, inspector }} />
|
||||||
<InspectorPattern {...{ renderProps, inspector }} />
|
|
||||||
) : (
|
|
||||||
<ShowPattern {...{ renderProps, inspector }} />
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<PanZoomContextProvider>
|
||||||
|
<div className="flex flex-col">
|
||||||
|
<ViewHeader
|
||||||
|
{...{
|
||||||
|
settings,
|
||||||
|
setSettings,
|
||||||
|
ui,
|
||||||
|
update,
|
||||||
|
control: account.control,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
<div className="flex flex-row">
|
<div className="flex flex-row">
|
||||||
<div className="w-2/3 shrink-0 grow lg:p-4 sticky top-0">{output}</div>
|
<div className="w-2/3 shrink-0 grow lg:p-4 sticky top-0">{output}</div>
|
||||||
<div className="w-1/3 shrink grow-0 lg:p-4 max-w-2xl h-screen overflow-scroll">
|
<div className="w-1/3 shrink grow-0 lg:p-4 max-w-2xl h-screen overflow-scroll">
|
||||||
|
@ -79,6 +88,7 @@ export const DraftView = ({
|
||||||
pattern,
|
pattern,
|
||||||
patternConfig,
|
patternConfig,
|
||||||
settings,
|
settings,
|
||||||
|
setSettings,
|
||||||
ui,
|
ui,
|
||||||
update,
|
update,
|
||||||
language,
|
language,
|
||||||
|
@ -92,5 +102,7 @@ export const DraftView = ({
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
</PanZoomContextProvider>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@ import {
|
||||||
} from 'shared/components/workbench/menus/design-options/index.mjs'
|
} from 'shared/components/workbench/menus/design-options/index.mjs'
|
||||||
import {
|
import {
|
||||||
CoreSettings,
|
CoreSettings,
|
||||||
|
ClearAllButton,
|
||||||
ns as coreMenuNs,
|
ns as coreMenuNs,
|
||||||
} from 'shared/components/workbench/menus/core-settings/index.mjs'
|
} from 'shared/components/workbench/menus/core-settings/index.mjs'
|
||||||
import { UiSettings, ns as uiNs } from 'shared/components/workbench/menus/ui-settings/index.mjs'
|
import { UiSettings, ns as uiNs } from 'shared/components/workbench/menus/ui-settings/index.mjs'
|
||||||
|
@ -14,6 +15,7 @@ export const ns = [...coreMenuNs, ...designMenuNs, ...uiNs, inspectorNs]
|
||||||
export const DraftMenu = ({
|
export const DraftMenu = ({
|
||||||
design,
|
design,
|
||||||
patternConfig,
|
patternConfig,
|
||||||
|
setSettings,
|
||||||
settings,
|
settings,
|
||||||
ui,
|
ui,
|
||||||
update,
|
update,
|
||||||
|
@ -39,10 +41,11 @@ export const DraftMenu = ({
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<nav className="grow mb-12">
|
<nav className="grow mb-12">
|
||||||
{ui.inspect ? <Inspector {...menuProps} {...{ ui, inspector, renderProps }} /> : null}
|
<Inspector {...menuProps} {...{ ui, inspector, renderProps }} />
|
||||||
<DesignOptions {...menuProps} />
|
<DesignOptions {...menuProps} />
|
||||||
<CoreSettings {...menuProps} />
|
<CoreSettings {...menuProps} />
|
||||||
<UiSettings {...menuProps} {...{ ui, view, setView }} />
|
<UiSettings {...menuProps} {...{ ui, view, setView }} />
|
||||||
|
<ClearAllButton setSettings={setSettings} />
|
||||||
</nav>
|
</nav>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
23
sites/shared/components/workbench/views/measies/editor.mjs
Normal file
23
sites/shared/components/workbench/views/measies/editor.mjs
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
import { MeasieInput, ns as inputNs } from 'shared/components/sets/measie-input.mjs'
|
||||||
|
import { useTranslation } from 'next-i18next'
|
||||||
|
|
||||||
|
export const ns = ['wbmeasies', ...inputNs]
|
||||||
|
|
||||||
|
export const MeasiesEditor = ({ Design, settings, update }) => {
|
||||||
|
const { t } = useTranslation(ns)
|
||||||
|
const mset = { measies: settings.measurements, imperial: settings.units === 'imperial' }
|
||||||
|
|
||||||
|
const onUpdate = (m, newVal) => {
|
||||||
|
update.settings(['measurements', m], newVal)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{Design.patternConfig.measurements.map((m) => (
|
||||||
|
<MeasieInput {...{ t, m, mset, onUpdate }} key={m}>
|
||||||
|
<span className="label">{t(m)}</span>
|
||||||
|
</MeasieInput>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
|
@ -1,7 +1,61 @@
|
||||||
export const ns = ['wbmeasies']
|
import { ns as authNs } from 'shared/components/wrappers/auth/index.mjs'
|
||||||
|
import { SetPicker, ns as setsNs } from 'shared/components/sets/set-picker.mjs'
|
||||||
|
import { Tabs, Tab } from 'shared/components/mdx/tabs.mjs'
|
||||||
|
import { MeasiesEditor } from './editor.mjs'
|
||||||
|
import { Popout } from 'shared/components/popout.mjs'
|
||||||
|
import { Collapse } from 'shared/components/collapse.mjs'
|
||||||
|
import { designMeasurements } from 'shared/utils.mjs'
|
||||||
|
import { useTranslation } from 'next-i18next'
|
||||||
|
import { useToast } from 'shared/hooks/use-toast.mjs'
|
||||||
|
|
||||||
export const MeasiesView = () => (
|
export const ns = ['wbmeasies', ...authNs, setsNs]
|
||||||
<div className="m-auto mt-24">
|
|
||||||
<h1 className="max-w-6xl m-auto text-center">fixme: Implement measies view</h1>
|
const tabNames = ['chooseNew', 'editCurrent']
|
||||||
|
export const MeasiesView = ({ design, Design, settings, update, missingMeasurements, setView }) => {
|
||||||
|
const { t } = useTranslation(['wbmeasies'])
|
||||||
|
const toast = useToast()
|
||||||
|
|
||||||
|
const tabs = tabNames.map((n) => t(n)).join(',')
|
||||||
|
|
||||||
|
const loadMeasurements = (set) => {
|
||||||
|
update.settings([
|
||||||
|
[['measurements'], designMeasurements(Design, set.measies)],
|
||||||
|
[['units'], set.imperial ? 'imperial' : 'metric'],
|
||||||
|
])
|
||||||
|
setView('draft')
|
||||||
|
toast.success(t('updatedMeasurements'))
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="m-6">
|
||||||
|
<h1 className="max-w-6xl m-auto text-center">{t('measurements')}</h1>
|
||||||
|
{missingMeasurements ? (
|
||||||
|
<Popout note compact dense noP>
|
||||||
|
<h5>{t('weLackSomeMeasies')}:</h5>
|
||||||
|
<p>
|
||||||
|
<b>{t('youCanPickOrEnter')}</b>
|
||||||
|
</p>
|
||||||
|
<Collapse title={t('seeMissingMeasies')}>
|
||||||
|
<ul className="list list-inside list-disc ml-4">
|
||||||
|
{missingMeasurements.map((m) => (
|
||||||
|
<li key={m}>{m}</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</Collapse>
|
||||||
|
</Popout>
|
||||||
|
) : (
|
||||||
|
<Popout tip compact dense noP>
|
||||||
|
<h5>{t('measiesOk')}</h5>
|
||||||
|
</Popout>
|
||||||
|
)}
|
||||||
|
<Tabs tabs={tabs}>
|
||||||
|
<Tab key="choose">
|
||||||
|
<SetPicker design={design} clickHandler={loadMeasurements} />
|
||||||
|
</Tab>
|
||||||
|
<Tab key="edit">
|
||||||
|
<MeasiesEditor {...{ Design, settings, update }} />
|
||||||
|
</Tab>
|
||||||
|
</Tabs>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
changeMeasies: Change Pattern Measurements
|
||||||
|
editCurrent: Edit Current Measurements
|
||||||
|
chooseNew: Choose a New Measurements Set
|
||||||
|
weLackSomeMeasies: We lack { nr } measurements to create this pattern
|
||||||
|
youCanPickOrEnter: You can either pick a measurements set, or enter them by hand, but we cannot proceed without these measurements.
|
||||||
|
measiesOk: We have all required measurements to create this pattern.
|
||||||
|
|
|
@ -40,6 +40,7 @@ export const PrintView = ({
|
||||||
pattern,
|
pattern,
|
||||||
patternConfig,
|
patternConfig,
|
||||||
settings,
|
settings,
|
||||||
|
setSettings,
|
||||||
ui,
|
ui,
|
||||||
update,
|
update,
|
||||||
language,
|
language,
|
||||||
|
@ -110,6 +111,7 @@ export const PrintView = ({
|
||||||
design,
|
design,
|
||||||
pattern,
|
pattern,
|
||||||
patternConfig,
|
patternConfig,
|
||||||
|
setSettings,
|
||||||
settings,
|
settings,
|
||||||
ui,
|
ui,
|
||||||
update,
|
update,
|
||||||
|
|
|
@ -4,6 +4,7 @@ import {
|
||||||
} from 'shared/components/workbench/menus/design-options/index.mjs'
|
} from 'shared/components/workbench/menus/design-options/index.mjs'
|
||||||
import {
|
import {
|
||||||
CoreSettings,
|
CoreSettings,
|
||||||
|
ClearAllButton,
|
||||||
ns as coreMenuNs,
|
ns as coreMenuNs,
|
||||||
} from 'shared/components/workbench/menus/core-settings/index.mjs'
|
} from 'shared/components/workbench/menus/core-settings/index.mjs'
|
||||||
import { PrintSettings, ns as printMenuNs } from './settings.mjs'
|
import { PrintSettings, ns as printMenuNs } from './settings.mjs'
|
||||||
|
@ -14,6 +15,7 @@ export const ns = [...coreMenuNs, ...designMenuNs, ...printMenuNs]
|
||||||
export const PrintMenu = ({
|
export const PrintMenu = ({
|
||||||
design,
|
design,
|
||||||
patternConfig,
|
patternConfig,
|
||||||
|
setSettings,
|
||||||
settings,
|
settings,
|
||||||
ui,
|
ui,
|
||||||
update,
|
update,
|
||||||
|
@ -37,8 +39,9 @@ export const PrintMenu = ({
|
||||||
<nav className="grow mb-12">
|
<nav className="grow mb-12">
|
||||||
<PrintActions {...menuProps} ui={ui} exportIt={exportIt} />
|
<PrintActions {...menuProps} ui={ui} exportIt={exportIt} />
|
||||||
<PrintSettings {...menuProps} ui={ui} />
|
<PrintSettings {...menuProps} ui={ui} />
|
||||||
<DesignOptions {...menuProps} />
|
<DesignOptions {...menuProps} isFirst={false} />
|
||||||
<CoreSettings {...menuProps} />
|
<CoreSettings {...menuProps} />
|
||||||
|
<ClearAllButton setSettings={setSettings} />
|
||||||
</nav>
|
</nav>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -41,6 +41,7 @@ export const PrintSettings = ({ update, settings, ui, account }) => {
|
||||||
ns,
|
ns,
|
||||||
passProps,
|
passProps,
|
||||||
updateFunc,
|
updateFunc,
|
||||||
|
isFirst: true,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
import { useTranslation } from 'next-i18next'
|
import { useTranslation } from 'next-i18next'
|
||||||
import { PanZoomPattern } from 'shared/components/workbench/pan-zoom-pattern.mjs'
|
import { PanZoomPattern } from 'shared/components/workbench/pan-zoom-pattern.mjs'
|
||||||
import { TestMenu, ns as menuNs } from './menu.mjs'
|
import { TestMenu, ns as menuNs } from './menu.mjs'
|
||||||
import { DraftViewHeader } from '../draft/index.mjs'
|
import { ViewHeader } from '../view-header.mjs'
|
||||||
|
import { PanZoomContextProvider } from 'shared/components/workbench/pattern/pan-zoom-context.mjs'
|
||||||
|
|
||||||
export const ns = menuNs
|
export const ns = menuNs
|
||||||
|
|
||||||
|
@ -9,6 +10,7 @@ export const TestView = ({
|
||||||
design,
|
design,
|
||||||
pattern,
|
pattern,
|
||||||
settings,
|
settings,
|
||||||
|
setSettings,
|
||||||
ui,
|
ui,
|
||||||
update,
|
update,
|
||||||
language,
|
language,
|
||||||
|
@ -25,10 +27,12 @@ export const TestView = ({
|
||||||
|
|
||||||
const title = t('testThing', { design, thing: t(settings.sample?.[settings.sample.type]) })
|
const title = t('testThing', { design, thing: t(settings.sample?.[settings.sample.type]) })
|
||||||
return (
|
return (
|
||||||
|
<PanZoomContextProvider>
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
<DraftViewHeader
|
<ViewHeader
|
||||||
{...{
|
{...{
|
||||||
settings,
|
settings,
|
||||||
|
setSettings,
|
||||||
ui,
|
ui,
|
||||||
update,
|
update,
|
||||||
control: account.control,
|
control: account.control,
|
||||||
|
@ -46,6 +50,7 @@ export const TestView = ({
|
||||||
pattern,
|
pattern,
|
||||||
patternConfig,
|
patternConfig,
|
||||||
settings,
|
settings,
|
||||||
|
setSettings,
|
||||||
ui,
|
ui,
|
||||||
update,
|
update,
|
||||||
language,
|
language,
|
||||||
|
@ -57,5 +62,6 @@ export const TestView = ({
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</PanZoomContextProvider>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { MenuItem } from 'shared/components/workbench/menus/shared/menu-item.mjs'
|
import { useRef } from 'react'
|
||||||
import { WorkbenchMenu } from 'shared/components/workbench/menus/shared/index.mjs'
|
import { WorkbenchMenu } from 'shared/components/workbench/menus/shared/index.mjs'
|
||||||
import {
|
import {
|
||||||
emojis,
|
emojis,
|
||||||
|
@ -9,34 +9,48 @@ import { optionsMenuStructure } from 'shared/utils.mjs'
|
||||||
|
|
||||||
export const ns = ['test-view', ...designMenuNs]
|
export const ns = ['test-view', ...designMenuNs]
|
||||||
|
|
||||||
const SampleInput = ({ changed, name, t, updateFunc, type }) => {
|
const closedClasses = `border-r-0 border-t-0 border-b-0 hover:cursor-pointer hover:bg-secondary border-secondary hover:bg-opacity-20`
|
||||||
return (
|
const openClasses = `border-l-0 border-r-0 border-b-2 lg:border lg:rounded-lg border-primary`
|
||||||
<>
|
|
||||||
<p>{t([`${name}.d`, ''])}</p>
|
export const SampleItem = ({ name, passProps, t, updateFunc }) => {
|
||||||
<div className="text-center">
|
const input = useRef(null)
|
||||||
<button
|
const checked = passProps.settings.sample?.[passProps.type] === name
|
||||||
className={`btn btn-primary`}
|
const onChange = (evt) => {
|
||||||
disabled={changed}
|
if (evt.target.checked) updateFunc([name], true)
|
||||||
onClick={() => updateFunc([name], true)}
|
}
|
||||||
>
|
|
||||||
{t(`testThis.${type}`)}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export const SampleItem = ({ name, passProps, ...rest }) => {
|
|
||||||
return (
|
return (
|
||||||
<MenuItem
|
<div
|
||||||
{...{
|
className={`collapse my-2 shadow border-solid border-l-[6px] min-h-10 rounded-none
|
||||||
...rest,
|
${checked ? openClasses : closedClasses}`}
|
||||||
name,
|
>
|
||||||
passProps,
|
<input
|
||||||
changed: passProps.settings.sample?.[passProps.type] === name,
|
ref={input}
|
||||||
Input: SampleInput,
|
type="radio"
|
||||||
}}
|
name="test-item"
|
||||||
|
onChange={onChange}
|
||||||
|
checked={checked}
|
||||||
|
className="min-h-0"
|
||||||
/>
|
/>
|
||||||
|
<div
|
||||||
|
className={`collapse-title flex items-center p-2 h-auto min-h-0 ${
|
||||||
|
checked ? 'bg-primary text-primary-content' : ''
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
ref={input}
|
||||||
|
type="radio"
|
||||||
|
checked={checked}
|
||||||
|
className="radio radio-primary mr-2 radio-sm"
|
||||||
|
/>
|
||||||
|
<span className="ml-2">{t([name + '.t', name])}</span>
|
||||||
|
</div>
|
||||||
|
{t(name + '.d', '') && (
|
||||||
|
<div className="collapse-content h-auto pb-0">
|
||||||
|
<p>{t(name + '.d', '')}</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
147
sites/shared/components/workbench/views/view-header.mjs
Normal file
147
sites/shared/components/workbench/views/view-header.mjs
Normal file
|
@ -0,0 +1,147 @@
|
||||||
|
import { useContext } from 'react'
|
||||||
|
import { PanZoomContext } from 'shared/components/workbench/pattern/pan-zoom-context.mjs'
|
||||||
|
import { useTranslation } from 'next-i18next'
|
||||||
|
import {
|
||||||
|
PaperlessIcon,
|
||||||
|
SaIcon,
|
||||||
|
RocketIcon,
|
||||||
|
BulletIcon,
|
||||||
|
UnitsIcon,
|
||||||
|
DetailIcon,
|
||||||
|
IconWrapper,
|
||||||
|
ClearIcon,
|
||||||
|
} from 'shared/components/icons.mjs'
|
||||||
|
import { ClearAllButton } from 'shared/components/workbench/menus/core-settings/index.mjs'
|
||||||
|
|
||||||
|
export const ns = ['common', 'core-settings', 'ui-settings']
|
||||||
|
|
||||||
|
const ZoomInIcon = (props) => (
|
||||||
|
<IconWrapper {...props}>
|
||||||
|
<path d="M21 21l-5.197-5.197m0 0A7.5 7.5 0 105.196 5.196a7.5 7.5 0 0010.607 10.607zM10.5 7.5v6m3-3h-6" />
|
||||||
|
</IconWrapper>
|
||||||
|
)
|
||||||
|
|
||||||
|
const ZoomOutIcon = (props) => (
|
||||||
|
<IconWrapper {...props}>
|
||||||
|
<path d="M21 21l-5.197-5.197m0 0A7.5 7.5 0 105.196 5.196a7.5 7.5 0 0010.607 10.607zM13.5 10.5h-6" />
|
||||||
|
</IconWrapper>
|
||||||
|
)
|
||||||
|
|
||||||
|
const IconButton = ({ Icon, onClick, dflt = true, title, hide = false }) => (
|
||||||
|
<div className="tooltip tooltip-bottom tooltip-primary flex items-center" data-tip={title}>
|
||||||
|
<button
|
||||||
|
onClick={onClick}
|
||||||
|
className={`text-${dflt ? 'neutral-content' : 'accent'} hover:text-secondary-focus ${
|
||||||
|
hide ? 'invisible' : ''
|
||||||
|
}`}
|
||||||
|
title={title}
|
||||||
|
>
|
||||||
|
<Icon />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
|
||||||
|
const ZoomButtons = ({ t }) => {
|
||||||
|
const { zoomFunctions, zoomed } = useContext(PanZoomContext)
|
||||||
|
|
||||||
|
if (!zoomFunctions) return null
|
||||||
|
return (
|
||||||
|
<div className="flex flex-row content-center gap-4">
|
||||||
|
<IconButton
|
||||||
|
Icon={ClearIcon}
|
||||||
|
onClick={zoomFunctions.reset}
|
||||||
|
title={t('resetZoom')}
|
||||||
|
hide={!zoomed}
|
||||||
|
/>
|
||||||
|
<IconButton
|
||||||
|
Icon={ZoomOutIcon}
|
||||||
|
onClick={() => zoomFunctions.zoomOut()}
|
||||||
|
title={t('zoomOut')}
|
||||||
|
dflt
|
||||||
|
/>
|
||||||
|
<IconButton
|
||||||
|
Icon={ZoomInIcon}
|
||||||
|
onClick={() => zoomFunctions.zoomIn()}
|
||||||
|
title={t('zoomIn')}
|
||||||
|
dflt
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const Spacer = () => <span className="opacity-50">|</span>
|
||||||
|
|
||||||
|
export const ViewHeader = ({ update, settings, ui, control, setSettings }) => {
|
||||||
|
const { t } = useTranslation(ns)
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={`flex flex-row flex-wrap gap-4 py-4 pt-4 w-full bg-neutral text-neutral-content items-center justify-center lg:sticky top-0 z-20 lg:group-[.header-shown]/layout:top-24 transition-[top] duration-300 ease-in-out`}
|
||||||
|
>
|
||||||
|
<div className="hidden lg:flex flex-row flex-wrap gap-4 py-4 pt-4 w-full bg-neutral text-neutral-content items-center justify-center">
|
||||||
|
<ZoomButtons t={t} />
|
||||||
|
<Spacer />
|
||||||
|
<div className="flex flex-row items-center gap-4">
|
||||||
|
<IconButton
|
||||||
|
Icon={SaIcon}
|
||||||
|
dflt={settings.sabool ? false : true}
|
||||||
|
onClick={() => update.toggleSa()}
|
||||||
|
title={t('core-settings:sabool.t')}
|
||||||
|
/>
|
||||||
|
<IconButton
|
||||||
|
Icon={PaperlessIcon}
|
||||||
|
dflt={settings.paperless ? false : true}
|
||||||
|
onClick={() => update.settings(['paperless'], !settings.paperless)}
|
||||||
|
title={t('core-settings:paperless.t')}
|
||||||
|
/>
|
||||||
|
<IconButton
|
||||||
|
Icon={DetailIcon}
|
||||||
|
dflt={settings.complete}
|
||||||
|
onClick={() =>
|
||||||
|
update.settings(
|
||||||
|
['complete'],
|
||||||
|
typeof settings.complete === 'undefined' ? 0 : settings.complete ? 0 : 1
|
||||||
|
)
|
||||||
|
}
|
||||||
|
title={t('core-settings:complete.t')}
|
||||||
|
/>
|
||||||
|
<IconButton
|
||||||
|
Icon={
|
||||||
|
settings.units !== 'imperial'
|
||||||
|
? UnitsIcon
|
||||||
|
: ({ className }) => <UnitsIcon className={`${className} rotate-180 w-6 h-6`} />
|
||||||
|
}
|
||||||
|
dflt={settings.units !== 'imperial'}
|
||||||
|
onClick={() =>
|
||||||
|
update.settings(['units'], settings.units === 'imperial' ? 'metric' : 'imperial')
|
||||||
|
}
|
||||||
|
title={t('core-settings:units.t')}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<Spacer />
|
||||||
|
<div
|
||||||
|
className="tooltip tooltip-primary tooltip-bottom flex flex-row items-center"
|
||||||
|
data-tip={t('ui-settings:control.t')}
|
||||||
|
>
|
||||||
|
{[1, 2, 3, 4, 5].map((score) => (
|
||||||
|
<button onClick={() => update.setControl(score)} className="text-primary" key={score}>
|
||||||
|
<BulletIcon fill={control >= score ? true : false} />
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<Spacer />
|
||||||
|
<div className="flex flex-row items-center gap-4">
|
||||||
|
<IconButton
|
||||||
|
Icon={RocketIcon}
|
||||||
|
dflt={ui.renderer !== 'svg'}
|
||||||
|
onClick={() => update.ui(['renderer'], ui.renderer === 'react' ? 'svg' : 'react')}
|
||||||
|
title={t('ui-settings:renderer.t')}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<Spacer />
|
||||||
|
<div className="flex flex-row items-center gap-4">
|
||||||
|
<ClearAllButton setSettings={setSettings} compact />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
|
@ -30,4 +30,4 @@ patternLogs: Pattern logs
|
||||||
patternInspector: Pattern Inspector
|
patternInspector: Pattern Inspector
|
||||||
docs: Documentation
|
docs: Documentation
|
||||||
configurePattern: Configure pattern
|
configurePattern: Configure pattern
|
||||||
measies: Measurements
|
measies: Pattern Measurements
|
||||||
|
|
26
sites/shared/components/wrappers/header.mjs
Normal file
26
sites/shared/components/wrappers/header.mjs
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
// Hooks
|
||||||
|
import { useContext } from 'react'
|
||||||
|
import { LoadingContext } from 'shared/context/loading-context.mjs'
|
||||||
|
import { Ribbon } from 'shared/components/ribbon.mjs'
|
||||||
|
|
||||||
|
export const HeaderWrapper = ({ show, children }) => {
|
||||||
|
const { loading } = useContext(LoadingContext)
|
||||||
|
return (
|
||||||
|
<header
|
||||||
|
className={`
|
||||||
|
fixed bottom-0 lg:bottom-auto lg:top-0 left-0
|
||||||
|
bg-neutral
|
||||||
|
w-full
|
||||||
|
z-30
|
||||||
|
transition-transform
|
||||||
|
duration-300 ease-in-out
|
||||||
|
${show || loading ? '' : 'bottom-0 lg:top-0 left-0 translate-y-36 lg:-translate-y-36'}
|
||||||
|
drop-shadow-xl
|
||||||
|
`}
|
||||||
|
>
|
||||||
|
{' '}
|
||||||
|
{children}
|
||||||
|
<Ribbon />
|
||||||
|
</header>
|
||||||
|
)
|
||||||
|
}
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { useState, useEffect } from 'react'
|
||||||
import Head from 'next/head'
|
import Head from 'next/head'
|
||||||
import { Header, ns as headerNs } from 'site/components/header/index.mjs'
|
import { Header, ns as headerNs } from 'site/components/header/index.mjs'
|
||||||
import { Footer, ns as footerNs } from 'shared/components/footer/index.mjs'
|
import { Footer, ns as footerNs } from 'shared/components/footer/index.mjs'
|
||||||
|
@ -14,25 +15,51 @@ export const LayoutWrapper = ({
|
||||||
}) => {
|
}) => {
|
||||||
const ChosenHeader = header ? header : Header
|
const ChosenHeader = header ? header : Header
|
||||||
|
|
||||||
|
const [prevScrollPos, setPrevScrollPos] = useState(0)
|
||||||
|
const [showHeader, setShowHeader] = useState(true)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (typeof window !== 'undefined') {
|
||||||
|
const handleScroll = () => {
|
||||||
|
const curScrollPos = typeof window !== 'undefined' ? window.pageYOffset : 0
|
||||||
|
if (curScrollPos >= prevScrollPos) {
|
||||||
|
if (showHeader && curScrollPos > 20) setShowHeader(false)
|
||||||
|
} else setShowHeader(true)
|
||||||
|
setPrevScrollPos(curScrollPos)
|
||||||
|
}
|
||||||
|
window.addEventListener('scroll', handleScroll)
|
||||||
|
return () => window.removeEventListener('scroll', handleScroll)
|
||||||
|
}
|
||||||
|
}, [prevScrollPos, showHeader])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={`
|
className={`
|
||||||
flex flex-col justify-between
|
flex flex-col justify-between
|
||||||
min-h-screen
|
min-h-screen
|
||||||
bg-base-100
|
bg-base-100
|
||||||
|
group/layout
|
||||||
|
header-${showHeader ? 'shown' : 'hidden'}
|
||||||
`}
|
`}
|
||||||
>
|
>
|
||||||
<Head>
|
<Head>
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
</Head>
|
</Head>
|
||||||
<ChosenHeader setSearch={setSearch} />
|
<ChosenHeader show={showHeader} />
|
||||||
<main className="grow">{children}</main>
|
|
||||||
|
<main
|
||||||
|
className={`grow transition-margin duration-300 ease-in-out lg:group-[.header-shown]/layout:mt-24 lg:mt-4
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</main>
|
||||||
|
|
||||||
{!noSearch && search && (
|
{!noSearch && search && (
|
||||||
<>
|
<>
|
||||||
<div
|
<div
|
||||||
className={`
|
className={`
|
||||||
fixed w-full max-h-screen bg-base-100 top-0 z-30 pt-0 pb-16 px-8
|
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:rounded-lg
|
||||||
md:max-w-xl md:m-auto md:inset-x-12
|
md:max-w-xl md:m-auto md:inset-x-12
|
||||||
md:max-w-2xl
|
md:max-w-2xl
|
||||||
lg:max-w-4xl
|
lg:max-w-4xl
|
|
@ -16,7 +16,7 @@ import { useTranslation } from 'next-i18next'
|
||||||
//import { PrevNext } from '../mdx/prev-next.mjs'
|
//import { PrevNext } from '../mdx/prev-next.mjs'
|
||||||
//
|
//
|
||||||
//
|
//
|
||||||
const TimeAgo = ({ date, t }) => {
|
export const TimeAgo = ({ date, t }) => {
|
||||||
const i = Interval.fromDateTimes(DateTime.fromISO(date), DateTime.now())
|
const i = Interval.fromDateTimes(DateTime.fromISO(date), DateTime.now())
|
||||||
.toDuration(['hours', 'days', 'months', 'years'])
|
.toDuration(['hours', 'days', 'months', 'years'])
|
||||||
.toObject()
|
.toObject()
|
||||||
|
@ -101,9 +101,21 @@ const MetaData = ({ authors = [], maintainers = [], updated = '20220825', locale
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
||||||
export const MdxWrapper = ({ MDX = false, frontmatter = {}, components = {}, children = [] }) => {
|
export const PlainMdxWrapper = ({ MDX = false, components = {}, children, site = 'org' }) => {
|
||||||
|
const allComponents = { ...baseComponents(site), ...components }
|
||||||
|
|
||||||
|
return <div className="searchme">{MDX ? <MDX components={allComponents} /> : children}</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
export const MdxWrapper = ({
|
||||||
|
MDX = false,
|
||||||
|
frontmatter = {},
|
||||||
|
components = {},
|
||||||
|
children = [],
|
||||||
|
site = 'org',
|
||||||
|
}) => {
|
||||||
const { t } = useTranslation('docs')
|
const { t } = useTranslation('docs')
|
||||||
const allComponents = { ...baseComponents, ...components }
|
|
||||||
const { locale, slug } = useContext(NavigationContext)
|
const { locale, slug } = useContext(NavigationContext)
|
||||||
|
|
||||||
const updates = docUpdates[slug] || {}
|
const updates = docUpdates[slug] || {}
|
||||||
|
@ -116,7 +128,7 @@ export const MdxWrapper = ({ MDX = false, frontmatter = {}, components = {}, chi
|
||||||
updated={updates.u}
|
updated={updates.u}
|
||||||
{...{ locale, slug, t }}
|
{...{ locale, slug, t }}
|
||||||
/>
|
/>
|
||||||
<div className="searchme">{MDX ? <MDX components={allComponents} /> : children}</div>
|
<PlainMdxWrapper {...{ MDX, components, children }} />
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,8 +4,9 @@ import React, { useState, useEffect, useContext } from 'react'
|
||||||
// Hooks
|
// Hooks
|
||||||
import { useTheme } from 'shared/hooks/use-theme.mjs'
|
import { useTheme } from 'shared/hooks/use-theme.mjs'
|
||||||
// Components
|
// Components
|
||||||
|
import Head from 'next/head'
|
||||||
import { SwipeWrapper } from 'shared/components/wrappers/swipes.mjs'
|
import { SwipeWrapper } from 'shared/components/wrappers/swipes.mjs'
|
||||||
import { LayoutWrapper, ns as layoutNs } from 'site/components/wrappers/layout.mjs'
|
import { LayoutWrapper, ns as layoutNs } from 'shared/components/wrappers/layout.mjs'
|
||||||
import { DocsLayout, ns as docsNs } from 'site/components/layouts/docs.mjs'
|
import { DocsLayout, ns as docsNs } from 'site/components/layouts/docs.mjs'
|
||||||
import { Feeds } from 'site/components/feeds.mjs'
|
import { Feeds } from 'site/components/feeds.mjs'
|
||||||
import { ModalContext } from 'shared/context/modal-context.mjs'
|
import { ModalContext } from 'shared/context/modal-context.mjs'
|
||||||
|
@ -57,7 +58,7 @@ export const PageWrapper = (props) => {
|
||||||
})
|
})
|
||||||
setNavupdates(navupdates + 1)
|
setNavupdates(navupdates + 1)
|
||||||
}
|
}
|
||||||
}, [path, pageTitle, slug])
|
}, [path, pageTitle, slug, locale, navupdates, setNavigation])
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Hotkeys (keyboard actions)
|
* Hotkeys (keyboard actions)
|
||||||
|
@ -80,6 +81,11 @@ export const PageWrapper = (props) => {
|
||||||
// Return wrapper
|
// Return wrapper
|
||||||
return (
|
return (
|
||||||
<SwipeWrapper>
|
<SwipeWrapper>
|
||||||
|
{pageTitle && (
|
||||||
|
<Head>
|
||||||
|
<meta property="og:title" content={pageTitle} key="title" />
|
||||||
|
</Head>
|
||||||
|
)}
|
||||||
<div
|
<div
|
||||||
data-theme={currentTheme} // This facilitates CSS selectors
|
data-theme={currentTheme} // This facilitates CSS selectors
|
||||||
key={currentTheme} // This forces the data-theme update
|
key={currentTheme} // This forces the data-theme update
|
||||||
|
|
|
@ -120,6 +120,16 @@ const config = ({ site, jargon = {} }) => {
|
||||||
externalDir: true,
|
externalDir: true,
|
||||||
},
|
},
|
||||||
pageExtensions: ['mjs'],
|
pageExtensions: ['mjs'],
|
||||||
|
images: {
|
||||||
|
remotePatterns: [
|
||||||
|
{
|
||||||
|
protocol: 'https',
|
||||||
|
hostname: 'cdn.sanity.io',
|
||||||
|
pathname: '/images/**',
|
||||||
|
port: '',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
webpack: (config, options) => {
|
webpack: (config, options) => {
|
||||||
// Fixes npm packages that depend on node modules
|
// Fixes npm packages that depend on node modules
|
||||||
if (!options.isServer) {
|
if (!options.isServer) {
|
||||||
|
|
21
sites/shared/hooks/use-evaled-mdx.mjs
Normal file
21
sites/shared/hooks/use-evaled-mdx.mjs
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
import { compile, run } from '@mdx-js/mdx'
|
||||||
|
import * as runtime from 'react/jsx-runtime' // Production.
|
||||||
|
|
||||||
|
import { useState, useEffect } from 'react'
|
||||||
|
|
||||||
|
export const useEvaledMdx = (mdxStr = '') => {
|
||||||
|
const [mdxModule, setMdxModule] = useState(false)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
;(async () => {
|
||||||
|
const code = await compile(mdxStr, {
|
||||||
|
outputFormat: 'function-body',
|
||||||
|
development: false,
|
||||||
|
})
|
||||||
|
const evaled = await run(code, runtime)
|
||||||
|
setMdxModule(() => evaled.default)
|
||||||
|
})()
|
||||||
|
}, [mdxStr])
|
||||||
|
|
||||||
|
return mdxModule
|
||||||
|
}
|
6
sites/shared/hooks/use-pattern-settings.mjs
Normal file
6
sites/shared/hooks/use-pattern-settings.mjs
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
import { useAtom } from 'jotai'
|
||||||
|
import { atomWithHash } from 'jotai-location'
|
||||||
|
|
||||||
|
const baseSettings = atomWithHash('settings', false, { delayInit: true })
|
||||||
|
|
||||||
|
export const usePatternSettings = () => useAtom(baseSettings)
|
|
@ -17,14 +17,14 @@
|
||||||
"peerDependencies": {},
|
"peerDependencies": {},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@headlessui/react": "1.7.15",
|
"@headlessui/react": "1.7.15",
|
||||||
"@next/mdx": "13.4.6",
|
"@next/mdx": "13.4.7",
|
||||||
"@resvg/resvg-js": "2.4.1",
|
"@resvg/resvg-js": "2.4.1",
|
||||||
"@tailwindcss/typography": "0.5.9",
|
"@tailwindcss/typography": "0.5.9",
|
||||||
"Buffer": "0.0.0",
|
"Buffer": "0.0.0",
|
||||||
"d3-dispatch": "3.0.1",
|
"d3-dispatch": "3.0.1",
|
||||||
"d3-drag": "3.0.0",
|
"d3-drag": "3.0.0",
|
||||||
"d3-selection": "3.0.0",
|
"d3-selection": "3.0.0",
|
||||||
"daisyui": "3.1.0",
|
"daisyui": "3.1.1",
|
||||||
"feed": "4.2.2",
|
"feed": "4.2.2",
|
||||||
"file-saver": "2.0.5",
|
"file-saver": "2.0.5",
|
||||||
"front-matter": "4.0.2",
|
"front-matter": "4.0.2",
|
||||||
|
@ -53,8 +53,8 @@
|
||||||
"remark-smartypants": "2.0.0",
|
"remark-smartypants": "2.0.0",
|
||||||
"sharp": "0.32.1",
|
"sharp": "0.32.1",
|
||||||
"svg-to-pdfkit": "https://github.com/eriese/SVG-to-PDFKit",
|
"svg-to-pdfkit": "https://github.com/eriese/SVG-to-PDFKit",
|
||||||
"tlds": "1.239.0",
|
"tlds": "1.240.0",
|
||||||
"to-vfile": "7.2.4",
|
"to-vfile": "8.0.0",
|
||||||
"unist-util-visit": "4.1.2",
|
"unist-util-visit": "4.1.2",
|
||||||
"use-persisted-state": "0.3.3",
|
"use-persisted-state": "0.3.3",
|
||||||
"web-worker": "1.2.0"
|
"web-worker": "1.2.0"
|
||||||
|
|
|
@ -54,7 +54,7 @@ export const formatFraction128 = (fraction, format = 'html') => {
|
||||||
rest = fraction - inches
|
rest = fraction - inches
|
||||||
}
|
}
|
||||||
let fraction128 = Math.round(rest * 128)
|
let fraction128 = Math.round(rest * 128)
|
||||||
if (fraction128 == 0) return formatImperial(negative, inches, false, false, format)
|
if (fraction128 == 0) return formatImperial(negative, inches || fraction128, false, false, format)
|
||||||
|
|
||||||
for (let i = 1; i < 7; i++) {
|
for (let i = 1; i < 7; i++) {
|
||||||
const numoFactor = Math.pow(2, 7 - i)
|
const numoFactor = Math.pow(2, 7 - i)
|
||||||
|
@ -198,7 +198,9 @@ export const optionsMenuStructure = (options) => {
|
||||||
// Fixme: One day we should sort this based on the translation
|
// Fixme: One day we should sort this based on the translation
|
||||||
for (const option of orderBy(sorted, ['menu', 'name'], ['asc'])) {
|
for (const option of orderBy(sorted, ['menu', 'name'], ['asc'])) {
|
||||||
if (typeof option === 'object') {
|
if (typeof option === 'object') {
|
||||||
option.dflt = option.dflt || option[optionType(option)]
|
const oType = optionType(option)
|
||||||
|
option.dflt = option.dflt || option[oType]
|
||||||
|
if (oType === 'pct') option.dflt /= 100
|
||||||
if (option.menu) {
|
if (option.menu) {
|
||||||
set(menu, `${option.menu}.isGroup`, true)
|
set(menu, `${option.menu}.isGroup`, true)
|
||||||
set(menu, `${option.menu}.${option.name}`, option)
|
set(menu, `${option.menu}.${option.name}`, option)
|
||||||
|
@ -281,3 +283,24 @@ export const scrollTo = (id) => {
|
||||||
// eslint-disable-next-line no-undef
|
// eslint-disable-next-line no-undef
|
||||||
if (document) document.getElementById(id).scrollIntoView()
|
if (document) document.getElementById(id).scrollIntoView()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const structureMeasurementsAsDesign = (measurements) => ({ patternConfig: { measurements } })
|
||||||
|
|
||||||
|
export const designMeasurements = (Design, measies = {}, DesignIsMeasurementsPojo = false) => {
|
||||||
|
if (DesignIsMeasurementsPojo) Design = structureMeasurementsAsDesign(Design)
|
||||||
|
const measurements = {}
|
||||||
|
for (const m of Design.patternConfig?.measurements || []) measurements[m] = measies[m]
|
||||||
|
for (const m of Design.patternConfig?.optionalMeasurements || []) measurements[m] = measies[m]
|
||||||
|
|
||||||
|
return measurements
|
||||||
|
}
|
||||||
|
|
||||||
|
export const hasRequiredMeasurements = (Design, measies = {}, DesignIsMeasurementsPojo = false) => {
|
||||||
|
if (DesignIsMeasurementsPojo) Design = structureMeasurementsAsDesign(Design)
|
||||||
|
const missing = []
|
||||||
|
for (const m of Design.patternConfig?.measurements || []) {
|
||||||
|
if (typeof measies[m] === 'undefined') missing.push(m)
|
||||||
|
}
|
||||||
|
|
||||||
|
return [missing.length === 0, missing]
|
||||||
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { measurements } from '@freesewing/models'
|
import { measurements, cisFemaleAdult28 } from '@freesewing/models'
|
||||||
import designs from '../../config/software/designs.json' assert { type: 'json' }
|
import designs from '../../config/software/designs.json' assert { type: 'json' }
|
||||||
import chai from 'chai'
|
import chai from 'chai'
|
||||||
|
|
||||||
|
@ -89,6 +89,28 @@ export const testPatternConfig = (Pattern) => {
|
||||||
expect(measurements.indexOf(measurement)).to.not.equal(-1)
|
expect(measurements.indexOf(measurement)).to.not.equal(-1)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
it('Requests all measurements it uses', () => {
|
||||||
|
const requested = {}
|
||||||
|
const patternMeasies = patternConfig.measurements.concat(patternConfig.optionalMeasurements)
|
||||||
|
for (let measurement of patternMeasies) {
|
||||||
|
requested[measurement] = cisFemaleAdult28[measurement]
|
||||||
|
}
|
||||||
|
|
||||||
|
const draft = new Pattern({
|
||||||
|
measurements: requested,
|
||||||
|
}).draft()
|
||||||
|
|
||||||
|
const missWarnings = draft.setStores[0].logs.warning.filter((w, i, a) => {
|
||||||
|
return w.match(/tried to access \`measurements/) && a.indexOf(w) === i
|
||||||
|
})
|
||||||
|
chai.assert(
|
||||||
|
missWarnings.length === 0,
|
||||||
|
`expected part to request all used measurements. \nThe following measurements were requested in the config: ${patternMeasies.join(
|
||||||
|
', '
|
||||||
|
)} \nbut got the following warnings: \n${missWarnings.join('\n')}
|
||||||
|
`
|
||||||
|
)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test validity of the pattern's options
|
// Test validity of the pattern's options
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue