Merge branch 'develop' into eriese-v3-printing
This commit is contained in:
commit
271686b23b
47 changed files with 1539 additions and 3068 deletions
|
@ -875,7 +875,8 @@
|
|||
"profile": "https://github.com/BenJamesBen",
|
||||
"contributions": [
|
||||
"code",
|
||||
"doc"
|
||||
"doc",
|
||||
"bug"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
|
|
@ -91,6 +91,7 @@ module.exports = {
|
|||
],
|
||||
env: {
|
||||
mocha: true,
|
||||
node: true,
|
||||
},
|
||||
},
|
||||
|
||||
|
|
35
.github/ISSUE_TEMPLATE/01_bug-report.yaml
vendored
35
.github/ISSUE_TEMPLATE/01_bug-report.yaml
vendored
|
@ -1,8 +1,8 @@
|
|||
---
|
||||
name: 🐛 Bug report
|
||||
description: Report a problem, or something that went wrong
|
||||
title: "[bug]: "
|
||||
labels: [ "\U0001F41B bug" ]
|
||||
title: '[bug]: '
|
||||
labels: ["\U0001F41B bug"]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
|
@ -10,7 +10,7 @@ body:
|
|||
- type: textarea
|
||||
id: desc
|
||||
attributes:
|
||||
label: "What seems to be the problem? 🤔"
|
||||
label: 'What seems to be the problem? 🤔'
|
||||
description: Please provide a clear and concise description of the problem you encountered
|
||||
placeholder: |
|
||||
When I generate a Simone with my dimensions, the corners of the yoke appear malformed. \
|
||||
|
@ -18,7 +18,7 @@ body:
|
|||
- type: dropdown
|
||||
id: pkg
|
||||
attributes:
|
||||
label: "Design / Plugin / Package 🧐"
|
||||
label: 'Design / Plugin / Package 🧐'
|
||||
description: Do you know what design/plugin/package the bug is in?
|
||||
multiple: true
|
||||
options:
|
||||
|
@ -51,6 +51,7 @@ body:
|
|||
- designs/lucy
|
||||
- designs/lunetius
|
||||
- designs/noble
|
||||
- designs/octoplushy
|
||||
- designs/paco
|
||||
- designs/penelope
|
||||
- designs/plugintest
|
||||
|
@ -62,7 +63,6 @@ body:
|
|||
- designs/sven
|
||||
- designs/tamiko
|
||||
- designs/teagan
|
||||
- designs/theo
|
||||
- designs/tiberius
|
||||
- designs/titan
|
||||
- designs/trayvon
|
||||
|
@ -78,9 +78,9 @@ body:
|
|||
- plugins/plugin-bundle
|
||||
- plugins/plugin-bust
|
||||
- plugins/plugin-buttons
|
||||
- plugins/plugin-cutlist
|
||||
- plugins/plugin-cutonfold
|
||||
- plugins/plugin-dimension
|
||||
- plugins/plugin-export-dxf
|
||||
- plugins/plugin-flip
|
||||
- plugins/plugin-gore
|
||||
- plugins/plugin-grainline
|
||||
|
@ -94,33 +94,27 @@ body:
|
|||
- plugins/plugin-sprinkle
|
||||
- plugins/plugin-svgattr
|
||||
- plugins/plugin-theme
|
||||
- plugins/plugin-timing
|
||||
- plugins/plugin-title
|
||||
- plugins/plugin-validate
|
||||
- plugins/plugin-versionfree-svg
|
||||
- packages/components
|
||||
- packages/config-helpers
|
||||
- packages/core
|
||||
- packages/css-theme
|
||||
- packages/gatsby-remark-jargon
|
||||
- packages/i18n
|
||||
- packages/models
|
||||
- packages/mui-theme
|
||||
- packages/new-design
|
||||
- packages/pattern-info
|
||||
- packages/prettier-config
|
||||
- packages/rehype-highlight-lines
|
||||
- packages/rehype-jargon
|
||||
- packages/remark-jargon
|
||||
- packages/utils
|
||||
- packages/snapseries
|
||||
- type: dropdown
|
||||
id: patron
|
||||
attributes:
|
||||
label: Are you a FreeSewing patron? 😃
|
||||
description: "Patrons support us financially :pray: so they get priority"
|
||||
description: 'Patrons support us financially :pray: so they get priority'
|
||||
options:
|
||||
- "Yes, I am a tier-2 patron ❤️"
|
||||
- "Yes, I am a tier-4 patron ❤️ 💙"
|
||||
- "Yes, I am a tier-8 patron ❤️ 💙 💜"
|
||||
- "No, I am not 😞"
|
||||
- 'Yes, I am a tier-2 patron ❤️'
|
||||
- 'Yes, I am a tier-4 patron ❤️ 💙'
|
||||
- 'Yes, I am a tier-8 patron ❤️ 💙 💜'
|
||||
- 'No, I am not 😞'
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
|
@ -133,3 +127,4 @@ body:
|
|||
value: |
|
||||
Please keep in mind that **FreeSewing is a community project** that depends on **[your support](https://freesewing.org/community/join/)**.
|
||||
---
|
||||
|
||||
|
|
20
.github/boring-cyborg.yml
vendored
20
.github/boring-cyborg.yml
vendored
|
@ -1,29 +1,22 @@
|
|||
##### Labeler #####
|
||||
labelPRBasedOnFilePath:
|
||||
# "label": [ folder or subfolders ]
|
||||
":package: components": [ packages/components/* ]
|
||||
":package: config-helpers": [ packages/config-helpers/* ]
|
||||
":package: core": [ packages/core/* ]
|
||||
":package: create-freesewing-pattern": [ packages/create-freesewing-pattern/* ]
|
||||
":package: css-theme": [ packages/css-theme/* ]
|
||||
":package: gatsby-remark-jargon": [ packages/gatsby-remark-jargon/* ]
|
||||
":package: i18n": [ packages/i18n/* ]
|
||||
":package: models": [ packages/models/* ]
|
||||
":package: mui-theme": [ packages/mui-theme/* ]
|
||||
":package: new-design": [ packages/new-design/* ]
|
||||
":package: pattern-info": [ packages/pattern-info/* ]
|
||||
":package: prettier-config": [ packages/prettier-config/* ]
|
||||
":package: rehype-highlight-lines": [ packages/rehype-highlight-lines/* ]
|
||||
":package: rehype-jargon": [ packages/rehype-jargon/* ]
|
||||
":package: remark-jargon": [ packages/remark-jargon/* ]
|
||||
":package: utils": [ packages/utils/* ]
|
||||
":package: snapseries": [ packages/snapseries/* ]
|
||||
":electric_plug: plugin-banner": [ plugins/plugin-banner/* ]
|
||||
":electric_plug: plugin-bartack": [ plugins/plugin-bartack/* ]
|
||||
":electric_plug: plugin-bundle": [ plugins/plugin-bundle/* ]
|
||||
":electric_plug: plugin-bust": [ plugins/plugin-bust/* ]
|
||||
":electric_plug: plugin-buttons": [ plugins/plugin-buttons/* ]
|
||||
":electric_plug: plugin-cutlist": [ plugins/plugin-cutlist/* ]
|
||||
":electric_plug: plugin-cutonfold": [ plugins/plugin-cutonfold/* ]
|
||||
":electric_plug: plugin-dimension": [ plugins/plugin-dimension/* ]
|
||||
":electric_plug: plugin-export-dxf": [ plugins/plugin-export-dxf/* ]
|
||||
":electric_plug: plugin-flip": [ plugins/plugin-flip/* ]
|
||||
":electric_plug: plugin-gore": [ plugins/plugin-gore/* ]
|
||||
":electric_plug: plugin-grainline": [ plugins/plugin-grainline/* ]
|
||||
|
@ -37,18 +30,18 @@ labelPRBasedOnFilePath:
|
|||
":electric_plug: plugin-sprinkle": [ plugins/plugin-sprinkle/* ]
|
||||
":electric_plug: plugin-svgattr": [ plugins/plugin-svgattr/* ]
|
||||
":electric_plug: plugin-theme": [ plugins/plugin-theme/* ]
|
||||
":electric_plug: plugin-timing": [ plugins/plugin-timing/* ]
|
||||
":electric_plug: plugin-title": [ plugins/plugin-title/* ]
|
||||
":electric_plug: plugin-validate": [ plugins/plugin-validate/* ]
|
||||
":electric_plug: plugin-versionfree-svg": [ plugins/plugin-versionfree-svg/* ]
|
||||
":book: documentation": [ markdown/* ]
|
||||
":scroll: scripts": [ scripts/* ]
|
||||
":computer: backend": [ sites/backend/* ]
|
||||
":computer: dev": [ sites/dev/* ]
|
||||
":computer: email": [ sites/email/* ]
|
||||
":computer: lab": [ sites/lab/* ]
|
||||
":computer: org": [ sites/org/* ]
|
||||
":computer: sanity": [ sites/sanity/* ]
|
||||
":computer: shared": [ sites/shared/* ]
|
||||
":computer: strapi": [ sites/strapi/* ]
|
||||
":computer: svgtopdf": [ sites/svgtopdf/* ]
|
||||
":test_tube: tests": [ tests/* ]
|
||||
":gear: configuration": [ "config/*", ".github/*" ]
|
||||
":shirt: aaron": [ designs/aaron/* ]
|
||||
|
@ -93,7 +86,6 @@ labelPRBasedOnFilePath:
|
|||
":shirt: sven": [ designs/sven/* ]
|
||||
":shirt: tamiko": [ designs/tamiko/* ]
|
||||
":shirt: teagan": [ designs/teagan/* ]
|
||||
":shirt: theo": [ designs/theo/* ]
|
||||
":shirt: tiberius": [ designs/tiberius/* ]
|
||||
":shirt: titan": [ designs/titan/* ]
|
||||
":shirt: trayvon": [ designs/trayvon/* ]
|
||||
|
|
4
.github/workflows/checkdocs.yml
vendored
4
.github/workflows/checkdocs.yml
vendored
|
@ -23,9 +23,9 @@ jobs:
|
|||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
- name: Setup Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v1
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
- name: Install Remark
|
||||
|
|
8
.github/workflows/codeql-analysis.yml
vendored
8
.github/workflows/codeql-analysis.yml
vendored
|
@ -48,11 +48,11 @@ jobs:
|
|||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v1
|
||||
uses: github/codeql-action/init@v2
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||
|
@ -63,7 +63,7 @@ jobs:
|
|||
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
||||
# If this step fails, then you should remove it and run the build manually (see below)
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@v1
|
||||
uses: github/codeql-action/autobuild@v2
|
||||
|
||||
# ℹ️ Command-line programs to run using the OS shell.
|
||||
# 📚 https://git.io/JvXDl
|
||||
|
@ -77,4 +77,4 @@ jobs:
|
|||
# make release
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v1
|
||||
uses: github/codeql-action/analyze@v2
|
||||
|
|
4
.github/workflows/lint.all.yml
vendored
4
.github/workflows/lint.all.yml
vendored
|
@ -19,9 +19,9 @@ jobs:
|
|||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
- name: Setup Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v1
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
- name: Install dependencies
|
||||
|
|
6
.github/workflows/lint.diff.yml
vendored
6
.github/workflows/lint.diff.yml
vendored
|
@ -19,15 +19,15 @@ jobs:
|
|||
|
||||
steps:
|
||||
- name: Fetch PR base ref
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
ref: ${{ github.base_ref }}
|
||||
- name: Checkout PR merge ref
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
ref: ${{ github.ref }}
|
||||
- name: Setup Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v1
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
- name: Install dependencies
|
||||
|
|
5
.github/workflows/tests.all.yml
vendored
5
.github/workflows/tests.all.yml
vendored
|
@ -27,11 +27,12 @@ jobs:
|
|||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
- name: Setup Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v1
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
cache: 'yarn'
|
||||
- name: Install dependencies
|
||||
run: npx lerna bootstrap
|
||||
env:
|
||||
|
|
204
CONTRIBUTORS.md
204
CONTRIBUTORS.md
|
@ -9,136 +9,136 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
|
|||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="center" valign="top" width="14.28%"><a href="http://adamrtomkins.github.io/"><img src="https://avatars.githubusercontent.com/u/5709603?v=4?s=100" width="100px;" alt="Adam Tomkins"/><br /><sub><b>Adam Tomkins</b></sub></a><br /><a href="https://github.com/freesewing/freesewing/commits?author=AdamRTomkins" title="Documentation">📖</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="http://polymerisation-des-concepts.fr/"><img src="https://avatars.githubusercontent.com/u/365999?v=4?s=100" width="100px;" alt="Alexandre Ignjatovic"/><br /><sub><b>Alexandre Ignjatovic</b></sub></a><br /><a href="https://github.com/freesewing/freesewing/commits?author=bankair" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/AlfaLyr"><img src="https://avatars.githubusercontent.com/u/39273729?v=4?s=100" width="100px;" alt="AlfaLyr"/><br /><sub><b>AlfaLyr</b></sub></a><br /><a href="https://github.com/freesewing/freesewing/commits?author=AlfaLyr" title="Code">💻</a> <a href="#plugin-AlfaLyr" title="Plugin/utility libraries">🔌</a> <a href="#design-AlfaLyr" title="Design">🎨</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="http://thelettereph.com"><img src="https://avatars.githubusercontent.com/u/357684?v=4?s=100" width="100px;" alt="Andrew James"/><br /><sub><b>Andrew James</b></sub></a><br /><a href="https://github.com/freesewing/freesewing/commits?author=ephphatha" title="Documentation">📖</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/annekecaramin"><img src="https://avatars.githubusercontent.com/u/38046191?v=4?s=100" width="100px;" alt="Anneke"/><br /><sub><b>Anneke</b></sub></a><br /><a href="https://github.com/freesewing/freesewing/commits?author=annekecaramin" title="Documentation">📖</a> <a href="#translation-annekecaramin" title="Translation">🌍</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/anniekao"><img src="https://avatars.githubusercontent.com/u/1550506?v=4?s=100" width="100px;" alt="Annie Kao"/><br /><sub><b>Annie Kao</b></sub></a><br /><a href="https://github.com/freesewing/freesewing/commits?author=anniekao" title="Documentation">📖</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Anternative"><img src="https://avatars.githubusercontent.com/u/81079850?v=4?s=100" width="100px;" alt="Anternative"/><br /><sub><b>Anternative</b></sub></a><br /><a href="https://github.com/freesewing/freesewing/commits?author=Anternative" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://adamrtomkins.github.io/"><img src="https://avatars.githubusercontent.com/u/5709603?v=4?s=100" width="100px;" alt="Adam Tomkins"/><br /><sub><b>Adam Tomkins</b></sub></a><br /><a href="https://github.com/freesewing/freesewing/commits?author=AdamRTomkins" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://polymerisation-des-concepts.fr/"><img src="https://avatars.githubusercontent.com/u/365999?v=4?s=100" width="100px;" alt="Alexandre Ignjatovic"/><br /><sub><b>Alexandre Ignjatovic</b></sub></a><br /><a href="https://github.com/freesewing/freesewing/commits?author=bankair" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/AlfaLyr"><img src="https://avatars.githubusercontent.com/u/39273729?v=4?s=100" width="100px;" alt="AlfaLyr"/><br /><sub><b>AlfaLyr</b></sub></a><br /><a href="https://github.com/freesewing/freesewing/commits?author=AlfaLyr" title="Code">💻</a> <a href="#plugin-AlfaLyr" title="Plugin/utility libraries">🔌</a> <a href="#design-AlfaLyr" title="Design">🎨</a></td>
|
||||
<td align="center"><a href="http://thelettereph.com"><img src="https://avatars.githubusercontent.com/u/357684?v=4?s=100" width="100px;" alt="Andrew James"/><br /><sub><b>Andrew James</b></sub></a><br /><a href="https://github.com/freesewing/freesewing/commits?author=ephphatha" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/annekecaramin"><img src="https://avatars.githubusercontent.com/u/38046191?v=4?s=100" width="100px;" alt="Anneke"/><br /><sub><b>Anneke</b></sub></a><br /><a href="https://github.com/freesewing/freesewing/commits?author=annekecaramin" title="Documentation">📖</a> <a href="#translation-annekecaramin" title="Translation">🌍</a></td>
|
||||
<td align="center"><a href="https://github.com/anniekao"><img src="https://avatars.githubusercontent.com/u/1550506?v=4?s=100" width="100px;" alt="Annie Kao"/><br /><sub><b>Annie Kao</b></sub></a><br /><a href="https://github.com/freesewing/freesewing/commits?author=anniekao" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/Anternative"><img src="https://avatars.githubusercontent.com/u/81079850?v=4?s=100" width="100px;" alt="Anternative"/><br /><sub><b>Anternative</b></sub></a><br /><a href="https://github.com/freesewing/freesewing/commits?author=Anternative" title="Documentation">📖</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Quiltmaster"><img src="https://avatars.githubusercontent.com/u/71795777?v=4?s=100" width="100px;" alt="Anthony"/><br /><sub><b>Anthony</b></sub></a><br /><a href="#question-Quiltmaster" title="Answering Questions">💬</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/arigrayzel"><img src="https://avatars.githubusercontent.com/u/33040950?v=4?s=100" width="100px;" alt="Ari Grayzel-student"/><br /><sub><b>Ari Grayzel-student</b></sub></a><br /><a href="https://github.com/freesewing/freesewing/commits?author=arigrayzel" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Bart-PXL"><img src="https://avatars.githubusercontent.com/u/45118788?v=4?s=100" width="100px;" alt="Bart"/><br /><sub><b>Bart</b></sub></a><br /><a href="https://github.com/freesewing/freesewing/commits?author=Bart-PXL" title="Documentation">📖</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/BenJamesBen"><img src="https://avatars.githubusercontent.com/u/109869956?v=4?s=100" width="100px;" alt="BenJamesBen"/><br /><sub><b>BenJamesBen</b></sub></a><br /><a href="https://github.com/freesewing/freesewing/commits?author=BenJamesBen" title="Code">💻</a> <a href="https://github.com/freesewing/freesewing/commits?author=BenJamesBen" title="Documentation">📖</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/camerondubas"><img src="https://avatars.githubusercontent.com/u/6216460?v=4?s=100" width="100px;" alt="Cameron Dubas"/><br /><sub><b>Cameron Dubas</b></sub></a><br /><a href="https://github.com/freesewing/freesewing/commits?author=camerondubas" title="Documentation">📖</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/cabi"><img src="https://avatars.githubusercontent.com/u/2596253?v=4?s=100" width="100px;" alt="Carsten Biebricher"/><br /><sub><b>Carsten Biebricher</b></sub></a><br /><a href="https://github.com/freesewing/freesewing/commits?author=cabi" title="Documentation">📖</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/cathyzoller"><img src="https://avatars.githubusercontent.com/u/2120275?v=4?s=100" width="100px;" alt="Cathy Zoller"/><br /><sub><b>Cathy Zoller</b></sub></a><br /><a href="https://github.com/freesewing/freesewing/commits?author=cathyzoller" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/Quiltmaster"><img src="https://avatars.githubusercontent.com/u/71795777?v=4?s=100" width="100px;" alt="Anthony"/><br /><sub><b>Anthony</b></sub></a><br /><a href="#question-Quiltmaster" title="Answering Questions">💬</a></td>
|
||||
<td align="center"><a href="https://github.com/arigrayzel"><img src="https://avatars.githubusercontent.com/u/33040950?v=4?s=100" width="100px;" alt="Ari Grayzel-student"/><br /><sub><b>Ari Grayzel-student</b></sub></a><br /><a href="https://github.com/freesewing/freesewing/commits?author=arigrayzel" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/Bart-PXL"><img src="https://avatars.githubusercontent.com/u/45118788?v=4?s=100" width="100px;" alt="Bart"/><br /><sub><b>Bart</b></sub></a><br /><a href="https://github.com/freesewing/freesewing/commits?author=Bart-PXL" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/BenJamesBen"><img src="https://avatars.githubusercontent.com/u/109869956?v=4?s=100" width="100px;" alt="BenJamesBen"/><br /><sub><b>BenJamesBen</b></sub></a><br /><a href="https://github.com/freesewing/freesewing/commits?author=BenJamesBen" title="Code">💻</a> <a href="https://github.com/freesewing/freesewing/commits?author=BenJamesBen" title="Documentation">📖</a> <a href="https://github.com/freesewing/freesewing/issues?q=author%3ABenJamesBen" title="Bug reports">🐛</a></td>
|
||||
<td align="center"><a href="https://github.com/camerondubas"><img src="https://avatars.githubusercontent.com/u/6216460?v=4?s=100" width="100px;" alt="Cameron Dubas"/><br /><sub><b>Cameron Dubas</b></sub></a><br /><a href="https://github.com/freesewing/freesewing/commits?author=camerondubas" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/cabi"><img src="https://avatars.githubusercontent.com/u/2596253?v=4?s=100" width="100px;" alt="Carsten Biebricher"/><br /><sub><b>Carsten Biebricher</b></sub></a><br /><a href="https://github.com/freesewing/freesewing/commits?author=cabi" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/cathyzoller"><img src="https://avatars.githubusercontent.com/u/2120275?v=4?s=100" width="100px;" alt="Cathy Zoller"/><br /><sub><b>Cathy Zoller</b></sub></a><br /><a href="https://github.com/freesewing/freesewing/commits?author=cathyzoller" title="Documentation">📖</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Chantalbijoux"><img src="https://avatars.githubusercontent.com/u/39673694?v=4?s=100" width="100px;" alt="Chantal Lapointe"/><br /><sub><b>Chantal Lapointe</b></sub></a><br /><a href="#translation-Chantalbijoux" title="Translation">🌍</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/dpiquet"><img src="https://avatars.githubusercontent.com/u/4688628?v=4?s=100" width="100px;" alt="Damien PIQUET"/><br /><sub><b>Damien PIQUET</b></sub></a><br /><a href="https://github.com/freesewing/freesewing/commits?author=dpiquet" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://www.darigovresearch.com/"><img src="https://avatars.githubusercontent.com/u/30328618?v=4?s=100" width="100px;" alt="Darigov Research"/><br /><sub><b>Darigov Research</b></sub></a><br /><a href="https://github.com/freesewing/freesewing/commits?author=darigovresearch" title="Documentation">📖</a> <a href="#ideas-darigovresearch" title="Ideas, Planning, & Feedback">🤔</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/ElenaFdR"><img src="https://avatars.githubusercontent.com/u/5113815?v=4?s=100" width="100px;" alt="Elena FdR"/><br /><sub><b>Elena FdR</b></sub></a><br /><a href="https://github.com/freesewing/freesewing/commits?author=ElenaFdR" title="Documentation">📖</a> <a href="#blog-ElenaFdR" title="Blogposts">📝</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://emmanuelnyachoke.com/"><img src="https://avatars.githubusercontent.com/u/1908926?v=4?s=100" width="100px;" alt="Emmanuel Nyachoke"/><br /><sub><b>Emmanuel Nyachoke</b></sub></a><br /><a href="https://github.com/freesewing/freesewing/commits?author=enyachoke" title="Code">💻</a> <a href="https://github.com/freesewing/freesewing/commits?author=enyachoke" title="Documentation">📖</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="http://enochriese.com"><img src="https://avatars.githubusercontent.com/u/5298929?v=4?s=100" width="100px;" alt="Enoch Riese"/><br /><sub><b>Enoch Riese</b></sub></a><br /><a href="https://github.com/freesewing/freesewing/commits?author=eriese" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/EvEkSwed"><img src="https://avatars.githubusercontent.com/u/39723451?v=4?s=100" width="100px;" alt="EvEkSwed"/><br /><sub><b>EvEkSwed</b></sub></a><br /><a href="#translation-EvEkSwed" title="Translation">🌍</a></td>
|
||||
<td align="center"><a href="https://github.com/Chantalbijoux"><img src="https://avatars.githubusercontent.com/u/39673694?v=4?s=100" width="100px;" alt="Chantal Lapointe"/><br /><sub><b>Chantal Lapointe</b></sub></a><br /><a href="#translation-Chantalbijoux" title="Translation">🌍</a></td>
|
||||
<td align="center"><a href="https://github.com/dpiquet"><img src="https://avatars.githubusercontent.com/u/4688628?v=4?s=100" width="100px;" alt="Damien PIQUET"/><br /><sub><b>Damien PIQUET</b></sub></a><br /><a href="https://github.com/freesewing/freesewing/commits?author=dpiquet" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://www.darigovresearch.com/"><img src="https://avatars.githubusercontent.com/u/30328618?v=4?s=100" width="100px;" alt="Darigov Research"/><br /><sub><b>Darigov Research</b></sub></a><br /><a href="https://github.com/freesewing/freesewing/commits?author=darigovresearch" title="Documentation">📖</a> <a href="#ideas-darigovresearch" title="Ideas, Planning, & Feedback">🤔</a></td>
|
||||
<td align="center"><a href="https://github.com/ElenaFdR"><img src="https://avatars.githubusercontent.com/u/5113815?v=4?s=100" width="100px;" alt="Elena FdR"/><br /><sub><b>Elena FdR</b></sub></a><br /><a href="https://github.com/freesewing/freesewing/commits?author=ElenaFdR" title="Documentation">📖</a> <a href="#blog-ElenaFdR" title="Blogposts">📝</a></td>
|
||||
<td align="center"><a href="https://emmanuelnyachoke.com/"><img src="https://avatars.githubusercontent.com/u/1908926?v=4?s=100" width="100px;" alt="Emmanuel Nyachoke"/><br /><sub><b>Emmanuel Nyachoke</b></sub></a><br /><a href="https://github.com/freesewing/freesewing/commits?author=enyachoke" title="Code">💻</a> <a href="https://github.com/freesewing/freesewing/commits?author=enyachoke" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://enochriese.com"><img src="https://avatars.githubusercontent.com/u/5298929?v=4?s=100" width="100px;" alt="Enoch Riese"/><br /><sub><b>Enoch Riese</b></sub></a><br /><a href="https://github.com/freesewing/freesewing/commits?author=eriese" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/EvEkSwed"><img src="https://avatars.githubusercontent.com/u/39723451?v=4?s=100" width="100px;" alt="EvEkSwed"/><br /><sub><b>EvEkSwed</b></sub></a><br /><a href="#translation-EvEkSwed" title="Translation">🌍</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Fantastik-Maman"><img src="https://avatars.githubusercontent.com/u/39785382?v=4?s=100" width="100px;" alt="Fantastik-Maman"/><br /><sub><b>Fantastik-Maman</b></sub></a><br /><a href="#translation-Fantastik-Maman" title="Translation">🌍</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://www.forresto.com/"><img src="https://avatars.githubusercontent.com/u/395307?v=4?s=100" width="100px;" alt="Forrest O."/><br /><sub><b>Forrest O.</b></sub></a><br /><a href="https://github.com/freesewing/freesewing/commits?author=forresto" title="Documentation">📖</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/fmatray"><img src="https://avatars.githubusercontent.com/u/8267716?v=4?s=100" width="100px;" alt="Frédéric"/><br /><sub><b>Frédéric</b></sub></a><br /><a href="#translation-fmatray" title="Translation">🌍</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://www.linkedin.com/in/glennfmatthews/"><img src="https://avatars.githubusercontent.com/u/5603551?v=4?s=100" width="100px;" alt="Glenn Matthews"/><br /><sub><b>Glenn Matthews</b></sub></a><br /><a href="https://github.com/freesewing/freesewing/commits?author=glennmatthews" title="Documentation">📖</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://greg.technology/"><img src="https://avatars.githubusercontent.com/u/1017304?v=4?s=100" width="100px;" alt="Greg Sadetsky"/><br /><sub><b>Greg Sadetsky</b></sub></a><br /><a href="https://github.com/freesewing/freesewing/commits?author=gregsadetsky" title="Documentation">📖</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://kirby.zone"><img src="https://avatars.githubusercontent.com/u/75245963?v=4?s=100" width="100px;" alt="Igor Couto"/><br /><sub><b>Igor Couto</b></sub></a><br /><a href="https://github.com/freesewing/freesewing/issues?q=author%3Aiocouto" title="Bug reports">🐛</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://bandism.net/"><img src="https://avatars.githubusercontent.com/u/22633385?v=4?s=100" width="100px;" alt="Ikko Ashimine"/><br /><sub><b>Ikko Ashimine</b></sub></a><br /><a href="https://github.com/freesewing/freesewing/commits?author=eltociear" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/Fantastik-Maman"><img src="https://avatars.githubusercontent.com/u/39785382?v=4?s=100" width="100px;" alt="Fantastik-Maman"/><br /><sub><b>Fantastik-Maman</b></sub></a><br /><a href="#translation-Fantastik-Maman" title="Translation">🌍</a></td>
|
||||
<td align="center"><a href="https://www.forresto.com/"><img src="https://avatars.githubusercontent.com/u/395307?v=4?s=100" width="100px;" alt="Forrest O."/><br /><sub><b>Forrest O.</b></sub></a><br /><a href="https://github.com/freesewing/freesewing/commits?author=forresto" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/fmatray"><img src="https://avatars.githubusercontent.com/u/8267716?v=4?s=100" width="100px;" alt="Frédéric"/><br /><sub><b>Frédéric</b></sub></a><br /><a href="#translation-fmatray" title="Translation">🌍</a></td>
|
||||
<td align="center"><a href="https://www.linkedin.com/in/glennfmatthews/"><img src="https://avatars.githubusercontent.com/u/5603551?v=4?s=100" width="100px;" alt="Glenn Matthews"/><br /><sub><b>Glenn Matthews</b></sub></a><br /><a href="https://github.com/freesewing/freesewing/commits?author=glennmatthews" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://greg.technology/"><img src="https://avatars.githubusercontent.com/u/1017304?v=4?s=100" width="100px;" alt="Greg Sadetsky"/><br /><sub><b>Greg Sadetsky</b></sub></a><br /><a href="https://github.com/freesewing/freesewing/commits?author=gregsadetsky" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://kirby.zone"><img src="https://avatars.githubusercontent.com/u/75245963?v=4?s=100" width="100px;" alt="Igor Couto"/><br /><sub><b>Igor Couto</b></sub></a><br /><a href="https://github.com/freesewing/freesewing/issues?q=author%3Aiocouto" title="Bug reports">🐛</a></td>
|
||||
<td align="center"><a href="https://bandism.net/"><img src="https://avatars.githubusercontent.com/u/22633385?v=4?s=100" width="100px;" alt="Ikko Ashimine"/><br /><sub><b>Ikko Ashimine</b></sub></a><br /><a href="https://github.com/freesewing/freesewing/commits?author=eltociear" title="Documentation">📖</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Irapeke"><img src="https://avatars.githubusercontent.com/u/39604334?v=4?s=100" width="100px;" alt="Irapeke"/><br /><sub><b>Irapeke</b></sub></a><br /><a href="#translation-Irapeke" title="Translation">🌍</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/jsawo"><img src="https://avatars.githubusercontent.com/u/1294706?v=4?s=100" width="100px;" alt="Jacek Sawoszczuk"/><br /><sub><b>Jacek Sawoszczuk</b></sub></a><br /><a href="https://github.com/freesewing/freesewing/commits?author=jsawo" title="Documentation">📖</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/jgfichte"><img src="https://avatars.githubusercontent.com/u/1787162?v=4?s=100" width="100px;" alt="Jason Williams"/><br /><sub><b>Jason Williams</b></sub></a><br /><a href="https://github.com/freesewing/freesewing/commits?author=jgfichte" title="Documentation">📖</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/jejacks0n"><img src="https://avatars.githubusercontent.com/u/13765?v=4?s=100" width="100px;" alt="Jeremy Jackson"/><br /><sub><b>Jeremy Jackson</b></sub></a><br /><a href="https://github.com/freesewing/freesewing/commits?author=jejacks0n" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="http://jeroenhoek.nl"><img src="https://avatars.githubusercontent.com/u/683699?v=4?s=100" width="100px;" alt="Jeroen Hoek"/><br /><sub><b>Jeroen Hoek</b></sub></a><br /><a href="https://github.com/freesewing/freesewing/commits?author=jdhoek" title="Documentation">📖</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/joeschofield0"><img src="https://avatars.githubusercontent.com/u/47668691?v=4?s=100" width="100px;" alt="Joe Schofield"/><br /><sub><b>Joe Schofield</b></sub></a><br /><a href="https://github.com/freesewing/freesewing/commits?author=joeschofield0" title="Documentation">📖</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Joebidido"><img src="https://avatars.githubusercontent.com/u/39796210?v=4?s=100" width="100px;" alt="Joebidido"/><br /><sub><b>Joebidido</b></sub></a><br /><a href="#translation-Joebidido" title="Translation">🌍</a></td>
|
||||
<td align="center"><a href="https://github.com/Irapeke"><img src="https://avatars.githubusercontent.com/u/39604334?v=4?s=100" width="100px;" alt="Irapeke"/><br /><sub><b>Irapeke</b></sub></a><br /><a href="#translation-Irapeke" title="Translation">🌍</a></td>
|
||||
<td align="center"><a href="https://github.com/jsawo"><img src="https://avatars.githubusercontent.com/u/1294706?v=4?s=100" width="100px;" alt="Jacek Sawoszczuk"/><br /><sub><b>Jacek Sawoszczuk</b></sub></a><br /><a href="https://github.com/freesewing/freesewing/commits?author=jsawo" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/jgfichte"><img src="https://avatars.githubusercontent.com/u/1787162?v=4?s=100" width="100px;" alt="Jason Williams"/><br /><sub><b>Jason Williams</b></sub></a><br /><a href="https://github.com/freesewing/freesewing/commits?author=jgfichte" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/jejacks0n"><img src="https://avatars.githubusercontent.com/u/13765?v=4?s=100" width="100px;" alt="Jeremy Jackson"/><br /><sub><b>Jeremy Jackson</b></sub></a><br /><a href="https://github.com/freesewing/freesewing/commits?author=jejacks0n" title="Code">💻</a></td>
|
||||
<td align="center"><a href="http://jeroenhoek.nl"><img src="https://avatars.githubusercontent.com/u/683699?v=4?s=100" width="100px;" alt="Jeroen Hoek"/><br /><sub><b>Jeroen Hoek</b></sub></a><br /><a href="https://github.com/freesewing/freesewing/commits?author=jdhoek" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/joeschofield0"><img src="https://avatars.githubusercontent.com/u/47668691?v=4?s=100" width="100px;" alt="Joe Schofield"/><br /><sub><b>Joe Schofield</b></sub></a><br /><a href="https://github.com/freesewing/freesewing/commits?author=joeschofield0" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/Joebidido"><img src="https://avatars.githubusercontent.com/u/39796210?v=4?s=100" width="100px;" alt="Joebidido"/><br /><sub><b>Joebidido</b></sub></a><br /><a href="#translation-Joebidido" title="Translation">🌍</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://joost.at/"><img src="https://avatars.githubusercontent.com/u/1708494?v=4?s=100" width="100px;" alt="Joost De Cock"/><br /><sub><b>Joost De Cock</b></sub></a><br /><a href="#maintenance-joostdecock" title="Maintenance">🚧</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/joshessman"><img src="https://avatars.githubusercontent.com/u/9941074?v=4?s=100" width="100px;" alt="Josh Essman"/><br /><sub><b>Josh Essman</b></sub></a><br /><a href="https://github.com/freesewing/freesewing/commits?author=joshessman" title="Documentation">📖</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="http://www.earth.li/~kake/"><img src="https://avatars.githubusercontent.com/u/1956810?v=4?s=100" width="100px;" alt="Kake"/><br /><sub><b>Kake</b></sub></a><br /><a href="https://github.com/freesewing/freesewing/commits?author=KakeLP" title="Documentation">📖</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://twitter.com/kapunahele"><img src="https://avatars.githubusercontent.com/u/4116963?v=4?s=100" width="100px;" alt="Kapunahele Wong"/><br /><sub><b>Kapunahele Wong</b></sub></a><br /><a href="https://github.com/freesewing/freesewing/commits?author=kapunahelewong" title="Documentation">📖</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/tangerineshark"><img src="https://avatars.githubusercontent.com/u/70777269?v=4?s=100" width="100px;" alt="Karen"/><br /><sub><b>Karen</b></sub></a><br /><a href="https://github.com/freesewing/freesewing/commits?author=tangerineshark" title="Documentation">📖</a> <a href="#eventOrganizing-tangerineshark" title="Event Organizing">📋</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/mcgnly"><img src="https://avatars.githubusercontent.com/u/5653631?v=4?s=100" width="100px;" alt="Katie McGinley"/><br /><sub><b>Katie McGinley</b></sub></a><br /><a href="https://github.com/freesewing/freesewing/commits?author=mcgnly" title="Documentation">📖</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="http://www.kieranklaassen.com/"><img src="https://avatars.githubusercontent.com/u/209089?v=4?s=100" width="100px;" alt="Kieran Klaassen"/><br /><sub><b>Kieran Klaassen</b></sub></a><br /><a href="https://github.com/freesewing/freesewing/commits?author=kieranklaassen" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://joost.at/"><img src="https://avatars.githubusercontent.com/u/1708494?v=4?s=100" width="100px;" alt="Joost De Cock"/><br /><sub><b>Joost De Cock</b></sub></a><br /><a href="#maintenance-joostdecock" title="Maintenance">🚧</a></td>
|
||||
<td align="center"><a href="https://github.com/joshessman"><img src="https://avatars.githubusercontent.com/u/9941074?v=4?s=100" width="100px;" alt="Josh Essman"/><br /><sub><b>Josh Essman</b></sub></a><br /><a href="https://github.com/freesewing/freesewing/commits?author=joshessman" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://www.earth.li/~kake/"><img src="https://avatars.githubusercontent.com/u/1956810?v=4?s=100" width="100px;" alt="Kake"/><br /><sub><b>Kake</b></sub></a><br /><a href="https://github.com/freesewing/freesewing/commits?author=KakeLP" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://twitter.com/kapunahele"><img src="https://avatars.githubusercontent.com/u/4116963?v=4?s=100" width="100px;" alt="Kapunahele Wong"/><br /><sub><b>Kapunahele Wong</b></sub></a><br /><a href="https://github.com/freesewing/freesewing/commits?author=kapunahelewong" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/tangerineshark"><img src="https://avatars.githubusercontent.com/u/70777269?v=4?s=100" width="100px;" alt="Karen"/><br /><sub><b>Karen</b></sub></a><br /><a href="https://github.com/freesewing/freesewing/commits?author=tangerineshark" title="Documentation">📖</a> <a href="#eventOrganizing-tangerineshark" title="Event Organizing">📋</a></td>
|
||||
<td align="center"><a href="https://github.com/mcgnly"><img src="https://avatars.githubusercontent.com/u/5653631?v=4?s=100" width="100px;" alt="Katie McGinley"/><br /><sub><b>Katie McGinley</b></sub></a><br /><a href="https://github.com/freesewing/freesewing/commits?author=mcgnly" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://www.kieranklaassen.com/"><img src="https://avatars.githubusercontent.com/u/209089?v=4?s=100" width="100px;" alt="Kieran Klaassen"/><br /><sub><b>Kieran Klaassen</b></sub></a><br /><a href="https://github.com/freesewing/freesewing/commits?author=kieranklaassen" title="Code">💻</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Kittycatou"><img src="https://avatars.githubusercontent.com/u/48165583?v=4?s=100" width="100px;" alt="Kittycatou"/><br /><sub><b>Kittycatou</b></sub></a><br /><a href="#translation-Kittycatou" title="Translation">🌍</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://www.krishoward.org/"><img src="https://avatars.githubusercontent.com/u/5946286?v=4?s=100" width="100px;" alt="Kris"/><br /><sub><b>Kris</b></sub></a><br /><a href="https://github.com/freesewing/freesewing/commits?author=web-goddess" title="Documentation">📖</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/kristinruben"><img src="https://avatars.githubusercontent.com/u/17237479?v=4?s=100" width="100px;" alt="Kristin Ruben"/><br /><sub><b>Kristin Ruben</b></sub></a><br /><a href="https://github.com/freesewing/freesewing/commits?author=kristinruben" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Loudepeuter"><img src="https://avatars.githubusercontent.com/u/38081954?v=4?s=100" width="100px;" alt="Loudepeuter"/><br /><sub><b>Loudepeuter</b></sub></a><br /><a href="#translation-Loudepeuter" title="Translation">🌍</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/lucibytes"><img src="https://avatars.githubusercontent.com/u/77203781?v=4?s=100" width="100px;" alt="Lucian"/><br /><sub><b>Lucian</b></sub></a><br /><a href="#eventOrganizing-lucibytes" title="Event Organizing">📋</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/manufakturedelweiss"><img src="https://avatars.githubusercontent.com/u/38063391?v=4?s=100" width="100px;" alt="Marcus"/><br /><sub><b>Marcus</b></sub></a><br /><a href="#translation-manufakturedelweiss" title="Translation">🌍</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/martintribo"><img src="https://avatars.githubusercontent.com/u/1613442?v=4?s=100" width="100px;" alt="Martin Tribo"/><br /><sub><b>Martin Tribo</b></sub></a><br /><a href="https://github.com/freesewing/freesewing/commits?author=martintribo" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/Kittycatou"><img src="https://avatars.githubusercontent.com/u/48165583?v=4?s=100" width="100px;" alt="Kittycatou"/><br /><sub><b>Kittycatou</b></sub></a><br /><a href="#translation-Kittycatou" title="Translation">🌍</a></td>
|
||||
<td align="center"><a href="https://www.krishoward.org/"><img src="https://avatars.githubusercontent.com/u/5946286?v=4?s=100" width="100px;" alt="Kris"/><br /><sub><b>Kris</b></sub></a><br /><a href="https://github.com/freesewing/freesewing/commits?author=web-goddess" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/kristinruben"><img src="https://avatars.githubusercontent.com/u/17237479?v=4?s=100" width="100px;" alt="Kristin Ruben"/><br /><sub><b>Kristin Ruben</b></sub></a><br /><a href="https://github.com/freesewing/freesewing/commits?author=kristinruben" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/Loudepeuter"><img src="https://avatars.githubusercontent.com/u/38081954?v=4?s=100" width="100px;" alt="Loudepeuter"/><br /><sub><b>Loudepeuter</b></sub></a><br /><a href="#translation-Loudepeuter" title="Translation">🌍</a></td>
|
||||
<td align="center"><a href="https://github.com/lucibytes"><img src="https://avatars.githubusercontent.com/u/77203781?v=4?s=100" width="100px;" alt="Lucian"/><br /><sub><b>Lucian</b></sub></a><br /><a href="#eventOrganizing-lucibytes" title="Event Organizing">📋</a></td>
|
||||
<td align="center"><a href="https://github.com/manufakturedelweiss"><img src="https://avatars.githubusercontent.com/u/38063391?v=4?s=100" width="100px;" alt="Marcus"/><br /><sub><b>Marcus</b></sub></a><br /><a href="#translation-manufakturedelweiss" title="Translation">🌍</a></td>
|
||||
<td align="center"><a href="https://github.com/martintribo"><img src="https://avatars.githubusercontent.com/u/1613442?v=4?s=100" width="100px;" alt="Martin Tribo"/><br /><sub><b>Martin Tribo</b></sub></a><br /><a href="https://github.com/freesewing/freesewing/commits?author=martintribo" title="Documentation">📖</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/nadege"><img src="https://avatars.githubusercontent.com/u/3792171?v=4?s=100" width="100px;" alt="Nadege Michel"/><br /><sub><b>Nadege Michel</b></sub></a><br /><a href="https://github.com/freesewing/freesewing/commits?author=nadege" title="Tests">⚠️</a> <a href="https://github.com/freesewing/freesewing/commits?author=nadege" title="Documentation">📖</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/nataliasayang"><img src="https://avatars.githubusercontent.com/u/48160791?v=4?s=100" width="100px;" alt="Natalia"/><br /><sub><b>Natalia</b></sub></a><br /><a href="https://github.com/freesewing/freesewing/commits?author=nataliasayang" title="Code">💻</a> <a href="#design-nataliasayang" title="Design">🎨</a> <a href="#blog-nataliasayang" title="Blogposts">📝</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="http://yergler.net/"><img src="https://avatars.githubusercontent.com/u/510875?v=4?s=100" width="100px;" alt="Nathan Yergler"/><br /><sub><b>Nathan Yergler</b></sub></a><br /><a href="https://github.com/freesewing/freesewing/commits?author=nyergler" title="Documentation">📖</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/nicholasdower"><img src="https://avatars.githubusercontent.com/u/9117775?v=4?s=100" width="100px;" alt="Nick Dower"/><br /><sub><b>Nick Dower</b></sub></a><br /><a href="https://github.com/freesewing/freesewing/commits?author=nicholasdower" title="Documentation">📖</a> <a href="https://github.com/freesewing/freesewing/commits?author=nicholasdower" title="Code">💻</a> <a href="https://github.com/freesewing/freesewing/issues?q=author%3Anicholasdower" title="Bug reports">🐛</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/nchilada"><img src="https://avatars.githubusercontent.com/u/692925?v=4?s=100" width="100px;" alt="Nikhil Chelliah"/><br /><sub><b>Nikhil Chelliah</b></sub></a><br /><a href="https://github.com/freesewing/freesewing/commits?author=nchilada" title="Documentation">📖</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/OysteinHoiby"><img src="https://avatars.githubusercontent.com/u/49735055?v=4?s=100" width="100px;" alt="OysteinHoiby"/><br /><sub><b>OysteinHoiby</b></sub></a><br /><a href="https://github.com/freesewing/freesewing/commits?author=OysteinHoiby" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://pat.forringer.com/"><img src="https://avatars.githubusercontent.com/u/136456?v=4?s=100" width="100px;" alt="Patrick Forringer"/><br /><sub><b>Patrick Forringer</b></sub></a><br /><a href="#plugin-destos" title="Plugin/utility libraries">🔌</a></td>
|
||||
<td align="center"><a href="https://github.com/nadege"><img src="https://avatars.githubusercontent.com/u/3792171?v=4?s=100" width="100px;" alt="Nadege Michel"/><br /><sub><b>Nadege Michel</b></sub></a><br /><a href="https://github.com/freesewing/freesewing/commits?author=nadege" title="Tests">⚠️</a> <a href="https://github.com/freesewing/freesewing/commits?author=nadege" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/nataliasayang"><img src="https://avatars.githubusercontent.com/u/48160791?v=4?s=100" width="100px;" alt="Natalia"/><br /><sub><b>Natalia</b></sub></a><br /><a href="https://github.com/freesewing/freesewing/commits?author=nataliasayang" title="Code">💻</a> <a href="#design-nataliasayang" title="Design">🎨</a> <a href="#blog-nataliasayang" title="Blogposts">📝</a></td>
|
||||
<td align="center"><a href="http://yergler.net/"><img src="https://avatars.githubusercontent.com/u/510875?v=4?s=100" width="100px;" alt="Nathan Yergler"/><br /><sub><b>Nathan Yergler</b></sub></a><br /><a href="https://github.com/freesewing/freesewing/commits?author=nyergler" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/nicholasdower"><img src="https://avatars.githubusercontent.com/u/9117775?v=4?s=100" width="100px;" alt="Nick Dower"/><br /><sub><b>Nick Dower</b></sub></a><br /><a href="https://github.com/freesewing/freesewing/commits?author=nicholasdower" title="Documentation">📖</a> <a href="https://github.com/freesewing/freesewing/commits?author=nicholasdower" title="Code">💻</a> <a href="https://github.com/freesewing/freesewing/issues?q=author%3Anicholasdower" title="Bug reports">🐛</a></td>
|
||||
<td align="center"><a href="https://github.com/nchilada"><img src="https://avatars.githubusercontent.com/u/692925?v=4?s=100" width="100px;" alt="Nikhil Chelliah"/><br /><sub><b>Nikhil Chelliah</b></sub></a><br /><a href="https://github.com/freesewing/freesewing/commits?author=nchilada" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/OysteinHoiby"><img src="https://avatars.githubusercontent.com/u/49735055?v=4?s=100" width="100px;" alt="OysteinHoiby"/><br /><sub><b>OysteinHoiby</b></sub></a><br /><a href="https://github.com/freesewing/freesewing/commits?author=OysteinHoiby" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://pat.forringer.com/"><img src="https://avatars.githubusercontent.com/u/136456?v=4?s=100" width="100px;" alt="Patrick Forringer"/><br /><sub><b>Patrick Forringer</b></sub></a><br /><a href="#plugin-destos" title="Plugin/utility libraries">🔌</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" valign="top" width="14.28%"><a href="http://pd75.github.io/"><img src="https://avatars.githubusercontent.com/u/10294795?v=4?s=100" width="100px;" alt="Paul"/><br /><sub><b>Paul</b></sub></a><br /><a href="https://github.com/freesewing/freesewing/commits?author=PD75" title="Documentation">📖</a> <a href="#blog-PD75" title="Blogposts">📝</a> <a href="#translation-PD75" title="Translation">🌍</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/phillipthelen"><img src="https://avatars.githubusercontent.com/u/298062?v=4?s=100" width="100px;" alt="Phillip Thelen"/><br /><sub><b>Phillip Thelen</b></sub></a><br /><a href="https://github.com/freesewing/freesewing/commits?author=phillipthelen" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Pixieish"><img src="https://avatars.githubusercontent.com/u/32991415?v=4?s=100" width="100px;" alt="Pixieish"/><br /><sub><b>Pixieish</b></sub></a><br /><a href="https://github.com/freesewing/freesewing/commits?author=Pixieish" title="Documentation">📖</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://www.uza.be/persoon/prof-dr-sorcha-ni-dhubhghaill"><img src="https://avatars.githubusercontent.com/u/30624634?v=4?s=100" width="100px;" alt="Prof. dr. Sorcha Ní Dhubhghaill"/><br /><sub><b>Prof. dr. Sorcha Ní Dhubhghaill</b></sub></a><br /><a href="https://github.com/freesewing/freesewing/commits?author=sorchanidhubhghaill" title="Documentation">📖</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/QuentinFelix"><img src="https://avatars.githubusercontent.com/u/5288091?v=4?s=100" width="100px;" alt="Quentin FELIX"/><br /><sub><b>Quentin FELIX</b></sub></a><br /><a href="https://github.com/freesewing/freesewing/commits?author=QuentinFelix" title="Code">💻</a> <a href="#design-QuentinFelix" title="Design">🎨</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/RikHekker"><img src="https://avatars.githubusercontent.com/u/31843274?v=4?s=100" width="100px;" alt="Rik Hekker"/><br /><sub><b>Rik Hekker</b></sub></a><br /><a href="https://github.com/freesewing/freesewing/issues?q=author%3ARikHekker" title="Bug reports">🐛</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="http://resume.livingston-gray.com/faq.html"><img src="https://avatars.githubusercontent.com/u/6462?v=4?s=100" width="100px;" alt="Sam Livingston-Gray"/><br /><sub><b>Sam Livingston-Gray</b></sub></a><br /><a href="https://github.com/freesewing/freesewing/commits?author=geeksam" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://pd75.github.io/"><img src="https://avatars.githubusercontent.com/u/10294795?v=4?s=100" width="100px;" alt="Paul"/><br /><sub><b>Paul</b></sub></a><br /><a href="https://github.com/freesewing/freesewing/commits?author=PD75" title="Documentation">📖</a> <a href="#blog-PD75" title="Blogposts">📝</a> <a href="#translation-PD75" title="Translation">🌍</a></td>
|
||||
<td align="center"><a href="https://github.com/phillipthelen"><img src="https://avatars.githubusercontent.com/u/298062?v=4?s=100" width="100px;" alt="Phillip Thelen"/><br /><sub><b>Phillip Thelen</b></sub></a><br /><a href="https://github.com/freesewing/freesewing/commits?author=phillipthelen" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/Pixieish"><img src="https://avatars.githubusercontent.com/u/32991415?v=4?s=100" width="100px;" alt="Pixieish"/><br /><sub><b>Pixieish</b></sub></a><br /><a href="https://github.com/freesewing/freesewing/commits?author=Pixieish" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://www.uza.be/persoon/prof-dr-sorcha-ni-dhubhghaill"><img src="https://avatars.githubusercontent.com/u/30624634?v=4?s=100" width="100px;" alt="Prof. dr. Sorcha Ní Dhubhghaill"/><br /><sub><b>Prof. dr. Sorcha Ní Dhubhghaill</b></sub></a><br /><a href="https://github.com/freesewing/freesewing/commits?author=sorchanidhubhghaill" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/QuentinFelix"><img src="https://avatars.githubusercontent.com/u/5288091?v=4?s=100" width="100px;" alt="Quentin FELIX"/><br /><sub><b>Quentin FELIX</b></sub></a><br /><a href="https://github.com/freesewing/freesewing/commits?author=QuentinFelix" title="Code">💻</a> <a href="#design-QuentinFelix" title="Design">🎨</a></td>
|
||||
<td align="center"><a href="https://github.com/RikHekker"><img src="https://avatars.githubusercontent.com/u/31843274?v=4?s=100" width="100px;" alt="Rik Hekker"/><br /><sub><b>Rik Hekker</b></sub></a><br /><a href="https://github.com/freesewing/freesewing/issues?q=author%3ARikHekker" title="Bug reports">🐛</a></td>
|
||||
<td align="center"><a href="http://resume.livingston-gray.com/faq.html"><img src="https://avatars.githubusercontent.com/u/6462?v=4?s=100" width="100px;" alt="Sam Livingston-Gray"/><br /><sub><b>Sam Livingston-Gray</b></sub></a><br /><a href="https://github.com/freesewing/freesewing/commits?author=geeksam" title="Documentation">📖</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/sannek"><img src="https://avatars.githubusercontent.com/u/17491062?v=4?s=100" width="100px;" alt="Sanne"/><br /><sub><b>Sanne</b></sub></a><br /><a href="https://github.com/freesewing/freesewing/commits?author=sannek" title="Code">💻</a> <a href="https://github.com/freesewing/freesewing/commits?author=sannek" title="Documentation">📖</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Tyrannogina"><img src="https://avatars.githubusercontent.com/u/19556565?v=4?s=100" width="100px;" alt="Sara Latorre"/><br /><sub><b>Sara Latorre</b></sub></a><br /><a href="#translation-Tyrannogina" title="Translation">🌍</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/SeaZeeZee"><img src="https://avatars.githubusercontent.com/u/86711383?v=4?s=100" width="100px;" alt="SeaZeeZee"/><br /><sub><b>SeaZeeZee</b></sub></a><br /><a href="https://github.com/freesewing/freesewing/commits?author=SeaZeeZee" title="Documentation">📖</a> <a href="https://github.com/freesewing/freesewing/commits?author=SeaZeeZee" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/SimonbJohnson"><img src="https://avatars.githubusercontent.com/u/2110742?v=4?s=100" width="100px;" alt="SimonbJohnson"/><br /><sub><b>SimonbJohnson</b></sub></a><br /><a href="https://github.com/freesewing/freesewing/issues?q=author%3ASimonbJohnson" title="Bug reports">🐛</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/SirCharlotte"><img src="https://avatars.githubusercontent.com/u/63847870?v=4?s=100" width="100px;" alt="SirCharlotte"/><br /><sub><b>SirCharlotte</b></sub></a><br /><a href="#translation-SirCharlotte" title="Translation">🌍</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://www.instagram.com/celine_mge/"><img src="https://avatars.githubusercontent.com/u/57619777?v=4?s=100" width="100px;" alt="Slylele"/><br /><sub><b>Slylele</b></sub></a><br /><a href="https://github.com/freesewing/freesewing/commits?author=Slylele" title="Documentation">📖</a> <a href="#translation-Slylele" title="Translation">🌍</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Soazillon"><img src="https://avatars.githubusercontent.com/u/40845940?v=4?s=100" width="100px;" alt="Soazillon"/><br /><sub><b>Soazillon</b></sub></a><br /><a href="#translation-Soazillon" title="Translation">🌍</a></td>
|
||||
<td align="center"><a href="https://github.com/sannek"><img src="https://avatars.githubusercontent.com/u/17491062?v=4?s=100" width="100px;" alt="Sanne"/><br /><sub><b>Sanne</b></sub></a><br /><a href="https://github.com/freesewing/freesewing/commits?author=sannek" title="Code">💻</a> <a href="https://github.com/freesewing/freesewing/commits?author=sannek" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/Tyrannogina"><img src="https://avatars.githubusercontent.com/u/19556565?v=4?s=100" width="100px;" alt="Sara Latorre"/><br /><sub><b>Sara Latorre</b></sub></a><br /><a href="#translation-Tyrannogina" title="Translation">🌍</a></td>
|
||||
<td align="center"><a href="https://github.com/SeaZeeZee"><img src="https://avatars.githubusercontent.com/u/86711383?v=4?s=100" width="100px;" alt="SeaZeeZee"/><br /><sub><b>SeaZeeZee</b></sub></a><br /><a href="https://github.com/freesewing/freesewing/commits?author=SeaZeeZee" title="Documentation">📖</a> <a href="https://github.com/freesewing/freesewing/commits?author=SeaZeeZee" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/SimonbJohnson"><img src="https://avatars.githubusercontent.com/u/2110742?v=4?s=100" width="100px;" alt="SimonbJohnson"/><br /><sub><b>SimonbJohnson</b></sub></a><br /><a href="https://github.com/freesewing/freesewing/issues?q=author%3ASimonbJohnson" title="Bug reports">🐛</a></td>
|
||||
<td align="center"><a href="https://github.com/SirCharlotte"><img src="https://avatars.githubusercontent.com/u/63847870?v=4?s=100" width="100px;" alt="SirCharlotte"/><br /><sub><b>SirCharlotte</b></sub></a><br /><a href="#translation-SirCharlotte" title="Translation">🌍</a></td>
|
||||
<td align="center"><a href="https://www.instagram.com/celine_mge/"><img src="https://avatars.githubusercontent.com/u/57619777?v=4?s=100" width="100px;" alt="Slylele"/><br /><sub><b>Slylele</b></sub></a><br /><a href="https://github.com/freesewing/freesewing/commits?author=Slylele" title="Documentation">📖</a> <a href="#translation-Slylele" title="Translation">🌍</a></td>
|
||||
<td align="center"><a href="https://github.com/Soazillon"><img src="https://avatars.githubusercontent.com/u/40845940?v=4?s=100" width="100px;" alt="Soazillon"/><br /><sub><b>Soazillon</b></sub></a><br /><a href="#translation-Soazillon" title="Translation">🌍</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/SoneaTheBest"><img src="https://avatars.githubusercontent.com/u/64635425?v=4?s=100" width="100px;" alt="SoneaTheBest"/><br /><sub><b>SoneaTheBest</b></sub></a><br /><a href="#translation-SoneaTheBest" title="Translation">🌍</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="http://metafly.info/"><img src="https://avatars.githubusercontent.com/u/961256?v=4?s=100" width="100px;" alt="Stefan Sydow"/><br /><sub><b>Stefan Sydow</b></sub></a><br /><a href="#translation-stsydow" title="Translation">🌍</a> <a href="https://github.com/freesewing/freesewing/commits?author=stsydow" title="Documentation">📖</a> <a href="https://github.com/freesewing/freesewing/commits?author=stsydow" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/TriploidTree"><img src="https://avatars.githubusercontent.com/u/4170521?v=4?s=100" width="100px;" alt="Tríona"/><br /><sub><b>Tríona</b></sub></a><br /><a href="https://github.com/freesewing/freesewing/commits?author=TriploidTree" title="Documentation">📖</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/theUnmutual"><img src="https://avatars.githubusercontent.com/u/22374635?v=4?s=100" width="100px;" alt="Unmutual"/><br /><sub><b>Unmutual</b></sub></a><br /><a href="https://github.com/freesewing/freesewing/commits?author=theUnmutual" title="Documentation">📖</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/woutervdub"><img src="https://avatars.githubusercontent.com/u/24414629?v=4?s=100" width="100px;" alt="Wouter van Wageningen"/><br /><sub><b>Wouter van Wageningen</b></sub></a><br /><a href="https://github.com/freesewing/freesewing/commits?author=woutervdub" title="Code">💻</a> <a href="#design-woutervdub" title="Design">🎨</a> <a href="#tool-woutervdub" title="Tools">🔧</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/amysews"><img src="https://avatars.githubusercontent.com/u/25280778?v=4?s=100" width="100px;" alt="amysews"/><br /><sub><b>amysews</b></sub></a><br /><a href="https://github.com/freesewing/freesewing/commits?author=amysews" title="Documentation">📖</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/anna-puk"><img src="https://avatars.githubusercontent.com/u/100537439?v=4?s=100" width="100px;" alt="anna-puk"/><br /><sub><b>anna-puk</b></sub></a><br /><a href="https://github.com/freesewing/freesewing/commits?author=anna-puk" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/SoneaTheBest"><img src="https://avatars.githubusercontent.com/u/64635425?v=4?s=100" width="100px;" alt="SoneaTheBest"/><br /><sub><b>SoneaTheBest</b></sub></a><br /><a href="#translation-SoneaTheBest" title="Translation">🌍</a></td>
|
||||
<td align="center"><a href="http://metafly.info/"><img src="https://avatars.githubusercontent.com/u/961256?v=4?s=100" width="100px;" alt="Stefan Sydow"/><br /><sub><b>Stefan Sydow</b></sub></a><br /><a href="#translation-stsydow" title="Translation">🌍</a> <a href="https://github.com/freesewing/freesewing/commits?author=stsydow" title="Documentation">📖</a> <a href="https://github.com/freesewing/freesewing/commits?author=stsydow" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/TriploidTree"><img src="https://avatars.githubusercontent.com/u/4170521?v=4?s=100" width="100px;" alt="Tríona"/><br /><sub><b>Tríona</b></sub></a><br /><a href="https://github.com/freesewing/freesewing/commits?author=TriploidTree" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/theUnmutual"><img src="https://avatars.githubusercontent.com/u/22374635?v=4?s=100" width="100px;" alt="Unmutual"/><br /><sub><b>Unmutual</b></sub></a><br /><a href="https://github.com/freesewing/freesewing/commits?author=theUnmutual" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/woutervdub"><img src="https://avatars.githubusercontent.com/u/24414629?v=4?s=100" width="100px;" alt="Wouter van Wageningen"/><br /><sub><b>Wouter van Wageningen</b></sub></a><br /><a href="https://github.com/freesewing/freesewing/commits?author=woutervdub" title="Code">💻</a> <a href="#design-woutervdub" title="Design">🎨</a> <a href="#tool-woutervdub" title="Tools">🔧</a></td>
|
||||
<td align="center"><a href="https://github.com/amysews"><img src="https://avatars.githubusercontent.com/u/25280778?v=4?s=100" width="100px;" alt="amysews"/><br /><sub><b>amysews</b></sub></a><br /><a href="https://github.com/freesewing/freesewing/commits?author=amysews" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/anna-puk"><img src="https://avatars.githubusercontent.com/u/100537439?v=4?s=100" width="100px;" alt="anna-puk"/><br /><sub><b>anna-puk</b></sub></a><br /><a href="https://github.com/freesewing/freesewing/commits?author=anna-puk" title="Code">💻</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/beautifulsummermoon"><img src="https://avatars.githubusercontent.com/u/40396388?v=4?s=100" width="100px;" alt="beautifulsummermoon"/><br /><sub><b>beautifulsummermoon</b></sub></a><br /><a href="#translation-beautifulsummermoon" title="Translation">🌍</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/berce"><img src="https://avatars.githubusercontent.com/u/10439709?v=4?s=100" width="100px;" alt="berce"/><br /><sub><b>berce</b></sub></a><br /><a href="https://github.com/freesewing/freesewing/commits?author=berce" title="Documentation">📖</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/biou"><img src="https://avatars.githubusercontent.com/u/1340376?v=4?s=100" width="100px;" alt="biou"/><br /><sub><b>biou</b></sub></a><br /><a href="https://github.com/freesewing/freesewing/commits?author=biou" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/bobgeorgethe3rd"><img src="https://avatars.githubusercontent.com/u/16866285?v=4?s=100" width="100px;" alt="bobgeorgethe3rd"/><br /><sub><b>bobgeorgethe3rd</b></sub></a><br /><a href="https://github.com/freesewing/freesewing/commits?author=bobgeorgethe3rd" title="Code">💻</a> <a href="https://github.com/freesewing/freesewing/commits?author=bobgeorgethe3rd" title="Documentation">📖</a> <a href="#design-bobgeorgethe3rd" title="Design">🎨</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/brmlyklr"><img src="https://avatars.githubusercontent.com/u/22308713?v=4?s=100" width="100px;" alt="brmlyklr"/><br /><sub><b>brmlyklr</b></sub></a><br /><a href="https://github.com/freesewing/freesewing/commits?author=brmlyklr" title="Documentation">📖</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="http://www.chrisbarrett.fr"><img src="https://avatars.githubusercontent.com/u/2373249?v=4?s=100" width="100px;" alt="chri5b"/><br /><sub><b>chri5b</b></sub></a><br /><a href="https://github.com/freesewing/freesewing/commits?author=chri5b" title="Code">💻</a> <a href="https://github.com/freesewing/freesewing/commits?author=chri5b" title="Tests">⚠️</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/dingcycle"><img src="https://avatars.githubusercontent.com/u/1681985?v=4?s=100" width="100px;" alt="dingcycle"/><br /><sub><b>dingcycle</b></sub></a><br /><a href="#translation-dingcycle" title="Translation">🌍</a></td>
|
||||
<td align="center"><a href="https://github.com/beautifulsummermoon"><img src="https://avatars.githubusercontent.com/u/40396388?v=4?s=100" width="100px;" alt="beautifulsummermoon"/><br /><sub><b>beautifulsummermoon</b></sub></a><br /><a href="#translation-beautifulsummermoon" title="Translation">🌍</a></td>
|
||||
<td align="center"><a href="https://github.com/berce"><img src="https://avatars.githubusercontent.com/u/10439709?v=4?s=100" width="100px;" alt="berce"/><br /><sub><b>berce</b></sub></a><br /><a href="https://github.com/freesewing/freesewing/commits?author=berce" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/biou"><img src="https://avatars.githubusercontent.com/u/1340376?v=4?s=100" width="100px;" alt="biou"/><br /><sub><b>biou</b></sub></a><br /><a href="https://github.com/freesewing/freesewing/commits?author=biou" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/bobgeorgethe3rd"><img src="https://avatars.githubusercontent.com/u/16866285?v=4?s=100" width="100px;" alt="bobgeorgethe3rd"/><br /><sub><b>bobgeorgethe3rd</b></sub></a><br /><a href="https://github.com/freesewing/freesewing/commits?author=bobgeorgethe3rd" title="Code">💻</a> <a href="https://github.com/freesewing/freesewing/commits?author=bobgeorgethe3rd" title="Documentation">📖</a> <a href="#design-bobgeorgethe3rd" title="Design">🎨</a></td>
|
||||
<td align="center"><a href="https://github.com/brmlyklr"><img src="https://avatars.githubusercontent.com/u/22308713?v=4?s=100" width="100px;" alt="brmlyklr"/><br /><sub><b>brmlyklr</b></sub></a><br /><a href="https://github.com/freesewing/freesewing/commits?author=brmlyklr" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://www.chrisbarrett.fr"><img src="https://avatars.githubusercontent.com/u/2373249?v=4?s=100" width="100px;" alt="chri5b"/><br /><sub><b>chri5b</b></sub></a><br /><a href="https://github.com/freesewing/freesewing/commits?author=chri5b" title="Code">💻</a> <a href="https://github.com/freesewing/freesewing/commits?author=chri5b" title="Tests">⚠️</a></td>
|
||||
<td align="center"><a href="https://github.com/dingcycle"><img src="https://avatars.githubusercontent.com/u/1681985?v=4?s=100" width="100px;" alt="dingcycle"/><br /><sub><b>dingcycle</b></sub></a><br /><a href="#translation-dingcycle" title="Translation">🌍</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/drowned-in-books"><img src="https://avatars.githubusercontent.com/u/100040772?v=4?s=100" width="100px;" alt="drowned-in-books"/><br /><sub><b>drowned-in-books</b></sub></a><br /><a href="#question-drowned-in-books" title="Answering Questions">💬</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/econo202"><img src="https://avatars.githubusercontent.com/u/34138153?v=4?s=100" width="100px;" alt="econo202"/><br /><sub><b>econo202</b></sub></a><br /><a href="https://github.com/freesewing/freesewing/commits?author=econo202" title="Documentation">📖</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/ericamattos"><img src="https://avatars.githubusercontent.com/u/4341417?v=4?s=100" width="100px;" alt="ericamattos"/><br /><sub><b>ericamattos</b></sub></a><br /><a href="#translation-ericamattos" title="Translation">🌍</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/fightingrabbit"><img src="https://avatars.githubusercontent.com/u/25751445?v=4?s=100" width="100px;" alt="fightingrabbit"/><br /><sub><b>fightingrabbit</b></sub></a><br /><a href="https://github.com/freesewing/freesewing/commits?author=fightingrabbit" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/DocSpencer77"><img src="https://avatars.githubusercontent.com/u/43393580?v=4?s=100" width="100px;" alt="gaylyndie"/><br /><sub><b>gaylyndie</b></sub></a><br /><a href="https://github.com/freesewing/freesewing/commits?author=DocSpencer77" title="Documentation">📖</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/grimlokason"><img src="https://avatars.githubusercontent.com/u/5112238?v=4?s=100" width="100px;" alt="grimlokason"/><br /><sub><b>grimlokason</b></sub></a><br /><a href="https://github.com/freesewing/freesewing/commits?author=grimlokason" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://weblog.redisdead.net"><img src="https://avatars.githubusercontent.com/u/6494414?v=4?s=100" width="100px;" alt="hellgy"/><br /><sub><b>hellgy</b></sub></a><br /><a href="#design-hellgy" title="Design">🎨</a></td>
|
||||
<td align="center"><a href="https://github.com/drowned-in-books"><img src="https://avatars.githubusercontent.com/u/100040772?v=4?s=100" width="100px;" alt="drowned-in-books"/><br /><sub><b>drowned-in-books</b></sub></a><br /><a href="#question-drowned-in-books" title="Answering Questions">💬</a></td>
|
||||
<td align="center"><a href="https://github.com/econo202"><img src="https://avatars.githubusercontent.com/u/34138153?v=4?s=100" width="100px;" alt="econo202"/><br /><sub><b>econo202</b></sub></a><br /><a href="https://github.com/freesewing/freesewing/commits?author=econo202" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/ericamattos"><img src="https://avatars.githubusercontent.com/u/4341417?v=4?s=100" width="100px;" alt="ericamattos"/><br /><sub><b>ericamattos</b></sub></a><br /><a href="#translation-ericamattos" title="Translation">🌍</a></td>
|
||||
<td align="center"><a href="https://github.com/fightingrabbit"><img src="https://avatars.githubusercontent.com/u/25751445?v=4?s=100" width="100px;" alt="fightingrabbit"/><br /><sub><b>fightingrabbit</b></sub></a><br /><a href="https://github.com/freesewing/freesewing/commits?author=fightingrabbit" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/DocSpencer77"><img src="https://avatars.githubusercontent.com/u/43393580?v=4?s=100" width="100px;" alt="gaylyndie"/><br /><sub><b>gaylyndie</b></sub></a><br /><a href="https://github.com/freesewing/freesewing/commits?author=DocSpencer77" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/grimlokason"><img src="https://avatars.githubusercontent.com/u/5112238?v=4?s=100" width="100px;" alt="grimlokason"/><br /><sub><b>grimlokason</b></sub></a><br /><a href="https://github.com/freesewing/freesewing/commits?author=grimlokason" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://weblog.redisdead.net"><img src="https://avatars.githubusercontent.com/u/6494414?v=4?s=100" width="100px;" alt="hellgy"/><br /><sub><b>hellgy</b></sub></a><br /><a href="#design-hellgy" title="Design">🎨</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/jackseye"><img src="https://avatars.githubusercontent.com/u/27834526?v=4?s=100" width="100px;" alt="jackseye"/><br /><sub><b>jackseye</b></sub></a><br /><a href="https://github.com/freesewing/freesewing/commits?author=jackseye" title="Documentation">📖</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/marckiesel"><img src="https://avatars.githubusercontent.com/u/39653780?v=4?s=100" width="100px;" alt="marckiesel"/><br /><sub><b>marckiesel</b></sub></a><br /><a href="#translation-marckiesel" title="Translation">🌍</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Mesil"><img src="https://avatars.githubusercontent.com/u/14284175?v=4?s=100" width="100px;" alt="mesil"/><br /><sub><b>mesil</b></sub></a><br /><a href="https://github.com/freesewing/freesewing/issues?q=author%3Amesil" title="Bug reports">🐛</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/starfetch"><img src="https://avatars.githubusercontent.com/u/80041179?v=4?s=100" width="100px;" alt="starfetch"/><br /><sub><b>starfetch</b></sub></a><br /><a href="https://github.com/freesewing/freesewing/commits?author=starfetch" title="Code">💻</a> <a href="https://github.com/freesewing/freesewing/commits?author=starfetch" title="Documentation">📖</a> <a href="#translation-starfetch" title="Translation">🌍</a> <a href="#design-starfetch" title="Design">🎨</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/timorl"><img src="https://avatars.githubusercontent.com/u/4363804?v=4?s=100" width="100px;" alt="timorl"/><br /><sub><b>timorl</b></sub></a><br /><a href="https://github.com/freesewing/freesewing/commits?author=timorl" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/ttimearl"><img src="https://avatars.githubusercontent.com/u/77916590?v=4?s=100" width="100px;" alt="ttimearl"/><br /><sub><b>ttimearl</b></sub></a><br /><a href="#content-ttimearl" title="Content">🖋</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/chrisgloom"><img src="https://avatars.githubusercontent.com/u/15905991?v=4?s=100" width="100px;" alt="tuesgloomsday"/><br /><sub><b>tuesgloomsday</b></sub></a><br /><a href="https://github.com/freesewing/freesewing/commits?author=chrisgloom" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/jackseye"><img src="https://avatars.githubusercontent.com/u/27834526?v=4?s=100" width="100px;" alt="jackseye"/><br /><sub><b>jackseye</b></sub></a><br /><a href="https://github.com/freesewing/freesewing/commits?author=jackseye" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/marckiesel"><img src="https://avatars.githubusercontent.com/u/39653780?v=4?s=100" width="100px;" alt="marckiesel"/><br /><sub><b>marckiesel</b></sub></a><br /><a href="#translation-marckiesel" title="Translation">🌍</a></td>
|
||||
<td align="center"><a href="https://github.com/Mesil"><img src="https://avatars.githubusercontent.com/u/14284175?v=4?s=100" width="100px;" alt="mesil"/><br /><sub><b>mesil</b></sub></a><br /><a href="https://github.com/freesewing/freesewing/issues?q=author%3Amesil" title="Bug reports">🐛</a></td>
|
||||
<td align="center"><a href="https://github.com/starfetch"><img src="https://avatars.githubusercontent.com/u/80041179?v=4?s=100" width="100px;" alt="starfetch"/><br /><sub><b>starfetch</b></sub></a><br /><a href="https://github.com/freesewing/freesewing/commits?author=starfetch" title="Code">💻</a> <a href="https://github.com/freesewing/freesewing/commits?author=starfetch" title="Documentation">📖</a> <a href="#translation-starfetch" title="Translation">🌍</a> <a href="#design-starfetch" title="Design">🎨</a></td>
|
||||
<td align="center"><a href="https://github.com/timorl"><img src="https://avatars.githubusercontent.com/u/4363804?v=4?s=100" width="100px;" alt="timorl"/><br /><sub><b>timorl</b></sub></a><br /><a href="https://github.com/freesewing/freesewing/commits?author=timorl" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/ttimearl"><img src="https://avatars.githubusercontent.com/u/77916590?v=4?s=100" width="100px;" alt="ttimearl"/><br /><sub><b>ttimearl</b></sub></a><br /><a href="#content-ttimearl" title="Content">🖋</a></td>
|
||||
<td align="center"><a href="https://github.com/chrisgloom"><img src="https://avatars.githubusercontent.com/u/15905991?v=4?s=100" width="100px;" alt="tuesgloomsday"/><br /><sub><b>tuesgloomsday</b></sub></a><br /><a href="https://github.com/freesewing/freesewing/commits?author=chrisgloom" title="Documentation">📖</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/valadaptive"><img src="https://avatars.githubusercontent.com/u/79560998?v=4?s=100" width="100px;" alt="valadaptive"/><br /><sub><b>valadaptive</b></sub></a><br /><a href="https://github.com/freesewing/freesewing/commits?author=valadaptive" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/viocky"><img src="https://avatars.githubusercontent.com/u/39279173?v=4?s=100" width="100px;" alt="viocky"/><br /><sub><b>viocky</b></sub></a><br /><a href="#translation-viocky" title="Translation">🌍</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/woolishboy"><img src="https://avatars.githubusercontent.com/u/57816321?v=4?s=100" width="100px;" alt="woolishboy"/><br /><sub><b>woolishboy</b></sub></a><br /><a href="https://github.com/freesewing/freesewing/commits?author=woolishboy" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/cloutiy"><img src="https://avatars.githubusercontent.com/u/8433147?v=4?s=100" width="100px;" alt="yc"/><br /><sub><b>yc</b></sub></a><br /><a href="#translation-cloutiy" title="Translation">🌍</a></td>
|
||||
<td align="center"><a href="https://github.com/valadaptive"><img src="https://avatars.githubusercontent.com/u/79560998?v=4?s=100" width="100px;" alt="valadaptive"/><br /><sub><b>valadaptive</b></sub></a><br /><a href="https://github.com/freesewing/freesewing/commits?author=valadaptive" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/viocky"><img src="https://avatars.githubusercontent.com/u/39279173?v=4?s=100" width="100px;" alt="viocky"/><br /><sub><b>viocky</b></sub></a><br /><a href="#translation-viocky" title="Translation">🌍</a></td>
|
||||
<td align="center"><a href="https://github.com/woolishboy"><img src="https://avatars.githubusercontent.com/u/57816321?v=4?s=100" width="100px;" alt="woolishboy"/><br /><sub><b>woolishboy</b></sub></a><br /><a href="https://github.com/freesewing/freesewing/commits?author=woolishboy" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/cloutiy"><img src="https://avatars.githubusercontent.com/u/8433147?v=4?s=100" width="100px;" alt="yc"/><br /><sub><b>yc</b></sub></a><br /><a href="#translation-cloutiy" title="Translation">🌍</a></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
|
|
@ -6,7 +6,7 @@ import { designs, plugins, packages } from './software/index.mjs'
|
|||
* order. This file takes care of that
|
||||
*/
|
||||
|
||||
const first = ['core', 'config-helpers', 'remark-jargon', 'snapseries']
|
||||
const first = ['core', 'remark-jargon', 'snapseries']
|
||||
const blocks = ['brian', 'titan', 'bella', 'breanna']
|
||||
const extended = ['bent', 'simon', 'carlton', 'ursula']
|
||||
const last = ['i18n']
|
||||
|
|
|
@ -3,7 +3,6 @@ _types:
|
|||
peer:
|
||||
'@freesewing/core': &freesewing '^{{version}}'
|
||||
'@freesewing/plugin-bundle': *freesewing
|
||||
'@freesewing/config-helpers': *freesewing
|
||||
dev:
|
||||
'mocha': &mocha '^10.0.0'
|
||||
'chai': &chai '^4.2.0'
|
||||
|
@ -50,6 +49,7 @@ charlie:
|
|||
'@freesewing/plugin-bartack': *freesewing
|
||||
'@freesewing/plugin-mirror': *freesewing
|
||||
'@freesewing/titan': *freesewing
|
||||
'@freesewing/snapseries': *freesewing
|
||||
core:
|
||||
_:
|
||||
'bezier-js': '^6.1.0'
|
||||
|
@ -111,6 +111,7 @@ legend:
|
|||
paco:
|
||||
peer:
|
||||
'@freesewing/titan': *freesewing
|
||||
'@freesewing/snapseries': *freesewing
|
||||
plugin-bundle:
|
||||
dev:
|
||||
'@freesewing/plugin-banner': *freesewing
|
||||
|
@ -154,6 +155,10 @@ rehype-jargon:
|
|||
rehype-highlight-lines:
|
||||
_:
|
||||
'unist-util-remove': '^3.1.0'
|
||||
sandy:
|
||||
'@freesewing/snapseries': *freesewing
|
||||
shin:
|
||||
'@freesewing/snapseries': *freesewing
|
||||
simon:
|
||||
peer:
|
||||
'@freesewing/brian': *freesewing
|
||||
|
@ -176,6 +181,10 @@ teagan:
|
|||
peer:
|
||||
'@freesewing/brian': *freesewing
|
||||
'@freesewing/plugin-bust': *freesewing
|
||||
titan:
|
||||
'@freesewing/snapseries': *freesewing
|
||||
trayvon:
|
||||
'@freesewing/snapseries': *freesewing
|
||||
wahid:
|
||||
peer:
|
||||
'@freesewing/brian': *freesewing
|
||||
|
|
|
@ -8,13 +8,6 @@ core:
|
|||
- patterns
|
||||
- sewing
|
||||
- sewing patterns
|
||||
components:
|
||||
- react
|
||||
css-theme:
|
||||
- css
|
||||
- scss
|
||||
- sass
|
||||
- theme
|
||||
design:
|
||||
- design
|
||||
- diy
|
||||
|
@ -40,10 +33,6 @@ models:
|
|||
- fashion
|
||||
- measurements
|
||||
- sizes
|
||||
mui-theme:
|
||||
- material-ui
|
||||
- react
|
||||
- theme
|
||||
other:
|
||||
- design
|
||||
- diy
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
{
|
||||
"snapseries": "A FreeSewing package to facilitate snapped percentage options in designs",
|
||||
"core": "A library for creating made-to-measure sewing patterns",
|
||||
"i18n": "Translations for the FreeSewing project",
|
||||
"models": "Body measurements data for a range of default sizes",
|
||||
|
|
|
@ -13,6 +13,8 @@ import { version } from '../data.mjs'
|
|||
import { __loadPatternDefaults } from './config.mjs'
|
||||
import cloneDeep from 'lodash.clonedeep'
|
||||
|
||||
const DISTANCE_DEBUG = false
|
||||
|
||||
//////////////////////////////////////////////
|
||||
// CONSTRUCTOR //
|
||||
//////////////////////////////////////////////
|
||||
|
@ -474,14 +476,25 @@ Pattern.prototype.__addPartOptions = function (part) {
|
|||
// Keep design parts immutable in the pattern or risk subtle bugs
|
||||
this.config.options[optionName] = Object.freeze(part.options[optionName])
|
||||
this.store.log.debug(`🔵 __${optionName}__ option loaded from part \`${part.name}\``)
|
||||
} else if (
|
||||
this.__mutated.optionDistance[optionName] < this.__mutated.partDistance[part.name]
|
||||
) {
|
||||
} else {
|
||||
if (DISTANCE_DEBUG)
|
||||
this.store.log.debug(
|
||||
'optionDistance for ' +
|
||||
optionName +
|
||||
' is ' +
|
||||
this.__mutated.optionDistance[optionName] +
|
||||
', and partDistance for ' +
|
||||
part.name +
|
||||
' is ' +
|
||||
this.__mutated.partDistance[part.name]
|
||||
)
|
||||
if (this.__mutated.optionDistance[optionName] > this.__mutated.partDistance[part.name]) {
|
||||
this.config.options[optionName] = part.options[optionName]
|
||||
this.store.log.debug(`🟣 __${optionName}__ option overwritten by \`${part.name}\``)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (part.from) this.__addPartOptions(part.from)
|
||||
if (part.after) {
|
||||
if (Array.isArray(part.after)) {
|
||||
|
@ -1256,41 +1269,104 @@ Pattern.prototype.__resolveParts = function (count = 0, distance = 0) {
|
|||
}
|
||||
}
|
||||
distance++
|
||||
if (DISTANCE_DEBUG) this.store.log.debug('Distance incremented to ' + distance)
|
||||
for (const part of this.designConfig.parts) {
|
||||
if (typeof this.__mutated.partDistance[part.name] === 'undefined')
|
||||
if (typeof this.__mutated.partDistance[part.name] === 'undefined') {
|
||||
this.__mutated.partDistance[part.name] = distance
|
||||
if (DISTANCE_DEBUG)
|
||||
this.store.log.debug(
|
||||
'Base partDistance for ' + part.name + ' is ' + this.__mutated.partDistance[part.name]
|
||||
)
|
||||
}
|
||||
}
|
||||
for (const [name, part] of Object.entries(this.__designParts)) {
|
||||
const current_part_distance = this.__mutated.partDistance[part.name]
|
||||
const proposed_dependent_part_distance = current_part_distance + 1
|
||||
// Hide when hideAll is set
|
||||
if (part.hideAll) this.__mutated.partHide[part.name] = true
|
||||
// Inject (from)
|
||||
if (part.from) {
|
||||
if (DISTANCE_DEBUG) this.store.log.debug('Processing ' + part.name + ' "from:"')
|
||||
this.__setFromHide(part, name, part.from.name)
|
||||
this.__designParts[part.from.name] = part.from
|
||||
this.__inject[name] = part.from.name
|
||||
this.__mutated.partDistance[part.from.name] = distance
|
||||
if (
|
||||
typeof this.__mutated.partDistance[part.from.name] === 'undefined' ||
|
||||
this.__mutated.partDistance[part.from.name] < proposed_dependent_part_distance
|
||||
) {
|
||||
this.__mutated.partDistance[part.from.name] = proposed_dependent_part_distance
|
||||
if (DISTANCE_DEBUG)
|
||||
this.store.log.debug(
|
||||
'"from:" partDistance for ' +
|
||||
part.from.name +
|
||||
' is ' +
|
||||
this.__mutated.partDistance[part.from.name]
|
||||
)
|
||||
}
|
||||
}
|
||||
// Simple dependency (after)
|
||||
if (part.after) {
|
||||
if (DISTANCE_DEBUG) this.store.log.debug('Processing ' + part.name + ' "after:"')
|
||||
if (Array.isArray(part.after)) {
|
||||
for (const dep of part.after) {
|
||||
this.__setAfterHide(part, name, dep.name)
|
||||
this.__mutated.partDistance[dep.name] = distance
|
||||
this.__designParts[dep.name] = dep
|
||||
this.__addDependency(name, part, dep)
|
||||
if (
|
||||
typeof this.__mutated.partDistance[dep.name] === 'undefined' ||
|
||||
this.__mutated.partDistance[dep.name] < proposed_dependent_part_distance
|
||||
) {
|
||||
this.__mutated.partDistance[dep.name] = proposed_dependent_part_distance
|
||||
if (DISTANCE_DEBUG)
|
||||
this.store.log.debug(
|
||||
'"after:" partDistance for ' +
|
||||
dep.name +
|
||||
' is ' +
|
||||
this.__mutated.partDistance[dep.name]
|
||||
)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this.__setAfterHide(part, name, part.after.name)
|
||||
this.__mutated.partDistance[part.after.name] = distance
|
||||
this.__designParts[part.after.name] = part.after
|
||||
this.__addDependency(name, part, part.after)
|
||||
if (
|
||||
typeof this.__mutated.partDistance[part.after.name] === 'undefined' ||
|
||||
this.__mutated.partDistance[part.after.name] < proposed_dependent_part_distance
|
||||
) {
|
||||
this.__mutated.partDistance[part.after.name] = proposed_dependent_part_distance
|
||||
if (DISTANCE_DEBUG)
|
||||
this.store.log.debug(
|
||||
'"after:" partDistance for ' +
|
||||
part.after.name +
|
||||
' is ' +
|
||||
this.__mutated.partDistance[part.after.name]
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Did we discover any new dependencies?
|
||||
const len = Object.keys(this.__designParts).length
|
||||
// If so, resolve recursively
|
||||
if (len > count) return this.__resolveParts(len, distance)
|
||||
if (len > count) {
|
||||
if (DISTANCE_DEBUG) this.store.log.debug('Recursing...')
|
||||
return this.__resolveParts(len, distance)
|
||||
}
|
||||
// Print final part distances.
|
||||
for (const part of this.designConfig.parts) {
|
||||
let qualifier = ''
|
||||
if (DISTANCE_DEBUG) qualifier = 'final '
|
||||
this.store.log.debug(
|
||||
'⚪️ `' +
|
||||
part.name +
|
||||
'` ' +
|
||||
qualifier +
|
||||
'options priority is __' +
|
||||
this.__mutated.partDistance[part.name] +
|
||||
'__'
|
||||
)
|
||||
}
|
||||
|
||||
for (const part of Object.values(this.__designParts)) this.__addPartConfig(part)
|
||||
|
||||
|
|
|
@ -631,7 +631,7 @@ export function __asNumber(value, param, method, log) {
|
|||
value = Number(value)
|
||||
return value
|
||||
} catch {
|
||||
this.log.error(
|
||||
log.error(
|
||||
`Called \`${method}(${param})\` but \`${param}\` is not a number nor can it be cast to one`
|
||||
)
|
||||
}
|
||||
|
|
|
@ -63,7 +63,6 @@ describe('Part', () => {
|
|||
const part = {
|
||||
name: 'test',
|
||||
draft: ({ getId, part }) => {
|
||||
console.log(getId)
|
||||
id = getId()
|
||||
id = getId()
|
||||
id = getId()
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
import chai from 'chai'
|
||||
import { round, Path, Point, Design } from '../src/index.mjs'
|
||||
import { round, Path, Point } from '../src/index.mjs'
|
||||
import { pathsProxy } from '../src/path.mjs'
|
||||
|
||||
const expect = chai.expect
|
||||
|
||||
describe('Path', () => {
|
||||
describe('smurve', () => {
|
||||
it('Should draw a smurve', () => {
|
||||
const part = {
|
||||
name: 'test',
|
||||
draft: ({ Point, points, Path, paths, part }) => {
|
||||
const points = {}
|
||||
points.from = new Point(10, 20)
|
||||
points.cp1 = new Point(40, 10)
|
||||
points.cp2 = new Point(60, 30)
|
||||
|
@ -15,275 +15,157 @@ describe('Path', () => {
|
|||
points.scp2 = new Point(140, 10)
|
||||
points.sto = new Point(170, 20)
|
||||
|
||||
paths.test = new Path()
|
||||
const test = new Path()
|
||||
.move(points.from)
|
||||
.curve(points.cp1, points.cp2, points.to)
|
||||
.smurve(points.scp2, points.sto)
|
||||
|
||||
return part
|
||||
},
|
||||
}
|
||||
const design = new Design({ parts: [part] })
|
||||
const pattern = new design()
|
||||
pattern.draft().render()
|
||||
|
||||
expect(round(pattern.parts[0].test.paths.test.ops[2].cp1.x)).to.equal(120)
|
||||
expect(round(pattern.parts[0].test.paths.test.ops[2].cp1.y)).to.equal(10)
|
||||
expect(round(test.ops[2].cp1.x)).to.equal(120)
|
||||
expect(round(test.ops[2].cp1.y)).to.equal(10)
|
||||
})
|
||||
|
||||
it('Should draw a smurve_', () => {
|
||||
const part = {
|
||||
name: 'test',
|
||||
draft: ({ Point, points, Path, paths, part }) => {
|
||||
const points = {}
|
||||
points.from = new Point(10, 20)
|
||||
points.cp1 = new Point(40, 10)
|
||||
points.cp2 = new Point(60, 30)
|
||||
points.to = new Point(90, 20)
|
||||
points.sto = new Point(170, 20)
|
||||
|
||||
paths.test = new Path()
|
||||
const test = new Path()
|
||||
.move(points.from)
|
||||
.curve(points.cp1, points.cp2, points.to)
|
||||
.smurve_(points.sto)
|
||||
|
||||
return part
|
||||
},
|
||||
}
|
||||
const design = new Design({ parts: [part] })
|
||||
const pattern = new design()
|
||||
pattern.draft().render()
|
||||
|
||||
expect(round(pattern.parts[0].test.paths.test.ops[2].cp1.x)).to.equal(120)
|
||||
expect(round(pattern.parts[0].test.paths.test.ops[2].cp1.y)).to.equal(10)
|
||||
expect(round(test.ops[2].cp1.x)).to.equal(120)
|
||||
expect(round(test.ops[2].cp1.y)).to.equal(10)
|
||||
})
|
||||
|
||||
it('Should log a warning when passing a non-Point to smurve()', () => {
|
||||
const part = {
|
||||
name: 'test',
|
||||
draft: ({ Point, points, Path, paths, part }) => {
|
||||
const points = {}
|
||||
points.from = new Point(10, 20)
|
||||
points.cp1 = new Point(40, 10)
|
||||
points.cp2 = new Point(60, 30)
|
||||
points.to = new Point(90, 20)
|
||||
|
||||
paths.test = new Path()
|
||||
const messages = []
|
||||
const log = { warning: (msg) => messages.push(msg) }
|
||||
new Path()
|
||||
.__withLog(log)
|
||||
.move(points.from)
|
||||
.curve(points.cp1, points.cp2, points.to)
|
||||
.smurve('hi', 'there')
|
||||
|
||||
return part
|
||||
},
|
||||
}
|
||||
const design = new Design({ parts: [part] })
|
||||
const pattern = new design()
|
||||
pattern.draft()
|
||||
expect(pattern.setStores[0].logs.warning.length).to.equal(2)
|
||||
expect(pattern.setStores[0].logs.warning[0]).to.equal(
|
||||
'Called `Path.smurve(cp2, to)` but `to` is not a `Point` object'
|
||||
)
|
||||
expect(messages.length).to.equal(2)
|
||||
expect(messages[0]).to.equal('Called `Path.smurve(cp2, to)` but `to` is not a `Point` object')
|
||||
})
|
||||
|
||||
it('Should log a warning when passing a non-Point to smurve_()', () => {
|
||||
const part = {
|
||||
name: 'test',
|
||||
draft: ({ Path, paths, part }) => {
|
||||
paths.test = new Path().smurve_('hi')
|
||||
|
||||
return part
|
||||
},
|
||||
const messages = []
|
||||
const log = { warning: (msg) => messages.push(msg) }
|
||||
try {
|
||||
new Path().__withLog(log).smurve_('hi')
|
||||
} catch (e) {
|
||||
expect('' + e).to.contain("TypeError: Cannot read properties of undefined (reading 'cp2')")
|
||||
} finally {
|
||||
expect(messages.length).to.equal(1)
|
||||
expect(messages[0]).to.equal('Called `Path.smurve_(to)` but `to` is not a `Point` object')
|
||||
}
|
||||
const design = new Design({ parts: [part] })
|
||||
const pattern = new design()
|
||||
pattern.draft()
|
||||
expect(pattern.setStores[0].logs.warning.length).to.equal(1)
|
||||
expect(pattern.setStores[0].logs.warning[0]).to.equal(
|
||||
'Called `Path.smurve_(to)` but `to` is not a `Point` object'
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
it('Should log a warning when passing a non-Path to the paths proxy', () => {
|
||||
const part = {
|
||||
name: 'test',
|
||||
draft: ({ paths, part }) => {
|
||||
paths.test = 'Wriing code can get very lonely sometimes'
|
||||
const messages = []
|
||||
const log = { warning: (msg) => messages.push(msg) }
|
||||
const pathsObj = {}
|
||||
const paths = pathsProxy(pathsObj, log)
|
||||
paths.set(pathsObj, 'test', 'Writing code can get very lonely sometimes')
|
||||
|
||||
return part
|
||||
},
|
||||
}
|
||||
const design = new Design({ parts: [part] })
|
||||
const pattern = new design()
|
||||
pattern.draft()
|
||||
expect(pattern.setStores[0].logs.warning.length).to.equal(2)
|
||||
expect(pattern.setStores[0].logs.warning[0]).to.equal(
|
||||
'`paths.test` was set with a value that is not a `Path` object'
|
||||
)
|
||||
expect(pattern.setStores[0].logs.warning[1]).to.equal(
|
||||
'Could not set `name` property on `paths.test`'
|
||||
)
|
||||
expect(messages.length).to.equal(2)
|
||||
expect(messages[0]).to.equal('`paths.test` was set with a value that is not a `Path` object')
|
||||
expect(messages[1]).to.equal('Could not set `name` property on `paths.test`')
|
||||
})
|
||||
|
||||
describe('offset', () => {
|
||||
it('Should offset a line', () => {
|
||||
const part = {
|
||||
name: 'test',
|
||||
draft: ({ paths, Path, Point, part }) => {
|
||||
paths.line = new Path().move(new Point(0, 0)).line(new Point(0, 40))
|
||||
paths.offset = paths.line.offset(10)
|
||||
return part
|
||||
},
|
||||
}
|
||||
const design = new Design({ parts: [part] })
|
||||
const pattern = new design()
|
||||
pattern.draft().render()
|
||||
expect(pattern.parts[0].test.paths.offset.bottomRight.x).to.equal(-10)
|
||||
expect(pattern.parts[0].test.paths.offset.bottomRight.y).to.equal(40)
|
||||
const line = new Path().move(new Point(0, 0)).line(new Point(0, 40))
|
||||
const offLine = line.offset(10)
|
||||
const bbox = offLine.bbox()
|
||||
expect(bbox.bottomRight.x).to.equal(-10)
|
||||
expect(bbox.bottomRight.y).to.equal(40)
|
||||
})
|
||||
|
||||
it('Should offset a curve', () => {
|
||||
const part = {
|
||||
name: 'test',
|
||||
draft: ({ paths, Path, Point, part }) => {
|
||||
paths.curve = new Path()
|
||||
const curve = new Path()
|
||||
.move(new Point(0, 0))
|
||||
.curve(new Point(0, 40), new Point(123, 34), new Point(23, 4))
|
||||
paths.offset = paths.curve.offset(10)
|
||||
return part
|
||||
},
|
||||
}
|
||||
const design = new Design({ parts: [part] })
|
||||
const pattern = new design()
|
||||
pattern.draft().render()
|
||||
expect(round(pattern.parts[0].test.paths.offset.bottomRight.x)).to.equal(72.18)
|
||||
expect(round(pattern.parts[0].test.paths.offset.bottomRight.y)).to.equal(38.26)
|
||||
const offset = curve.offset(10)
|
||||
const bbox = offset.bbox()
|
||||
expect(round(bbox.bottomRight.x)).to.equal(72.18)
|
||||
expect(round(bbox.bottomRight.y)).to.equal(38.26)
|
||||
})
|
||||
|
||||
it('Should offset a curve where cp1 = start', () => {
|
||||
const part = {
|
||||
name: 'test',
|
||||
draft: ({ paths, Path, Point, part }) => {
|
||||
paths.curve = new Path().move(new Point(0, 0))._curve(new Point(123, 34), new Point(23, 4))
|
||||
paths.offset = paths.curve.offset(10)
|
||||
return part
|
||||
},
|
||||
}
|
||||
const design = new Design({ parts: [part] })
|
||||
const pattern = new design()
|
||||
pattern.draft().render()
|
||||
expect(round(pattern.parts[0].test.paths.offset.bottomRight.x)).to.equal(72.63)
|
||||
expect(round(pattern.parts[0].test.paths.offset.bottomRight.y)).to.equal(26.47)
|
||||
const curve = new Path().move(new Point(0, 0))._curve(new Point(123, 34), new Point(23, 4))
|
||||
const offset = curve.offset(10)
|
||||
const bbox = offset.bbox()
|
||||
expect(round(bbox.bottomRight.x)).to.equal(72.63)
|
||||
expect(round(bbox.bottomRight.y)).to.equal(26.47)
|
||||
})
|
||||
|
||||
it('Should offset a curve where cp2 = end', () => {
|
||||
const part = {
|
||||
name: 'test',
|
||||
draft: ({ paths, Path, Point, part }) => {
|
||||
paths.curve = new Path().move(new Point(0, 0)).curve_(new Point(40, 0), new Point(123, 34))
|
||||
paths.offset = paths.curve.offset(10)
|
||||
return part
|
||||
},
|
||||
}
|
||||
const design = new Design({ parts: [part] })
|
||||
const pattern = new design()
|
||||
pattern.draft().render()
|
||||
expect(round(pattern.parts[0].test.paths.offset.bottomRight.x)).to.equal(119.86)
|
||||
expect(round(pattern.parts[0].test.paths.offset.bottomRight.y)).to.equal(43.49)
|
||||
const curve = new Path().move(new Point(0, 0)).curve_(new Point(40, 0), new Point(123, 34))
|
||||
const offset = curve.offset(10)
|
||||
const bbox = offset.bbox()
|
||||
expect(round(bbox.bottomRight.x)).to.equal(119.86)
|
||||
expect(round(bbox.bottomRight.y)).to.equal(43.49)
|
||||
})
|
||||
})
|
||||
|
||||
describe('length', () => {
|
||||
it('Should return the length of a line', () => {
|
||||
const part = {
|
||||
name: 'test',
|
||||
draft: ({ paths, Path, Point, part }) => {
|
||||
paths.line = new Path().move(new Point(0, 0)).line(new Point(40, 0))
|
||||
return part
|
||||
},
|
||||
}
|
||||
const design = new Design({ parts: [part] })
|
||||
const pattern = new design()
|
||||
pattern.draft().render()
|
||||
expect(pattern.parts[0].test.paths.line.length()).to.equal(40)
|
||||
const line = new Path().move(new Point(0, 0)).line(new Point(40, 0))
|
||||
expect(line.length()).to.equal(40)
|
||||
})
|
||||
|
||||
it('Should return the length of a curve', () => {
|
||||
const part = {
|
||||
name: 'test',
|
||||
draft: ({ paths, Path, Point, part }) => {
|
||||
paths.curve = new Path()
|
||||
const curve = new Path()
|
||||
.move(new Point(0, 0))
|
||||
.curve(new Point(0, 40), new Point(123, 34), new Point(23, 4))
|
||||
.close()
|
||||
return part
|
||||
},
|
||||
}
|
||||
const design = new Design({ parts: [part] })
|
||||
const pattern = new design()
|
||||
pattern.draft().render()
|
||||
expect(round(pattern.parts[0].test.paths.curve.length())).to.equal(145.11)
|
||||
expect(round(curve.length())).to.equal(145.11)
|
||||
})
|
||||
})
|
||||
|
||||
it('Should return the rough length of a curve', () => {
|
||||
const part = {
|
||||
name: 'test',
|
||||
draft: ({ paths, Path, Point, part }) => {
|
||||
paths.curve = new Path()
|
||||
const curve = new Path()
|
||||
.move(new Point(0, 0))
|
||||
.curve(new Point(0, 50), new Point(100, 50), new Point(100, 0))
|
||||
.close()
|
||||
return part
|
||||
},
|
||||
}
|
||||
const design = new Design({ parts: [part] })
|
||||
const pattern = new design()
|
||||
pattern.draft().render()
|
||||
expect(round(pattern.parts[0].test.paths.curve.roughLength())).to.equal(300)
|
||||
expect(round(curve.roughLength())).to.equal(300)
|
||||
})
|
||||
|
||||
it('Should return the rough length of a line', () => {
|
||||
const part = {
|
||||
name: 'test',
|
||||
draft: ({ paths, Path, Point, part }) => {
|
||||
paths.line = new Path().move(new Point(0, 0)).line(new Point(0, 50))
|
||||
return part
|
||||
},
|
||||
}
|
||||
const design = new Design({ parts: [part] })
|
||||
const pattern = new design()
|
||||
pattern.draft().render()
|
||||
expect(round(pattern.parts[0].test.paths.line.roughLength())).to.equal(50)
|
||||
const line = new Path().move(new Point(0, 0)).line(new Point(0, 50))
|
||||
expect(round(line.roughLength())).to.equal(50)
|
||||
})
|
||||
|
||||
it('Should return the path start point', () => {
|
||||
const part = {
|
||||
name: 'test',
|
||||
draft: ({ paths, Path, Point, part }) => {
|
||||
paths.curve = new Path()
|
||||
const curve = new Path()
|
||||
.move(new Point(123, 456))
|
||||
.curve(new Point(0, 40), new Point(123, 34), new Point(23, 4))
|
||||
.close()
|
||||
return part
|
||||
},
|
||||
}
|
||||
const design = new Design({ parts: [part] })
|
||||
const pattern = new design()
|
||||
pattern.draft().render()
|
||||
expect(pattern.parts[0].test.paths.curve.start().x).to.equal(123)
|
||||
expect(pattern.parts[0].test.paths.curve.start().y).to.equal(456)
|
||||
expect(curve.start().x).to.equal(123)
|
||||
expect(curve.start().y).to.equal(456)
|
||||
})
|
||||
|
||||
it('Should return the path end point', () => {
|
||||
const part = {
|
||||
name: 'test',
|
||||
draft: ({ paths, Path, Point, part }) => {
|
||||
paths.curve = new Path()
|
||||
const curve = new Path()
|
||||
.move(new Point(123, 456))
|
||||
.curve(new Point(0, 40), new Point(123, 34), new Point(23, 4))
|
||||
.close()
|
||||
return part
|
||||
},
|
||||
}
|
||||
const design = new Design({ parts: [part] })
|
||||
const pattern = new design()
|
||||
pattern.draft().render()
|
||||
expect(pattern.parts[0].test.paths.curve.end().x).to.equal(123)
|
||||
expect(pattern.parts[0].test.paths.curve.end().y).to.equal(456)
|
||||
expect(curve.end().x).to.equal(123)
|
||||
expect(curve.end().y).to.equal(456)
|
||||
})
|
||||
|
||||
it('Should calculate that path boundary', () => {
|
||||
|
@ -810,24 +692,17 @@ describe('Path', () => {
|
|||
})
|
||||
|
||||
it('Should overwrite a path attribute', () => {
|
||||
const part = {
|
||||
name: 'test',
|
||||
draft: ({ paths, Path, Point, part }) => {
|
||||
paths.line = new Path()
|
||||
const line = new Path()
|
||||
line.log = { debug: () => {} }
|
||||
line
|
||||
.move(new Point(0, 0))
|
||||
.line(new Point(0, 40))
|
||||
.attr('class', 'foo')
|
||||
.attr('class', 'bar')
|
||||
.attr('class', 'overwritten', true)
|
||||
return part
|
||||
},
|
||||
}
|
||||
const design = new Design({ parts: [part] })
|
||||
const pattern = new design()
|
||||
pattern.draft().render()
|
||||
|
||||
// Paths from shorthand have the log method
|
||||
expect(pattern.parts[0].test.paths.line.attributes.get('class')).to.equal('overwritten')
|
||||
expect(line.attributes.get('class')).to.equal('overwritten')
|
||||
})
|
||||
|
||||
it('Should move along a path even if it lands just on a joint', () => {
|
||||
|
@ -1054,7 +929,6 @@ describe('Path', () => {
|
|||
it('Should log a warning when setting an attribute without a name', () => {
|
||||
let invalid = false
|
||||
const log = { warning: () => (invalid = true) }
|
||||
expect(invalid).to.equal(false)
|
||||
new Path().__withLog(log).attr()
|
||||
expect(invalid).to.equal(true)
|
||||
})
|
||||
|
@ -1062,52 +936,42 @@ describe('Path', () => {
|
|||
it('Should log a warning when setting an attribute without a value', () => {
|
||||
let invalid = false
|
||||
const log = { warning: () => (invalid = true) }
|
||||
expect(invalid).to.equal(false)
|
||||
new Path().__withLog(log).attr('test')
|
||||
expect(invalid).to.equal(true)
|
||||
})
|
||||
|
||||
it('Should log a warning when calling offset without a distance', () => {
|
||||
const part = {
|
||||
name: 'test',
|
||||
draft: ({ paths, Path, Point, points }) => {
|
||||
paths.line = new Path().move(new Point(0, 0)).line(new Point(0, 40)).attr('class', 'foo')
|
||||
paths.a = new Path().move(points.a).line(points.b)
|
||||
paths.b = paths.a.offset()
|
||||
return part
|
||||
},
|
||||
}
|
||||
const design = new Design({ parts: [part] })
|
||||
const pattern = new design()
|
||||
pattern.draft()
|
||||
expect(pattern.setStores[0].logs.error.length).to.equal(2)
|
||||
expect(pattern.setStores[0].logs.error[0]).to.equal(
|
||||
'Called `Path.offset(distance)` but `distance` is not a number'
|
||||
)
|
||||
it('Should log an error when calling offset without a distance', () => {
|
||||
let invalid = true
|
||||
const log = { warning: () => {}, error: () => (invalid = true) }
|
||||
const pointLog = { error: () => {} }
|
||||
const pointA = new Point(0, 0).__withLog(pointLog)
|
||||
const pointB = new Point(0, 40).__withLog(pointLog)
|
||||
const a = new Path().__withLog(log).move(pointA).line(pointB)
|
||||
a.offset()
|
||||
expect(invalid).to.equal(true)
|
||||
})
|
||||
|
||||
it('Should log a warning when calling join without a path', () => {
|
||||
const part = {
|
||||
name: 'test',
|
||||
draft: ({ paths, Path, Point, points }) => {
|
||||
paths.line = new Path().move(new Point(0, 0)).line(new Point(0, 40)).attr('class', 'foo')
|
||||
paths.a = new Path().move(points.a).line(points.b).join()
|
||||
return part
|
||||
},
|
||||
it('Should log an error when calling join without a path', () => {
|
||||
let invalid = false
|
||||
const log = { error: () => (invalid = true) }
|
||||
const line = new Path()
|
||||
.move(new Point(0, 0))
|
||||
.line(new Point(0, 40))
|
||||
.attr('class', 'foo')
|
||||
.__withLog(log)
|
||||
|
||||
try {
|
||||
line.join()
|
||||
} catch (e) {
|
||||
expect('' + e).to.contain("TypeError: Cannot read properties of undefined (reading 'ops')")
|
||||
} finally {
|
||||
expect(invalid).to.equal(true)
|
||||
}
|
||||
const design = new Design({ parts: [part] })
|
||||
const pattern = new design()
|
||||
pattern.draft()
|
||||
expect(pattern.setStores[0].logs.error.length).to.equal(2)
|
||||
expect(pattern.setStores[0].logs.error[0]).to.equal(
|
||||
'Called `Path.join(that)` but `that` is not a `Path` object'
|
||||
)
|
||||
})
|
||||
|
||||
it('Should log a warning when calling start on a path without drawing operations', () => {
|
||||
let invalid = false
|
||||
const log = { error: () => (invalid = true) }
|
||||
expect(invalid).to.equal(false)
|
||||
try {
|
||||
new Path().__withLog(log).start()
|
||||
} catch (err) {
|
||||
|
@ -1116,10 +980,9 @@ describe('Path', () => {
|
|||
expect(invalid).to.equal(true)
|
||||
})
|
||||
|
||||
it('Should log a warning when calling end on a path without drawing operations', () => {
|
||||
it('Should log an error when calling end on a path without drawing operations', () => {
|
||||
let invalid = false
|
||||
const log = { error: () => (invalid = true) }
|
||||
expect(invalid).to.equal(false)
|
||||
try {
|
||||
new Path().__withLog(log).end()
|
||||
} catch (err) {
|
||||
|
@ -1128,7 +991,7 @@ describe('Path', () => {
|
|||
expect(invalid).to.equal(true)
|
||||
})
|
||||
|
||||
it('Should log a warning when calling shiftAlong but distance is not a number', () => {
|
||||
it('Should log an error when calling shiftAlong but distance is not a number', () => {
|
||||
let invalid = false
|
||||
const log = { error: () => (invalid = true) }
|
||||
expect(invalid).to.equal(false)
|
||||
|
@ -1136,53 +999,36 @@ describe('Path', () => {
|
|||
expect(invalid).to.equal(true)
|
||||
})
|
||||
|
||||
it('Should log a warning when calling shiftFractionalong but fraction is not a number', () => {
|
||||
const part = {
|
||||
name: 'test',
|
||||
draft: ({ Path, Point, points }) => {
|
||||
points.a = new Path().move(new Point(0, 0)).line(new Point(0, 40)).shiftFractionAlong()
|
||||
return part
|
||||
},
|
||||
}
|
||||
const design = new Design({ parts: [part] })
|
||||
const pattern = new design()
|
||||
pattern.draft()
|
||||
expect(pattern.setStores[0].logs.error[0]).to.equal(
|
||||
'Called `Path.shiftFractionAlong(fraction)` but `fraction` is not a number'
|
||||
)
|
||||
it('Should log an error when calling shiftFractionalong but fraction is not a number', () => {
|
||||
let invalid = false
|
||||
const log = { error: () => (invalid = true) }
|
||||
new Path().__withLog(log).move(new Point(0, 0)).line(new Point(0, 40)).shiftFractionAlong()
|
||||
expect(invalid).to.equal(true)
|
||||
})
|
||||
|
||||
it('Should log a warning when splitting a path on a non-point', () => {
|
||||
const part = {
|
||||
name: 'test',
|
||||
draft: ({ Path, Point, points, part }) => {
|
||||
points.a = new Path().move(new Point(0, 0)).line(new Point(0, 40)).split()
|
||||
return part
|
||||
},
|
||||
}
|
||||
const design = new Design({ parts: [part] })
|
||||
const pattern = new design()
|
||||
pattern.draft()
|
||||
expect(pattern.setStores[0].logs.error[0]).to.equal(
|
||||
'Called `Path.split(point)` but `point` is not a `Point` object'
|
||||
it('Should log an error when splitting a path on a non-point', () => {
|
||||
let invalid = false
|
||||
const log = { error: () => (invalid = true) }
|
||||
const pointLog = { warning: () => {} }
|
||||
try {
|
||||
new Path()
|
||||
.__withLog(log)
|
||||
.move(new Point(0, 0).__withLog(pointLog))
|
||||
.line(new Point(0, 40).__withLog(pointLog))
|
||||
.split()
|
||||
} catch (e) {
|
||||
expect(e.toString()).to.include(
|
||||
"TypeError: Cannot read properties of undefined (reading '__check')"
|
||||
)
|
||||
} finally {
|
||||
expect(invalid).to.equal(true)
|
||||
}
|
||||
})
|
||||
|
||||
it('Should add a class', () => {
|
||||
const part = {
|
||||
name: 'test',
|
||||
draft: ({ Path, paths, Point, part }) => {
|
||||
paths.line = new Path()
|
||||
.move(new Point(0, 0))
|
||||
.line(new Point(10, 10))
|
||||
.addClass('fabric banana')
|
||||
return part
|
||||
},
|
||||
}
|
||||
const design = new Design({ parts: [part] })
|
||||
const pattern = new design()
|
||||
pattern.draft()
|
||||
expect(pattern.parts[0].test.paths.line.attributes.get('class')).to.equal('fabric banana')
|
||||
const line = new Path().move(new Point(0, 0)).line(new Point(10, 10)).addClass('fabric banana')
|
||||
|
||||
expect(line.attributes.get('class')).to.equal('fabric banana')
|
||||
})
|
||||
|
||||
it('Should (un)hide a path with hide()/unhide()', () => {
|
||||
|
|
|
@ -132,26 +132,30 @@ describe('Point', () => {
|
|||
expect(round(ss.shiftTowards(se, 200).y)).to.equal(-18.42)
|
||||
})
|
||||
|
||||
describe('shiftFractionTowards', () => {
|
||||
it('Should shift a point a fraction towards another', () => {
|
||||
let origin = new Point(0, 0)
|
||||
let n = new Point(0, -10)
|
||||
let e = new Point(10, 0)
|
||||
let s = new Point(0, 10)
|
||||
let w = new Point(-10, 0)
|
||||
let sn = origin.shiftFractionTowards(n, 1.5)
|
||||
let se = origin.shiftFractionTowards(e, 1.5)
|
||||
let ss = origin.shiftFractionTowards(s, 0.5)
|
||||
let sw = origin.shiftFractionTowards(w, 2.5)
|
||||
expect(round(sn.x)).to.equal(0)
|
||||
expect(round(sn.y)).to.equal(-15)
|
||||
expect(round(se.x)).to.equal(15)
|
||||
expect(round(se.y)).to.equal(0)
|
||||
expect(round(ss.x)).to.equal(0)
|
||||
expect(round(ss.y)).to.equal(5)
|
||||
expect(round(sw.x)).to.equal(-25)
|
||||
expect(round(sw.y)).to.equal(0)
|
||||
expect(round(sw.shiftFractionTowards(sn, 100).x)).to.equal(2475)
|
||||
expect(round(ss.shiftFractionTowards(se, 200).y)).to.equal(-995)
|
||||
})
|
||||
|
||||
it('Should shift a point a fraction beyond another if the fraction is > 1', () => {
|
||||
let origin = new Point(0, 0)
|
||||
let n = new Point(0, -10)
|
||||
let sn = origin.shiftFractionTowards(n, 1.5)
|
||||
expect(round(sn.x)).to.equal(0)
|
||||
expect(round(sn.y)).to.equal(-15)
|
||||
})
|
||||
|
||||
it('Should shift a point a fraction away from another if the fraction is < 0', () => {
|
||||
let origin = new Point(0, 0)
|
||||
let n = new Point(0, -10)
|
||||
let sn = origin.shiftFractionTowards(n, -0.5)
|
||||
expect(round(sn.x)).to.equal(0)
|
||||
expect(round(sn.y)).to.equal(5)
|
||||
})
|
||||
})
|
||||
|
||||
it('Should shift a point beyond another', () => {
|
||||
|
|
|
@ -154,6 +154,7 @@ describe('Utils', () => {
|
|||
expect(hits.length).to.equal(3)
|
||||
})
|
||||
|
||||
describe('curvesIntersect', function () {
|
||||
it('Should find 9 intersections between two curves', () => {
|
||||
let A = new Point(10, 10)
|
||||
let Acp = new Point(310, 40)
|
||||
|
@ -196,6 +197,7 @@ describe('Utils', () => {
|
|||
let hit = curvesIntersect(A, Acp, Bcp, B, C, Ccp, Dcp, D)
|
||||
expect(hit).to.equal(false)
|
||||
})
|
||||
})
|
||||
|
||||
it('Should correctly format units', () => {
|
||||
expect(units(123.456)).to.equal('12.35cm')
|
||||
|
|
|
@ -1,25 +1,37 @@
|
|||
const fs = require('fs')
|
||||
const path = require('path');
|
||||
const path = require('path')
|
||||
const spawn = require('child_process').spawn
|
||||
|
||||
const projectRoot = path.normalize(path.join(__dirname, '..'));
|
||||
const outputLog = path.join(projectRoot, '.test-failures.log');
|
||||
const collectorScript = path.join(projectRoot, 'scripts', 'test-failure-collector.js');
|
||||
const projectRoot = path.normalize(path.join(__dirname, '..'))
|
||||
const outputLog = path.join(projectRoot, '.test-failures.log')
|
||||
const collectorScript = path.join(projectRoot, 'scripts', 'test-failure-collector.js')
|
||||
|
||||
// Start with a fresh output log on each run.
|
||||
if (fs.existsSync(outputLog)) {
|
||||
fs.unlinkSync(outputLog);
|
||||
fs.unlinkSync(outputLog)
|
||||
}
|
||||
|
||||
// Run all tests, specifying the collector script.
|
||||
spawn('lerna', ['run', '--no-bail', 'testci', '--stream', '--parallel', '--loglevel', 'error', '--', '--file', `${collectorScript}`, '--no-warnings'], { stdio: 'inherit' })
|
||||
.on('exit', function(code) {
|
||||
spawn(
|
||||
'lerna',
|
||||
[
|
||||
'run',
|
||||
'--no-bail',
|
||||
'testci',
|
||||
'--loglevel',
|
||||
'error',
|
||||
'--',
|
||||
'--file',
|
||||
`${collectorScript}`,
|
||||
'--no-warnings',
|
||||
],
|
||||
{ stdio: 'inherit' }
|
||||
).on('exit', function (code) {
|
||||
// If a failure occurred, the log file will have been created. Print it.
|
||||
if (fs.existsSync(outputLog)) {
|
||||
console.error(fs.readFileSync(outputLog, 'utf8').trim());
|
||||
console.error(fs.readFileSync(outputLog, 'utf8').trim())
|
||||
}
|
||||
|
||||
// Propagate the exit code.
|
||||
process.exit(code);
|
||||
});
|
||||
|
||||
process.exit(code)
|
||||
})
|
||||
|
|
|
@ -7,7 +7,6 @@
|
|||
"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"
|
||||
},
|
||||
|
@ -30,10 +29,13 @@
|
|||
"crypto": "^1.0.1",
|
||||
"express": "4.18.2",
|
||||
"mustache": "^4.2.0",
|
||||
"passport": "^0.6.0",
|
||||
"passport-http": "^0.3.0",
|
||||
"passport-jwt": "^4.0.0",
|
||||
"pino": "^8.7.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"chai-http": "^4.3.0",
|
||||
"mocha": "^10.1.0",
|
||||
"mocha-steps": "^1.3.0",
|
||||
"prisma": "4.5.0"
|
||||
|
|
|
@ -73,9 +73,12 @@ model Pattern {
|
|||
data String
|
||||
design String
|
||||
img String?
|
||||
name String @default("")
|
||||
notes String
|
||||
person Person? @relation(fields: [personId], references: [id])
|
||||
personId Int?
|
||||
notes String
|
||||
public Boolean @default(false)
|
||||
settings String
|
||||
user User @relation(fields: [userId], references: [id])
|
||||
userId Int
|
||||
updatedAt DateTime @updatedAt
|
||||
|
|
Binary file not shown.
|
@ -4,7 +4,7 @@ import { log } from '../utils/log.mjs'
|
|||
import { ApikeyModel } from '../models/apikey.mjs'
|
||||
import { UserModel } from '../models/user.mjs'
|
||||
|
||||
export function ApikeyController() {}
|
||||
export function ApikeysController() {}
|
||||
|
||||
/*
|
||||
* Create API key
|
||||
|
@ -12,7 +12,7 @@ export function ApikeyController() {}
|
|||
* This is the endpoint that handles creation of API keys/tokens
|
||||
* See: https://freesewing.dev/reference/backend/api/apikey
|
||||
*/
|
||||
ApikeyController.prototype.create = async (req, res, tools) => {
|
||||
ApikeysController.prototype.create = async (req, res, tools) => {
|
||||
const Apikey = new ApikeyModel(tools)
|
||||
await Apikey.create(req)
|
||||
|
||||
|
@ -25,7 +25,7 @@ ApikeyController.prototype.create = async (req, res, tools) => {
|
|||
* This is the endpoint that handles creation of API keys/tokens
|
||||
* See: https://freesewing.dev/reference/backend/api/apikey
|
||||
*/
|
||||
ApikeyController.prototype.read = async (req, res, tools) => {
|
||||
ApikeysController.prototype.read = async (req, res, tools) => {
|
||||
const Apikey = new ApikeyModel(tools)
|
||||
await Apikey.guardedRead(req)
|
||||
|
||||
|
@ -39,7 +39,7 @@ ApikeyController.prototype.read = async (req, res, tools) => {
|
|||
* request
|
||||
* See: https://freesewing.dev/reference/backend/api/apikey
|
||||
*/
|
||||
ApikeyController.prototype.whoami = async (req, res, tools) => {
|
||||
ApikeysController.prototype.whoami = async (req, res, tools) => {
|
||||
const User = new UserModel(tools)
|
||||
const Apikey = new ApikeyModel(tools)
|
||||
|
||||
|
@ -69,7 +69,7 @@ ApikeyController.prototype.whoami = async (req, res, tools) => {
|
|||
* This is the endpoint that handles removal of API keys/tokens
|
||||
* See: https://freesewing.dev/reference/backend/api/apikey
|
||||
*/
|
||||
ApikeyController.prototype.delete = async (req, res, tools) => {
|
||||
ApikeysController.prototype.delete = async (req, res, tools) => {
|
||||
const Apikey = new ApikeyModel(tools)
|
||||
await Apikey.guardedDelete(req)
|
||||
|
58
sites/backend/src/controllers/patterns.mjs
Normal file
58
sites/backend/src/controllers/patterns.mjs
Normal file
|
@ -0,0 +1,58 @@
|
|||
import { PatternModel } from '../models/pattern.mjs'
|
||||
|
||||
export function PatternsController() {}
|
||||
|
||||
/*
|
||||
* Create a pattern
|
||||
* See: https://freesewing.dev/reference/backend/api
|
||||
*/
|
||||
PatternsController.prototype.create = async (req, res, tools) => {
|
||||
const Pattern = new PatternModel(tools)
|
||||
await Pattern.guardedCreate(req)
|
||||
|
||||
return Pattern.sendResponse(res)
|
||||
}
|
||||
|
||||
/*
|
||||
* Read a pattern
|
||||
* See: https://freesewing.dev/reference/backend/api
|
||||
*/
|
||||
PatternsController.prototype.read = async (req, res, tools) => {
|
||||
const Pattern = new PatternModel(tools)
|
||||
await Pattern.guardedRead(req)
|
||||
|
||||
return Pattern.sendResponse(res)
|
||||
}
|
||||
|
||||
/*
|
||||
* Update a pattern
|
||||
* See: https://freesewing.dev/reference/backend/api
|
||||
*/
|
||||
PatternsController.prototype.update = async (req, res, tools) => {
|
||||
const Pattern = new PatternModel(tools)
|
||||
await Pattern.guardedUpdate(req)
|
||||
|
||||
return Pattern.sendResponse(res)
|
||||
}
|
||||
|
||||
/*
|
||||
* Remove a pattern
|
||||
* See: https://freesewing.dev/reference/backend/api
|
||||
*/
|
||||
PatternsController.prototype.delete = async (req, res, tools) => {
|
||||
const Pattern = new PatternModel(tools)
|
||||
await Pattern.guardedDelete(req)
|
||||
|
||||
return Pattern.sendResponse(res)
|
||||
}
|
||||
|
||||
/*
|
||||
* Clone a pattern
|
||||
* See: https://freesewing.dev/reference/backend/api
|
||||
*/
|
||||
PatternsController.prototype.clone = async (req, res, tools) => {
|
||||
const Pattern = new PatternModel(tools)
|
||||
await Pattern.guardedClone(req)
|
||||
|
||||
return Pattern.sendResponse(res)
|
||||
}
|
|
@ -1,12 +1,12 @@
|
|||
import { PersonModel } from '../models/person.mjs'
|
||||
|
||||
export function PersonController() {}
|
||||
export function PeopleController() {}
|
||||
|
||||
/*
|
||||
* Create a person for the authenticated user
|
||||
* See: https://freesewing.dev/reference/backend/api
|
||||
*/
|
||||
PersonController.prototype.create = async (req, res, tools) => {
|
||||
PeopleController.prototype.create = async (req, res, tools) => {
|
||||
const Person = new PersonModel(tools)
|
||||
await Person.guardedCreate(req)
|
||||
|
||||
|
@ -17,7 +17,7 @@ PersonController.prototype.create = async (req, res, tools) => {
|
|||
* Read a person
|
||||
* See: https://freesewing.dev/reference/backend/api
|
||||
*/
|
||||
PersonController.prototype.read = async (req, res, tools) => {
|
||||
PeopleController.prototype.read = async (req, res, tools) => {
|
||||
const Person = new PersonModel(tools)
|
||||
await Person.guardedRead(req)
|
||||
|
||||
|
@ -28,7 +28,7 @@ PersonController.prototype.read = async (req, res, tools) => {
|
|||
* Update a person
|
||||
* See: https://freesewing.dev/reference/backend/api
|
||||
*/
|
||||
PersonController.prototype.update = async (req, res, tools) => {
|
||||
PeopleController.prototype.update = async (req, res, tools) => {
|
||||
const Person = new PersonModel(tools)
|
||||
await Person.guardedUpdate(req)
|
||||
|
||||
|
@ -39,7 +39,7 @@ PersonController.prototype.update = async (req, res, tools) => {
|
|||
* Remove a person
|
||||
* See: https://freesewing.dev/reference/backend/api
|
||||
*/
|
||||
PersonController.prototype.delete = async (req, res, tools) => {
|
||||
PeopleController.prototype.delete = async (req, res, tools) => {
|
||||
const Person = new PersonModel(tools)
|
||||
await Person.guardedDelete(req)
|
||||
|
||||
|
@ -50,7 +50,7 @@ PersonController.prototype.delete = async (req, res, tools) => {
|
|||
* Clone a person
|
||||
* See: https://freesewing.dev/reference/backend/api
|
||||
*/
|
||||
PersonController.prototype.clone = async (req, res, tools) => {
|
||||
PeopleController.prototype.clone = async (req, res, tools) => {
|
||||
const Person = new PersonModel(tools)
|
||||
await Person.guardedClone(req)
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
import { UserModel } from '../models/user.mjs'
|
||||
|
||||
export function UserController() {}
|
||||
export function UsersController() {}
|
||||
|
||||
/*
|
||||
* Signup
|
||||
|
@ -8,7 +8,7 @@ export function UserController() {}
|
|||
* This is the endpoint that handles account signups
|
||||
* See: https://freesewing.dev/reference/backend/api
|
||||
*/
|
||||
UserController.prototype.signup = async (req, res, tools) => {
|
||||
UsersController.prototype.signup = async (req, res, tools) => {
|
||||
const User = new UserModel(tools)
|
||||
await User.guardedCreate(req)
|
||||
|
||||
|
@ -21,7 +21,7 @@ UserController.prototype.signup = async (req, res, tools) => {
|
|||
* This is the endpoint that fully unlocks the account if the user gives their consent
|
||||
* See: https://freesewing.dev/reference/backend/api
|
||||
*/
|
||||
UserController.prototype.confirm = async (req, res, tools) => {
|
||||
UsersController.prototype.confirm = async (req, res, tools) => {
|
||||
const User = new UserModel(tools)
|
||||
await User.confirm(req)
|
||||
|
||||
|
@ -34,7 +34,7 @@ UserController.prototype.confirm = async (req, res, tools) => {
|
|||
* This is the endpoint that provides traditional username/password login
|
||||
* See: https://freesewing.dev/reference/backend/api
|
||||
*/
|
||||
UserController.prototype.login = async function (req, res, tools) {
|
||||
UsersController.prototype.login = async function (req, res, tools) {
|
||||
const User = new UserModel(tools)
|
||||
await User.passwordLogin(req)
|
||||
|
||||
|
@ -46,7 +46,7 @@ UserController.prototype.login = async function (req, res, tools) {
|
|||
*
|
||||
* See: https://freesewing.dev/reference/backend/api
|
||||
*/
|
||||
UserController.prototype.whoami = async (req, res, tools) => {
|
||||
UsersController.prototype.whoami = async (req, res, tools) => {
|
||||
const User = new UserModel(tools)
|
||||
await User.guardedRead({ id: req.user.uid }, req)
|
||||
|
||||
|
@ -58,7 +58,7 @@ UserController.prototype.whoami = async (req, res, tools) => {
|
|||
*
|
||||
* See: https://freesewing.dev/reference/backend/api
|
||||
*/
|
||||
UserController.prototype.update = async (req, res, tools) => {
|
||||
UsersController.prototype.update = async (req, res, tools) => {
|
||||
const User = new UserModel(tools)
|
||||
await User.guardedRead({ id: req.user.uid }, req)
|
||||
await User.guardedUpdate(req)
|
317
sites/backend/src/models/pattern.mjs
Normal file
317
sites/backend/src/models/pattern.mjs
Normal file
|
@ -0,0 +1,317 @@
|
|||
import { log } from '../utils/log.mjs'
|
||||
import { setPatternAvatar } from '../utils/sanity.mjs'
|
||||
|
||||
export function PatternModel(tools) {
|
||||
this.config = tools.config
|
||||
this.prisma = tools.prisma
|
||||
this.decrypt = tools.decrypt
|
||||
this.encrypt = tools.encrypt
|
||||
this.encryptedFields = ['data', 'img', 'name', 'notes', 'settings']
|
||||
this.clear = {} // For holding decrypted data
|
||||
|
||||
return this
|
||||
}
|
||||
/*
|
||||
id Int @id @default(autoincrement())
|
||||
createdAt DateTime @default(now())
|
||||
data String
|
||||
design String
|
||||
img String?
|
||||
person Person? @relation(fields: [personId], references: [id])
|
||||
personId Int?
|
||||
name String @default("")
|
||||
notes String
|
||||
public
|
||||
settings String
|
||||
user User @relation(fields: [userId], references: [id])
|
||||
userId Int
|
||||
updatedAt DateTime @updatedAt
|
||||
*/
|
||||
|
||||
PatternModel.prototype.guardedCreate = async function ({ body, user }) {
|
||||
if (user.level < 3) return this.setResponse(403, 'insufficientAccessLevel')
|
||||
if (Object.keys(body) < 2) return this.setResponse(400, 'postBodyMissing')
|
||||
if (!body.person) return this.setResponse(400, 'personMissing')
|
||||
if (typeof body.person !== 'number') return this.setResponse(400, 'personNotNumeric')
|
||||
if (typeof body.settings !== 'object') return this.setResponse(400, 'settingsNotAnObject')
|
||||
if (body.data && typeof body.data !== 'object') return this.setResponse(400, 'dataNotAnObject')
|
||||
if (!body.design && !body.data?.design) return this.setResponse(400, 'designMissing')
|
||||
if (typeof body.design !== 'string') return this.setResponse(400, 'designNotStringy')
|
||||
|
||||
// Prepare data
|
||||
const data = {
|
||||
design: body.design,
|
||||
personId: body.person,
|
||||
settings: body.settings,
|
||||
}
|
||||
// Data (will be encrypted, so always set _some_ value)
|
||||
if (typeof body.data === 'object') data.data = body.data
|
||||
else data.data = {}
|
||||
// Name (will be encrypted, so always set _some_ value)
|
||||
if (typeof body.name === 'string' && body.name.length > 0) data.name = body.name
|
||||
else data.name = '--'
|
||||
// Notes (will be encrypted, so always set _some_ value)
|
||||
if (typeof body.notes === 'string' && body.notes.length > 0) data.notes = body.notes
|
||||
else data.notes = '--'
|
||||
// Public
|
||||
if (body.public === true) data.public = true
|
||||
data.userId = user.uid
|
||||
// Set this one initially as we need the ID to create a custom img via Sanity
|
||||
data.img = this.config.avatars.pattern
|
||||
|
||||
// Create record
|
||||
await this.unguardedCreate(data)
|
||||
|
||||
// Update img? (now that we have the ID)
|
||||
const img =
|
||||
this.config.use.sanity &&
|
||||
typeof body.img === 'string' &&
|
||||
(!body.unittest || (body.unittest && this.config.use.tests?.sanity))
|
||||
? await setPatternAvatar(this.record.id, body.img)
|
||||
: false
|
||||
|
||||
if (img) await this.unguardedUpdate(this.cloak({ img: img.url }))
|
||||
else await this.read({ id: this.record.id })
|
||||
|
||||
return this.setResponse(201, 'created', { pattern: this.asPattern() })
|
||||
}
|
||||
|
||||
PatternModel.prototype.unguardedCreate = async function (data) {
|
||||
try {
|
||||
this.record = await this.prisma.pattern.create({ data: this.cloak(data) })
|
||||
} catch (err) {
|
||||
log.warn(err, 'Could not create pattern')
|
||||
return this.setResponse(500, 'createPatternFailed')
|
||||
}
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
/*
|
||||
* Loads a pattern from the database based on the where clause you pass it
|
||||
*
|
||||
* Stores result in this.record
|
||||
*/
|
||||
PatternModel.prototype.read = async function (where) {
|
||||
try {
|
||||
this.record = await this.prisma.pattern.findUnique({ where })
|
||||
} catch (err) {
|
||||
log.warn({ err, where }, 'Could not read pattern')
|
||||
}
|
||||
|
||||
this.reveal()
|
||||
|
||||
return this.setExists()
|
||||
}
|
||||
|
||||
/*
|
||||
* Loads a pattern from the database based on the where clause you pass it
|
||||
* In addition prepares it for returning the pattern data
|
||||
*
|
||||
* Stores result in this.record
|
||||
*/
|
||||
PatternModel.prototype.guardedRead = async function ({ params, user }) {
|
||||
if (user.level < 1) return this.setResponse(403, 'insufficientAccessLevel')
|
||||
if (user.iss && user.status < 1) return this.setResponse(403, 'accountStatusLacking')
|
||||
|
||||
await this.read({ id: parseInt(params.id) })
|
||||
if (this.record.userId !== user.uid && user.level < 5) {
|
||||
return this.setResponse(403, 'insufficientAccessLevel')
|
||||
}
|
||||
|
||||
return this.setResponse(200, false, {
|
||||
result: 'success',
|
||||
pattern: this.asPattern(),
|
||||
})
|
||||
}
|
||||
|
||||
/*
|
||||
* Clones a pattern
|
||||
* In addition prepares it for returning the pattern data
|
||||
*
|
||||
* Stores result in this.record
|
||||
*/
|
||||
PatternModel.prototype.guardedClone = async function ({ params, user }) {
|
||||
if (user.level < 3) return this.setResponse(403, 'insufficientAccessLevel')
|
||||
if (user.iss && user.status < 1) return this.setResponse(403, 'accountStatusLacking')
|
||||
|
||||
await this.read({ id: parseInt(params.id) })
|
||||
if (this.record.userId !== user.uid && !this.record.public && user.level < 5) {
|
||||
return this.setResponse(403, 'insufficientAccessLevel')
|
||||
}
|
||||
|
||||
// Clone pattern
|
||||
const data = this.asPattern()
|
||||
delete data.id
|
||||
data.name += ` (cloned from #${this.record.id})`
|
||||
data.notes += ` (Note: This pattern was cloned from pattern #${this.record.id})`
|
||||
await this.unguardedCreate(data)
|
||||
|
||||
// Update unencrypted data
|
||||
this.reveal()
|
||||
|
||||
return this.setResponse(200, false, {
|
||||
result: 'success',
|
||||
pattern: this.asPattern(),
|
||||
})
|
||||
}
|
||||
|
||||
/*
|
||||
* Helper method to decrypt at-rest data
|
||||
*/
|
||||
PatternModel.prototype.reveal = async function () {
|
||||
this.clear = {}
|
||||
if (this.record) {
|
||||
for (const field of this.encryptedFields) {
|
||||
this.clear[field] = this.decrypt(this.record[field])
|
||||
}
|
||||
}
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
/*
|
||||
* Helper method to encrypt at-rest data
|
||||
*/
|
||||
PatternModel.prototype.cloak = function (data) {
|
||||
for (const field of this.encryptedFields) {
|
||||
if (typeof data[field] !== 'undefined') {
|
||||
data[field] = this.encrypt(data[field])
|
||||
}
|
||||
}
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
/*
|
||||
* Checks this.record and sets a boolean to indicate whether
|
||||
* the pattern exists or not
|
||||
*
|
||||
* Stores result in this.exists
|
||||
*/
|
||||
PatternModel.prototype.setExists = function () {
|
||||
this.exists = this.record ? true : false
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
/*
|
||||
* Updates the pattern data - Used when we create the data ourselves
|
||||
* so we know it's safe
|
||||
*/
|
||||
PatternModel.prototype.unguardedUpdate = async function (data) {
|
||||
try {
|
||||
this.record = await this.prisma.pattern.update({
|
||||
where: { id: this.record.id },
|
||||
data,
|
||||
})
|
||||
} catch (err) {
|
||||
log.warn(err, 'Could not update pattern record')
|
||||
process.exit()
|
||||
return this.setResponse(500, 'updatePatternFailed')
|
||||
}
|
||||
await this.reveal()
|
||||
|
||||
return this.setResponse(200)
|
||||
}
|
||||
|
||||
/*
|
||||
* Updates the pattern data - Used when we pass through user-provided data
|
||||
* so we can't be certain it's safe
|
||||
*/
|
||||
PatternModel.prototype.guardedUpdate = async function ({ params, body, user }) {
|
||||
if (user.level < 3) return this.setResponse(403, 'insufficientAccessLevel')
|
||||
if (user.iss && user.status < 1) return this.setResponse(403, 'accountStatusLacking')
|
||||
await this.read({ id: parseInt(params.id) })
|
||||
if (this.record.userId !== user.uid && user.level < 8) {
|
||||
return this.setResponse(403, 'insufficientAccessLevel')
|
||||
}
|
||||
const data = {}
|
||||
// Name
|
||||
if (typeof body.name === 'string') data.name = body.name
|
||||
// Notes
|
||||
if (typeof body.notes === 'string') data.notes = body.notes
|
||||
// Public
|
||||
if (body.public === true || body.public === false) data.public = body.public
|
||||
// Data
|
||||
if (typeof body.data === 'object') data.data = body.data
|
||||
// Settings
|
||||
if (typeof body.settings === 'object') data.settings = body.settings
|
||||
// Image (img)
|
||||
if (typeof body.img === 'string') {
|
||||
const img = await setPatternAvatar(params.id, body.img)
|
||||
data.img = img.url
|
||||
}
|
||||
|
||||
// Now update the record
|
||||
await this.unguardedUpdate(this.cloak(data))
|
||||
|
||||
return this.setResponse(200, false, { pattern: this.asPattern() })
|
||||
}
|
||||
|
||||
/*
|
||||
* Removes the pattern - No questions asked
|
||||
*/
|
||||
PatternModel.prototype.unguardedDelete = async function () {
|
||||
await this.prisma.pattern.delete({ here: { id: this.record.id } })
|
||||
this.record = null
|
||||
this.clear = null
|
||||
|
||||
return this.setExists()
|
||||
}
|
||||
|
||||
/*
|
||||
* Removes the pattern - Checks permissions
|
||||
*/
|
||||
PatternModel.prototype.guardedDelete = async function ({ params, body, user }) {
|
||||
if (user.level < 3) return this.setResponse(403, 'insufficientAccessLevel')
|
||||
if (user.iss && user.status < 1) return this.setResponse(403, 'accountStatusLacking')
|
||||
|
||||
await this.read({ id: parseInt(params.id) })
|
||||
if (this.record.userId !== user.uid && user.level < 8) {
|
||||
return this.setResponse(403, 'insufficientAccessLevel')
|
||||
}
|
||||
|
||||
await this.unguardedDelete()
|
||||
|
||||
return this.setResponse(204, false)
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns record data
|
||||
*/
|
||||
PatternModel.prototype.asPattern = function () {
|
||||
return {
|
||||
...this.record,
|
||||
...this.clear,
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Helper method to set the response code, result, and body
|
||||
*
|
||||
* Will be used by this.sendResponse()
|
||||
*/
|
||||
PatternModel.prototype.setResponse = function (status = 200, error = false, data = {}) {
|
||||
this.response = {
|
||||
status,
|
||||
body: {
|
||||
result: 'success',
|
||||
...data,
|
||||
},
|
||||
}
|
||||
if (status > 201) {
|
||||
this.response.body.error = error
|
||||
this.response.body.result = 'error'
|
||||
this.error = true
|
||||
} else this.error = false
|
||||
|
||||
return this.setExists()
|
||||
}
|
||||
|
||||
/*
|
||||
* Helper method to send response
|
||||
*/
|
||||
PatternModel.prototype.sendResponse = async function (res) {
|
||||
return res.status(this.response.status).send(this.response.body)
|
||||
}
|
|
@ -19,7 +19,12 @@ PersonModel.prototype.guardedCreate = async function ({ body, user }) {
|
|||
|
||||
// Prepare data
|
||||
const data = { name: body.name }
|
||||
// Name (will be encrypted, so always set _some_ value)
|
||||
if (typeof body.name === 'string') data.name = body.name
|
||||
else data.name = '--'
|
||||
// Notes (will be encrypted, so always set _some_ value)
|
||||
if (body.notes || typeof body.notes === 'string') data.notes = body.notes
|
||||
else data.notes = '--'
|
||||
if (body.public === true) data.public = true
|
||||
if (body.measies) data.measies = this.sanitizeMeasurements(body.measies)
|
||||
data.imperial = body.imperial === true ? true : false
|
||||
|
|
|
@ -223,9 +223,9 @@ UserModel.prototype.guardedCreate = async function ({ body }) {
|
|||
* Login based on username + password
|
||||
*/
|
||||
UserModel.prototype.passwordLogin = async function (req) {
|
||||
if (Object.keys(req.body) < 1) return this.setReponse(400, 'postBodyMissing')
|
||||
if (!req.body.username) return this.setReponse(400, 'usernameMissing')
|
||||
if (!req.body.password) return this.setReponse(400, 'passwordMissing')
|
||||
if (Object.keys(req.body) < 1) return this.setResponse(400, 'postBodyMissing')
|
||||
if (!req.body.username) return this.setResponse(400, 'usernameMissing')
|
||||
if (!req.body.password) return this.setResponse(400, 'passwordMissing')
|
||||
|
||||
await this.find(req.body)
|
||||
if (!this.exists) {
|
||||
|
@ -255,7 +255,7 @@ UserModel.prototype.passwordLogin = async function (req) {
|
|||
* Confirms a user account
|
||||
*/
|
||||
UserModel.prototype.confirm = async function ({ body, params }) {
|
||||
if (!params.id) return this.setReponse(404, 'missingConfirmationId')
|
||||
if (!params.id) return this.setResponse(404, 'missingConfirmationId')
|
||||
if (Object.keys(body) < 1) return this.setResponse(400, 'postBodyMissing')
|
||||
if (!body.consent || typeof body.consent !== 'number' || body.consent < 1)
|
||||
return this.setResponse(400, 'consentRequired')
|
||||
|
|
|
@ -1,38 +0,0 @@
|
|||
import { ApikeyController } from '../controllers/apikey.mjs'
|
||||
|
||||
const Apikey = new ApikeyController()
|
||||
const jwt = ['jwt', { session: false }]
|
||||
const bsc = ['basic', { session: false }]
|
||||
|
||||
export function apikeyRoutes(tools) {
|
||||
const { app, passport } = tools
|
||||
|
||||
// Create Apikey
|
||||
app.post('/apikey/jwt', passport.authenticate(...jwt), (req, res) =>
|
||||
Apikey.create(req, res, tools)
|
||||
)
|
||||
app.post('/apikey/key', passport.authenticate(...bsc), (req, res) =>
|
||||
Apikey.create(req, res, tools)
|
||||
)
|
||||
|
||||
// Read Apikey
|
||||
app.get('/apikey/:id/jwt', passport.authenticate(...jwt), (req, res) =>
|
||||
Apikey.read(req, res, tools)
|
||||
)
|
||||
app.get('/apikey/:id/key', passport.authenticate(...bsc), (req, res) =>
|
||||
Apikey.read(req, res, tools)
|
||||
)
|
||||
|
||||
// Read current Apikey
|
||||
app.get('/whoami/key', passport.authenticate(...bsc), (req, res) =>
|
||||
Apikey.whoami(req, res, tools)
|
||||
)
|
||||
|
||||
// Remove Apikey
|
||||
app.delete('/apikey/:id/jwt', passport.authenticate(...jwt), (req, res) =>
|
||||
Apikey.delete(req, res, tools)
|
||||
)
|
||||
app.delete('/apikey/:id/key', passport.authenticate(...bsc), (req, res) =>
|
||||
Apikey.delete(req, res, tools)
|
||||
)
|
||||
}
|
38
sites/backend/src/routes/apikeys.mjs
Normal file
38
sites/backend/src/routes/apikeys.mjs
Normal file
|
@ -0,0 +1,38 @@
|
|||
import { ApikeysController } from '../controllers/apikeys.mjs'
|
||||
|
||||
const Apikeys = new ApikeysController()
|
||||
const jwt = ['jwt', { session: false }]
|
||||
const bsc = ['basic', { session: false }]
|
||||
|
||||
export function apikeysRoutes(tools) {
|
||||
const { app, passport } = tools
|
||||
|
||||
// Create Apikey
|
||||
app.post('/apikeys/jwt', passport.authenticate(...jwt), (req, res) =>
|
||||
Apikeys.create(req, res, tools)
|
||||
)
|
||||
app.post('/apikeys/key', passport.authenticate(...bsc), (req, res) =>
|
||||
Apikeys.create(req, res, tools)
|
||||
)
|
||||
|
||||
// Read Apikey
|
||||
app.get('/apikeys/:id/jwt', passport.authenticate(...jwt), (req, res) =>
|
||||
Apikeys.read(req, res, tools)
|
||||
)
|
||||
app.get('/apikeys/:id/key', passport.authenticate(...bsc), (req, res) =>
|
||||
Apikeys.read(req, res, tools)
|
||||
)
|
||||
|
||||
// Read current Apikey
|
||||
app.get('/whoami/key', passport.authenticate(...bsc), (req, res) =>
|
||||
Apikeys.whoami(req, res, tools)
|
||||
)
|
||||
|
||||
// Remove Apikey
|
||||
app.delete('/apikeys/:id/jwt', passport.authenticate(...jwt), (req, res) =>
|
||||
Apikeys.delete(req, res, tools)
|
||||
)
|
||||
app.delete('/apikeys/:id/key', passport.authenticate(...bsc), (req, res) =>
|
||||
Apikeys.delete(req, res, tools)
|
||||
)
|
||||
}
|
|
@ -1,9 +1,11 @@
|
|||
import { apikeyRoutes } from './apikey.mjs'
|
||||
import { userRoutes } from './user.mjs'
|
||||
import { personRoutes } from './person.mjs'
|
||||
import { apikeysRoutes } from './apikeys.mjs'
|
||||
import { usersRoutes } from './users.mjs'
|
||||
import { peopleRoutes } from './people.mjs'
|
||||
import { patternsRoutes } from './patterns.mjs'
|
||||
|
||||
export const routes = {
|
||||
apikeyRoutes,
|
||||
userRoutes,
|
||||
personRoutes,
|
||||
apikeysRoutes,
|
||||
usersRoutes,
|
||||
peopleRoutes,
|
||||
patternsRoutes,
|
||||
}
|
||||
|
|
49
sites/backend/src/routes/patterns.mjs
Normal file
49
sites/backend/src/routes/patterns.mjs
Normal file
|
@ -0,0 +1,49 @@
|
|||
import { PatternsController } from '../controllers/patterns.mjs'
|
||||
|
||||
const Patterns = new PatternsController()
|
||||
const jwt = ['jwt', { session: false }]
|
||||
const bsc = ['basic', { session: false }]
|
||||
|
||||
export function patternsRoutes(tools) {
|
||||
const { app, passport } = tools
|
||||
|
||||
// Create pattern
|
||||
app.post('/patterns/jwt', passport.authenticate(...jwt), (req, res) =>
|
||||
Patterns.create(req, res, tools)
|
||||
)
|
||||
app.post('/patterns/key', passport.authenticate(...bsc), (req, res) =>
|
||||
Patterns.create(req, res, tools)
|
||||
)
|
||||
|
||||
// Clone pattern
|
||||
app.post('/patterns/:id/clone/jwt', passport.authenticate(...jwt), (req, res) =>
|
||||
Patterns.clone(req, res, tools)
|
||||
)
|
||||
app.post('/patterns/:id/clone/key', passport.authenticate(...bsc), (req, res) =>
|
||||
Patterns.clone(req, res, tools)
|
||||
)
|
||||
|
||||
// Read pattern
|
||||
app.get('/patterns/:id/jwt', passport.authenticate(...jwt), (req, res) =>
|
||||
Patterns.read(req, res, tools)
|
||||
)
|
||||
app.get('/patterns/:id/key', passport.authenticate(...bsc), (req, res) =>
|
||||
Patterns.read(req, res, tools)
|
||||
)
|
||||
|
||||
// Update pattern
|
||||
app.put('/patterns/:id/jwt', passport.authenticate(...jwt), (req, res) =>
|
||||
Patterns.update(req, res, tools)
|
||||
)
|
||||
app.put('/patterns/:id/key', passport.authenticate(...bsc), (req, res) =>
|
||||
Patterns.update(req, res, tools)
|
||||
)
|
||||
|
||||
// Delete pattern
|
||||
app.delete('/patterns/:id/jwt', passport.authenticate(...jwt), (req, res) =>
|
||||
Patterns.delete(req, res, tools)
|
||||
)
|
||||
app.delete('/patterns/:id/key', passport.authenticate(...bsc), (req, res) =>
|
||||
Patterns.delete(req, res, tools)
|
||||
)
|
||||
}
|
|
@ -1,49 +1,49 @@
|
|||
import { PersonController } from '../controllers/person.mjs'
|
||||
import { PeopleController } from '../controllers/people.mjs'
|
||||
|
||||
const Person = new PersonController()
|
||||
const People = new PeopleController()
|
||||
const jwt = ['jwt', { session: false }]
|
||||
const bsc = ['basic', { session: false }]
|
||||
|
||||
export function personRoutes(tools) {
|
||||
export function peopleRoutes(tools) {
|
||||
const { app, passport } = tools
|
||||
|
||||
// Create person
|
||||
app.post('/people/jwt', passport.authenticate(...jwt), (req, res) =>
|
||||
Person.create(req, res, tools)
|
||||
People.create(req, res, tools)
|
||||
)
|
||||
app.post('/people/key', passport.authenticate(...bsc), (req, res) =>
|
||||
Person.create(req, res, tools)
|
||||
People.create(req, res, tools)
|
||||
)
|
||||
|
||||
// Clone person
|
||||
app.post('/people/:id/clone/jwt', passport.authenticate(...jwt), (req, res) =>
|
||||
Person.clone(req, res, tools)
|
||||
People.clone(req, res, tools)
|
||||
)
|
||||
app.post('/people/:id/clone/key', passport.authenticate(...bsc), (req, res) =>
|
||||
Person.clone(req, res, tools)
|
||||
People.clone(req, res, tools)
|
||||
)
|
||||
|
||||
// Read person
|
||||
app.get('/people/:id/jwt', passport.authenticate(...jwt), (req, res) =>
|
||||
Person.read(req, res, tools)
|
||||
People.read(req, res, tools)
|
||||
)
|
||||
app.get('/people/:id/key', passport.authenticate(...bsc), (req, res) =>
|
||||
Person.read(req, res, tools)
|
||||
People.read(req, res, tools)
|
||||
)
|
||||
|
||||
// Update person
|
||||
app.put('/people/:id/jwt', passport.authenticate(...jwt), (req, res) =>
|
||||
Person.update(req, res, tools)
|
||||
People.update(req, res, tools)
|
||||
)
|
||||
app.put('/people/:id/key', passport.authenticate(...bsc), (req, res) =>
|
||||
Person.update(req, res, tools)
|
||||
People.update(req, res, tools)
|
||||
)
|
||||
|
||||
// Delete person
|
||||
app.delete('/people/:id/jwt', passport.authenticate(...jwt), (req, res) =>
|
||||
Person.delete(req, res, tools)
|
||||
People.delete(req, res, tools)
|
||||
)
|
||||
app.delete('/people/:id/key', passport.authenticate(...bsc), (req, res) =>
|
||||
Person.delete(req, res, tools)
|
||||
People.delete(req, res, tools)
|
||||
)
|
||||
}
|
|
@ -1,30 +1,38 @@
|
|||
import { UserController } from '../controllers/user.mjs'
|
||||
import { UsersController } from '../controllers/users.mjs'
|
||||
|
||||
const User = new UserController()
|
||||
const Users = new UsersController()
|
||||
const jwt = ['jwt', { session: false }]
|
||||
const bsc = ['basic', { session: false }]
|
||||
|
||||
export function userRoutes(tools) {
|
||||
export function usersRoutes(tools) {
|
||||
const { app, passport } = tools
|
||||
|
||||
// Sign up
|
||||
app.post('/signup', (req, res) => User.signup(req, res, tools))
|
||||
app.post('/signup', (req, res) => Users.signup(req, res, tools))
|
||||
|
||||
// Confirm account
|
||||
app.post('/confirm/signup/:id', (req, res) => User.confirm(req, res, tools))
|
||||
app.post('/confirm/signup/:id', (req, res) => Users.confirm(req, res, tools))
|
||||
|
||||
// Login
|
||||
app.post('/login', (req, res) => User.login(req, res, tools))
|
||||
app.post('/login', (req, res) => Users.login(req, res, tools))
|
||||
|
||||
// Read current jwt
|
||||
|
||||
app.get('/whoami/jwt', passport.authenticate(...jwt), (req, res) => User.whoami(req, res, tools))
|
||||
app.get('/account/jwt', passport.authenticate(...jwt), (req, res) => User.whoami(req, res, tools))
|
||||
app.get('/account/key', passport.authenticate(...bsc), (req, res) => User.whoami(req, res, tools))
|
||||
app.get('/whoami/jwt', passport.authenticate(...jwt), (req, res) => Users.whoami(req, res, tools))
|
||||
app.get('/account/jwt', passport.authenticate(...jwt), (req, res) =>
|
||||
Users.whoami(req, res, tools)
|
||||
)
|
||||
app.get('/account/key', passport.authenticate(...bsc), (req, res) =>
|
||||
Users.whoami(req, res, tools)
|
||||
)
|
||||
|
||||
// Update account
|
||||
app.put('/account/jwt', passport.authenticate(...jwt), (req, res) => User.update(req, res, tools))
|
||||
app.put('/account/key', passport.authenticate(...bsc), (req, res) => User.update(req, res, tools))
|
||||
app.put('/account/jwt', passport.authenticate(...jwt), (req, res) =>
|
||||
Users.update(req, res, tools)
|
||||
)
|
||||
app.put('/account/key', passport.authenticate(...bsc), (req, res) =>
|
||||
Users.update(req, res, tools)
|
||||
)
|
||||
|
||||
/*
|
||||
|
|
@ -33,6 +33,7 @@ async function getAvatar(type, id) {
|
|||
*/
|
||||
export const setUserAvatar = async (id, data) => setAvatar('user', id, data)
|
||||
export const setPersonAvatar = async (id, data) => setAvatar('person', id, data)
|
||||
export const setPatternAvatar = async (id, data) => setAvatar('pattern', id, data)
|
||||
export async function setAvatar(type, id, data) {
|
||||
// Step 1: Upload the image as asset
|
||||
const [contentType, binary] = b64ToBinaryWithType(data)
|
||||
|
|
|
@ -3,7 +3,7 @@ export const apikeyTests = async (chai, config, expect, store) => {
|
|||
step(`${store.icon('key', 'jwt')} Create API Key (jwt)`, (done) => {
|
||||
chai
|
||||
.request(config.api)
|
||||
.post('/apikey/jwt')
|
||||
.post('/apikeys/jwt')
|
||||
.set('Authorization', 'Bearer ' + store.account.token)
|
||||
.send({
|
||||
name: 'Test API key',
|
||||
|
@ -27,7 +27,7 @@ export const apikeyTests = async (chai, config, expect, store) => {
|
|||
step(`${store.icon('key', 'key')} Create API Key (key)`, (done) => {
|
||||
chai
|
||||
.request(config.api)
|
||||
.post('/apikey/key')
|
||||
.post('/apikeys/key')
|
||||
.auth(store.apikey1.key, store.apikey1.secret)
|
||||
.send({
|
||||
name: 'Test API key with key',
|
||||
|
@ -67,7 +67,7 @@ export const apikeyTests = async (chai, config, expect, store) => {
|
|||
step(`${store.icon('key', 'key')} Read API key (key)`, (done) => {
|
||||
chai
|
||||
.request(config.api)
|
||||
.get(`/apikey/${store.apikey1.key}/key`)
|
||||
.get(`/apikeys/${store.apikey1.key}/key`)
|
||||
.auth(store.apikey2.key, store.apikey2.secret)
|
||||
.end((err, res) => {
|
||||
expect(res.status).to.equal(200)
|
||||
|
@ -83,7 +83,7 @@ export const apikeyTests = async (chai, config, expect, store) => {
|
|||
step(`${store.icon('key', 'jwt')} Read API key (jwt)`, (done) => {
|
||||
chai
|
||||
.request(config.api)
|
||||
.get(`/apikey/${store.apikey2.key}/jwt`)
|
||||
.get(`/apikeys/${store.apikey2.key}/jwt`)
|
||||
.set('Authorization', 'Bearer ' + store.account.token)
|
||||
.end((err, res) => {
|
||||
expect(res.status).to.equal(200)
|
||||
|
@ -99,7 +99,7 @@ export const apikeyTests = async (chai, config, expect, store) => {
|
|||
step(`${store.icon('key', 'key')} Remove API key (key)`, (done) => {
|
||||
chai
|
||||
.request(config.api)
|
||||
.delete(`/apikey/${store.apikey2.key}/key`)
|
||||
.delete(`/apikeys/${store.apikey2.key}/key`)
|
||||
.auth(store.apikey2.key, store.apikey2.secret)
|
||||
.end((err, res) => {
|
||||
expect(res.status).to.equal(204)
|
||||
|
@ -110,7 +110,7 @@ export const apikeyTests = async (chai, config, expect, store) => {
|
|||
step(`${store.icon('key', 'jwt')} Remove API key (jwt)`, (done) => {
|
||||
chai
|
||||
.request(config.api)
|
||||
.delete(`/apikey/${store.apikey1.key}/jwt`)
|
||||
.delete(`/apikeys/${store.apikey1.key}/jwt`)
|
||||
.set('Authorization', 'Bearer ' + store.account.token)
|
||||
.end((err, res) => {
|
||||
expect(res.status).to.equal(204)
|
||||
|
|
|
@ -2,6 +2,7 @@ import { userTests } from './user.mjs'
|
|||
import { accountTests } from './account.mjs'
|
||||
import { apikeyTests } from './apikey.mjs'
|
||||
import { personTests } from './person.mjs'
|
||||
import { patternTests } from './pattern.mjs'
|
||||
import { setup } from './shared.mjs'
|
||||
|
||||
const runTests = async (...params) => {
|
||||
|
@ -9,6 +10,7 @@ const runTests = async (...params) => {
|
|||
await apikeyTests(...params)
|
||||
await accountTests(...params)
|
||||
await personTests(...params)
|
||||
await patternTests(...params)
|
||||
}
|
||||
|
||||
// Load initial data required for tests
|
||||
|
|
326
sites/backend/tests/pattern.mjs
Normal file
326
sites/backend/tests/pattern.mjs
Normal file
|
@ -0,0 +1,326 @@
|
|||
import { cat } from './cat.mjs'
|
||||
|
||||
export const patternTests = async (chai, config, expect, store) => {
|
||||
store.account.patterns = {}
|
||||
for (const auth of ['jwt', 'key']) {
|
||||
describe(`${store.icon('pattern', auth)} Pattern tests (${auth})`, () => {
|
||||
it(`${store.icon('pattern', auth)} Should create a new pattern (${auth})`, (done) => {
|
||||
chai
|
||||
.request(config.api)
|
||||
.post(`/patterns/${auth}`)
|
||||
.set(
|
||||
'Authorization',
|
||||
auth === 'jwt'
|
||||
? 'Bearer ' + store.account.token
|
||||
: 'Basic ' +
|
||||
new Buffer(`${store.account.apikey.key}:${store.account.apikey.secret}`).toString(
|
||||
'base64'
|
||||
)
|
||||
)
|
||||
.send({
|
||||
design: 'aaron',
|
||||
settings: {},
|
||||
person: store.account.people.her.id,
|
||||
})
|
||||
.end((err, res) => {
|
||||
expect(err === null).to.equal(true)
|
||||
expect(res.status).to.equal(201)
|
||||
expect(res.body.result).to.equal(`success`)
|
||||
expect(typeof res.body.pattern?.id).to.equal('number')
|
||||
expect(res.body.pattern.userId).to.equal(store.account.id)
|
||||
expect(res.body.pattern.personId).to.equal(store.account.people.her.id)
|
||||
expect(res.body.pattern.design).to.equal('aaron')
|
||||
expect(res.body.pattern.public).to.equal(false)
|
||||
store.account.patterns[auth] = res.body.pattern
|
||||
done()
|
||||
})
|
||||
}).timeout(5000)
|
||||
|
||||
for (const field of ['name', 'notes']) {
|
||||
it(`${store.icon('pattern', auth)} Should update the ${field} field (${auth})`, (done) => {
|
||||
const data = {}
|
||||
const val = store.account.patterns[auth][field] + '_updated'
|
||||
data[field] = val
|
||||
chai
|
||||
.request(config.api)
|
||||
.put(`/patterns/${store.account.patterns[auth].id}/${auth}`)
|
||||
.set(
|
||||
'Authorization',
|
||||
auth === 'jwt'
|
||||
? 'Bearer ' + store.account.token
|
||||
: 'Basic ' +
|
||||
new Buffer(
|
||||
`${store.account.apikey.key}:${store.account.apikey.secret}`
|
||||
).toString('base64')
|
||||
)
|
||||
.send(data)
|
||||
.end((err, res) => {
|
||||
expect(err === null).to.equal(true)
|
||||
expect(res.status).to.equal(200)
|
||||
expect(res.body.result).to.equal(`success`)
|
||||
expect(res.body.pattern[field]).to.equal('--_updated')
|
||||
done()
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
it(`${store.icon('person', auth)} Should update the public field (${auth})`, (done) => {
|
||||
chai
|
||||
.request(config.api)
|
||||
.put(`/patterns/${store.account.patterns[auth].id}/${auth}`)
|
||||
.set(
|
||||
'Authorization',
|
||||
auth === 'jwt'
|
||||
? 'Bearer ' + store.account.token
|
||||
: 'Basic ' +
|
||||
new Buffer(`${store.account.apikey.key}:${store.account.apikey.secret}`).toString(
|
||||
'base64'
|
||||
)
|
||||
)
|
||||
.send({ public: true })
|
||||
.end((err, res) => {
|
||||
expect(err === null).to.equal(true)
|
||||
expect(res.status).to.equal(200)
|
||||
expect(res.body.result).to.equal(`success`)
|
||||
expect(res.body.pattern.public).to.equal(true)
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
it(`${store.icon('person', auth)} Should not update the design field (${auth})`, (done) => {
|
||||
chai
|
||||
.request(config.api)
|
||||
.put(`/patterns/${store.account.patterns[auth].id}/${auth}`)
|
||||
.set(
|
||||
'Authorization',
|
||||
auth === 'jwt'
|
||||
? 'Bearer ' + store.account.token
|
||||
: 'Basic ' +
|
||||
new Buffer(`${store.account.apikey.key}:${store.account.apikey.secret}`).toString(
|
||||
'base64'
|
||||
)
|
||||
)
|
||||
.send({ design: 'updated' })
|
||||
.end((err, res) => {
|
||||
expect(err === null).to.equal(true)
|
||||
expect(res.status).to.equal(200)
|
||||
expect(res.body.result).to.equal(`success`)
|
||||
expect(res.body.pattern.design).to.equal('aaron')
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
it(`${store.icon('person', auth)} Should not update the person field (${auth})`, (done) => {
|
||||
chai
|
||||
.request(config.api)
|
||||
.put(`/patterns/${store.account.patterns[auth].id}/${auth}`)
|
||||
.set(
|
||||
'Authorization',
|
||||
auth === 'jwt'
|
||||
? 'Bearer ' + store.account.token
|
||||
: 'Basic ' +
|
||||
new Buffer(`${store.account.apikey.key}:${store.account.apikey.secret}`).toString(
|
||||
'base64'
|
||||
)
|
||||
)
|
||||
.send({ person: 1 })
|
||||
.end((err, res) => {
|
||||
expect(err === null).to.equal(true)
|
||||
expect(res.status).to.equal(200)
|
||||
expect(res.body.result).to.equal(`success`)
|
||||
expect(res.body.pattern.personId).to.equal(store.account.people.her.id)
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
for (const field of ['data', 'settings']) {
|
||||
it(`${store.icon('person', auth)} Should update the ${field} field (${auth})`, (done) => {
|
||||
const data = {}
|
||||
data[field] = { test: { value: 'hello' } }
|
||||
chai
|
||||
.request(config.api)
|
||||
.put(`/patterns/${store.account.patterns[auth].id}/${auth}`)
|
||||
.set(
|
||||
'Authorization',
|
||||
auth === 'jwt'
|
||||
? 'Bearer ' + store.account.token
|
||||
: 'Basic ' +
|
||||
new Buffer(
|
||||
`${store.account.apikey.key}:${store.account.apikey.secret}`
|
||||
).toString('base64')
|
||||
)
|
||||
.send(data)
|
||||
.end((err, res) => {
|
||||
expect(err === null).to.equal(true)
|
||||
expect(res.status).to.equal(200)
|
||||
expect(res.body.result).to.equal(`success`)
|
||||
expect(res.body.pattern[field].test.value).to.equal('hello')
|
||||
done()
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
it(`${store.icon('pattern', auth)} Should read a pattern (${auth})`, (done) => {
|
||||
chai
|
||||
.request(config.api)
|
||||
.get(`/patterns/${store.account.patterns[auth].id}/${auth}`)
|
||||
.set(
|
||||
'Authorization',
|
||||
auth === 'jwt'
|
||||
? 'Bearer ' + store.account.token
|
||||
: 'Basic ' +
|
||||
new Buffer(`${store.account.apikey.key}:${store.account.apikey.secret}`).toString(
|
||||
'base64'
|
||||
)
|
||||
)
|
||||
.end((err, res) => {
|
||||
expect(err === null).to.equal(true)
|
||||
expect(res.status).to.equal(200)
|
||||
expect(res.body.result).to.equal(`success`)
|
||||
expect(res.body.pattern.data.test.value).to.equal('hello')
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
it(`${store.icon(
|
||||
'person',
|
||||
auth
|
||||
)} Should not allow reading another user's pattern (${auth})`, (done) => {
|
||||
chai
|
||||
.request(config.api)
|
||||
.get(`/patterns/${store.account.patterns[auth].id}/${auth}`)
|
||||
.set(
|
||||
'Authorization',
|
||||
auth === 'jwt'
|
||||
? 'Bearer ' + store.altaccount.token
|
||||
: 'Basic ' +
|
||||
new Buffer(
|
||||
`${store.altaccount.apikey.key}:${store.altaccount.apikey.secret}`
|
||||
).toString('base64')
|
||||
)
|
||||
.end((err, res) => {
|
||||
expect(err === null).to.equal(true)
|
||||
expect(res.status).to.equal(403)
|
||||
expect(res.body.result).to.equal(`error`)
|
||||
expect(res.body.error).to.equal(`insufficientAccessLevel`)
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
it(`${store.icon(
|
||||
'person',
|
||||
auth
|
||||
)} Should not allow updating another user's pattern (${auth})`, (done) => {
|
||||
chai
|
||||
.request(config.api)
|
||||
.put(`/patterns/${store.account.patterns[auth].id}/${auth}`)
|
||||
.set(
|
||||
'Authorization',
|
||||
auth === 'jwt'
|
||||
? 'Bearer ' + store.altaccount.token
|
||||
: 'Basic ' +
|
||||
new Buffer(
|
||||
`${store.altaccount.apikey.key}:${store.altaccount.apikey.secret}`
|
||||
).toString('base64')
|
||||
)
|
||||
.send({
|
||||
name: 'I have been taken over',
|
||||
})
|
||||
.end((err, res) => {
|
||||
expect(err === null).to.equal(true)
|
||||
expect(res.status).to.equal(403)
|
||||
expect(res.body.result).to.equal(`error`)
|
||||
expect(res.body.error).to.equal(`insufficientAccessLevel`)
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
it(`${store.icon(
|
||||
'person',
|
||||
auth
|
||||
)} Should not allow removing another user's pattern (${auth})`, (done) => {
|
||||
chai
|
||||
.request(config.api)
|
||||
.delete(`/patterns/${store.account.patterns[auth].id}/${auth}`)
|
||||
.set(
|
||||
'Authorization',
|
||||
auth === 'jwt'
|
||||
? 'Bearer ' + store.altaccount.token
|
||||
: 'Basic ' +
|
||||
new Buffer(
|
||||
`${store.altaccount.apikey.key}:${store.altaccount.apikey.secret}`
|
||||
).toString('base64')
|
||||
)
|
||||
.end((err, res) => {
|
||||
expect(err === null).to.equal(true)
|
||||
expect(res.status).to.equal(403)
|
||||
expect(res.body.result).to.equal(`error`)
|
||||
expect(res.body.error).to.equal(`insufficientAccessLevel`)
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
/*
|
||||
it(`${store.icon('person', auth)} Should clone a person (${auth})`, (done) => {
|
||||
chai
|
||||
.request(config.api)
|
||||
.post(`/people/${store.person[auth].id}/clone/${auth}`)
|
||||
.set(
|
||||
'Authorization',
|
||||
auth === 'jwt'
|
||||
? 'Bearer ' + store.account.token
|
||||
: 'Basic ' +
|
||||
new Buffer(`${store.account.apikey.key}:${store.account.apikey.secret}`).toString(
|
||||
'base64'
|
||||
)
|
||||
)
|
||||
.end((err, res) => {
|
||||
expect(err === null).to.equal(true)
|
||||
expect(res.status).to.equal(200)
|
||||
expect(res.body.result).to.equal(`success`)
|
||||
expect(typeof res.body.error).to.equal(`undefined`)
|
||||
expect(typeof res.body.person.id).to.equal(`number`)
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
it(`${store.icon(
|
||||
'person',
|
||||
auth
|
||||
)} Should (not) clone a public person across accounts (${auth})`, (done) => {
|
||||
chai
|
||||
.request(config.api)
|
||||
.post(`/people/${store.person[auth].id}/clone/${auth}`)
|
||||
.set(
|
||||
'Authorization',
|
||||
auth === 'jwt'
|
||||
? 'Bearer ' + store.altaccount.token
|
||||
: 'Basic ' +
|
||||
new Buffer(
|
||||
`${store.altaccount.apikey.key}:${store.altaccount.apikey.secret}`
|
||||
).toString('base64')
|
||||
)
|
||||
.end((err, res) => {
|
||||
if (store.person[auth].public) {
|
||||
expect(err === null).to.equal(true)
|
||||
expect(res.status).to.equal(200)
|
||||
expect(res.body.result).to.equal(`success`)
|
||||
expect(typeof res.body.error).to.equal(`undefined`)
|
||||
expect(typeof res.body.person.id).to.equal(`number`)
|
||||
} else {
|
||||
expect(err === null).to.equal(true)
|
||||
expect(res.status).to.equal(403)
|
||||
expect(res.body.result).to.equal(`error`)
|
||||
expect(res.body.error).to.equal(`insufficientAccessLevel`)
|
||||
}
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
// TODO:
|
||||
// - Clone person
|
||||
// - Clone person accross accounts of they are public
|
||||
*/
|
||||
})
|
||||
}
|
||||
}
|
|
@ -4,12 +4,17 @@ import chai from 'chai'
|
|||
import http from 'chai-http'
|
||||
import { verifyConfig } from '../src/config.mjs'
|
||||
import { randomString } from '../src/utils/crypto.mjs'
|
||||
import {
|
||||
cisFemaleAdult34 as her,
|
||||
cisMaleAdult42 as him,
|
||||
} from '../../../packages/models/src/index.mjs'
|
||||
|
||||
dotenv.config()
|
||||
|
||||
const config = verifyConfig(true)
|
||||
const expect = chai.expect
|
||||
chai.use(http)
|
||||
const people = { her, him }
|
||||
|
||||
export const setup = async () => {
|
||||
// Initial store contents
|
||||
|
@ -21,17 +26,20 @@ export const setup = async () => {
|
|||
email: `test_${randomString()}@${config.tests.domain}`,
|
||||
language: 'en',
|
||||
password: randomString(),
|
||||
people: {},
|
||||
},
|
||||
altaccount: {
|
||||
email: `test_${randomString()}@${config.tests.domain}`,
|
||||
language: 'en',
|
||||
password: randomString(),
|
||||
people: {},
|
||||
},
|
||||
icons: {
|
||||
user: '🧑 ',
|
||||
jwt: '🎫 ',
|
||||
key: '🎟️ ',
|
||||
person: '🧕 ',
|
||||
pattern: '👕 ',
|
||||
},
|
||||
randomString,
|
||||
}
|
||||
|
@ -63,12 +71,12 @@ export const setup = async () => {
|
|||
}
|
||||
store[acc].token = result.data.token
|
||||
store[acc].username = result.data.account.username
|
||||
store[acc].userid = result.data.account.id
|
||||
store[acc].id = result.data.account.id
|
||||
|
||||
// Create API key
|
||||
try {
|
||||
result = await axios.post(
|
||||
`${store.config.api}/apikey/jwt`,
|
||||
`${store.config.api}/apikeys/jwt`,
|
||||
{
|
||||
name: 'Test API key',
|
||||
level: 4,
|
||||
|
@ -85,6 +93,29 @@ export const setup = async () => {
|
|||
process.exit()
|
||||
}
|
||||
store[acc].apikey = result.data.apikey
|
||||
|
||||
// Create people key
|
||||
for (const name in people) {
|
||||
try {
|
||||
result = await axios.post(
|
||||
`${store.config.api}/people/jwt`,
|
||||
{
|
||||
name: `This is ${name} name`,
|
||||
name: `These are ${name} notes`,
|
||||
measies: people[name],
|
||||
},
|
||||
{
|
||||
headers: {
|
||||
authorization: `Bearer ${store[acc].token}`,
|
||||
},
|
||||
}
|
||||
)
|
||||
} catch (err) {
|
||||
console.log('Failed at API key creation request', err)
|
||||
process.exit()
|
||||
}
|
||||
store[acc].people[name] = result.data.person
|
||||
}
|
||||
}
|
||||
|
||||
return { chai, config, expect, store }
|
||||
|
|
|
@ -184,12 +184,12 @@ export const userTests = async (chai, config, expect, store) => {
|
|||
})
|
||||
})
|
||||
|
||||
step(`${store.icon('user')} Should login with userid and password`, (done) => {
|
||||
step(`${store.icon('user')} Should login with id and password`, (done) => {
|
||||
chai
|
||||
.request(config.api)
|
||||
.post('/login')
|
||||
.send({
|
||||
username: store.account.userid,
|
||||
username: store.account.id,
|
||||
password: store.account.password,
|
||||
})
|
||||
.end((err, res) => {
|
||||
|
|
|
@ -11,13 +11,12 @@ import { designs, plugins } from '../../../config/software/index.mjs'
|
|||
* srcPkgs: Array of folders in the monorepo/packages that should be aliased
|
||||
* so they are loaded from source, rather than from a compiled bundle
|
||||
*/
|
||||
const config = (site, remarkPlugins=[]) => ({
|
||||
const config = (site, remarkPlugins = []) => ({
|
||||
experimental: {
|
||||
externalDir: true,
|
||||
},
|
||||
pageExtensions: [ 'js', 'md', 'mjs' ],
|
||||
pageExtensions: ['js', 'md', 'mjs'],
|
||||
webpack: (config, options) => {
|
||||
|
||||
// Fixes npm packages that depend on node modules
|
||||
if (!options.isServer) {
|
||||
config.resolve.fallback.fs = false
|
||||
|
@ -34,35 +33,30 @@ const config = (site, remarkPlugins=[]) => ({
|
|||
loader: '@mdx-js/loader',
|
||||
//providerImportSource: '@mdx-js/react',
|
||||
options: {
|
||||
remarkPlugins: [
|
||||
remarkGfm,
|
||||
...remarkPlugins,
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
remarkPlugins: [remarkGfm, ...remarkPlugins],
|
||||
},
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
// YAML support
|
||||
config.module.rules.push({
|
||||
test: /\.ya?ml$/,
|
||||
use: 'yaml-loader'
|
||||
use: 'yaml-loader',
|
||||
})
|
||||
|
||||
// Fix for nextjs bug #17806
|
||||
config.module.rules.push({
|
||||
test: /index.mjs$/,
|
||||
type: "javascript/auto",
|
||||
type: 'javascript/auto',
|
||||
resolve: {
|
||||
fullySpecified: false
|
||||
}
|
||||
fullySpecified: false,
|
||||
},
|
||||
})
|
||||
|
||||
// Suppress warnings about importing version from package.json
|
||||
// We'll deal with it in v3 of FreeSewing
|
||||
config.ignoreWarnings = [
|
||||
/only default export is available soon/
|
||||
]
|
||||
config.ignoreWarnings = [/only default export is available soon/]
|
||||
|
||||
// Aliases
|
||||
config.resolve.alias.shared = path.resolve('../shared/')
|
||||
|
@ -75,19 +69,25 @@ const config = (site, remarkPlugins=[]) => ({
|
|||
|
||||
// Load designs from source, rather than compiled package
|
||||
for (const design in designs) {
|
||||
config.resolve.alias[`@freesewing/${design}$`] = path.resolve(`../../designs/${design}/src/index.mjs`)
|
||||
config.resolve.alias[`@freesewing/${design}$`] = path.resolve(
|
||||
`../../designs/${design}/src/index.mjs`
|
||||
)
|
||||
}
|
||||
// Load plugins from source, rather than compiled package
|
||||
for (const plugin in plugins) {
|
||||
config.resolve.alias[`@freesewing/${plugin}$`] = path.resolve(`../../plugins/${plugin}/src/index.mjs`)
|
||||
config.resolve.alias[`@freesewing/${plugin}$`] = path.resolve(
|
||||
`../../plugins/${plugin}/src/index.mjs`
|
||||
)
|
||||
}
|
||||
// Load these from source, rather than compiled package
|
||||
for (const pkg of ['core', 'config-helpers', 'i18n', 'models']) {
|
||||
config.resolve.alias[`@freesewing/${pkg}$`] = path.resolve(`../../packages/${pkg}/src/index.mjs`)
|
||||
for (const pkg of ['core', 'i18n', 'models', 'snapseries']) {
|
||||
config.resolve.alias[`@freesewing/${pkg}$`] = path.resolve(
|
||||
`../../packages/${pkg}/src/index.mjs`
|
||||
)
|
||||
}
|
||||
|
||||
return config
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
export default config
|
||||
|
|
|
@ -4,6 +4,7 @@ import chai from 'chai'
|
|||
import { timingPlugin } from '@freesewing/plugin-timing'
|
||||
|
||||
const expect = chai.expect
|
||||
const ciTimeout = 10000
|
||||
|
||||
/*
|
||||
* This runs unit tests for pattern drafting
|
||||
|
@ -39,7 +40,7 @@ export const testPatternDrafting = (Pattern, log = false) => {
|
|||
*/
|
||||
if (family !== 'utilities') {
|
||||
describe('Draft for humans:', function () {
|
||||
this.timeout(5000)
|
||||
this.timeout(ciTimeout)
|
||||
for (const type of ['cisFemale', 'cisMale']) {
|
||||
describe(type, () => {
|
||||
for (const size in adult[type]) {
|
||||
|
@ -62,7 +63,7 @@ export const testPatternDrafting = (Pattern, log = false) => {
|
|||
const fams = { doll, giant }
|
||||
for (const family of ['doll', 'giant']) {
|
||||
describe(`Draft for ${family}:`, function () {
|
||||
this.timeout(5000)
|
||||
this.timeout(ciTimeout)
|
||||
for (const type of ['cisFemale', 'cisMale']) {
|
||||
describe(type, () => {
|
||||
for (const size in fams[family][type]) {
|
||||
|
|
|
@ -6,6 +6,7 @@ class TerseReporter {
|
|||
constructor(runner) {
|
||||
runner.on(EVENT_TEST_FAIL, (test, err) => {
|
||||
console.log(` FAIL: ${test.fullTitle()}`)
|
||||
console.log(err)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue