1
0
Fork 0

feat: More work towards v4

This commit is contained in:
joostdecock 2024-12-10 12:08:44 +01:00
parent e36c98ea5b
commit 035cc04572
56 changed files with 4287 additions and 98 deletions

View file

@ -1088,6 +1088,12 @@
### models
#### Changed
- Migrated from Rollup to Esbuild for all builds
### utils
#### Changed
- Migrated from Rollup to Esbuild for all builds
@ -2037,6 +2043,12 @@
## 2.17.3 (2021-08-16)
### utils
#### Fixed
- Added missing `bustPointToUnderbust` measurement to `neckstimate`
## 2.17.2 (2021-08-15)
@ -2383,6 +2395,13 @@
- Changed `department` setting in config in line with new grouping
### utils
#### Changed
- neckstimate now takes an extra `noRound` parameter to return the unrounded value
- measurementDiffers takes an extra `absolute` value that can be set to false to get the non-absolute and non-rounded value
## 2.16.0 (2021-05-24)
@ -2605,6 +2624,12 @@
- Added missing sleeve notch on yoke
### utils
#### Added
- Pass pattern handle to tiler
## 2.13.0 (2021-02-13)
@ -2969,6 +2994,12 @@
- Added a new `debug` setting
- Shorthand now proxies objects to allow debug and raise
### utils
#### Added
- Added backend calls for creating gists/issues on Github
## 2.7.0 (2020-07-12)
@ -3254,6 +3285,21 @@
- Ported models to the crotchDepth measurement. See [#425](https://github.com/freesewing/freesewing/issues/425)
- Removed `Circumference` suffix from measurement names
### utils
#### Added
- Added new `isDegMeasurement` method. See [#358](https://github.com/freesewing/freesewing/issues/358)
- `neckStimate` now supports all new measurements. See [#416](https://github.com/freesewing/freesewing/issues/416)
#### Changed
- Changed `neckstimate` to handle new `shoulderSlope` degree measurement. See [#358](https://github.com/freesewing/freesewing/issues/358)
- Changed `neckstimate` to support all new measurements. See [#416](https://github.com/freesewing/freesewing/issues/416)
- Ported `neckstimate` to the crotchDepth measurement. See [#425](https://github.com/freesewing/freesewing/issues/425)
- Removed `Circumference` suffix from measurement names
- Added the `isDegMeasurement` method
## 2.6.0 (2020-05-01)
@ -3325,6 +3371,12 @@
- Check whether frontScyeDart option is zero prior to implementing it
### utils
#### Changed
- neckstimate() now returns values rounded to nearest mm
## 2.4.4 (2020-03-15)
@ -3367,6 +3419,12 @@
## 2.4.1 (2020-03-04)
### utils
#### Fixed
- [#542](https://github.com/freesewing/freesewing.org/issues/542): Prevent neckstimate from throwing when getting an unexpected measurement
## 2.4.0 (2020-02-29)
@ -3536,6 +3594,12 @@
- Extended the menswear size range to have 10 different sizes, just like womenswear
### utils
#### Changed
- Neckstimate now uses proportions only
## 2.1.9 (2020-01-18)
@ -3573,6 +3637,12 @@
## 2.1.6 (2019-11-24)
### utils
#### Fixed
- [#317](https://github.com/freesewing/freesewing.org/issues/317): Fixed bug where format was not passed to formatImperial
## 2.1.5 (2019-11-19)
@ -3582,12 +3652,28 @@
## 2.1.3 (2019-10-18)
### utils
#### Changed
- Adjusted slope of the shoulderToShoulder measurement in neckstimate data
#### Fixed
- [#250](https://github.com/freesewing/freesewing.org/issues/250): Model page stays empty with pre 2.0 model data: Error: 'neckstimate() requires a valid measurement name as second parameter. (received underBust)'
## 2.1.2 (2019-10-14)
## 2.1.1 (2019-10-13)
### utils
#### Fixed
- Fixed an issue with the formatMm method not adding units
## 2.1.0 (2019-10-06)
@ -3633,6 +3719,17 @@
- The pattern super constructor now sets a `config` property that holds the pattern configuration. This means that unlike before, there is no need to instantiate a pattern to access its config. You can just import the pattern, and it's config property will contain the pattern config.
### utils
#### Added
- Added backend methods for administration
- Added the resendActivationEmail method to backend
#### Fixed
- Fixed an issue where optionDefault was not handling list options correctly
## 2.0.4 (2019-09-27)
@ -3651,6 +3748,12 @@
- [#106](https://github.com/freesewing/freesewing/issues/106): Fix incorrect hem allowance
### utils
#### Fixed
- Fix measurementDiffers to pass breasts parameter to neckstimate
## 2.0.2 (2019-09-06)
@ -3674,6 +3777,12 @@
- [#102](https://github.com/freesewing/freesewing.org/issues/102): Fixed 'Snippets not defined' error when drafting a seperate button placket
- [#103](https://github.com/freesewing/freesewing.org/issues/103): Fixed 'hemSa not defined' when drafting paperless Simon without seam allowance
### utils
#### Fixed
- Removed lingering debug statement in formatImperial
## 2.0.1 (2019-09-01)
@ -3692,6 +3801,12 @@
- [#86](https://github.com/freesewing/freesewing/issues/86): The `seatCircumference` measurement was missing, thus making it unavailable on the website
### utils
#### Added
- The `measurementDiffers` method is new.
## 2.0.0 (2019-08-25)
@ -3881,4 +3996,10 @@
- Initial release
### utils
#### Added
- Initial release

View file

@ -184,6 +184,75 @@ yuri:
'@freesewing/brian': *freesewing
'@freesewing/plugin-bust': *freesewing
collection:
_:
# Designs
'@freesewing/aaron': *freesewing
'@freesewing/albert': *freesewing
'@freesewing/bee': *freesewing
'@freesewing/bella': *freesewing
'@freesewing/benjamin': *freesewing
'@freesewing/bent': *freesewing
'@freesewing/bibi': *freesewing
'@freesewing/bob': *freesewing
'@freesewing/breanna': *freesewing
'@freesewing/brian': *freesewing
'@freesewing/bruce': *freesewing
'@freesewing/carlita': *freesewing
'@freesewing/carlton': *freesewing
'@freesewing/cathrin': *freesewing
'@freesewing/charlie': *freesewing
'@freesewing/cornelius': *freesewing
'@freesewing/diana': *freesewing
'@freesewing/florence': *freesewing
'@freesewing/florent': *freesewing
'@freesewing/gozer': *freesewing
'@freesewing/hi': *freesewing
'@freesewing/holmes': *freesewing
'@freesewing/hortensia': *freesewing
'@freesewing/huey': *freesewing
'@freesewing/hugo': *freesewing
'@freesewing/jaeger': *freesewing
'@freesewing/jane': *freesewing
'@freesewing/lily': *freesewing
'@freesewing/lucy': *freesewing
'@freesewing/lumina': *freesewing
'@freesewing/lumira': *freesewing
'@freesewing/lunetius': *freesewing
'@freesewing/noble': *freesewing
'@freesewing/octoplushy': *freesewing
'@freesewing/onyx': *freesewing
'@freesewing/opal': *freesewing
'@freesewing/otis': *freesewing
'@freesewing/paco': *freesewing
'@freesewing/penelope': *freesewing
'@freesewing/sandy': *freesewing
'@freesewing/shelly': *freesewing
'@freesewing/shin': *freesewing
'@freesewing/simon': *freesewing
'@freesewing/simone': *freesewing
'@freesewing/skully': *freesewing
'@freesewing/sven': *freesewing
'@freesewing/tamiko': *freesewing
'@freesewing/teagan': *freesewing
'@freesewing/tiberius': *freesewing
'@freesewing/titan': *freesewing
'@freesewing/trayvon': *freesewing
'@freesewing/tristan': *freesewing
'@freesewing/uma': *freesewing
'@freesewing/umbra': *freesewing
'@freesewing/wahid': *freesewing
'@freesewing/walburga': *freesewing
'@freesewing/waralee': *freesewing
'@freesewing/yuri': *freesewing
# Plugins
'@freesewing/plugin-bust': *freesewing
'@freesewing/plugin-flip': *freesewing
'@freesewing/core-plugins': *freesewing
# Other
'@freesewing/core': *freesewing
'@freesewing/snapseries': *freesewing
# Sites go here
backend:

View file

@ -18,6 +18,12 @@ packageJson:
author: bobgeorgethe3rd (https://github.com/bobgeorgethe3rd)
benjamin:
author: woutervdub (https://github.com/woutervdub)
collection:
exports: '!'
module: "./src/index.mjs"
config:
exports: '!'
module: "./src/index.mjs"
code:
c8:
reporter: html
@ -84,12 +90,26 @@ packageJson:
"./pattern": "./src/pattern/index.mjs"
"./xray": "./src/pattern-xray/index.mjs"
# Components
"./components/Breadcrumbs": "./components/Breadcrumbs/index.mjs"
"./components/Button": "./components/Button/index.mjs"
"./components/DocusaurusPage": "./components/DocusaurusPage/index.mjs"
"./components/Icon": "./components/Icon/index.mjs"
"./components/Input": "./components/Input/index.mjs"
"./components/Layout": "./components/Layout/index.mjs"
"./components/LineDrawing": "./components/LineDrawing/index.mjs"
"./components/Link": "./components/Link/index.mjs"
"./components/Logo": "./components/Logo/index.mjs"
"./components/Modal": "./components/Modal/index.mjs"
"./components/SignIn": "./components/SignIn/index.mjs"
"./components/Spinner": "./components/Spinner/index.mjs"
"./components/Tab": "./components/Tab/index.mjs"
# Config
"./config/freesewing": "./config/freesewing/index.mjs"
# Context
"./context/loadingStatus": "./context/loadingStatus/index.mjs"
"./context/LoadingStatus": "./context/LoadingStatus/index.mjs"
"./context/Modal": "./context/Modal/index.mjs"
# Hooks
"./hooks/useAccount": "./hooks/useAccount/index.mjs"
"./hooks/useBackend": "./hooks/useBackend/index.mjs"
# Lib
"./lib/RestClient": "./lib/RestClient/index.mjs"
@ -108,6 +128,9 @@ packageJson:
author: AlfaLyr (https://github.com/alfalyr)
shelly:
author: Thrunic (https://github.com/Thrunic)
utils:
exports: '!'
module: "./src/index.mjs"
onyx:
author: Thrunic (https://github.com/Thrunic)
opal:

View file

@ -1,10 +1,13 @@
{
"collection": "All FreeSewing designs bundles into one pacakge, our collection",
"config": "Various configurations for FreeSewing",
"core": "A library for creating made-to-measure sewing patterns",
"models": "Body measurements data for a range of default sizes",
"new-design": "Initializer package for a new FreeSewing design: npx @freesewing/new-design",
"prettier-config": "FreeSewing's shared configuration for prettier",
"react": "React components, hooks and context by/for FreeSewing",
"snapseries": "A series of common sizes for elastics and other series to be used with snapped percentage options",
"utils": "A number of utilities, typically used by FreeSewing frontend code",
"rehype-jargon": "A Rehype plugin for jargon terms",
"rehype-highlight-lines": "A Rehype plugin to add highlighted lines to code blocks"
}

View file

@ -0,0 +1,17 @@
# Change log for: @freesewing/collection
## 3.0.0 (2023-09-30)
### Changed
- All FreeSewing packages are now ESM only.
- All FreeSewing packages now use named exports.
- Dropped support for NodeJS 14. NodeJS 18 (LTS/hydrogen) or more recent is now required.
This is the **initial release**, and the start of this change log.
> Prior to version 2, FreeSewing was not a JavaScript project.
> As such, that history is out of scope for this change log.

View file

@ -0,0 +1,162 @@
<p align='center'><a
href="https://www.npmjs.com/package/@freesewing/collection"
title="@freesewing/collection on NPM"
><img src="https://img.shields.io/npm/v/@freesewing/collection.svg"
alt="@freesewing/collection on NPM"/>
</a><a
href="https://opensource.org/licenses/MIT"
title="License: MIT"
><img src="https://img.shields.io/npm/l/@freesewing/collection.svg?label=License"
alt="License: MIT"/>
</a><a
href="https://deepscan.io/dashboard#view=project&tid=2114&pid=2993&bid=23256"
title="Code quality on DeepScan"
><img src="https://deepscan.io/api/teams/2114/projects/2993/branches/23256/badge/grade.svg"
alt="Code quality on DeepScan"/>
</a><a
href="https://github.com/freesewing/freesewing/issues?q=is%3Aissue+is%3Aopen+label%3Apkg%3Acollection"
title="Open issues tagged pkg:collection"
><img src="https://img.shields.io/github/issues/freesewing/freesewing/pkg:collection.svg?label=Issues"
alt="Open issues tagged pkg:collection"/>
</a><a
href="#contributors-"
title="All Contributors"
><img src="https://img.shields.io/badge/all_contributors-131-pink.svg"
alt="All Contributors"/>
</a></p><p align='center'><a
href="https://twitter.com/freesewing_org"
title="Follow @freesewing_org on Twitter"
><img src="https://img.shields.io/badge/%F3%A0%80%A0-Follow%20us-blue.svg?logo=twitter&logoColor=white&logoWidth=15"
alt="Follow @freesewing_org on Twitter"/>
</a><a
href="https://chat.freesewing.org"
title="Chat with us on Discord"
><img src="https://img.shields.io/discord/698854858052075530?label=Chat%20on%20Discord"
alt="Chat with us on Discord"/>
</a><a
href="https://freesewing.org/patrons/join"
title="Become a FreeSewing Patron"
><img src="https://img.shields.io/badge/%F3%A0%80%A0-Support%20us-blueviolet.svg?logo=cash-app&logoColor=white&logoWidth=15"
alt="Become a FreeSewing Patron"/>
</a><a
href="https://instagram.com/freesewing_org"
title="Follow @freesewing_org on Twitter"
><img src="https://img.shields.io/badge/%F3%A0%80%A0-Follow%20us-E4405F.svg?logo=instagram&logoColor=white&logoWidth=15"
alt="Follow @freesewing_org on Twitter"/>
</a></p>
# @freesewing/collection
All FreeSewing designs bundles into one pacakge, our collection
# FreeSewing
> [!TIP]
>#### Support FreeSewing: Become a patron, or make a one-time donation 🥰
>
> FreeSewing is an open source project maintained by Joost De Cock and financially supported by the FreeSewing patrons.
>
> If you feel FreeSewing is worthwhile, and you can spend a few coins without
hardship, then you should [join us and become a patron](https://freesewing.org/community/join).
## What am I looking at? 🤔
This repository is the FreeSewing *monorepo* holding all FreeSewing's websites, documentation, designs, plugins, and other NPM packages.
This folder holds: @freesewing/collection
If you're not entirely sure what to do or how to start, type this command:
```
npm run tips
```
> [!NOTE]
> If you don't want to set up a dev environment, you can run it in your browser:
>
> [![Open in Gitpod](https://gitpod.io/button/open-in-gitpod.svg)](https://gitpod.io/#https://github.com/freesewing/freesewing)
>
> We recommend that you fork our repository and then
> put `gitpod.io/#<entire-url-of-your-fork` into a browser
> to start up a browser-based dev environment of your own.
## About FreeSewing 💀
Where the world of makers and developers collide, that's where you'll find FreeSewing.
If you're a maker, checkout [freesewing.org](https://freesewing.org/) where you can generate
sewing patterns adapted to your measurements.
If you're a developer, the FreeSewing documentation lives at [freesewing.dev](https://freesewing.dev/).
The FreeSewing [core library](https://freesewing.dev/reference/api/) is a *batteries-included* toolbox
for parametric design of sewing patterns. But FreeSewing also provides a range
of [plugins](https://freesewing.dev/reference/plugins/) that further extend the
functionality of the platform.
If you have NodeJS installed, you can try it right now by running:
```bash
npx @freesewing/new-design
```
Getting started guides are available for:
- [Linux](https://freesewing.dev/tutorials/getting-started-linux/)
- [MacOS](https://freesewing.dev/tutorials/getting-started-mac/)
- [Windows](https://freesewing.dev/tutorials/getting-started-windows/)
The [pattern design tutorial](https://freesewing.dev/tutorials/pattern-design/) will
show you how to create your first parametric design.
## Getting started ⚡
To get started with FreeSewing, you can spin up our development environment with:
```bash
npx @freesewing/new-design
```
To work with FreeSewing's monorepo, you'll need [NodeJS v18](https://nodejs.org), [lerna](https://lerna.js.org/) and [yarn](https://yarnpkg.com/) on your system.
Once you have those, clone (or fork) this repo and run `yarn kickstart`:
```bash
git clone git@github.com:freesewing/freesewing.git
cd freesewing
yarn kickstart
```
## Links 👩‍💻
**Official channels**
- 💻 Makers website: [FreeSewing.org](https://freesewing.org)
- 💻 Developers website: [FreeSewing.dev](https://freesewing.dev)
- ✅ [Support](https://github.com/freesewing/freesewing/issues/new/choose),
[Issues](https://github.com/freesewing/freesewing/issues) &
[Discussions](https://github.com/freesewing/freesewing/discussions) on
[GitHub](https://github.com/freesewing/freesewing)
**Social media**
- 🐦 Twitter: [@freesewing_org](https://twitter.com/freesewing_org)
- 📷 Instagram: [@freesewing_org](https://instagram.com/freesewing_org)
**Places the FreeSewing community hangs out**
- 💬 [Discord](https://discord.freesewing.org/)
- 💬 [Facebook](https://www.facebook.com/groups/627769821272714/)
- 💬 [Reddit](https://www.reddit.com/r/freesewing/)
## License: MIT 🤓
© [Joost De Cock](https://github.com/joostdecock).
See [the license file](https://github.com/freesewing/freesewing/blob/develop/LICENSE) for details.
## Where to get help 🤯
For [Support](https://github.com/freesewing/freesewing/issues/new/choose),
please use the [Issues](https://github.com/freesewing/freesewing/issues) &
[Discussions](https://github.com/freesewing/freesewing/discussions) on
[GitHub](https://github.com/freesewing/freesewing).

View file

@ -0,0 +1,35 @@
/* This script will build the package with esbuild */
import esbuild from 'esbuild'
import pkg from './package.json' assert { type: 'json' }
// Create banner based on package info
const banner = `/**
* ${pkg.name} | v${pkg.version}
* ${pkg.description}
* (c) ${new Date().getFullYear()} ${pkg.author}
* @license ${pkg.license}
*/`
// Shared esbuild options
const options = {
banner: { js: banner },
bundle: true,
entryPoints: ['src/index.mjs'],
format: 'esm',
outfile: 'dist/index.mjs',
external: ['@freesewing'],
metafile: process.env.VERBOSE ? true : false,
minify: process.env.NO_MINIFY ? false : true,
sourcemap: true,
}
// Let esbuild generate the build
const build = async () => {
const result = await esbuild.build(options).catch(() => process.exit(1))
if (process.env.VERBOSE) {
const info = await esbuild.analyzeMetafile(result.metafile)
console.log(info)
}
}
build()

View file

@ -0,0 +1,4 @@
// This file is auto-generated | All changes you make will be overwritten.
export const name = '@freesewing/collection'
export const version = '3.3.0-rc.1'
export const data = { name, version }

View file

@ -0,0 +1,114 @@
{
"name": "@freesewing/collection",
"version": "3.3.0-rc.1",
"description": "All FreeSewing designs bundles into one pacakge, our collection",
"author": "Joost De Cock <joost@joost.at> (https://github.com/joostdecock)",
"homepage": "https://freesewing.org/",
"repository": "github:freesewing/freesewing",
"license": "MIT",
"bugs": {
"url": "https://github.com/freesewing/freesewing/issues"
},
"funding": {
"type": "individual",
"url": "https://freesewing.org/patrons/join"
},
"keywords": [
"freesewing",
"freesewing"
],
"type": "module",
"module": "./src/index.mjs",
"scripts": {
"build": "node build.mjs",
"build:all": "yarn build",
"clean": "rimraf dist",
"mbuild": "NO_MINIFY=1 node build.mjs",
"symlink": "mkdir -p ./node_modules/@freesewing && cd ./node_modules/@freesewing && ln -s -f ../../../* . && cd -",
"test": "echo \"collection: No tests configured. Perhaps you could write some?\" && exit 0",
"vbuild": "VERBOSE=1 node build.mjs",
"lab": "cd ../../sites/lab && yarn start",
"tips": "node ../../scripts/help.mjs",
"lint": "npx eslint 'src/**' 'tests/*.mjs'",
"wbuild": "node build.mjs",
"wbuild:all": "yarn wbuild"
},
"peerDependencies": {},
"dependencies": {
"@freesewing/aaron": "3.3.0-rc.1",
"@freesewing/albert": "3.3.0-rc.1",
"@freesewing/bee": "3.3.0-rc.1",
"@freesewing/bella": "3.3.0-rc.1",
"@freesewing/benjamin": "3.3.0-rc.1",
"@freesewing/bent": "3.3.0-rc.1",
"@freesewing/bibi": "3.3.0-rc.1",
"@freesewing/bob": "3.3.0-rc.1",
"@freesewing/breanna": "3.3.0-rc.1",
"@freesewing/brian": "3.3.0-rc.1",
"@freesewing/bruce": "3.3.0-rc.1",
"@freesewing/carlita": "3.3.0-rc.1",
"@freesewing/carlton": "3.3.0-rc.1",
"@freesewing/cathrin": "3.3.0-rc.1",
"@freesewing/charlie": "3.3.0-rc.1",
"@freesewing/cornelius": "3.3.0-rc.1",
"@freesewing/diana": "3.3.0-rc.1",
"@freesewing/florence": "3.3.0-rc.1",
"@freesewing/florent": "3.3.0-rc.1",
"@freesewing/gozer": "3.3.0-rc.1",
"@freesewing/hi": "3.3.0-rc.1",
"@freesewing/holmes": "3.3.0-rc.1",
"@freesewing/hortensia": "3.3.0-rc.1",
"@freesewing/huey": "3.3.0-rc.1",
"@freesewing/hugo": "3.3.0-rc.1",
"@freesewing/jaeger": "3.3.0-rc.1",
"@freesewing/jane": "3.3.0-rc.1",
"@freesewing/lily": "3.3.0-rc.1",
"@freesewing/lucy": "3.3.0-rc.1",
"@freesewing/lumina": "3.3.0-rc.1",
"@freesewing/lumira": "3.3.0-rc.1",
"@freesewing/lunetius": "3.3.0-rc.1",
"@freesewing/noble": "3.3.0-rc.1",
"@freesewing/octoplushy": "3.3.0-rc.1",
"@freesewing/onyx": "3.3.0-rc.1",
"@freesewing/opal": "3.3.0-rc.1",
"@freesewing/otis": "3.3.0-rc.1",
"@freesewing/paco": "3.3.0-rc.1",
"@freesewing/penelope": "3.3.0-rc.1",
"@freesewing/sandy": "3.3.0-rc.1",
"@freesewing/shelly": "3.3.0-rc.1",
"@freesewing/shin": "3.3.0-rc.1",
"@freesewing/simon": "3.3.0-rc.1",
"@freesewing/simone": "3.3.0-rc.1",
"@freesewing/skully": "3.3.0-rc.1",
"@freesewing/sven": "3.3.0-rc.1",
"@freesewing/tamiko": "3.3.0-rc.1",
"@freesewing/teagan": "3.3.0-rc.1",
"@freesewing/tiberius": "3.3.0-rc.1",
"@freesewing/titan": "3.3.0-rc.1",
"@freesewing/trayvon": "3.3.0-rc.1",
"@freesewing/tristan": "3.3.0-rc.1",
"@freesewing/uma": "3.3.0-rc.1",
"@freesewing/umbra": "3.3.0-rc.1",
"@freesewing/wahid": "3.3.0-rc.1",
"@freesewing/walburga": "3.3.0-rc.1",
"@freesewing/waralee": "3.3.0-rc.1",
"@freesewing/yuri": "3.3.0-rc.1",
"@freesewing/plugin-bust": "3.3.0-rc.1",
"@freesewing/plugin-flip": "3.3.0-rc.1",
"@freesewing/core-plugins": "3.3.0-rc.1",
"@freesewing/core": "3.3.0-rc.1",
"@freesewing/snapseries": "3.3.0-rc.1"
},
"devDependencies": {},
"files": [
"dist/*",
"README.md"
],
"publishConfig": {
"access": "public",
"tag": "next"
},
"engines": {
"node": ">= 18.17.0"
}
}

View file

@ -0,0 +1,121 @@
import { Aaron as aaron } from '@freesewing/aaron'
import { Albert as albert } from '@freesewing/albert'
import { Bee as bee } from '@freesewing/bee'
import { Bella as bella } from '@freesewing/bella'
import { Benjamin as benjamin } from '@freesewing/benjamin'
import { Bent as bent } from '@freesewing/bent'
import { Bibi as bibi } from '@freesewing/bibi'
import { Bob as bob } from '@freesewing/bob'
import { Breanna as breanna } from '@freesewing/breanna'
import { Brian as brian } from '@freesewing/brian'
import { Bruce as bruce } from '@freesewing/bruce'
import { Carlita as carlita } from '@freesewing/carlita'
import { Carlton as carlton } from '@freesewing/carlton'
import { Cathrin as cathrin } from '@freesewing/cathrin'
import { Charlie as charlie } from '@freesewing/charlie'
import { Cornelius as cornelius } from '@freesewing/cornelius'
import { Diana as diana } from '@freesewing/diana'
import { Florence as florence } from '@freesewing/florence'
import { Florent as florent } from '@freesewing/florent'
import { Gozer as gozer } from '@freesewing/gozer'
import { Hi as hi } from '@freesewing/hi'
import { Holmes as holmes } from '@freesewing/holmes'
import { Hortensia as hortensia } from '@freesewing/hortensia'
import { Huey as huey } from '@freesewing/huey'
import { Hugo as hugo } from '@freesewing/hugo'
import { Jaeger as jaeger } from '@freesewing/jaeger'
import { Jane as jane } from '@freesewing/jane'
import { Lucy as lucy } from '@freesewing/lucy'
import { Lumina as lumina } from '@freesewing/lumina'
import { Lumira as lumira } from '@freesewing/lumira'
import { Lunetius as lunetius } from '@freesewing/lunetius'
import { Noble as noble } from '@freesewing/noble'
import { Octoplushy as octoplushy } from '@freesewing/octoplushy'
import { Onyx as onyx } from '@freesewing/onyx'
import { Opal as opal } from '@freesewing/opal'
import { Otis as otis } from '@freesewing/otis'
import { Paco as paco } from '@freesewing/paco'
import { Penelope as penelope } from '@freesewing/penelope'
import { Sandy as sandy } from '@freesewing/sandy'
import { Shelly as shelly } from '@freesewing/shelly'
import { Shin as shin } from '@freesewing/shin'
import { Simon as simon } from '@freesewing/simon'
import { Simone as simone } from '@freesewing/simone'
import { Skully as skully } from '@freesewing/skully'
import { Sven as sven } from '@freesewing/sven'
import { Tamiko as tamiko } from '@freesewing/tamiko'
import { Teagan as teagan } from '@freesewing/teagan'
import { Tiberius as tiberius } from '@freesewing/tiberius'
import { Titan as titan } from '@freesewing/titan'
import { Trayvon as trayvon } from '@freesewing/trayvon'
import { Tristan as tristan } from '@freesewing/tristan'
import { Uma as uma } from '@freesewing/uma'
import { Umbra as umbra } from '@freesewing/umbra'
import { Wahid as wahid } from '@freesewing/wahid'
import { Walburga as walburga } from '@freesewing/walburga'
import { Waralee as waralee } from '@freesewing/waralee'
import { Yuri as yuri } from '@freesewing/yuri'
import { Lily as lily } from '@freesewing/lily'
export const designs = {
aaron,
albert,
bee,
bella,
benjamin,
bent,
bibi,
bob,
breanna,
brian,
bruce,
carlita,
carlton,
cathrin,
charlie,
cornelius,
diana,
florence,
florent,
gozer,
hi,
holmes,
hortensia,
huey,
hugo,
jaeger,
jane,
lucy,
lumina,
lumira,
lunetius,
noble,
octoplushy,
onyx,
opal,
otis,
paco,
penelope,
sandy,
shelly,
shin,
simon,
simone,
skully,
sven,
tamiko,
teagan,
tiberius,
titan,
trayvon,
tristan,
uma,
umbra,
wahid,
walburga,
waralee,
yuri,
lily,
}
export const collection = Object.keys(designs)

View file

@ -0,0 +1,17 @@
# Change log for: @freesewing/config
## 3.0.0 (2023-09-30)
### Changed
- All FreeSewing packages are now ESM only.
- All FreeSewing packages now use named exports.
- Dropped support for NodeJS 14. NodeJS 18 (LTS/hydrogen) or more recent is now required.
This is the **initial release**, and the start of this change log.
> Prior to version 2, FreeSewing was not a JavaScript project.
> As such, that history is out of scope for this change log.

162
packages/config/README.md Normal file
View file

@ -0,0 +1,162 @@
<p align='center'><a
href="https://www.npmjs.com/package/@freesewing/config"
title="@freesewing/config on NPM"
><img src="https://img.shields.io/npm/v/@freesewing/config.svg"
alt="@freesewing/config on NPM"/>
</a><a
href="https://opensource.org/licenses/MIT"
title="License: MIT"
><img src="https://img.shields.io/npm/l/@freesewing/config.svg?label=License"
alt="License: MIT"/>
</a><a
href="https://deepscan.io/dashboard#view=project&tid=2114&pid=2993&bid=23256"
title="Code quality on DeepScan"
><img src="https://deepscan.io/api/teams/2114/projects/2993/branches/23256/badge/grade.svg"
alt="Code quality on DeepScan"/>
</a><a
href="https://github.com/freesewing/freesewing/issues?q=is%3Aissue+is%3Aopen+label%3Apkg%3Aconfig"
title="Open issues tagged pkg:config"
><img src="https://img.shields.io/github/issues/freesewing/freesewing/pkg:config.svg?label=Issues"
alt="Open issues tagged pkg:config"/>
</a><a
href="#contributors-"
title="All Contributors"
><img src="https://img.shields.io/badge/all_contributors-131-pink.svg"
alt="All Contributors"/>
</a></p><p align='center'><a
href="https://twitter.com/freesewing_org"
title="Follow @freesewing_org on Twitter"
><img src="https://img.shields.io/badge/%F3%A0%80%A0-Follow%20us-blue.svg?logo=twitter&logoColor=white&logoWidth=15"
alt="Follow @freesewing_org on Twitter"/>
</a><a
href="https://chat.freesewing.org"
title="Chat with us on Discord"
><img src="https://img.shields.io/discord/698854858052075530?label=Chat%20on%20Discord"
alt="Chat with us on Discord"/>
</a><a
href="https://freesewing.org/patrons/join"
title="Become a FreeSewing Patron"
><img src="https://img.shields.io/badge/%F3%A0%80%A0-Support%20us-blueviolet.svg?logo=cash-app&logoColor=white&logoWidth=15"
alt="Become a FreeSewing Patron"/>
</a><a
href="https://instagram.com/freesewing_org"
title="Follow @freesewing_org on Twitter"
><img src="https://img.shields.io/badge/%F3%A0%80%A0-Follow%20us-E4405F.svg?logo=instagram&logoColor=white&logoWidth=15"
alt="Follow @freesewing_org on Twitter"/>
</a></p>
# @freesewing/config
Various configurations for FreeSewing
# FreeSewing
> [!TIP]
>#### Support FreeSewing: Become a patron, or make a one-time donation 🥰
>
> FreeSewing is an open source project maintained by Joost De Cock and financially supported by the FreeSewing patrons.
>
> If you feel FreeSewing is worthwhile, and you can spend a few coins without
hardship, then you should [join us and become a patron](https://freesewing.org/community/join).
## What am I looking at? 🤔
This repository is the FreeSewing *monorepo* holding all FreeSewing's websites, documentation, designs, plugins, and other NPM packages.
This folder holds: @freesewing/config
If you're not entirely sure what to do or how to start, type this command:
```
npm run tips
```
> [!NOTE]
> If you don't want to set up a dev environment, you can run it in your browser:
>
> [![Open in Gitpod](https://gitpod.io/button/open-in-gitpod.svg)](https://gitpod.io/#https://github.com/freesewing/freesewing)
>
> We recommend that you fork our repository and then
> put `gitpod.io/#<entire-url-of-your-fork` into a browser
> to start up a browser-based dev environment of your own.
## About FreeSewing 💀
Where the world of makers and developers collide, that's where you'll find FreeSewing.
If you're a maker, checkout [freesewing.org](https://freesewing.org/) where you can generate
sewing patterns adapted to your measurements.
If you're a developer, the FreeSewing documentation lives at [freesewing.dev](https://freesewing.dev/).
The FreeSewing [core library](https://freesewing.dev/reference/api/) is a *batteries-included* toolbox
for parametric design of sewing patterns. But FreeSewing also provides a range
of [plugins](https://freesewing.dev/reference/plugins/) that further extend the
functionality of the platform.
If you have NodeJS installed, you can try it right now by running:
```bash
npx @freesewing/new-design
```
Getting started guides are available for:
- [Linux](https://freesewing.dev/tutorials/getting-started-linux/)
- [MacOS](https://freesewing.dev/tutorials/getting-started-mac/)
- [Windows](https://freesewing.dev/tutorials/getting-started-windows/)
The [pattern design tutorial](https://freesewing.dev/tutorials/pattern-design/) will
show you how to create your first parametric design.
## Getting started ⚡
To get started with FreeSewing, you can spin up our development environment with:
```bash
npx @freesewing/new-design
```
To work with FreeSewing's monorepo, you'll need [NodeJS v18](https://nodejs.org), [lerna](https://lerna.js.org/) and [yarn](https://yarnpkg.com/) on your system.
Once you have those, clone (or fork) this repo and run `yarn kickstart`:
```bash
git clone git@github.com:freesewing/freesewing.git
cd freesewing
yarn kickstart
```
## Links 👩‍💻
**Official channels**
- 💻 Makers website: [FreeSewing.org](https://freesewing.org)
- 💻 Developers website: [FreeSewing.dev](https://freesewing.dev)
- ✅ [Support](https://github.com/freesewing/freesewing/issues/new/choose),
[Issues](https://github.com/freesewing/freesewing/issues) &
[Discussions](https://github.com/freesewing/freesewing/discussions) on
[GitHub](https://github.com/freesewing/freesewing)
**Social media**
- 🐦 Twitter: [@freesewing_org](https://twitter.com/freesewing_org)
- 📷 Instagram: [@freesewing_org](https://instagram.com/freesewing_org)
**Places the FreeSewing community hangs out**
- 💬 [Discord](https://discord.freesewing.org/)
- 💬 [Facebook](https://www.facebook.com/groups/627769821272714/)
- 💬 [Reddit](https://www.reddit.com/r/freesewing/)
## License: MIT 🤓
© [Joost De Cock](https://github.com/joostdecock).
See [the license file](https://github.com/freesewing/freesewing/blob/develop/LICENSE) for details.
## Where to get help 🤯
For [Support](https://github.com/freesewing/freesewing/issues/new/choose),
please use the [Issues](https://github.com/freesewing/freesewing/issues) &
[Discussions](https://github.com/freesewing/freesewing/discussions) on
[GitHub](https://github.com/freesewing/freesewing).

35
packages/config/build.mjs Normal file
View file

@ -0,0 +1,35 @@
/* This script will build the package with esbuild */
import esbuild from 'esbuild'
import pkg from './package.json' assert { type: 'json' }
// Create banner based on package info
const banner = `/**
* ${pkg.name} | v${pkg.version}
* ${pkg.description}
* (c) ${new Date().getFullYear()} ${pkg.author}
* @license ${pkg.license}
*/`
// Shared esbuild options
const options = {
banner: { js: banner },
bundle: true,
entryPoints: ['src/index.mjs'],
format: 'esm',
outfile: 'dist/index.mjs',
external: ['@freesewing'],
metafile: process.env.VERBOSE ? true : false,
minify: process.env.NO_MINIFY ? false : true,
sourcemap: true,
}
// Let esbuild generate the build
const build = async () => {
const result = await esbuild.build(options).catch(() => process.exit(1))
if (process.env.VERBOSE) {
const info = await esbuild.analyzeMetafile(result.metafile)
console.log(info)
}
}
build()

4
packages/config/data.mjs Normal file
View file

@ -0,0 +1,4 @@
// This file is auto-generated | All changes you make will be overwritten.
export const name = '@freesewing/config'
export const version = '3.3.0-rc.1'
export const data = { name, version }

View file

@ -0,0 +1,50 @@
{
"name": "@freesewing/config",
"version": "3.3.0-rc.1",
"description": "Various configurations for FreeSewing",
"author": "Joost De Cock <joost@joost.at> (https://github.com/joostdecock)",
"homepage": "https://freesewing.org/",
"repository": "github:freesewing/freesewing",
"license": "MIT",
"bugs": {
"url": "https://github.com/freesewing/freesewing/issues"
},
"funding": {
"type": "individual",
"url": "https://freesewing.org/patrons/join"
},
"keywords": [
"freesewing",
"freesewing"
],
"type": "module",
"module": "./src/index.mjs",
"scripts": {
"build": "node build.mjs",
"build:all": "yarn build",
"clean": "rimraf dist",
"mbuild": "NO_MINIFY=1 node build.mjs",
"symlink": "mkdir -p ./node_modules/@freesewing && cd ./node_modules/@freesewing && ln -s -f ../../../* . && cd -",
"test": "echo \"config: No tests configured. Perhaps you could write some?\" && exit 0",
"vbuild": "VERBOSE=1 node build.mjs",
"lab": "cd ../../sites/lab && yarn start",
"tips": "node ../../scripts/help.mjs",
"lint": "npx eslint 'src/**' 'tests/*.mjs'",
"wbuild": "node build.mjs",
"wbuild:all": "yarn wbuild"
},
"peerDependencies": {},
"dependencies": {},
"devDependencies": {},
"files": [
"dist/*",
"README.md"
],
"publishConfig": {
"access": "public",
"tag": "next"
},
"engines": {
"node": ">= 18.17.0"
}
}

View file

@ -0,0 +1,148 @@
/*
* This configuration file holds various control levels.
* Control is a setting that determines what to hide/show in the UI
*
* This file has the following named exports:
*
* accountControlLevels: Fields and control levels, and other account related settings
* controlLevels: Consolidated object holding all control levels
* defaultControlLevel: The default control level to apply when we have no user-specific level
* editorControlLevels: Configuration for the pattern editor
*
* They are all re-exported from index.mjs
*/
/*
* The default control level in case we have nothing more specific
*/
const dflt = 3
/*
* Structure of the various account fields and their control levels
*/
const account = {
fields: {
data: {
bookmarks: 2,
sets: 1,
patterns: 1,
apikeys: 4,
},
info: {
username: 2,
bio: 2,
img: 2,
email: 3,
},
settings: {
language: 2,
units: 2,
newsletter: 2,
compare: 3,
control: 1,
consent: 2,
},
security: {
password: 2,
mfa: 3,
apikeys: 4,
},
identities: {
github: 3,
instagram: 3,
mastodon: 3,
reddit: 3,
twitch: 3,
tiktok: 3,
website: 3,
},
},
sets: {
name: 1,
img: 1,
public: 3,
units: 1,
notes: 2,
createdAt: 2,
updatedAt: 2,
id: 4,
},
patterns: {
name: 1,
img: 1,
public: 3,
notes: 2,
createdAt: 2,
updatedAt: 2,
id: 4,
},
statuses: {
0: {
name: 'inactive',
color: 'neutral',
},
1: {
name: 'active',
color: 'success',
},
'-1': {
name: 'paused',
color: 'warning',
},
'-2': {
name: 'disabled',
color: 'error',
},
},
}
/*
* Editor control levels
*/
const editor = {
core: {
sa: 2,
paperless: 2,
locale: 3,
units: 1,
complete: 4,
expand: 4,
only: 4,
scale: 4,
margin: 4,
},
ui: {
renderer: 4,
kiosk: 2,
},
views: {
draft: 1,
measies: 1,
test: 3,
time: 3,
print: 1,
export: 1,
save: 1,
edit: 4,
logs: 2,
inspect: 4,
docs: 1,
},
}
export const control = {
account,
editor,
dflt,
flat: {
...account.fields.data,
...account.fields.info,
...account.fields.settings,
...account.fields.security,
...account.fields.identities,
sets: account.sets,
core: editor.core,
ui: editor.ui,
views: editor.views,
},
}

View file

@ -0,0 +1,8 @@
import { urls } from './urls.mjs'
import { control } from './control.mjs'
import { measurements, degreeMeasurements, isDegreeMeasurement } from './measurements.mjs'
/*
* This top-level file bundles all (named) exports for the config package
*/
export { control, urls, measurements, degreeMeasurements, isDegreeMeasurement }

View file

@ -0,0 +1,47 @@
/* A list of all measurements used by FreeSewing */
export const measurements = [
'ankle',
'biceps',
'bustFront',
'bustPointToUnderbust',
'bustSpan',
'chest',
'crossSeam',
'crossSeamFront',
'crotchDepth',
'heel',
'head',
'highBust',
'highBustFront',
'hips',
'hpsToBust',
'hpsToWaistBack',
'hpsToWaistFront',
'inseam',
'knee',
'neck',
'seat',
'seatBack',
'shoulderSlope',
'shoulderToElbow',
'shoulderToShoulder',
'shoulderToWrist',
'underbust',
'upperLeg',
'waist',
'waistBack',
'waistToArmpit',
'waistToFloor',
'waistToHips',
'waistToKnee',
'waistToSeat',
'waistToUnderbust',
'waistToUpperLeg',
'wrist',
]
/* A list of measurments that are degrees (rather than mm) */
export const degreeMeasurements = ['shoulderSlope']
/* Helper method to determine whether a measurement uses degrees */
export const isDegreeMeasurement = (measie) => degreeMeasurements.indexOf(measie) !== -1

View file

@ -0,0 +1,24 @@
/*
* This configuration file exports various URLs.
*/
export const urls = {
// FreeSewing Backend
backend: 'https://backend3.freesewing.org',
// FreeSewing website
website: 'https://freesewing.org',
// FreeSewing monorepo
monorepo: 'https://github.com/freesewing/freesewing',
// FreeSewing github organisation
github: 'https://github.com/freesewing',
// Social media and other account links for FreeSewing
social: {
YouTube: 'https://www.youtube.com/@freesewing',
Discord: 'https://discord.freesewing.org/',
Instagram: 'https://instagram.com/freesewing_org',
Facebook: 'https://www.facebook.com/groups/627769821272714/',
GitHub: 'https://github.com/freesewing',
Reddit: 'https://www.reddit.com/r/freesewing/',
Mastodon: 'https://freesewing.social/@freesewing',
Bluesky: 'https://bsky.app/profile/freesewing.org',
},
}

View file

@ -0,0 +1,47 @@
import React from 'react'
/*
* The actual Breadcrumbs component
*
* @param {object} props - All the React props
* @param {array} props.crumbs - The crumbs, an array with objects with href, label keys
* @param {function} Link - An optional custom component to use to render the Link
* @param {text} title - The title of the current page
*/
export const Breadcrumbs = ({ crumbs = [], title, Link = false }) => {
if (Link === false) Link = RegularLink
return (
<div className="tailwind-container p-0">
<ul className="flex flex-row items-center gap-2 m-0 py-4" style={{ paddingLeft: 0 }}>
<li className="inline">
<Link href="/">Home</Link>
</li>
<Spacer />
{crumbs.map((crumb, i) => (
<li key={i} className="inline">
<Link href={crumb.href}>{crumb.label}</Link>
</li>
))}
<li className="inline">{title}</li>
</ul>
</div>
)
}
/*
* We always start from home
*/
const homeCrumbs = { label: 'Home', href: '/' }
/*
* People can pass in a custom Link component,
* which is useful when using one from your framework.
* If not, we use a regular a tag
*/
const RegularLink = ({ href, children }) => <a href={href}>{children}</a>
/*
* This goes between breadcrumbs
*/
const Spacer = () => <li className="inline">&raquo;</li>

View file

@ -0,0 +1,33 @@
import React from 'react'
const sizes = {
lg: 'text-lg padding-4',
base: 'text-base padding-2',
sm: 'text-sm padding-2',
xs: 'text-xs padding-1',
}
const variants = {
base: '',
outline: '',
ghost: '',
link: '',
}
const shared = 'gap-2 font-semibold no-underline duration-200 ease-out'
/**
* A button component
*
* @param {object} props - All React props
* @param {array} props.children - Content to go inside the button
* @param {string} props.color - One of the named colors (primary, secondary, accent, neutral, warning, error, success, info)
* @param {string} props.size - One of the sizes (lg, base, sm, xs)
* @param {string} props.variant - The button variant, one of base, outline, ghost, or link
* @param {string} className - Any additional CSS classes to add
*/
export const Button = ({ children = [], color = 'primary', size = 'base', variant = 'base' }) => {
//if (variant === "outline") return <OutlineButton {...props} />
return <button className={`${shared} ${sizes[size]}`}>{children}</button>
}

View file

@ -0,0 +1,69 @@
import React, { useState, useEffect, useContext } from 'react'
import {
LoadingStatusContext,
LoadingStatusContextProvider,
} from '@freesewing/react/context/LoadingStatus'
import { ModalContext, ModalContextProvider } from '@freesewing/react/context/Modal'
import { Layout as DefaultLayout } from '@freesewing/react/components/Layout'
/*
* This component should be the top level of a Docusaurus page
*
* This sets up the various context providers before
* passing all props down to the InnerPageWrapper.
* This is required because the context providers need to
* be setup for the modal and loading state work we do in the InnerPageWrapper
*
* We also re-use the Docusaurus Layout component here, which needs to be at
* the top level of the page
*/
export const DocusaurusPage = (props) => {
const DocusaurusLayout = props.DocusaurusLayout
console.log({ props })
return DocusaurusLayout ? (
<DocusaurusLayout title={props.title} description={props.description}>
<ModalContextProvider>
<LoadingStatusContextProvider>
<InnerDocusaurusPage {...props} />
</LoadingStatusContextProvider>
</ModalContextProvider>
</DocusaurusLayout>
) : (
<p>You need to pass in the DocusaurusLayout component</p>
)
}
/*
* This component needs to be a child of the ContextWrapper
*
* @param {object} props - All React props
* @param {function} Layout - A specific React component to use for a non-default Layout
* @param {array} children - Child components / content
* @param {array} crumbs - An array to construct breadcrumbs from
* @param {string} description - Description for the page metadata
* @param {string} title - Description for the page title
*/
const InnerDocusaurusPage = ({
Layout = DefaultLayout,
children = [],
crumbs = [],
description = 'Free Bespoke Sewing Patterns',
title = 'FreeSewing',
}) => {
/*
* Set up contexts
*/
const { modalContent } = useContext(ModalContext)
const { LoadingStatus } = useContext(LoadingStatusContext)
/*
* Return inner page wrapper
*/
return (
<div className="tailwind-container">
<LoadingStatus />
{Layout ? <Layout {...{ title, description, crumbs }}>{children}</Layout> : children}
{typeof modalContent === 'function' ? modalContent() : modalContent}
</div>
)
}

View file

@ -0,0 +1,574 @@
import React from 'react'
import { logoPath } from '@freesewing/react/components/Logo'
/*
* Used inside the pattern editor
*/
export const IconWrapper = ({
className = 'w-6 h-6',
stroke = 2,
children = null,
fill = false,
fillOpacity = 1,
dasharray = null,
wrapped = true,
}) =>
wrapped ? (
<svg
xmlns="http://www.w3.org/2000/svg"
fill={fill ? 'currentColor' : 'none'}
fillOpacity={fillOpacity}
strokeOpacity={fillOpacity}
viewBox="0 0 24 24"
strokeWidth={stroke}
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeDasharray={dasharray ? dasharray : ''}
className={className + ' icon'}
>
{children}
</svg>
) : (
<> {children} </>
)
// Looks like a play triangle
export const ApplyIcon = (props) => (
<IconWrapper {...props}>
<path d="M5.25 5.653c0-.856.917-1.398 1.667-.986l11.54 6.347a1.125 1.125 0 0 1 0 1.972l-11.54 6.347a1.125 1.125 0 0 1-1.667-.986V5.653Z" />
</IconWrapper>
)
// Looks like a lab beaker
export const BeakerIcon = (props) => (
<IconWrapper {...props}>
<path d="M9.75 3.104v5.714a2.25 2.25 0 01-.659 1.591L5 14.5M9.75 3.104c-.251.023-.501.05-.75.082m.75-.082a24.301 24.301 0 014.5 0m0 0v5.714c0 .597.237 1.17.659 1.591L19.8 15.3M14.25 3.104c.251.023.501.05.75.082M19.8 15.3l-1.57.393A9.065 9.065 0 0112 15a9.065 9.065 0 00-6.23-.693L5 14.5m14.8.8l1.402 1.402c1.232 1.232.65 3.318-1.067 3.611A48.309 48.309 0 0112 21c-2.773 0-5.491-.235-8.135-.687-1.718-.293-2.3-2.379-1.067-3.61L5 14.5" />
</IconWrapper>
)
// Looks like a left U-turn that we slightly rotate
export const BackIcon = (props) => (
<IconWrapper {...props} className={`${props.className || 'w-6 h-h'} -rotate-45`}>
<path d="M9 15 3 9m0 0 6-6M3 9h12a6 6 0 0 1 0 12h-3" />
</IconWrapper>
)
// Looks like a red X
export const BoolNoIcon = ({ size = 6 }) => (
<NoIcon className={`w-${size} h-${size} text-error`} stroke={4} />
)
// Looks like a green checkbox
export const BoolYesIcon = ({ size = 6 }) => (
<OkIcon className={`w-${size} h-${size} text-success`} stroke={4} />
)
// Looks like a bookmark
export const BookmarkIcon = (props) => (
<IconWrapper {...props}>
<path d="M17.593 3.322c1.1.128 1.907 1.077 1.907 2.185V21L12 17.25 4.5 21V5.507c0-1.108.806-2.057 1.907-2.185a48.507 48.507 0 0111.186 0z" />
</IconWrapper>
)
// Looks lik a speech bubble
export const ChatIcon = (props) => (
<IconWrapper {...props}>
<path d="M2.25 12.76c0 1.6 1.123 2.994 2.707 3.227 1.087.16 2.185.283 3.293.369V21l4.076-4.076a1.526 1.526 0 011.037-.443 48.282 48.282 0 005.68-.494c1.584-.233 2.707-1.626 2.707-3.228V6.741c0-1.602-1.123-2.995-2.707-3.228A48.394 48.394 0 0012 3c-2.392 0-4.744.175-7.043.513C3.373 3.746 2.25 5.14 2.25 6.741v6.018z" />
</IconWrapper>
)
// Looks like a circle
export const CircleIcon = (props) => (
<IconWrapper {...props}>
<circle cx="12" cy="12" r="10" />
</IconWrapper>
)
// Looks like a X
export const CloseIcon = (props) => (
<IconWrapper {...props}>
<path d="M6 18L18 6M6 6l12 12" />
</IconWrapper>
)
// Looks like a museum building
export const CuratedMeasurementsSetIcon = (props) => (
<IconWrapper {...props}>
<path d="M12 21v-8.25M15.75 21v-8.25M8.25 21v-8.25M3 9l9-6 9 6m-1.5 12V10.332A48.36 48.36 0 0012 9.75c-2.551 0-5.056.2-7.5.582V21M3 21h18M12 6.75h.008v.008H12V6.75z" />
</IconWrapper>
)
// Looks like a coathanger
export const DesignIcon = (props) => (
<IconWrapper {...props} stroke={0} fill>
<path d="m11.975 2.9104c-1.5285 0-2.7845 1.2563-2.7845 2.7848 0 0.7494 0.30048 1.4389 0.78637 1.9394a0.79437 0.79437 0 0 0 0.0084 0.00839c0.38087 0.38087 0.74541 0.62517 0.94538 0.82483 0.19998 0.19966 0.25013 0.2645 0.25013 0.51907v0.65964l-9.1217 5.2665c-0.28478 0.16442-0.83603 0.46612-1.3165 0.9611-0.48047 0.49498-0.92451 1.3399-0.66684 2.2585 0.22026 0.78524 0.7746 1.3486 1.3416 1.5878 0.56697 0.23928 1.0982 0.23415 1.4685 0.23415h18.041c0.37033 0 0.90158 0.0051 1.4686-0.23415 0.56697-0.23928 1.1215-0.80261 1.3418-1.5878 0.25767-0.91859-0.18662-1.7636-0.66709-2.2585-0.48046-0.49498-1.0315-0.79669-1.3162-0.9611l-8.9844-5.1873v-0.73889c0-0.70372-0.35623-1.2837-0.71653-1.6435-0.35778-0.3572-0.70316-0.58503-0.93768-0.81789-0.20864-0.21601-0.33607-0.50298-0.33607-0.83033 0-0.67 0.52595-1.1962 1.1959-1.1962 0.67001 0 1.1962 0.5262 1.1962 1.1962a0.79429 0.79429 0 0 0 0.79434 0.79427 0.79429 0.79429 0 0 0 0.79427-0.79427c0-1.5285-1.2563-2.7848-2.7848-2.7848zm-0.06859 8.2927 8.9919 5.1914c0.28947 0.16712 0.69347 0.41336 0.94393 0.67138 0.25046 0.25803 0.31301 0.3714 0.24754 0.60483-0.10289 0.36677-0.19003 0.40213-0.35969 0.47373-0.16967 0.07161-0.47013 0.09952-0.80336 0.09952h-18.041c-0.33323 0-0.6337-0.02792-0.80336-0.09952-0.16967-0.07161-0.25675-0.10696-0.35963-0.47373-0.06548-0.23342-0.00303-0.3468 0.24748-0.60483 0.25046-0.25803 0.65471-0.50426 0.94418-0.67138z" />
</IconWrapper>
)
// Looks like a left and right pane with different level of detail
export const DetailIcon = (props) => (
<IconWrapper {...props}>
<path d="M3.98 8.223A10.477 10.477 0 0 0 1.934 12C3.226 16.338 7.244 19.5 12 19.5c.993 0 1.953-.138 2.863-.395M6.228 6.228A10.451 10.451 0 0 1 12 4.5c4.756 0 8.773 3.162 10.065 7.498a10.522 10.522 0 0 1-4.293 5.774M6.228 6.228 3 3m3.228 3.228 3.65 3.65m7.894 7.894L21 21m-3.228-3.228-3.65-3.65m0 0a3 3 0 1 0-4.243-4.243m4.242 4.242L9.88 9.88" />
</IconWrapper>
)
// Looks like a document icon
export const DocsIcon = (props) => (
<IconWrapper {...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" />
</IconWrapper>
)
// Looks like a down pointing chevron
export const DownIcon = (props) => (
<IconWrapper {...props}>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={props.stroke || 2}
d="M19 9l-7 7-7-7"
/>
</IconWrapper>
)
// Looks like a pencil
export const EditIcon = (props) => (
<IconWrapper {...props}>
<path d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z" />
</IconWrapper>
)
// Looks like an envelope
export const EmailIcon = (props) => (
<IconWrapper {...props}>
<path d="M21.75 6.75v10.5a2.25 2.25 0 01-2.25 2.25h-15a2.25 2.25 0 01-2.25-2.25V6.75m19.5 0A2.25 2.25 0 0019.5 4.5h-15a2.25 2.25 0 00-2.25 2.25m19.5 0v.243a2.25 2.25 0 01-1.07 1.916l-7.5 4.615a2.25 2.25 0 01-2.36 0L3.32 8.91a2.25 2.25 0 01-1.07-1.916V6.75" />
</IconWrapper>
)
// Looks like FIXME
export const ErrorIcon = (props) => (
<IconWrapper {...props}>
<path d="M12 9v3.75m9-.75a9 9 0 11-18 0 9 9 0 0118 0zm-9 3.75h.008v.008H12v-.008z" />
</IconWrapper>
)
// Looks like arrows pointing outwards
export const ExpandIcon = (props) => (
<IconWrapper {...props}>
<path d="M3.75 3.75v4.5m0-4.5h4.5m-4.5 0L9 9M3.75 20.25v-4.5m0 4.5h4.5m-4.5 0L9 15M20.25 3.75h-4.5m4.5 0v4.5m0-4.5L15 9m5.25 11.25h-4.5m4.5 0v-4.5m0 4.5L15 15" />
</IconWrapper>
)
// Looks like a file/sheet with an arrow pointing downwards
export const ExportIcon = (props) => (
<IconWrapper {...props}>
<path d="M12 10v6m0 0l-3-3m3 3l3-3m2 8H7a2 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>
)
// Looks like a ! in a triangle, is intended to be shown on an error background
export const FailureIcon = ({ size = 6 }) => (
<NoIcon className={`w-${size} h-${size} text-secondary-content`} stroke={4} />
)
// Looks lik a flag
export const FlagIcon = (props) => (
<IconWrapper {...props}>
<path d="M3 3v1.5M3 21v-6m0 0l2.77-.693a9 9 0 016.208.682l.108.054a9 9 0 006.086.71l3.114-.732a48.524 48.524 0 01-.005-10.499l-3.11.732a9 9 0 01-6.085-.711l-.108-.054a9 9 0 00-6.208-.682L3 4.5M3 15V4.5" />
</IconWrapper>
)
// Looks like skully
export const FreeSewingIcon = (props) => (
<IconWrapper {...props} stroke={0} fill>
<path d={logoPath} />
</IconWrapper>
)
// Looks like a gauge or speedometer
export const GaugeIcon = (props) => (
<IconWrapper {...props}>
<path d="M 4.9580501,20.694732 A 9.9588146,9.9588156 45 0 1 4.9523103,6.6165865 9.9588146,9.9588156 45 0 1 19.030446,6.5993628 9.9588146,9.9588156 45 0 1 19.059151,20.67748 Z" />
<path d="m 13.346899,14.905658 c 0.185287,0.691503 -0.22474,1.402361 -0.916092,1.588212 -0.691356,0.185836 -1.402539,-0.223628 -1.588938,-0.914833 -0.186401,-0.691204 0.222481,-1.402721 0.913533,-1.589686 l 4.660195,-6.2056598 z" />
</IconWrapper>
)
// Looks like the octocat
export const GitHubIcon = (props) => (
<IconWrapper {...props} stroke={0} fill>
<path d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12" />
</IconWrapper>
)
// Looks like the Google G
export const GoogleIcon = (props) => (
<IconWrapper {...props} fill>
<path d="M 12.25009,0 C 7.5567085,0 3.5033589,2.69334 1.530043,6.613315 0.71674427,8.240005 0.25,10.06676 0.25,12.00009 c 0,1.93333 0.46674427,3.759905 1.280043,5.386595 C 3.5033589,21.30666 7.5567085,24 12.25009,24 c 3.239959,0 5.959944,-1.066635 7.94668,-2.906575 2.266629,-2.093365 3.573349,-5.173415 3.573349,-8.826735 0,-0.98666 -0.08023,-1.70661 -0.253496,-2.453265 l -11.266533,0 0,4.45322 6.613137,0 c -0.133283,1.106705 -0.853233,2.77333 -2.453266,3.89327 -1.013315,0.706675 -2.373243,1.199975 -4.159871,1.199975 -3.173318,0 -5.8666835,-2.09327 -6.826777,-4.986605 -0.2533286,-0.746655 -0.399991,-1.54657 -0.399991,-2.373195 0,-0.82672 0.1467055,-1.62672 0.386706,-2.373375 C 6.3834495,6.73338 9.076772,4.63993 12.25009,4.63993 c 2.253301,0 3.773228,0.973465 4.639932,1.786855 L 20.27666,3.12004 C 18.196718,1.186705 15.490049,0 12.25009,0 Z" />
</IconWrapper>
)
// Looks like abox
export const GroupIcon = (props) => (
<IconWrapper {...props}>
<path d="M20 7l-8-4-8 4m16 0l-8 4m8-4v10l-8 4m0-10L4 7m8 4v10M4 7v10l8 4" />
</IconWrapper>
)
// Looks like a question mark in a circle
export const HelpIcon = (props) => (
<IconWrapper {...props}>
<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" />
</IconWrapper>
)
// Looks like a pie with a slice a bit out of it
export const IncludeIcon = (props) => (
<IconWrapper {...props}>
<path d="M10.5 6a7.5 7.5 0 107.5 7.5h-7.5V6z" />
<path d="M13.5 10.5H21A7.5 7.5 0 0013.5 3v7.5z" />
</IconWrapper>
)
// Looks like a key
export const KeyIcon = (props) => (
<IconWrapper {...props}>
<path d="M15.75 5.25a3 3 0 013 3m3 0a6 6 0 01-7.029 5.912c-.563-.097-1.159.026-1.563.43L10.5 17.25H8.25v2.25H6v2.25H2.25v-2.818c0-.597.237-1.17.659-1.591l6.499-6.499c.404-.404.527-1 .43-1.563A6 6 0 1121.75 8.25z" />
</IconWrapper>
)
// Looks like a rectangle with rounded corners (like a full screen display)
export const KioskIcon = (props) => (
<IconWrapper {...props}>
<path d="M 3,17.033898 V 7.2838983 c 0,-1.242641 1.007359,-2.25 2.25,-2.25 h 13.5 c 1.242641,0 2.25,1.007359 2.25,2.25 v 9.7499997 m -18,0 c 0,1.242641 1.007359,2.25 2.25,2.25 h 13.5 c 1.242641,0 2.25,-1.007359 2.25,-2.25" />
</IconWrapper>
)
// Looks like a left pointing chevron
export const LeftIcon = (props) => (
<IconWrapper {...props}>
<path d="M15 19l-7-7 7-7" />
</IconWrapper>
)
// Looks like a bullet list
export const ListIcon = (props) => (
<IconWrapper {...props}>
<path d="M8.25 6.75h12M8.25 12h12m-12 5.25h12M3.75 6.75h.007v.008H3.75V6.75Zm.375 0a.375.375 0 1 1-.75 0 .375.375 0 0 1 .75 0ZM3.75 12h.007v.008H3.75V12Zm.375 0a.375.375 0 1 1-.75 0 .375.375 0 0 1 .75 0Zm-.375 5.25h.007v.008H3.75v-.008Zm.375 0a.375.375 0 1 1-.75 0 .375.375 0 0 1 .75 0Z" />
</IconWrapper>
)
// Looks like a padlock
export const LockIcon = (props) => (
<IconWrapper {...props}>
<path d="M16.5 10.5V6.75a4.5 4.5 0 10-9 0v3.75m-.75 11.25h10.5a2.25 2.25 0 002.25-2.25v-6.75a2.25 2.25 0 00-2.25-2.25H6.75a2.25 2.25 0 00-2.25 2.25v6.75a2.25 2.25 0 002.25 2.25z" />
</IconWrapper>
)
// Looks like 4 boxes spaces out
export const MarginIcon = (props) => (
<IconWrapper {...props}>
<path d="m 2.4889452,14.488945 h 7.0221096 v 7.02211 H 2.4889452 Z M 14.488945,2.4889452 h 7.02211 v 7.0221096 h -7.02211 z m -11.9999998,0 H 9.5110548 V 9.5110548 H 2.4889452 Z M 14.488945,14.488945 h 7.02211 v 7.02211 h -7.02211 z" />
</IconWrapper>
)
// Looks like a tape measure
export const MeasurementsIcon = (props) => (
<IconWrapper {...props}>
<path d="m 23.045963,7.9355562 v 3.7987498 l -0.01469,-0.056 C 23.346411,18.817891 1.2185835,21.36545 1.2185835,21.36545 v -3.836793 m 0.075177,-5.192339 V 8.3573441 M 6.7939469,16.638959 C 5.6484749,16.381008 4.6127423,16.03325 3.7570739,15.581871 2.2780992,14.801689 1.3370697,13.711938 1.2971261,12.241292 M 2.0464728,10.122206 C 4.5831883,3.7286119 20.382021,3.7454996 22.30006,10.110938 M 1.2185835,17.535357 c 0,0 23.2148625,-2.584129 21.7494555,-10.2183133 C 21.611034,0.24754253 2.7448834,0.46882453 1.373585,7.5355232 0.41141246,12.493877 8.5647942,13.74509 15.368311,13.344749 v 1.037311" />
</IconWrapper>
)
// Looks like two people's heads next/behinf to each other, one bigger, one smaller
export const MeasurementsSetIcon = (props) => (
<IconWrapper {...props}>
<path d="M15 19.128a9.38 9.38 0 002.625.372 9.337 9.337 0 004.121-.952 4.125 4.125 0 00-7.533-2.493M15 19.128v-.003c0-1.113-.285-2.16-.786-3.07M15 19.128v.106A12.318 12.318 0 018.624 21c-2.331 0-4.512-.645-6.374-1.766l-.001-.109a6.375 6.375 0 0111.964-3.07M12 6.375a3.375 3.375 0 11-6.75 0 3.375 3.375 0 016.75 0zm8.25 2.25a2.625 2.625 0 11-5.25 0 2.625 2.625 0 015.25 0z" />
</IconWrapper>
)
// Looks like 3 horizontal lines (hamburger menu)
export const MenuIcon = (props) => (
<IconWrapper {...props}>
<path d="M4 6h16M4 12h16M4 18h16" />
</IconWrapper>
)
// Looks like a X
export const NoIcon = (props) => (
<IconWrapper {...props}>
<path d="M6,6 L 18,18 M 18,6 L 6,18" />
</IconWrapper>
)
// Looks like a checkmark
export const OkIcon = (props) => (
<IconWrapper {...props}>
<path d="M4.5 12.75l6 6 9-13.5" />
</IconWrapper>
)
// Looks like sliders on a mixing panel
export const OptionsIcon = (props) => (
<IconWrapper {...props}>
<path d="M10.5 6h9.75M10.5 6a1.5 1.5 0 11-3 0m3 0a1.5 1.5 0 10-3 0M3.75 6H7.5m3 12h9.75m-9.75 0a1.5 1.5 0 01-3 0m3 0a1.5 1.5 0 00-3 0m-3.75 0H7.5m9-6h3.75m-3.75 0a1.5 1.5 0 01-3 0m3 0a1.5 1.5 0 00-3 0m-9.75 0h9.75" />
</IconWrapper>
)
// Looks like a grid
export const PaperlessIcon = (props) => (
<IconWrapper {...props}>
<path d="M 1.5867219,1.58672 H 22.413278 V 22.41328 H 1.5867219 Z" />
<path
d="M 22.007133,15.500122 H 1.97864 m 20.028493,-7 H 1.97864 M 15.492887,1.9858756 V 22.014369 m -7,-20.0284934 V 22.014369"
strokeWidth={props.stroke / 2 || 1.1}
/>
</IconWrapper>
)
// Looks like a +
export const PlusIcon = (props) => (
<IconWrapper {...props}>
<path d="M12 9v6m3-3H9m12 0a9 9 0 11-18 0 9 9 0 0118 0z" />
</IconWrapper>
)
// Looks like a printer
export const PrintIcon = (props) => (
<IconWrapper {...props}>
<path d="M17 17h2a2 2 0 002-2v-4a2 2 0 00-2-2H5a2 2 0 00-2 2v4a2 2 0 002 2h2m2 4h6a2 2 0 002-2v-4a2 2 0 00-2-2H9a2 2 0 00-2 2v4a2 2 0 002 2zm8-12V5a2 2 0 00-2-2H9a2 2 0 00-2 2v4h10z" />
</IconWrapper>
)
// Looks like a single rewind arrow
export const ResetIcon = (props) => (
<IconWrapper {...props}>
<path d="M16 18 l 0 -12 l -8 6 z M 6 6 l 0 12 l 1 0 l 0 -10 z" />
</IconWrapper>
)
// Looks like a double rewind arrow
export const ResetAllIcon = (props) => (
<IconWrapper {...props}>
<path d="M12 18 l 0 -12 l -8 6 z M 20 18 l 0 -12 l -8 6 z M 2 6 l 0 12 l 1 0 l 0 -10 z" />
</IconWrapper>
)
// Looks like a right pointing chevron
export const RightIcon = (props) => (
<IconWrapper {...props}>
<path d="M9 5l7 7-7 7" />
</IconWrapper>
)
// Looks like a rocket
export const RocketIcon = (props) => (
<IconWrapper {...props}>
<path d="M15.59 14.37a6 6 0 01-5.84 7.38v-4.8m5.84-2.58a14.98 14.98 0 006.16-12.12A14.98 14.98 0 009.631 8.41m5.96 5.96a14.926 14.926 0 01-5.841 2.58m-.119-8.54a6 6 0 00-7.381 5.84h4.8m2.581-5.84a14.927 14.927 0 00-2.58 5.84m2.699 2.7c-.103.021-.207.041-.311.06a15.09 15.09 0 01-2.448-2.448 14.9 14.9 0 01.06-.312m-2.24 2.39a4.493 4.493 0 00-1.757 4.306 4.493 4.493 0 004.306-1.758M16.5 9a1.5 1.5 0 11-3 0 1.5 1.5 0 013 0z" />
</IconWrapper>
)
// Looks like two arrows in a circular layout
export const RotateIcon = (props) => (
<IconWrapper {...props}>
<path d="M16.023 9.348h4.992v-.001M2.985 19.644v-4.992m0 0h4.992m-4.993 0 3.181 3.183a8.25 8.25 0 0 0 13.803-3.7M4.031 9.865a8.25 8.25 0 0 1 13.803-3.7l3.181 3.182m0-4.991v4.99" />
</IconWrapper>
)
// Looks like a cloud with a plus sign in it
export const SaveIcon = (props) => (
<IconWrapper {...props}>
<path d="M12 24V12.5m0 0l3 3m-3-3l-3 3M6.75 19.5a4.5 4.5 0 01-1.41-8.775 5.25 5.25 0 0110.233-2.33 3 3 0 013.758 3.848A3.752 3.752 0 0118 19.5H6.75z" />
</IconWrapper>
)
// Looks like a cloud with a plus sign in it
export const SaveAsIcon = (props) => (
<IconWrapper {...props}>
<path d="M2.25 15a4.5 4.5 0 004.5 4.5H18a3.75 3.75 0 001.332-7.257 3 3 0 00-3.758-3.848 5.25 5.25 0 00-10.233 2.33A4.502 4.502 0 002.25 15z M 11.5,10 v6 M 8.5,13 h 6" />
</IconWrapper>
)
// Looks like a small solid circle with a larger dashed circle around it
export const SaIcon = (props) => (
<IconWrapper {...props}>
<circle cx="12" cy="12" r="5" />
<circle cx="12" cy="12" r="10" strokeDasharray="2 4" />
</IconWrapper>
)
// Looks like lines of varying thickness
export const ScaleIcon = (props) => (
<IconWrapper {...props}>
<path d="M 2 20 h 20" strokeWidth={props.stroke / 2 || 1} />
<path d="M 2 12 h 20" />
<path d="M 2 4 h 20" strokeWidth={props.stroke * 2 || 4} />
</IconWrapper>
)
// Looks like a gear
export const SettingsIcon = (props) => (
<IconWrapper {...props}>
<path d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z" />
<path d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
</IconWrapper>
)
// Looks like a spinning circle
export const SpinnerIcon = (props) => (
<IconWrapper
{...props}
className={`${props.className ? props.className : 'h-6 w-6'} animate-spin`}
>
<circle
className="opacity-25"
cx="12"
cy="12"
r="10"
stroke="currentColor"
strokeWidth="4"
></circle>
<path
className="opacity-85"
fill="currentColor"
stroke="none"
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
></path>
</IconWrapper>
)
// Looks like a white checkbox, intended to be shown on a success-colored background
export const SuccessIcon = ({ size = 6 }) => (
<OkIcon className={`w-${size} h-${size} text-secondary-content`} stroke={4} />
)
// Looks like a light bulb
export const TipIcon = (props) => (
<IconWrapper {...props}>
<path d="M9.663 17h4.673M12 3v1m6.364 1.636l-.707.707M21 12h-1M4 12H3m3.343-5.657l-.707-.707m2.828 9.9a5 5 0 117.072 0l-.548.547A3.374 3.374 0 0014 18.469V19a2 2 0 11-4 0v-.531c0-.895-.356-1.754-.988-2.386l-.548-.547z" />
</IconWrapper>
)
// Looks like a trashcan
export const TrashIcon = (props) => (
<IconWrapper {...props} stroke={props.stroke || 2}>
<path d="M14.74 9l-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 01-2.244 2.077H8.084a2.25 2.25 0 01-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 00-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 013.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 00-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 00-7.5 0" />
</IconWrapper>
)
// Looks like a desktop screen
export const UiIcon = (props) => (
<IconWrapper {...props}>
<path d="M9 17.25v1.007a3 3 0 01-.879 2.122L7.5 21h9l-.621-.621A3 3 0 0115 18.257V17.25m6-12V15a2.25 2.25 0 01-2.25 2.25H5.25A2.25 2.25 0 013 15V5.25m18 0A2.25 2.25 0 0018.75 3H5.25A2.25 2.25 0 003 5.25m18 0V12a2.25 2.25 0 01-2.25 2.25H5.25A2.25 2.25 0 013 12V5.25" />
</IconWrapper>
)
// Looks like a rewind arrow, but takes text to go inside it
export const UndoIcon = (props) => (
<IconWrapper {...props}>
<path d="m 2.447878,2.716835 v 4.38448 h 4.38448 M 3.1307,6.882055 c 1.771085,-3.0536 5.075566,-5.10727 8.859706,-5.10727 5.65253,0 10.234811,4.58228 10.234811,10.23481 0,5.65253 -4.582281,10.23481 -10.234811,10.23481 -5.440329,0 -9.889258,-4.24469 -10.215624,-9.60291" />
{props.text ? (
<text
x="12"
y="17"
style={{
fontSize: '15px',
textAnchor: 'middle',
fontWeight: 500,
fill: 'currentColor',
stroke: 'none',
}}
>
{props.text}
</text>
) : null}
</IconWrapper>
)
// Looks like a bit of measuring tape
export const UnitsIcon = (props) => (
<IconWrapper {...props}>
<path d="m 1.5,4.5 h 21 v 15 h -21 z" />
<path
d="m 3.5,19.316406 v -3.708984 z m 2.1035156,0 v -3.708984 z m 2.1035156,0 v -3.708984 z m 2.1035157,0 v -3.708984 z m 4.3789061,0 v -3.708984 z m 2.103516,0 v -3.708984 z m 2.103515,0 v -3.708984 z m 2.103516,0 V 15.607422 Z M 12,19.130859 v -5.082031 z m 0,-8.986328 V 5.0625001 Z M 5.6035156,8.5859371 v -3.708984 z m 12.7929684,0 v -3.708984 z"
strokeWidth={props.stroke / 2 || 1.1}
/>
</IconWrapper>
)
// Looks like an up pointing chevron
export const UpIcon = (props) => (
<IconWrapper {...props}>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={props.stroke || 2}
d="M19 16l-7 -7-7 7"
/>
</IconWrapper>
)
// Looks like a cloud with an arrow pointing upwards in it
export const UploadIcon = (props) => (
<IconWrapper {...props}>
<path d="M12 16.5V9.75m0 0l3 3m-3-3l-3 3M6.75 19.5a4.5 4.5 0 01-1.41-8.775 5.25 5.25 0 0110.233-2.33 3 3 0 013.758 3.848A3.752 3.752 0 0118 19.5H6.75z" />
</IconWrapper>
)
// Looks like a person's face
export const UserIcon = (props) => (
<IconWrapper {...props}>
<path d="M5.121 17.804A13.937 13.937 0 0112 16c2.5 0 4.847.655 6.879 1.804M15 10a3 3 0 11-6 0 3 3 0 016 0zm6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
</IconWrapper>
)
// Looks like old-timey scales
export const UxIcon = (props) => (
<IconWrapper {...props}>
<path d="M12 3v17.25m0 0c-1.472 0-2.882.265-4.185.75M12 20.25c1.472 0 2.882.265 4.185.75M18.75 4.97A48.416 48.416 0 0012 4.5c-2.291 0-4.545.16-6.75.47m13.5 0c1.01.143 2.01.317 3 .52m-3-.52l2.62 10.726c.122.499-.106 1.028-.589 1.202a5.988 5.988 0 01-2.031.352 5.988 5.988 0 01-2.031-.352c-.483-.174-.711-.703-.59-1.202L18.75 4.971zm-16.5.52c.99-.203 1.99-.377 3-.52m0 0l2.62 10.726c.122.499-.106 1.028-.589 1.202a5.989 5.989 0 01-2.031.352 5.989 5.989 0 01-2.031-.352c-.483-.174-.711-.703-.59-1.202L5.25 4.971z" />
</IconWrapper>
)
// Looks like an ! in a triangle
export const WarningIcon = (props) => (
<IconWrapper {...props}>
<path d="M12 9v3.75m-9.303 3.376c-.866 1.5.217 3.374 1.948 3.374h14.71c1.73 0 2.813-1.874 1.948-3.374L13.949 3.378c-.866-1.5-3.032-1.5-3.898 0L2.697 16.126zM12 15.75h.007v.008H12v-.008z" />
</IconWrapper>
)
// Looks like a wrench
export const WrenchIcon = (props) => (
<IconWrapper {...props}>
<path d="M11.42 15.17L17.25 21A2.652 2.652 0 0021 17.25l-5.877-5.877M11.42 15.17l2.496-3.03c.317-.384.74-.626 1.208-.766M11.42 15.17l-4.655 5.653a2.548 2.548 0 11-3.586-3.586l6.837-5.63m5.108-.233c.55-.164 1.163-.188 1.743-.14a4.5 4.5 0 004.486-6.336l-3.276 3.277a3.004 3.004 0 01-2.25-2.25l3.276-3.276a4.5 4.5 0 00-6.336 4.486c.091 1.076-.071 2.264-.904 2.95l-.102.085m-1.745 1.437L5.909 7.5H4.5L2.25 3.75l1.5-1.5L7.5 4.5v1.409l4.26 4.26m-1.745 1.437l1.745-1.437m6.615 8.206L15.75 15.75M4.867 19.125h.008v.008h-.008v-.008z" />
</IconWrapper>
)
// Looks like a box in dashed lines
export const XrayIcon = (props) => (
<IconWrapper {...props}>
<path d="M14 10l-2 1m0 0l-2-1m2 1v2.5M20 7l-2 1m2-1l-2-1m2 1v2.5M14 4l-2-1-2 1M4 7l2-1M4 7l2 1M4 7v2.5M12 21l-2-1m2 1l2-1m-2 1v-2.5M6 18l-2-1v-2.5M18 18l2-1v-2.5" />
</IconWrapper>
)
// These icons all reuse existing icons
export const ViewDraftIcon = OptionsIcon
export const ViewMeasurementsIcon = MeasurementsIcon
export const ViewTestIcon = BeakerIcon
export const ViewTimingIcon = GaugeIcon
export const ViewPrintLayoutIcon = PrintIcon
export const ViewSaveIcon = SaveIcon
export const ViewExportIcon = ExportIcon
export const ViewEditSettingsIcon = EditIcon
export const ViewLogsIcon = ListIcon
export const ViewInspectIcon = XrayIcon
export const ViewDocsIcon = DocsIcon
export const ViewDesignsIcon = DesignIcon
export const ViewViewPickerIcon = UiIcon
export const ViewUndosIcon = BackIcon
// Flag icons
export const FlagNoteIcon = ChatIcon
export const FlagInfoIcon = DocsIcon
export const FlagTipIcon = TipIcon
export const FlagWarningIcon = WarningIcon
export const FlagErrorIcon = ErrorIcon
export const FlagFixmeIcon = WrenchIcon
export const FlagExpandIcon = ExpandIcon
export const FlagOtionsIcon = OptionsIcon

View file

@ -0,0 +1,695 @@
// Dependencies
import {
cloudflareImageUrl,
measurementAsMm,
measurementAsUnits,
distanceAsMm,
} from '@freesewing/utils'
import { collection } from '@freesewing/collection'
// Context
import { ModalContext } from '@freesewing/react/context/Modal'
import { LoadingStatusContext } from '@freesewing/react/context/LoadingStatus'
// Hooks
import React, { useState, useCallback, useContext } from 'react'
import { useDropzone } from 'react-dropzone'
import { useBackend } from '@freesewing/react/hooks/useBackend'
// Components
//import { Mdx } from 'shared/components/mdx/dynamic.mjs'
import { ResetIcon, DocsIcon, UploadIcon } from '@freesewing/react/components/Icon'
import { ModalWrapper } from '@freesewing/react/components/Modal'
import { isDegreeMeasurement } from '@freesewing/config'
import { Tabs, Tab } from '@freesewing/react/components/Tab'
export const ns = ['account', 'measurements', 'designs']
/*
* Helper component to display a tab heading
*/
export const _Tab = ({
id, // The tab ID
label, // A label for the tab, if not set we'll use the ID
activeTab, // Which tab (id) is active
setActiveTab, // Method to set the active tab
}) => (
<button
className={`text-lg font-bold capitalize tab tab-bordered grow
${activeTab === id ? 'tab-active' : ''}`}
onClick={() => setActiveTab(id)}
>
{label ? label : id}
</button>
)
/*
* Helper component to wrap a form control with a label
*/
export const FormControl = ({
label, // the (top-left) label
children, // Children to go inside the form control
docs = false, // Optional top-right label
labelBL = false, // Optional bottom-left label
labelBR = false, // Optional bottom-right label
forId = false, // ID of the for element we are wrapping
}) => {
const { setModal } = useContext(ModalContext)
if (labelBR && !labelBL) labelBL = <span></span>
const topLabelChildren = (
<>
<span className="daisy-label-text text-sm lg:text-base font-bold mb-1 text-inherit">
{label}
</span>
{docs ? (
<span className="daisy-label-text-alt">
<button
className="daisy-btn daisy-btn-ghost daisy-btn-sm daisy-btn-circle hover:daisy-btn-secondary"
onClick={() =>
setModal(
<ModalWrapper
flex="col"
justify="top lg:justify-center"
slideFrom="right"
keepOpenOnClick
>
<div className="markdown max-w-prose">{docs}</div>
</ModalWrapper>
)
}
>
<DocsIcon />
</button>
</span>
) : null}
</>
)
const bottomLabelChildren = (
<>
{labelBL ? <span className="daisy-label-text-alt">{labelBL}</span> : null}
{labelBR ? <span className="daisy-label-text-alt">{labelBR}</span> : null}
</>
)
return (
<div className="daisy-form-control w-full mt-2">
{forId ? (
<label className="daisy-label pb-0" htmlFor={forId}>
{topLabelChildren}
</label>
) : (
<div className="daisy-label pb-0">{topLabelChildren}</div>
)}
{children}
{labelBL || labelBR ? (
forId ? (
<label className="daisy-label" htmlFor={forId}>
{bottomLabelChildren}
</label>
) : (
<div className="daisy-label">{bottomLabelChildren}</div>
)
) : null}
</div>
)
}
/*
* Helper method to wrap content in a button
*/
export const ButtonFrame = ({
children, // Children of the button
onClick, // onClick handler
active, // Whether or not to render the button as active/selected
accordion = false, // Set this to true to not set a background color when active
dense = false, // Use less padding
}) => (
<button
className={`
daisy-btn daisy-btn-ghost daisy-btn-secondary
w-full ${dense ? 'mt-1 py-0 daisy-btn-sm' : 'mt-2 py-4 h-auto content-start'}
border-2 border-secondary text-left bg-opacity-20
${accordion ? 'hover:bg-transparent' : 'hover:bg-secondary hover:bg-opacity-10'}
hover:border-secondary hover:border-solid hover:border-2
${active ? 'border-solid' : 'border-dotted'}
${active && !accordion ? 'bg-secondary' : 'bg-transparent'}
`}
onClick={onClick}
>
{children}
</button>
)
/*
* Input for integers
*/
export const NumberInput = ({
label, // Label to use
update, // onChange handler
valid, // Method that should return whether the value is valid or not
current, // The current value
original, // The original value
placeholder, // The placeholder text
docs = false, // Docs to load, if any
id = '', // An id to tie the input to the label
labelBL = false, // Bottom-Left label
labelBR = false, // Bottom-Right label
max = 0,
min = 220,
step = 1,
}) => (
<FormControl {...{ label, labelBL, labelBR, docs }} forId={id}>
<input
id={id}
type="text"
inputMode="decimal"
placeholder={placeholder}
value={current}
onChange={(evt) => update(evt.target.value)}
className={`daisy-input w-full daisy-input-bordered ${
current === original ? 'input-secondary' : valid(current) ? 'input-success' : 'input-error'
}`}
{...{ max, min, step }}
/>
</FormControl>
)
/*
* Input for strings
*/
export const StringInput = ({
label, // Label to use
update, // onChange handler
valid, // Method that should return whether the value is valid or not
current, // The current value
original, // The original value
placeholder, // The placeholder text
docs = false, // Docs to load, if any
id = '', // An id to tie the input to the label
labelBL = false, // Bottom-Left label
labelBR = false, // Bottom-Right label
}) => (
<FormControl {...{ label, labelBL, labelBR, docs }} forId={id}>
<input
id={id}
type="text"
placeholder={placeholder}
value={current}
onChange={(evt) => update(evt.target.value)}
className={`daisy-input w-full daisy-input-bordered ${
current === original
? 'daisy-input-secondary'
: valid(current)
? 'daisy-input-success'
: 'daisy-input-error'
}`}
/>
</FormControl>
)
/*
* Input for MFA code
*/
export const MfaInput = ({
update, // onChange handler
current, // The current value
id = 'mfa', // An id to tie the input to the label
}) => {
return (
<StringInput
label="MFA Code"
valid={(val) => val.length > 4}
{...{ update, current, id }}
placeholder="MFA Code"
docs={false}
/>
)
}
/*
* Input for passwords
*/
export const PasswordInput = ({
label, // Label to use
update, // onChange handler
valid, // Method that should return whether the value is valid or not
current, // The current value
placeholder = '¯\\_(ツ)_/¯', // The placeholder text
docs = false, // Docs to load, if any
id = '', // An id to tie the input to the label
onKeyDown = false, // Optionall capture certain keys (like enter)
}) => {
const [reveal, setReveal] = useState(false)
const extraProps = onKeyDown ? { onKeyDown } : {}
return (
<FormControl
label={label}
docs={docs}
forId={id}
labelBR={
<button
className="btn btn-primary btn-ghost btn-xs -mt-2"
onClick={() => setReveal(!reveal)}
>
{reveal ? 'Hide Password' : 'Reveal Password'}
</button>
}
>
<input
id={id}
type={reveal ? 'text' : 'password'}
placeholder={placeholder}
value={current}
onChange={(evt) => update(evt.target.value)}
className={`daisy-input w-full daisy-input-bordered ${
valid(current) ? 'input-success' : 'input-error'
}`}
{...extraProps}
/>
</FormControl>
)
}
/*
* Input for email addresses
*/
export const EmailInput = ({
label, // Label to use
update, // onChange handler
valid, // Method that should return whether the value is valid or not
current, // The current value
original, // The original value
placeholder, // The placeholder text
docs = false, // Docs to load, if any
id = '', // An id to tie the input to the label
labelBL = false, // Bottom-Left label
labelBR = false, // Bottom-Right label
}) => (
<FormControl {...{ label, docs, labelBL, labelBR }} forId={id}>
<input
id={id}
type="email"
placeholder={placeholder}
value={current}
onChange={(evt) => update(evt.target.value)}
className={`input w-full input-bordered ${
current === original ? 'input-secondary' : valid(current) ? 'input-success' : 'input-error'
}`}
/>
</FormControl>
)
/*
* Dropdown for designs
*/
export const DesignDropdown = ({
label, // Label to use
update, // onChange handler
current, // The current value
docs = false, // Docs to load, if any
firstOption = null, // Any first option to add in addition to designs
id = '', // An id to tie the input to the label
}) => {
return (
<FormControl label={label} docs={docs} forId={id}>
<select
id={id}
className="select select-bordered w-full"
onChange={(evt) => update(evt.target.value)}
value={current}
>
{firstOption}
{collection.map((design) => (
<option key={design} value={design}>
{design}
</option>
))}
</select>
</FormControl>
)
}
/*
* Input for an image
*/
export const ImageInput = ({
label, // The label
update, // The onChange handler
current, // The current value
original, // The original value
docs = false, // Docs to load, if any
active = false, // Whether or not to upload images
imgType = 'showcase', // The image type
imgSubid, // The image sub-id
imgSlug, // The image slug or other unique identifier to use in the image ID
id = '', // An id to tie the input to the label
}) => {
const backend = useBackend()
const { setLoadingStatus } = useContext(LoadingStatusContext)
const [url, setUrl] = useState(false)
const [uploadedId, setUploadedId] = useState(false)
const upload = async (img, fromUrl = false) => {
setLoadingStatus([true, 'uploadingImage'])
const data = {
type: imgType,
subId: imgSubid,
slug: imgSlug,
}
if (fromUrl) data.url = img
else data.img = img
const result = await backend.uploadAnonImage(data)
setLoadingStatus([true, 'allDone', true, true])
if (result.success) {
update(result.data.imgId)
setUploadedId(result.data.imgId)
} else setLoadingStatus([true, 'backendError', true, false])
}
const onDrop = useCallback(
(acceptedFiles) => {
const reader = new FileReader()
reader.onload = async () => {
if (active) upload(reader.result)
else update(reader.result)
}
acceptedFiles.forEach((file) => reader.readAsDataURL(file))
},
[current]
)
const { getRootProps, getInputProps } = useDropzone({ onDrop })
if (current)
return (
<FormControl label={label} docs={docs}>
<div
className="bg-base-100 w-full h-36 mb-2 mx-auto flex flex-col items-center text-center justify-center"
style={{
backgroundImage: `url(${
uploadedId ? cloudflareImageUrl({ type: 'public', id: uploadedId }) : current
})`,
backgroundSize: 'contain',
backgroundRepeat: 'no-repeat',
backgroundPosition: '50%',
}}
>
<button
className="btn btn-neutral btn-circle opacity-50 hover:opacity-100"
onClick={() => update(original)}
>
<ResetIcon />
</button>
</div>
</FormControl>
)
return (
<FormControl label={label} docs={docs} forId={id}>
<div
{...getRootProps()}
className={`
flex rounded-lg w-full flex-col items-center justify-center
lg:p-6 lg:border-4 lg:border-secondary lg:border-dashed
`}
>
<input {...getInputProps()} />
<p className="hidden lg:block p-0 m-0">Drag and drop and image here</p>
<p className="hidden lg:block p-0 my-2">or</p>
<button className={`btn btn-secondary btn-outline mt-4 px-8`}>
Select an image to use
</button>
</div>
<p className="p-0 my-2 text-center">or</p>
<div className="flex flex-row items-center">
<input
id={id}
type="url"
className="input input-secondary w-full input-bordered"
placeholder="Paste an image URL here"
value={current}
onChange={active ? (evt) => setUrl(evt.target.value) : (evt) => update(evt.target.value)}
/>
{active && (
<button
className="btn btn-secondary ml-2 capitalize"
disabled={!url || url.length < 1}
onClick={() => upload(url, true)}
>
<UploadIcon /> Upload
</button>
)}
</div>
</FormControl>
)
}
/*
* Input for an image that is active (it does upload the image)
*/
export const ActiveImageInput = (props) => <ImageInput {...props} active={true} />
/*
* Input for an image that is passive (it does not upload the image)
*/
export const PassiveImageInput = (props) => <ImageInput {...props} active={false} />
/*
* Input for a list of things to pick from
*/
export const ListInput = ({
update, // the onChange handler
label, // The label
list, // The list of items to present { val, label, desc }
current, // The (value of the) current item
docs = false, // Docs to load, if any
}) => (
<FormControl label={label} docs={docs}>
{list.map((item, i) => (
<ButtonFrame key={i} active={item.val === current} onClick={() => update(item.val)}>
<div className="w-full flex flex-col gap-2">
<div className="w-full text-lg leading-5">{item.label}</div>
{item.desc ? (
<div className="w-full text-normal font-normal normal-case pt-1 leading-5">
{item.desc}
</div>
) : null}
</div>
</ButtonFrame>
))}
</FormControl>
)
/*
* Input for markdown content
*/
export const MarkdownInput = ({
label, // The label
current, // The current value (markdown)
update, // The onChange handler
placeholder, // The placeholder content
docs = false, // Docs to load, if any
id = '', // An id to tie the input to the label
labelBL = false, // Bottom-Left label
labelBR = false, // Bottom-Right label
}) => (
<FormControl {...{ label, labelBL, labelBR, docs }} forId={id}>
<Tabs tabs={['edit', 'preview']}>
<Tab key="edit">
<div className="flex flex-row items-center mt-4">
<textarea
id={id}
rows="5"
className="textarea textarea-bordered textarea-lg w-full"
value={current}
placeholder={placeholder}
onChange={(evt) => update(evt.target.value)}
/>
</div>
</Tab>
<Tab key="preview">
<div className="flex flex-row items-center mt-4">
<Mdx md={current} />
</div>
</Tab>
</Tabs>
</FormControl>
)
export const MeasieInput = ({
imperial, // True for imperial, False for metric
m, // The measurement name
original, // The original value
update, // The onChange handler
placeholder, // The placeholder content
docs = false, // Docs to load, if any
id = '', // An id to tie the input to the label
}) => {
const isDegree = isDegreeMeasurement(m)
const units = imperial ? 'imperial' : 'metric'
const [localVal, setLocalVal] = useState(
typeof original === 'undefined'
? original
: isDegree
? Number(original)
: measurementAsUnits(original, units)
)
const [validatedVal, setValidatedVal] = useState(measurementAsUnits(original, units))
const [valid, setValid] = useState(null)
// Update onChange
const localUpdate = (newVal) => {
setLocalVal(newVal)
const parsedVal = isDegree ? Number(newVal) : parseDistanceInput(newVal, imperial)
if (parsedVal) {
update(m, isDegree ? parsedVal : measurementAsMm(parsedVal, units))
setValid(true)
setValidatedVal(parsedVal)
} else setValid(false)
}
if (!m) return null
// Various visual indicators for validating the input
let inputClasses = 'input-secondary'
let bottomLeftLabel = null
if (valid === true) {
inputClasses = 'input-success'
const val = `${validatedVal}${isDegree ? '°' : imperial ? '"' : 'cm'}`
bottomLeftLabel = <span className="font-medium text-base text-success -mt-2 block">{val}</span>
} else if (valid === false) {
inputClasses = 'input-error'
bottomLeftLabel = (
<span className="font-medium text-error text-base -mt-2 block">¯\_()_/¯</span>
)
}
/*
* I'm on the fence here about using a text input rather than number
* Obviously, number is the more correct option, but when the user enter
* text, it won't fire an onChange event and thus they can enter text and it
* will not be marked as invalid input.
* See: https://github.com/facebook/react/issues/16554
*/
return (
<FormControl
label={t(m) + (isDegree ? ' (°)' : '')}
docs={docs}
forId={id}
labelBL={bottomLeftLabel}
>
<input
id={id}
type="text"
inputMode="numeric"
pattern="[0-9]*"
placeholder={placeholder}
value={localVal}
onChange={(evt) => localUpdate(evt.target.value)}
className={`input w-full input-bordered ${inputClasses}`}
/>
</FormControl>
)
}
export const FileInput = ({
label, // The label
valid = () => true, // Method that should return whether the value is valid or not
update, // The onChange handler
current, // The current value
original, // The original value
id = '', // An id to tie the input to the label
dropzoneConfig = {}, // Configuration for react-dropzone
}) => {
/*
* Ondrop handler
*/
const onDrop = useCallback(
(acceptedFiles) => {
const reader = new FileReader()
reader.onload = async () => update(reader.result)
acceptedFiles.forEach((file) => reader.readAsDataURL(file))
},
[update]
)
/*
* Dropzone hook
*/
const { getRootProps, getInputProps } = useDropzone({ onDrop, ...dropzoneConfig })
/*
* If we have a current file, return this
*/
if (current)
return (
<FormControl label={label} isValid={valid(current)}>
<div className="bg-base-100 w-full h-36 mb-2 mx-auto flex flex-col items-center text-center justify-center">
<button
className="btn btn-neutral btn-circle opacity-50 hover:opacity-100"
onClick={() => update(original)}
>
<ResetIcon />
</button>
</div>
</FormControl>
)
/*
* Return upload form
*/
return (
<FormControl label={label} forId={id} isValid={valid(current)}>
<div
{...getRootProps()}
className={`
flex rounded-lg w-full flex-col items-center justify-center
sm:p-6 sm:border-4 sm:border-secondary sm:border-dashed
`}
>
<input {...getInputProps()} />
<p className="hidden lg:block p-0 m-0">Drag and drop your file here</p>
<button className={`btn btn-secondary btn-outline mt-4 px-8`}>Browse...</button>
</div>
</FormControl>
)
}
/*
* Input for booleans
*/
export const ToggleInput = ({
label, // Label to use
update, // onChange handler
current, // The current value
disabled = false, // Allows rendering a disabled view
list = [true, false], // The values to chose between
labels = ['Yes', 'No'], // The labels for the values
on = true, // The value that should show the toggle in the 'on' state
id = '', // An id to tie the input to the label
labelTR = false, // Top-Right label
labelBL = false, // Bottom-Left label
labelBR = false, // Bottom-Right label
}) => (
<FormControl
{...{ labelBL, labelBR, labelTR }}
label={
label
? `${label} (${current === on ? labels[0] : labels[1]})`
: `${current === on ? labels[0] : labels[1]}`
}
forId={id}
>
<input
id={id}
disabled={disabled}
type="checkbox"
value={current}
onChange={() => update(list.indexOf(current) === 0 ? list[1] : list[0])}
className="toggle my-3 toggle-primary"
checked={list.indexOf(current) === 0 ? true : false}
/>
</FormControl>
)

View file

@ -0,0 +1,98 @@
import React from 'react'
import { Breadcrumbs } from '@freesewing/react/components/Breadcrumbs'
import { Link as DefaultLink } from '@freesewing/react/components/Link'
/*
* This is the default layout, including title and breadcrumbs
*
* @param {object} props - All React props
* @param {array} props.children - The content to go in the layout
* @param {array} props.crumbs - Data for the breadcrumbs
* @param {string} props.description - The page description
* @param {function} props.Link - An optional framework specific Link component
* @param {string} props.title - The page title
*/
export const Layout = ({ children = [], crumbs = [], description, Link = false, title }) => {
if (!Link) Link = DefaultLink
return (
<BaseLayout>
<div className="max-w-xl w-full mx-auto">
<Breadcrumbs {...{ crumbs, title, Link }} />
<h1 className="break-words">{title}</h1>
<div className="xl:pl-4">{children}</div>
</div>
</BaseLayout>
)
}
/*
* This is the base layout
*
* @param {object} props - All React props
* @param {array} props.children - The content to go in the layout
*/
export const BaseLayout = ({ children }) => (
<div className="flex flex-row items-start w-full justify-between 2xl:px-36 xl:px-12 px-4 gap-0 lg:gap-4 xl:gap-8 3xl: gap-12">
{children}
</div>
)
/*
* The left column of the base layout
*
* @param {object} props - All React props
* @param {array} props.children - The content to go in the layout
*/
export const BaseLayoutLeft = ({ children = [] }) => (
<div className="max-w-96 w-1/4 hidden lg:block shrink-0 my-8 sticky top-4 max-h-screen overflow-scroll">
{children}
</div>
)
/*
* The right column of the base layout
*
* @param {object} props - All React props
* @param {array} props.children - The content to go in the layout
*/
export const BaseLayoutRight = ({ children = [] }) => (
<div className="max-w-96 w-1/4 hidden xl:block my-8 sticky top-2">{children}</div>
)
/*
* The main column for prose (text like docs and so on)
*
* @param {object} props - All React props
* @param {array} props.children - The content to go in the layout
* @param {array} props.wide - Whether or not to use the wide view
*/
export const BaseLayoutProse = ({ children = [], wide = false }) => (
<div className={`grow w-full m-auto max-w-${wide ? 'full' : 'prose'} my-8`}>{children}</div>
)
/*
* The central column for wide content (no max-width)
*
* @param {object} props - All React props
* @param {array} props.children - The content to go in the layout
*/
export const BaseLayoutWide = ({ children = [] }) => (
<div className="grow w-full m-auto my-8 grow">{children}</div>
)
/*
* A layout for pages that do their own title/layout, like the sign in page
*
* @param {object} props - All React props
* @param {array} props.children - The content to go in the layout
*/
export const NoTitleLayout = ({ children }) => {
return (
<BaseLayout>
<div className="max-w-xl w-full mx-auto">
<div className="xl:pl-4">{children}</div>
</div>
</BaseLayout>
)
}

View file

@ -0,0 +1,36 @@
import React from 'react'
/*
* These classes are what makes a link a link
*/
export const linkClasses =
'underline decoration-2 hover:decoration-4 text-secondary hover:text-secondary-focus'
/**
* An anchor link component
*
* @param {object} props - All React props
* @param {array} props.children - The content to go in the layout
* @param {array} props.id - The ID of the anchor to link to
* @param {array} props.title - An optional link title
*/
export const AnchorLink = ({ children, id = '', title = false }) => (
<a href={`#${id}`} className={linkClasses} title={title ? title : ''}>
{children}
</a>
)
/**
* A regular link component
*
* @param {object} props - All React props
* @param {array} props.children - The content to go in the layout
* @param {array} props.href - The target to link to
* @param {array} props.title - An optional link title
* @param {string} props.className - Any non-default CSS classes to apply
*/
export const Link = ({ href, title = false, children, className = linkClasses }) => (
<a href={href} className={className} title={title ? title : ''}>
{children}
</a>
)

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,95 @@
import React, { useState, useEffect, useContext } from 'react'
import { ModalContext } from '@freesewing/react/context/Modal'
import { CloseIcon } from '@freesewing/react/components/Icon'
const slideClasses = {
left: '-translate-x-full',
right: 'translate-x-full',
top: '-translate-y-full',
bottom: 'translate-y-full',
}
/**
* This component wraps modal content, making sure the layout is ok and handling transitions
*
* @param {object} props - All React props
* @param {array} children - Content to render inside the modal
* @param {string} flex - Flexbox direction (row or col)
* @param {string} justify - Flexbox justify value
* @param {string} items - Flexbox items value
* @param {string} bg - Background
* @param {string} bgOpacity - Background opacity
* @param {bool} bare - Set true to not handle layout
* @param {bool} keepOpenOnClick - Set to true to prevent a click in the modal content to close the modal
* @param {string} slideFrom - Direction to slide in from
* @param {bool} fullWidth - Set to true to not constrain the width
*/
export const ModalWrapper = ({
children = null,
flex = 'row',
justify = 'center',
items = 'center',
bg = 'base-100 lg:bg-base-300',
bgOpacity = '100 lg:bg-opacity-95',
bare = false,
keepOpenOnClick = false,
slideFrom = 'left',
fullWidth = false,
}) => {
const { clearModal } = useContext(ModalContext)
const [animate, setAnimate] = useState('in')
const close = (evt) => {
// Only process the first event
if (evt?.event) evt.event.stopPropagation()
setAnimate('out')
window.setTimeout(clearModal, 150)
}
useEffect(() => {
// only turn off animation if it's animating in
if (animate === 'in') setAnimate(false)
}, [animate])
// CSS classes for animation
const animation = animate
? `lg:opacity-0 ${slideClasses[slideFrom]} lg:translate-x-0 lg:translate-y-0`
: 'opacity-100 translate-none'
const stopClick = (evt) => {
/*
* Do not keep modal open for links (with a href)
* but do keep it open for buttons (like a new modal context)
*/
if (!evt.target.attributes.href) evt.stopPropagation()
}
return (
<div
className={`fixed top-0 left-0 m-0 p-0 shadow w-full h-screen
transform-all duration-150 ${animation}
bg-${bg} bg-opacity-${bgOpacity} z-50 hover:cursor-pointer
flex flex-${flex} justify-${justify} items-${items} lg:p-12`}
onClick={close}
>
{bare ? (
children
) : (
<div
onClick={keepOpenOnClick ? stopClick : null}
className={`z-30 bg-base-100 p-4 lg:px-8 lg:rounded-lg lg:shadow-lg max-h-full overflow-auto hover:cursor-default ${
fullWidth ? 'w-full' : ''
}`}
>
{children}
<button
className="fixed bottom-2 right-2 btn btn-neutral btn-circle lg:hidden"
onClick={close}
>
<CloseIcon className="w-8 h-8" />
</button>
</div>
)}
</div>
)
}

View file

@ -0,0 +1,300 @@
// Utils
import { horFlexClasses, horFlexClassesNoSm, capitalize } from '@freesewing/utils'
// Context
import { LoadingStatusContext } from '@freesewing/react/context/LoadingStatus'
// Hooks
import React, { useState, useEffect, useContext } from 'react'
import { useAccount } from '@freesewing/react/hooks/useAccount'
import { useBackend } from '@freesewing/react/hooks/useBackend'
// Components
import { Link } from '@freesewing/react/components/Link'
import {
EmailIcon,
KeyIcon,
LockIcon,
WarningIcon,
GoogleIcon,
GitHubIcon,
FreeSewingIcon,
UserIcon,
} from '@freesewing/react/components/Icon'
import { MfaInput, StringInput, PasswordInput } from '@freesewing/react/components/Input'
/*
* This SignIn component holds the entire sign-in form
*
* @param {object} props - All React props
* @param {function} props.onSuccess - A method to run when the sign in is successful
*/
export const SignIn = ({ onSuccess = false }) => {
const { setAccount, setToken, seenUser, setSeenUser } = useAccount()
const backend = useBackend()
const { setLoadingStatus } = useContext(LoadingStatusContext)
const [username, setUsername] = useState('')
const [password, setPassword] = useState('')
const [magicLink, setMagicLink] = useState(true)
const [signInFailed, setSignInFailed] = useState(false)
const [magicLinkSent, setMagicLinkSent] = useState(false)
const [seenBefore, setSeenBefore] = useState(false)
const [mfa, setMfa] = useState(false)
const [mfaCode, setMfaCode] = useState('')
useEffect(() => {
if (typeof window !== 'undefined' && signInFailed) {
window.setTimeout(() => setSignInFailed(false), 1750)
}
}, [signInFailed])
// Avoid SSR rendering mismatch by setting this in effect
useEffect(() => {
if (seenUser) {
setSeenBefore(seenUser)
setUsername(seenUser)
} else {
setSeenBefore(false)
setUsername('')
}
}, [seenUser])
const triggerSubmit = (evt) => {
if (evt.key === 'Enter') signinHandler(evt)
}
const signinHandler = async (evt) => {
evt.preventDefault()
setLoadingStatus([true, 'Contacting FreeSewing backend'])
console.log({ magicLink })
const result = magicLink
? await backend.signIn({ username, password: false })
: await backend.signIn({ username, password, token: mfaCode })
const [status, body] = Array.isArray(result) ? result : [false, false]
if (!status) {
setSignInFailed('Unexpected error when attempting sign-in')
return setLoadingStatus([
true,
'Unexpected error when attempting sign-in. Please report this.',
true,
false,
])
}
// Sign-in succeeded
if (status === 200) {
if (magicLink) {
setLoadingStatus([true, 'Email sent', true, true])
setMagicLinkSent(true)
} else {
setAccount(body.account)
setToken(body.token)
setSeenUser(body.account.username)
setLoadingStatus([true, `Welcome back ${body.account.username}`, true, true])
// Call the onSuccess handler
onSuccess(body)
}
}
// Sign-in failed
if (status === 401) {
const msg = magicLink ? 'Unable to find this user' : 'Sign-In failed'
setSignInFailed(msg)
setLoadingStatus([true, msg, true, false])
}
// Bad request
if (status === 400) {
let msg
if (result.data.error === 'usernameMissing') msg = 'Please provide your username'
else if (result.data.error === 'passwordMissing') msg = 'Please provide your password'
setSignInFailed(msg)
setLoadingStatus([true, msg, true, false])
}
// MFA active
if (status === 403 && body.error === 'mfaTokenRequired') {
setMfa(true)
setLoadingStatus([
true,
'Please provide a one-time MFA code, or a backup scratch code',
true,
true,
])
}
}
const initOauth = async (provider) => {
setLoadingStatus([true, 'Contacting the FreeSewing backend'])
const result = await backend.oauthInit({ provider, language: 'en' })
if (result.success) {
setLoadingStatus([true, `Contacting ${capitalize(provider)}`])
window.location.href = result.data.authUrl
}
}
const btnClasses = `daisy-btn capitalize w-full mt-4 ${
signInFailed ? 'daisy-btn-warning' : 'daisy-btn-primary'
} transition-colors ease-in-out duration-300 ${horFlexClasses}`
const noBueno = (
<>
<WarningIcon />
<span className="pl-2">{signInFailed}</span>
<WarningIcon />
</>
)
if (magicLinkSent)
return (
<>
<h1 className="text-inherit text-3xl lg:text-5xl mb-4 pb-0 text-center">Email Sent</h1>
<p className="text-inherit text-lg text-center">
Go check your inbox for an email from <b>FreeSewing.org</b>
</p>
<p className="text-inherit text-lg text-center">
Click the sign-in link in that email to sign in to your FreeSewing account.
</p>
<div className="flex flex-row gap-4 items-center justify-center p-8">
<button className="daisy-btn daisy-btn-ghost" onClick={() => setMagicLinkSent(false)}>
Back
</button>
<Link href="/support" className="daisy-btn daisy-btn-ghost">
Contact support
</Link>
</div>
</>
)
if (mfa)
return (
<>
<h1 className="text-inherit text-3xl lg:text-5xl mb-4 pb-0 text-center">MFA Code</h1>
<p className="text-inherit text-lg text-center">
Please provide a one-time MFA code, or a backup scratch code
</p>
<MfaInput
label="Please provide a one-time MFA code, or a backup scratch code"
update={setMfaCode}
value={mfaCode}
/>
<button className={btnClasses} tabIndex="-1" role="button" onClick={signinHandler}>
{signInFailed ? (
noBueno
) : (
<>
<span className="hidden lg:block">
<KeyIcon />
</span>
<span className="pl-2">Sign In</span>
<span className="hidden lg:block">
<LockIcon />
</span>
</>
)}
</button>
<div className="flex flex-row gap-4 items-center justify-center p-8">
<button className="daisy-btn daisy-btn-ghost" onClick={() => setMfa(false)}>
Back
</button>
<Link href="/support" className="daisy-btn daisy-btn-ghost">
Contact support
</Link>
</div>
</>
)
return (
<div className="tailwind-container">
<h1>{seenBefore ? `Welcome back ${seenUser}` : 'Welcome'}</h1>
<h3>Sign in to FreeSewing</h3>
{!seenBefore && (
<StringInput
label="Your Email address, Username, or User #"
update={setUsername}
placeholder="Your Email address, Username, or User #"
value={username}
valid={(val) => val.length > 1}
/>
)}
{magicLink ? (
<button
className={`${btnClasses} daisy-btn-lg`}
tabIndex="-1"
role="button"
onClick={signinHandler}
>
{signInFailed ? (
noBueno
) : (
<>
<span className="hidden lg:block">
<EmailIcon />
</span>
<span className="pl-2">Email me a sign-in link</span>
<span className="hidden lg:block">
<EmailIcon />
</span>
</>
)}
</button>
) : (
<>
<PasswordInput
label="Your Password"
update={setPassword}
current={password}
valid={(val) => val.length > 0}
onKeyDown={triggerSubmit}
/>
<button className={btnClasses} tabIndex="-1" role="button" onClick={signinHandler}>
{signInFailed ? (
noBueno
) : (
<>
<span className="hidden lg:block">
<KeyIcon />
</span>
<span className="pl-2">Sign in</span>
<span className="hidden lg:block">
<LockIcon />
</span>
</>
)}
</button>
</>
)}
<button
className={`block md:flex md:flex-row md:justify-between md:items-center daisy-btn daisy-btn-primary daisy-btn-outline w-full mt-8`}
onClick={() => setMagicLink(!magicLink)}
>
<span className="hidden lg:block">{magicLink ? <LockIcon /> : <EmailIcon />}</span>
{magicLink ? 'Use your password' : 'Email me a sign-in link'}
<span className="hidden lg:block">{magicLink ? <KeyIcon /> : <EmailIcon />}</span>
</button>
<div className="grid grid-cols-1 md:grid-cols-2 gap-2 items-center mt-2">
{['Google', 'Github'].map((provider) => (
<button
key={provider}
id={provider}
className={`${horFlexClasses} daisy-btn daisy-btn-secondary`}
onClick={() => initOauth(provider)}
>
{provider === 'Google' ? <GoogleIcon stroke={0} /> : <GitHubIcon />}
<span>Sign in with {provider}</span>
</button>
))}
</div>
{seenBefore ? (
<button
className={`${horFlexClassesNoSm} daisy-btn daisy-btn-neutral daisy-btn-outline mt-2 w-full`}
onClick={() => setSeenUser(false)}
>
<UserIcon />
Sign in as a different user
</button>
) : (
<Link
className={`${horFlexClasses} daisy-btn daisy-btn-lg daisy-btn-neutral mt-2`}
href="/signup"
>
<FreeSewingIcon className="h-10 w-10" />
Sign up here
</Link>
)}
</div>
)
}

View file

@ -0,0 +1,27 @@
import React from 'react'
/*
* A simple spinner
*/
export const Spinner = ({ className = 'h-6 w-6' }) => (
<svg
className={`animate-spin ${className}`}
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
>
<circle
className="opacity-25"
cx="12"
cy="12"
r="10"
stroke="currentColor"
strokeWidth="4"
></circle>
<path
className="opacity-75"
fill="currentColor"
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
></path>
</svg>
)

View file

@ -0,0 +1,80 @@
import React, { useState, useContext } from 'react'
import { ModalContext } from '@freesewing/react/context/Modal'
import { ModalWrapper } from '@freesewing/react/components/Modal'
import { KioskIcon } from '@freesewing/react/components/Icon'
/**
* FreeSewing Tabs component, typically used for dev examples
*
* @param {string} tabs - The list of tabs
* @param {number} active - The nr of the active tab
* @param {array} children - Content to render within the tabs
* @param {bool} withModal - Set to true to load tab content in a modal window when kiosk icon is clicked
*/
export const Tabs = ({ tabs = '', active = 0, children, withModal = false }) => {
const { setModal } = useContext(ModalContext)
// Keep active tab in state
const [activeTab, setActiveTab] = useState(active)
/*
* Parse tab list
* Comma-seperated tabs passed as a string are how it works in MDX
*/
const tablist = Array.isArray(tabs) ? tabs : tabs.split(',').map((tab) => tab.trim())
if (!tablist) return null
// Pass down activeTab and tabId for conditional rendering
const childrenWithTabSetter = children.map((child, tabId) =>
React.cloneElement(child, { activeTab, tabId, key: tabId })
)
return (
<div className="my-4">
<div className="tabs">
{tablist.map((title, tabId) => {
const btnClasses = `text-lg font-bold capitalize tab h-auto tab-bordered grow py-2 ${
activeTab === tabId ? 'tab-active' : ''
}`
return withModal && activeTab === tabId ? (
<button
key={tabId}
className={btnClasses}
onClick={() =>
setModal(
<ModalWrapper
flex="col"
justify="top lg:justify-center"
slideFrom="right"
fullWidth
>
{childrenWithTabSetter}
</ModalWrapper>
)
}
>
<span className="pr-2">{title}</span>
<KioskIcon className="w-6 h-6 hover:text-secondary" />
</button>
) : (
<button key={tabId} className={btnClasses} onClick={() => setActiveTab(tabId)}>
{title}
</button>
)
})}
</div>
<div>{childrenWithTabSetter}</div>
</div>
)
}
/**
* FreeSewing Tab component, use it together with Tabs
*
* @param {number} tabId - The ID of this tab
* @param {number} activeTab - The ID of the active tab
* @param {array} children - Content to render within the tab
*/
export const Tab = ({ children, tabId, activeTab }) => (activeTab === tabId ? children : null)

View file

@ -102,35 +102,33 @@ export const defaultControlLevel = 3
* Editor control levels
*/
export const editor = {
control: {
core: {
sa: 2,
paperless: 2,
locale: 3,
units: 1,
complete: 4,
expand: 4,
only: 4,
scale: 4,
margin: 4,
},
ui: {
renderer: 4,
kiosk: 2,
},
views: {
draft: 1,
measies: 1,
test: 3,
time: 3,
print: 1,
export: 1,
save: 1,
edit: 4,
logs: 2,
inspect: 4,
docs: 1,
},
core: {
sa: 2,
paperless: 2,
locale: 3,
units: 1,
complete: 4,
expand: 4,
only: 4,
scale: 4,
margin: 4,
},
ui: {
renderer: 4,
kiosk: 2,
},
views: {
draft: 1,
measies: 1,
test: 3,
time: 3,
print: 1,
export: 1,
save: 1,
edit: 4,
logs: 2,
inspect: 4,
docs: 1,
},
}
@ -163,8 +161,7 @@ export const controlLevels = {
...account.fields.security,
...account.fields.identities,
sets: account.sets,
patterns: editor.account.patterns,
core: editor.control.core,
ui: editor.control.ui,
views: editor.control.views,
core: editor.core,
ui: editor.ui,
views: editor.views,
}

View file

@ -1,14 +1,10 @@
// __SDEFILE__ - This file is a dependency for the stand-alone environment
/* eslint-disable */
// Not sure why but eslint does not seem to understand this file
// and I don't have time to hold its hand.
import { useState, useEffect, createContext } from 'react'
import { Spinner } from 'shared/components/spinner.mjs'
import { OkIcon, WarningIcon } from 'shared/components/icons.mjs'
import { useTranslation } from 'next-i18next'
export const ns = ['status']
import React, { useState, useEffect, createContext } from 'react'
import { Spinner } from '@freesewing/react/components/Spinner'
import { OkIcon, WarningIcon } from '@freesewing/react/components/Icon'
/*
* The actual context
*/
export const LoadingStatusContext = createContext([false])
/*
@ -16,9 +12,10 @@ export const LoadingStatusContext = createContext([false])
*/
const timeout = 2
/*
* The React component displaying the loading status
*/
const LoadingStatus = ({ loadingStatus }) => {
const { t } = useTranslation(ns)
const [fade, setFade] = useState('opacity-100')
const [timer, setTimer] = useState(false)
@ -26,9 +23,12 @@ const LoadingStatus = ({ loadingStatus }) => {
if (loadingStatus[2]) {
if (timer) clearTimeout(timer)
setTimer(
window.setTimeout(() => {
setFade('opacity-0')
}, timeout * 1000 - 350)
window.setTimeout(
() => {
setFade('opacity-0')
},
timeout * 1000 - 350
)
)
}
}, [loadingStatus[2]])
@ -54,14 +54,15 @@ const LoadingStatus = ({ loadingStatus }) => {
md:rounded-lg shadow text-secondary-content text-lg lg:text-xl font-medium md:bg-opacity-90`}
>
<span className="shrink-0">{icon}</span>
{typeof loadingStatus[1] === 'object' && loadingStatus[1].props
? loadingStatus[1]
: t(loadingStatus[1])}
{loadingStatus[1]}
</div>
</div>
)
}
/*
* An animated loading state
*/
const LoadingProgress = ({ val = 0, max = 1, msg }) => (
<div className="flex flex-col gap-2 w-full grow-0">
{msg}
@ -69,6 +70,9 @@ const LoadingProgress = ({ val = 0, max = 1, msg }) => (
</div>
)
/*
* The Context provider
*/
export const LoadingStatusContextProvider = ({ children }) => {
/*
* LoadingStatus should hold an array with 1 to 4 elements:

View file

@ -0,0 +1,33 @@
import React, { createContext, useState } from 'react'
/*
* The actual context
*/
export const ModalContext = createContext(null)
/*
* The context provider
*/
export const ModalContextProvider = ({ children }) => {
function clearModal() {
__setModal({
...__modal,
modalContent: null,
})
}
function setModal(content) {
__setModal({
...__modal,
modalContent: content,
})
}
const [__modal, __setModal] = useState({
setModal,
clearModal,
modalContent: null,
})
return <ModalContext.Provider value={__modal}>{children}</ModalContext.Provider>
}

View file

@ -1,5 +1,5 @@
import useLocalStorageState from 'use-local-storage-state'
import { defaultControlLevel } from '@freesewing/react-components/config/freesewing'
import { defaultControlLevel } from '@freesewing/react/config/freesewing'
/*
* When there is no account, we use this making it easy to check for username

View file

@ -1,7 +1,7 @@
import { useMemo } from 'react'
import { backend } from '@freesewing/react-components/config/freesewing'
import { RestClient } from '@freesewing/react-components/lib/restClient'
import { useAccount } from '@freesewing/react-components/hooks/useAccount'
import { backend } from '@freesewing/react/config/freesewing'
import { RestClient } from '@freesewing/react/lib/RestClient'
import { useAccount } from '@freesewing/react/hooks/useAccount'
/**
* The useBackend hook

View file

@ -0,0 +1,123 @@
import { Aaron as aaron } from '@freesewing/aaron'
import { Albert as albert } from '@freesewing/albert'
import { Bee as bee } from '@freesewing/bee'
import { Bella as bella } from '@freesewing/bella'
import { Benjamin as benjamin } from '@freesewing/benjamin'
import { Bent as bent } from '@freesewing/bent'
import { Bibi as bibi } from '@freesewing/bibi'
import { Bob as bob } from '@freesewing/bob'
import { Breanna as breanna } from '@freesewing/breanna'
import { Brian as brian } from '@freesewing/brian'
import { Bruce as bruce } from '@freesewing/bruce'
import { Carlita as carlita } from '@freesewing/carlita'
import { Carlton as carlton } from '@freesewing/carlton'
import { Cathrin as cathrin } from '@freesewing/cathrin'
import { Charlie as charlie } from '@freesewing/charlie'
import { Cornelius as cornelius } from '@freesewing/cornelius'
import { Diana as diana } from '@freesewing/diana'
import { Florence as florence } from '@freesewing/florence'
import { Florent as florent } from '@freesewing/florent'
import { Gozer as gozer } from '@freesewing/gozer'
import { Hi as hi } from '@freesewing/hi'
import { Holmes as holmes } from '@freesewing/holmes'
import { Hortensia as hortensia } from '@freesewing/hortensia'
import { Huey as huey } from '@freesewing/huey'
import { Hugo as hugo } from '@freesewing/hugo'
import { Jaeger as jaeger } from '@freesewing/jaeger'
import { Jane as jane } from '@freesewing/jane'
import { Lucy as lucy } from '@freesewing/lucy'
import { Lumina as lumina } from '@freesewing/lumina'
import { Lumira as lumira } from '@freesewing/lumira'
import { Lunetius as lunetius } from '@freesewing/lunetius'
import { Noble as noble } from '@freesewing/noble'
import { Octoplushy as octoplushy } from '@freesewing/octoplushy'
import { Onyx as onyx } from '@freesewing/onyx'
import { Opal as opal } from '@freesewing/opal'
import { Otis as otis } from '@freesewing/otis'
import { Paco as paco } from '@freesewing/paco'
import { Penelope as penelope } from '@freesewing/penelope'
import { Sandy as sandy } from '@freesewing/sandy'
import { Shelly as shelly } from '@freesewing/shelly'
import { Shin as shin } from '@freesewing/shin'
import { Simon as simon } from '@freesewing/simon'
import { Simone as simone } from '@freesewing/simone'
import { Skully as skully } from '@freesewing/skully'
import { Sven as sven } from '@freesewing/sven'
import { Tamiko as tamiko } from '@freesewing/tamiko'
import { Teagan as teagan } from '@freesewing/teagan'
import { Tiberius as tiberius } from '@freesewing/tiberius'
import { Titan as titan } from '@freesewing/titan'
import { Trayvon as trayvon } from '@freesewing/trayvon'
import { Tristan as tristan } from '@freesewing/tristan'
import { Uma as uma } from '@freesewing/uma'
import { Umbra as umbra } from '@freesewing/umbra'
import { Wahid as wahid } from '@freesewing/wahid'
import { Walburga as walburga } from '@freesewing/walburga'
import { Waralee as waralee } from '@freesewing/waralee'
import { Yuri as yuri } from '@freesewing/yuri'
import { Lily as lily } from '@freesewing/lily'
export const designs = {
aaron,
albert,
bee,
bella,
benjamin,
bent,
bibi,
bob,
breanna,
brian,
bruce,
carlita,
carlton,
cathrin,
charlie,
cornelius,
diana,
florence,
florent,
gozer,
hi,
holmes,
hortensia,
huey,
hugo,
jaeger,
jane,
lucy,
lumina,
lumira,
lunetius,
noble,
octoplushy,
onyx,
opal,
otis,
paco,
penelope,
sandy,
shelly,
shin,
simon,
simone,
skully,
sven,
tamiko,
teagan,
tiberius,
titan,
trayvon,
tristan,
uma,
umbra,
wahid,
walburga,
waralee,
yuri,
lily,
}
export const useDesign = (design) => (designs[design] ? designs[design] : false)
export const collection = Object.keys(designs)

View file

@ -115,6 +115,18 @@ async function withBody(method = 'POST', url, data, headers, raw = false, log =
/*
* If we end up here, status code is 400 or higher so it's an error
* We still attempt to parse the body though
*/
return [response?.status || 500, false]
let body
try {
body = raw ? await response.text() : await response.json()
} catch (err) {
try {
body = await response.text()
} catch (err) {
body = false
}
}
return [response?.status || 500, body]
}

View file

@ -0,0 +1,121 @@
import { Aaron as aaron } from '@freesewing/aaron'
import { Albert as albert } from '@freesewing/albert'
import { Bee as bee } from '@freesewing/bee'
import { Bella as bella } from '@freesewing/bella'
import { Benjamin as benjamin } from '@freesewing/benjamin'
import { Bent as bent } from '@freesewing/bent'
import { Bibi as bibi } from '@freesewing/bibi'
import { Bob as bob } from '@freesewing/bob'
import { Breanna as breanna } from '@freesewing/breanna'
import { Brian as brian } from '@freesewing/brian'
import { Bruce as bruce } from '@freesewing/bruce'
import { Carlita as carlita } from '@freesewing/carlita'
import { Carlton as carlton } from '@freesewing/carlton'
import { Cathrin as cathrin } from '@freesewing/cathrin'
import { Charlie as charlie } from '@freesewing/charlie'
import { Cornelius as cornelius } from '@freesewing/cornelius'
import { Diana as diana } from '@freesewing/diana'
import { Florence as florence } from '@freesewing/florence'
import { Florent as florent } from '@freesewing/florent'
import { Gozer as gozer } from '@freesewing/gozer'
import { Hi as hi } from '@freesewing/hi'
import { Holmes as holmes } from '@freesewing/holmes'
import { Hortensia as hortensia } from '@freesewing/hortensia'
import { Huey as huey } from '@freesewing/huey'
import { Hugo as hugo } from '@freesewing/hugo'
import { Jaeger as jaeger } from '@freesewing/jaeger'
import { Jane as jane } from '@freesewing/jane'
import { Lucy as lucy } from '@freesewing/lucy'
import { Lumina as lumina } from '@freesewing/lumina'
import { Lumira as lumira } from '@freesewing/lumira'
import { Lunetius as lunetius } from '@freesewing/lunetius'
import { Noble as noble } from '@freesewing/noble'
import { Octoplushy as octoplushy } from '@freesewing/octoplushy'
import { Onyx as onyx } from '@freesewing/onyx'
import { Opal as opal } from '@freesewing/opal'
import { Otis as otis } from '@freesewing/otis'
import { Paco as paco } from '@freesewing/paco'
import { Penelope as penelope } from '@freesewing/penelope'
import { Sandy as sandy } from '@freesewing/sandy'
import { Shelly as shelly } from '@freesewing/shelly'
import { Shin as shin } from '@freesewing/shin'
import { Simon as simon } from '@freesewing/simon'
import { Simone as simone } from '@freesewing/simone'
import { Skully as skully } from '@freesewing/skully'
import { Sven as sven } from '@freesewing/sven'
import { Tamiko as tamiko } from '@freesewing/tamiko'
import { Teagan as teagan } from '@freesewing/teagan'
import { Tiberius as tiberius } from '@freesewing/tiberius'
import { Titan as titan } from '@freesewing/titan'
import { Trayvon as trayvon } from '@freesewing/trayvon'
import { Tristan as tristan } from '@freesewing/tristan'
import { Uma as uma } from '@freesewing/uma'
import { Umbra as umbra } from '@freesewing/umbra'
import { Wahid as wahid } from '@freesewing/wahid'
import { Walburga as walburga } from '@freesewing/walburga'
import { Waralee as waralee } from '@freesewing/waralee'
import { Yuri as yuri } from '@freesewing/yuri'
import { Lily as lily } from '@freesewing/lily'
export const designs = {
aaron,
albert,
bee,
bella,
benjamin,
bent,
bibi,
bob,
breanna,
brian,
bruce,
carlita,
carlton,
cathrin,
charlie,
cornelius,
diana,
florence,
florent,
gozer,
hi,
holmes,
hortensia,
huey,
hugo,
jaeger,
jane,
lucy,
lumina,
lumira,
lunetius,
noble,
octoplushy,
onyx,
opal,
otis,
paco,
penelope,
sandy,
shelly,
shin,
simon,
simone,
skully,
sven,
tamiko,
teagan,
tiberius,
titan,
trayvon,
tristan,
uma,
umbra,
wahid,
walburga,
waralee,
yuri,
lily,
}
export const collection = Object.keys(designs)

View file

@ -27,9 +27,23 @@
"./linedrawings": "./src/linedrawings/index.mjs",
"./pattern": "./src/pattern/index.mjs",
"./xray": "./src/pattern-xray/index.mjs",
"./components/Breadcrumbs": "./components/Breadcrumbs/index.mjs",
"./components/Button": "./components/Button/index.mjs",
"./components/DocusaurusPage": "./components/DocusaurusPage/index.mjs",
"./components/Icon": "./components/Icon/index.mjs",
"./components/Input": "./components/Input/index.mjs",
"./components/Layout": "./components/Layout/index.mjs",
"./components/LineDrawing": "./components/LineDrawing/index.mjs",
"./components/Link": "./components/Link/index.mjs",
"./components/Logo": "./components/Logo/index.mjs",
"./components/Modal": "./components/Modal/index.mjs",
"./components/SignIn": "./components/SignIn/index.mjs",
"./components/Spinner": "./components/Spinner/index.mjs",
"./components/Tab": "./components/Tab/index.mjs",
"./config/freesewing": "./config/freesewing/index.mjs",
"./context/loadingStatus": "./context/loadingStatus/index.mjs",
"./context/LoadingStatus": "./context/LoadingStatus/index.mjs",
"./context/Modal": "./context/Modal/index.mjs",
"./hooks/useAccount": "./hooks/useAccount/index.mjs",
"./hooks/useBackend": "./hooks/useBackend/index.mjs",
"./lib/RestClient": "./lib/RestClient/index.mjs"
},

138
packages/utils/CHANGELOG.md Normal file
View file

@ -0,0 +1,138 @@
# Change log for: @freesewing/utils
## 3.0.0 (2023-09-30)
### Changed
- All FreeSewing packages are now ESM only.
- All FreeSewing packages now use named exports.
- Dropped support for NodeJS 14. NodeJS 18 (LTS/hydrogen) or more recent is now required.
## 2.21.0 (2022-06-27)
### Changed
- Migrated from Rollup to Esbuild for all builds
## 2.17.3 (2021-08-16)
### Fixed
- Added missing `bustPointToUnderbust` measurement to `neckstimate`
## 2.16.1 (2021-05-30)
### Changed
- neckstimate now takes an extra `noRound` parameter to return the unrounded value
- measurementDiffers takes an extra `absolute` value that can be set to false to get the non-absolute and non-rounded value
## 2.13.1 (2021-02-14)
### Added
- Pass pattern handle to tiler
## 2.7.1 (2020-07-24)
### Added
- Added backend calls for creating gists/issues on Github
## 2.7.0 (2020-07-12)
### Added
- Added new `isDegMeasurement` method. See [#358](https://github.com/freesewing/freesewing/issues/358)
- `neckStimate` now supports all new measurements. See [#416](https://github.com/freesewing/freesewing/issues/416)
### Changed
- Changed `neckstimate` to handle new `shoulderSlope` degree measurement. See [#358](https://github.com/freesewing/freesewing/issues/358)
- Changed `neckstimate` to support all new measurements. See [#416](https://github.com/freesewing/freesewing/issues/416)
- Ported `neckstimate` to the crotchDepth measurement. See [#425](https://github.com/freesewing/freesewing/issues/425)
- Removed `Circumference` suffix from measurement names
- Added the `isDegMeasurement` method
## 2.4.5 (2020-03-19)
### Changed
- neckstimate() now returns values rounded to nearest mm
## 2.4.1 (2020-03-04)
### Fixed
- [#542](https://github.com/freesewing/freesewing.org/issues/542): Prevent neckstimate from throwing when getting an unexpected measurement
## 2.2.0 (2020-02-22)
### Changed
- Neckstimate now uses proportions only
## 2.1.6 (2019-11-24)
### Fixed
- [#317](https://github.com/freesewing/freesewing.org/issues/317): Fixed bug where format was not passed to formatImperial
## 2.1.3 (2019-10-18)
### Changed
- Adjusted slope of the shoulderToShoulder measurement in neckstimate data
### Fixed
- [#250](https://github.com/freesewing/freesewing.org/issues/250): Model page stays empty with pre 2.0 model data: Error: 'neckstimate() requires a valid measurement name as second parameter. (received underBust)'
## 2.1.1 (2019-10-13)
### Fixed
- Fixed an issue with the formatMm method not adding units
## 2.1.0 (2019-10-06)
### Added
- Added backend methods for administration
- Added the resendActivationEmail method to backend
### Fixed
- Fixed an issue where optionDefault was not handling list options correctly
## 2.0.3 (2019-09-15)
### Fixed
- Fix measurementDiffers to pass breasts parameter to neckstimate
## 2.0.2 (2019-09-06)
### Fixed
- Removed lingering debug statement in formatImperial
## 2.0.1 (2019-09-01)
### Added
- The `measurementDiffers` method is new.
## 2.0.0 (2019-08-25)
### Added
- Initial release
This is the **initial release**, and the start of this change log.
> Prior to version 2, FreeSewing was not a JavaScript project.
> As such, that history is out of scope for this change log.

162
packages/utils/README.md Normal file
View file

@ -0,0 +1,162 @@
<p align='center'><a
href="https://www.npmjs.com/package/@freesewing/utils"
title="@freesewing/utils on NPM"
><img src="https://img.shields.io/npm/v/@freesewing/utils.svg"
alt="@freesewing/utils on NPM"/>
</a><a
href="https://opensource.org/licenses/MIT"
title="License: MIT"
><img src="https://img.shields.io/npm/l/@freesewing/utils.svg?label=License"
alt="License: MIT"/>
</a><a
href="https://deepscan.io/dashboard#view=project&tid=2114&pid=2993&bid=23256"
title="Code quality on DeepScan"
><img src="https://deepscan.io/api/teams/2114/projects/2993/branches/23256/badge/grade.svg"
alt="Code quality on DeepScan"/>
</a><a
href="https://github.com/freesewing/freesewing/issues?q=is%3Aissue+is%3Aopen+label%3Apkg%3Autils"
title="Open issues tagged pkg:utils"
><img src="https://img.shields.io/github/issues/freesewing/freesewing/pkg:utils.svg?label=Issues"
alt="Open issues tagged pkg:utils"/>
</a><a
href="#contributors-"
title="All Contributors"
><img src="https://img.shields.io/badge/all_contributors-131-pink.svg"
alt="All Contributors"/>
</a></p><p align='center'><a
href="https://twitter.com/freesewing_org"
title="Follow @freesewing_org on Twitter"
><img src="https://img.shields.io/badge/%F3%A0%80%A0-Follow%20us-blue.svg?logo=twitter&logoColor=white&logoWidth=15"
alt="Follow @freesewing_org on Twitter"/>
</a><a
href="https://chat.freesewing.org"
title="Chat with us on Discord"
><img src="https://img.shields.io/discord/698854858052075530?label=Chat%20on%20Discord"
alt="Chat with us on Discord"/>
</a><a
href="https://freesewing.org/patrons/join"
title="Become a FreeSewing Patron"
><img src="https://img.shields.io/badge/%F3%A0%80%A0-Support%20us-blueviolet.svg?logo=cash-app&logoColor=white&logoWidth=15"
alt="Become a FreeSewing Patron"/>
</a><a
href="https://instagram.com/freesewing_org"
title="Follow @freesewing_org on Twitter"
><img src="https://img.shields.io/badge/%F3%A0%80%A0-Follow%20us-E4405F.svg?logo=instagram&logoColor=white&logoWidth=15"
alt="Follow @freesewing_org on Twitter"/>
</a></p>
# @freesewing/utils
A number of utilities, typically used by FreeSewing frontend code
# FreeSewing
> [!TIP]
>#### Support FreeSewing: Become a patron, or make a one-time donation 🥰
>
> FreeSewing is an open source project maintained by Joost De Cock and financially supported by the FreeSewing patrons.
>
> If you feel FreeSewing is worthwhile, and you can spend a few coins without
hardship, then you should [join us and become a patron](https://freesewing.org/community/join).
## What am I looking at? 🤔
This repository is the FreeSewing *monorepo* holding all FreeSewing's websites, documentation, designs, plugins, and other NPM packages.
This folder holds: @freesewing/utils
If you're not entirely sure what to do or how to start, type this command:
```
npm run tips
```
> [!NOTE]
> If you don't want to set up a dev environment, you can run it in your browser:
>
> [![Open in Gitpod](https://gitpod.io/button/open-in-gitpod.svg)](https://gitpod.io/#https://github.com/freesewing/freesewing)
>
> We recommend that you fork our repository and then
> put `gitpod.io/#<entire-url-of-your-fork` into a browser
> to start up a browser-based dev environment of your own.
## About FreeSewing 💀
Where the world of makers and developers collide, that's where you'll find FreeSewing.
If you're a maker, checkout [freesewing.org](https://freesewing.org/) where you can generate
sewing patterns adapted to your measurements.
If you're a developer, the FreeSewing documentation lives at [freesewing.dev](https://freesewing.dev/).
The FreeSewing [core library](https://freesewing.dev/reference/api/) is a *batteries-included* toolbox
for parametric design of sewing patterns. But FreeSewing also provides a range
of [plugins](https://freesewing.dev/reference/plugins/) that further extend the
functionality of the platform.
If you have NodeJS installed, you can try it right now by running:
```bash
npx @freesewing/new-design
```
Getting started guides are available for:
- [Linux](https://freesewing.dev/tutorials/getting-started-linux/)
- [MacOS](https://freesewing.dev/tutorials/getting-started-mac/)
- [Windows](https://freesewing.dev/tutorials/getting-started-windows/)
The [pattern design tutorial](https://freesewing.dev/tutorials/pattern-design/) will
show you how to create your first parametric design.
## Getting started ⚡
To get started with FreeSewing, you can spin up our development environment with:
```bash
npx @freesewing/new-design
```
To work with FreeSewing's monorepo, you'll need [NodeJS v18](https://nodejs.org), [lerna](https://lerna.js.org/) and [yarn](https://yarnpkg.com/) on your system.
Once you have those, clone (or fork) this repo and run `yarn kickstart`:
```bash
git clone git@github.com:freesewing/freesewing.git
cd freesewing
yarn kickstart
```
## Links 👩‍💻
**Official channels**
- 💻 Makers website: [FreeSewing.org](https://freesewing.org)
- 💻 Developers website: [FreeSewing.dev](https://freesewing.dev)
- ✅ [Support](https://github.com/freesewing/freesewing/issues/new/choose),
[Issues](https://github.com/freesewing/freesewing/issues) &
[Discussions](https://github.com/freesewing/freesewing/discussions) on
[GitHub](https://github.com/freesewing/freesewing)
**Social media**
- 🐦 Twitter: [@freesewing_org](https://twitter.com/freesewing_org)
- 📷 Instagram: [@freesewing_org](https://instagram.com/freesewing_org)
**Places the FreeSewing community hangs out**
- 💬 [Discord](https://discord.freesewing.org/)
- 💬 [Facebook](https://www.facebook.com/groups/627769821272714/)
- 💬 [Reddit](https://www.reddit.com/r/freesewing/)
## License: MIT 🤓
© [Joost De Cock](https://github.com/joostdecock).
See [the license file](https://github.com/freesewing/freesewing/blob/develop/LICENSE) for details.
## Where to get help 🤯
For [Support](https://github.com/freesewing/freesewing/issues/new/choose),
please use the [Issues](https://github.com/freesewing/freesewing/issues) &
[Discussions](https://github.com/freesewing/freesewing/discussions) on
[GitHub](https://github.com/freesewing/freesewing).

35
packages/utils/build.mjs Normal file
View file

@ -0,0 +1,35 @@
/* This script will build the package with esbuild */
import esbuild from 'esbuild'
import pkg from './package.json' assert { type: 'json' }
// Create banner based on package info
const banner = `/**
* ${pkg.name} | v${pkg.version}
* ${pkg.description}
* (c) ${new Date().getFullYear()} ${pkg.author}
* @license ${pkg.license}
*/`
// Shared esbuild options
const options = {
banner: { js: banner },
bundle: true,
entryPoints: ['src/index.mjs'],
format: 'esm',
outfile: 'dist/index.mjs',
external: ['@freesewing'],
metafile: process.env.VERBOSE ? true : false,
minify: process.env.NO_MINIFY ? false : true,
sourcemap: true,
}
// Let esbuild generate the build
const build = async () => {
const result = await esbuild.build(options).catch(() => process.exit(1))
if (process.env.VERBOSE) {
const info = await esbuild.analyzeMetafile(result.metafile)
console.log(info)
}
}
build()

4
packages/utils/data.mjs Normal file
View file

@ -0,0 +1,4 @@
// This file is auto-generated | All changes you make will be overwritten.
export const name = '@freesewing/utils'
export const version = '3.3.0-rc.1'
export const data = { name, version }

View file

@ -0,0 +1,50 @@
{
"name": "@freesewing/utils",
"version": "3.3.0-rc.1",
"description": "A number of utilities, typically used by FreeSewing frontend code",
"author": "Joost De Cock <joost@joost.at> (https://github.com/joostdecock)",
"homepage": "https://freesewing.org/",
"repository": "github:freesewing/freesewing",
"license": "MIT",
"bugs": {
"url": "https://github.com/freesewing/freesewing/issues"
},
"funding": {
"type": "individual",
"url": "https://freesewing.org/patrons/join"
},
"keywords": [
"freesewing",
"freesewing"
],
"type": "module",
"module": "./src/index.mjs",
"scripts": {
"build": "node build.mjs",
"build:all": "yarn build",
"clean": "rimraf dist",
"mbuild": "NO_MINIFY=1 node build.mjs",
"symlink": "mkdir -p ./node_modules/@freesewing && cd ./node_modules/@freesewing && ln -s -f ../../../* . && cd -",
"test": "echo \"utils: No tests configured. Perhaps you could write some?\" && exit 0",
"vbuild": "VERBOSE=1 node build.mjs",
"lab": "cd ../../sites/lab && yarn start",
"tips": "node ../../scripts/help.mjs",
"lint": "npx eslint 'src/**' 'tests/*.mjs'",
"wbuild": "node build.mjs",
"wbuild:all": "yarn wbuild"
},
"peerDependencies": {},
"dependencies": {},
"devDependencies": {},
"files": [
"dist/*",
"README.md"
],
"publishConfig": {
"access": "public",
"tag": "next"
},
"engines": {
"node": ">= 18.17.0"
}
}

View file

@ -0,0 +1,156 @@
/*
* VARIABLES
*/
/*
* CSS classes to spread icon + text horizontally on a button
*/
export const horFlexClasses = 'flex flex-row items-center justify-between gap-4 w-full'
/*
* CSS classes to spread icon + text horizontally on a button, only from md upwards
*/
export const horFlexClassesNoSm =
'md:flex md:flex-row md:items-center md:justify-between md:gap-4 md-w-full'
/*
* FUNCTIONS
*/
/**
* A method to capitalize a string
*
* @param {string} string - The input string
* @return {string} String - The input string capitalized (first letter only)
*/
export function capitalize(string) {
return typeof string === 'string' ? string.charAt(0).toUpperCase() + string.slice(1) : ''
}
/*
* Returns the URL of a cloudflare image
* based on the ID and Variant
*
* @param {string} id - The image ID
* @param {string} variant - One of the cloudflare image variants
* @return {string} url - The image URL
*/
export function cloudflareImageUrl({ id = 'default-avatar', variant = 'public' }) {
/*
* Return something default so that people will actually change it
*/
if (!id || id === 'default-avatar') return cloudflareConfig.dflt
/*
* If the variant is invalid, set it to the smallest thumbnail so
* people don't load enourmous images by accident
*/
if (!cloudflareConfig.variants.includes(variant)) variant = 'sq100'
return `${cloudflareConfig.url}${id}/${variant}`
}
/*
* Parses value that should be a distance (cm or inch) into a value in mm
*
* This essentially exists for the benefit of imperial users who might input
* a string like `2 3/4` and we then have to make sense of that.
*
* @param {string} val - The original input
* @param {string} imperial - True if units are imperial (not metric)
* @return {number} mm - The result in millimeter
*/
export function distanceAsMm(val = false, imperial = false) {
// No input is not valid
if (!val) return false
// Cast to string, and replace comma with period
val = val.toString().trim().replace(',', '.')
// Regex pattern for regular numbers with decimal seperator or fractions
const regex = imperial
? /^-?[0-9]*(\s?[0-9]+\/|[.])?[0-9]+$/ // imperial (fractions)
: /^-?[0-9]*[.]?[0-9]+$/ // metric (no fractions)
if (!val.match(regex)) return false
// if fractions are allowed, parse for fractions, otherwise use the number as a value
if (imperial) val = fractionToDecimal(val)
return isNaN(val) ? false : Number(val)
}
/** convert a value that may contain a fraction to a decimal */
export function fractionToDecimal(value) {
// if it's just a number, return it
if (!isNaN(value)) return value
// keep a running total
let total = 0
// split by spaces
let chunks = String(value).split(' ')
if (chunks.length > 2) return Number.NaN // too many spaces to parse
// a whole number with a fraction
if (chunks.length === 2) {
// shift the whole number from the array
const whole = Number(chunks.shift())
// if it's not a number, return NaN
if (isNaN(whole)) return Number.NaN
// otherwise add it to the total
total += whole
}
// now we have only one chunk to parse
let fraction = chunks[0]
// split it to get numerator and denominator
let fChunks = fraction.trim().split('/')
// not really a fraction. return NaN
if (fChunks.length !== 2 || fChunks[1] === '') return Number.NaN
// do the division
let num = Number(fChunks[0])
let denom = Number(fChunks[1])
if (isNaN(num) || isNaN(denom)) return NaN
return total + num / denom
}
/*
* Convert a measurement to millimeter
*
* @param {number} value - The current value
* @param {string} units - One of metric or imperial
* @return {number} mm - The value in millimeter
*/
export function measurementAsMm(value, units = 'metric') {
if (typeof value === 'number') return value * (units === 'imperial' ? 25.4 : 10)
if (String(value).endsWith('.')) return false
if (units === 'metric') {
value = Number(value)
if (isNaN(value)) return false
return value * 10
} else {
const decimal = fractionToDecimal(value)
if (isNaN(decimal)) return false
return decimal * 24.5
}
}
/** convert a millimeter value to a Number value in the given units */
export function measurementAsUnits(mmValue, units = 'metric') {
return round(mmValue / (units === 'imperial' ? 25.4 : 10), 3)
}
/*
* Generic rounding method
*
* @param {number} val - The input number
* @param {number} decimals - Number of decimals to round to
* @return {number} result - The input val rounded to the number of decimals specified
*/
export function round(val, decimals = 1) {
return Math.round(val * Math.pow(10, decimals)) / Math.pow(10, decimals)
}

View file

@ -16,16 +16,6 @@ import Size from '@site/docs/designs/octoplushy/options/size/readme.mdx'
import Type from '@site/docs/designs/octoplushy/options/type/readme.mdx'
## Conditional Options {#conditional}
### Bottom arm reduction (plushy) {#bottomarmreductionplushy}
**This changes how long the bottom fabric of the arms is compared to the top fabric. It makes the arms more curly**
- Type: **Percentage**
- Default: **80%**
- Minimum: **75%**
- Maximum: **125%**
<Bottomarmreductionplushy />
### Bottom arm reduction {#bottomarmreduction}
**This changes how long the bottom fabric of the arms is compared to the top fabric. It makes the arms more curly**
@ -36,6 +26,16 @@ import Type from '@site/docs/designs/octoplushy/options/type/readme.mdx'
<Bottomarmreduction />
### Bottom arm reduction (plushy) {#bottomarmreductionplushy}
**This changes how long the bottom fabric of the arms is compared to the top fabric. It makes the arms more curly**
- Type: **Percentage**
- Default: **80%**
- Minimum: **75%**
- Maximum: **125%**
<Bottomarmreductionplushy />
## Style {#style}
### Arm length {#armlength}

View file

@ -1,13 +0,0 @@
import React from 'react'
export const HomeIcon = () => (
<svg
viewBox="0 0 24 24"
className="breadcrumbHomeIcon_------node_modules-@docusaurus-theme-classic-lib-theme-DocBreadcrumbs-Items-Home-styles-module"
>
<path
d="M10 19v-5h4v5c0 .55.45 1 1 1h3c.55 0 1-.45 1-1v-7h1.7c.46 0 .68-.57.33-.87L12.67 3.6c-.38-.34-.96-.34-1.34 0l-8.36 7.53c-.34.3-.13.87.33.87H5v7c0 .55.45 1 1 1h3c.55 0 1-.45 1-1z"
fill="currentColor"
></path>
</svg>
)

View file

@ -8,10 +8,48 @@
* Add Tailwindcss but only inside a container
* This is needed because docusaurus and tailwind will clash
*/
.tailwind-container {
@tailwind base;
@tailwind components;
@tailwind utilities;
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer components {
/* Applied styles for common HTML tags */
h1 {
@apply text-base-content text-4xl py-5 font-bold lg:text-6xl;
}
h2 {
@apply text-base-content text-3xl pt-4 pb-3 m-0 font-medium lg:text-4xl;
}
h3 {
@apply text-base-content text-2xl pt-3 pb-2 m-0 font-medium lg:text-3xl;
}
h4 {
@apply text-base-content text-xl pt-2 pb-1 m-0 font-medium lg:text-2xl;
}
h5 {
@apply text-base-content text-lg py-1 m-0 font-medium lg:text-xl;
}
h6 {
@apply text-base-content text-base py-0 m-0 font-medium lg:text-lg;
}
p {
@apply text-base-content my-1 py-2 text-base leading-6;
}
.btn {
@apply normal-case;
}
/* Fix color issues when Tailwind clashes with Infima*/
h1,
h2,
h3,
h4,
h5,
h6,
p,
label,
.daisy-label {
color: var(--ifm-font-color-base);
}
}
/* You can override the default Infima variables here. */

View file

@ -0,0 +1,28 @@
import DocusaurusLayout from '@theme/Layout'
import { DocusaurusPage } from '@freesewing/react/components/DocusaurusPage'
/*
* Some things should never generated as SSR
* So for these, we run a dynamic import and disable SSR rendering
const DynamicAuthWrapper = dynamic(
() => import('shared/components/wrappers/auth/index.mjs').then((mod) => mod.AuthWrapper),
{ ssr: false }
)
const DynamicAccountOverview = dynamic(
() => import('shared/components/account/overview.mjs').then((mod) => mod.AccountOverview),
{ ssr: false }
)
*/
export default function AccountIndexPage() {
return (
<DocusaurusPage
DocusaurusLayout={DocusaurusLayout}
title="Account"
description="Sign In to your FreeSewing account to unlock all features"
>
<pre>account here</pre>
</DocusaurusPage>
)
}

View file

@ -1 +0,0 @@
Account goes here

View file

@ -0,0 +1,30 @@
import DocusaurusLayout from '@theme/Layout'
import { DocusaurusPage } from '@freesewing/react/components/DocusaurusPage'
import { NoTitleLayout } from '@freesewing/react/components/Layout'
import { SignIn } from '@freesewing/react/components/SignIn'
import { useHistory } from 'react-router-dom'
/*
* This is the sign in page. Each page MUST be wrapped in the DocusaurusPage component.
* You also MUST pass in the DocusaurusLayout compoment.
*/
export default function SignInPage() {
const history = useHistory()
const onSuccess = (data) => history.push('/account')
return (
<DocusaurusPage
DocusaurusLayout={DocusaurusLayout}
Layout={NoTitleLayout}
title="Sign In"
description="Sign In to your FreeSewing account to unlock all features"
>
<div className="flex flex-col items-center h-screen justify-center text-base-content px-4">
<div className="max-w-lg w-full">
<SignIn onSuccess={onSuccess} />
</div>
</div>
</DocusaurusPage>
)
}

View file

@ -3,7 +3,7 @@ import { designs, designInfo } from '@site/src/lib/designs.mjs'
import { pluginInfo } from '@site/src/lib/plugins.mjs'
import { capitalize, optionsMenuStructure, optionType } from '@site/src/lib/utils.mjs'
import Link from '@docusaurus/Link'
import { lineDrawings } from '@freesewing/react-components/linedrawings'
import { lineDrawings } from '@freesewing/react/components/LineDrawing'
import {
designs as designTranslations,
measurements as measurementTranslations,

View file

@ -1,13 +1,13 @@
import React from 'react';
import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
import React from 'react'
import Tabs from '@theme/Tabs'
import TabItem from '@theme/TabItem'
import { pluginFlip } from '@freesewing/plugin-flip'
import { pluginGore } from '@freesewing/plugin-gore'
import { pluginRingsector } from '@freesewing/plugin-ringsector'
import { Design } from '@freesewing/core'
import yaml from 'js-yaml'
import { Pattern } from '@freesewing/react-components/pattern'
import { PatternXray } from '@freesewing/react-components/xray'
import { Pattern } from '@freesewing/react/pattern'
import { PatternXray } from '@freesewing/react/xray'
//import { t } from '@freesewing/react-components/methods'
import Markdown from 'react-markdown'
@ -120,7 +120,11 @@ export const Example = ({
</TabItem>
)
if (children) {
const codeTab = <TabItem key="code" value="code" label="Code">{children}</TabItem>
const codeTab = (
<TabItem key="code" value="code" label="Code">
{children}
</TabItem>
)
if (tutorial && !previewFirst) tabs.unshift(codeTab)
else tabs.push(codeTab)
}
@ -132,9 +136,7 @@ export const Example = ({
return (
<div className="my-8">
<Tabs>
{tabs}
</Tabs>
<Tabs>{tabs}</Tabs>
{caption && (
<div className="caption">
<Markdown>{caption}</Markdown>

View file

@ -1,6 +1,6 @@
import React from 'react'
import { Legend as LegendDesign } from '@freesewing/legend'
import { Pattern } from '@freesewing/react-components/pattern'
import { Pattern } from '@freesewing/react/pattern'
export const Legend = ({ part = '' }) => {
const settings = {

View file

@ -6,10 +6,13 @@ export default {
content: [
'./src/**/*.{js,mjs,mdx}',
'../../node_modules/daisyui/**/*.{js,mjs,ts,tsx}',
'../../packages/react/components/**/*.mjs',
'../../packages/react/context/**/*.mjs',
'../../packages/react/hooks/**/*.mjs',
'./tailwind-force.html',
],
plugins: [daisyui],
corePlugins: { preflight: false },
//corePlugins: { preflight: false },
darkMode: ['class', "[data-theme='dark']"],
//prefix: 'tw-',
daisyui: {