diff --git a/markdown/dev/guides/plugins/conditionally-loading-build-time-plugins/en.md b/markdown/dev/guides/plugins/conditionally-loading-build-time-plugins/en.md deleted file mode 100644 index 5983d8abbd7..00000000000 --- a/markdown/dev/guides/plugins/conditionally-loading-build-time-plugins/en.md +++ /dev/null @@ -1,65 +0,0 @@ ---- -title: Conditionally loading build-time plugins -order: 30 ---- - -You can choose to load your build-time plugin conditionally based on run-time data. - -To do so, you need to create a `condition` method that will determine whether the -plugin will be loaded. This method receives the complete settings object and should -return `true` if the plugin is to be loaded, and `false` if it should not be loaded. - -```js -const condition = settings => { - if (settings) { - // Remember, settings contains: - // settings.options => The user's options - // settings.measurements => The measurements - return true // Load the plugin - } - else return false // Do not load the plugin -} -``` - -You pass your plugin and condition method as a third parameter to the Design constructor -with the `plugin` and `condition` keys respectively. - -Let's look at a complete example to illustrate this: - -```js -import freesewing from '@freesewing/core' -import plugins from '@freesewing/plugin-bundle' -import myConditionalPlugin from '@freesewing/plugin-bust' - -const myConditionalPluginCheck = (settings = false) => - settings && - settings.options && - settings.options.draftForHighBust && - settings.measurements.highBust - ? true - : false - -const Pattern = new freesewing.Design( - config, - plugins, - { - plugin: myConditionalPlugin, - condition: myConditionalPluginCheck - } -) -``` - -Our condition method will return `true` only if the following conditions are met: - -- A `settings` object is passed into the method -- `settings.options` is _truthy_ -- `settings.options.draftForHighBust` is _truthy_ -- `settings.options.measurements.highBust` is _truthy_ - -This is a real-world example from our Teagan pattern. A t-shirt pattern that can be -drafted to the high bust (rather than the full chest circumference) if the user -choses so. - -But that feat is handled auto-magically by `plugin-bust` which is a build-time plugin. -So whether to load this plugin or not hinges on the user settings, which is why we -load this plugin conditionally. diff --git a/markdown/dev/guides/plugins/en.md b/markdown/dev/guides/plugins/en.md index eed12613d62..523adc42ee0 100644 --- a/markdown/dev/guides/plugins/en.md +++ b/markdown/dev/guides/plugins/en.md @@ -1,25 +1,23 @@ --- title: Plugin guide -order: 400 -icons: - - logo - - plugin -for: developers -about: | - This guide shows you everything you need to know to understand plugins in FreeSewing, and create your own. -goals: - - Know about build-time plugins vs run-time plugins - - Understanding plugin structure - - Hooks and how to use them - - Using hooks without a plugin - - Using macros --- -Plugins allow you to extend FreeSewing. +Plugins allow you to extend FreeSewing with new features and functionality. +A FreeSewing plugin can extend FreeSewing in 3 different ways: -We have [a list of available plugins](/reference/plugins/), but +- It can [provide macros](/guides/plugins/macros), which are a way to automate a number of steps into a + single command. +- It can [hook into the pattern](/guides/plugins/hooks), which allows you to manipulate the pattern or + interact with it at various stages of it's lifecycle. +- It can [provide store methods](/guides/plugins/store), which allows you to add new ways to handle data + in the pattern, including providing a custom logger. + +We have [a list of plugins](/reference/plugins/) that we maintain, but if you can't find what you're looking for, you can write your own plugin. -We'll cover the following topics in this guide: +If you plan on doing that, or if you would like to understand how plugins work, +this guide is for you. + +We'll cover the following topics: diff --git a/markdown/dev/guides/plugins/hooks/en.md b/markdown/dev/guides/plugins/hooks/en.md index 9402e60ddd1..4d311b1701f 100644 --- a/markdown/dev/guides/plugins/hooks/en.md +++ b/markdown/dev/guides/plugins/hooks/en.md @@ -1,20 +1,53 @@ --- -title: Hooks -order: 60 +title: Lifecycle hook methods +order: 110 --- -A **hook** is a lifecycle event. The available hooks are: +FreeSewing plugins can provide hooks, which is a way to hook into the pattern's +lifecycle. -- [preRender](/reference/hooks/prerender/): Called at the start of [`Pattern.render()`](/reference/api/pattern/render) -- [postRender](/reference/hooks/postrender/): Called at the end of [`Pattern.render()`](/reference/api/pattern/render) -- [insertText](/reference/hooks/inserttext/): Called when inserting text -- [preDraft](/reference/hooks/predraft/): Called at the start of [`Pattern.draft()`](/reference/api/pattern/draft) -- [postDraft](/reference/hooks/postdraft/): Called at the end of [`Pattern.draft()`](/reference/api/pattern/draft) -- [preSample](/reference/hooks/presample/): Called at the start of [`Pattern.sample()`](/reference/api/pattern/sample) -- [postSample](/reference/hooks/postsample/): Called at the end of [`Pattern.sample()`](/reference/api/pattern/sample) +## Signature -You can register a method for a hook. When the hook is triggered, your method will be -called. It will receive two parameters: +To provide one or more hooks, your plugin should have a `hooks` property that +is an object where the keys are the lifecycle hook name, and the value holds a +method. When the lifecycle hook is triggered, your method will be called. -- An object relevant to the hook. See the [hooks API reference](/reference/hooks/) for details. +```mjs +const myPlugin = { + name: 'example', + version: '0.0.1', + hooks: { + hookName: function (obj, data = {}) { + } + } +} +``` + +If you want to attach multiple methods to the same lifecycle hook, you can pass +them as an array: + +```mjs +const myPlugin = { + name: 'example', + version: '0.0.1', + hooks: { + hookName: [ + function one (obj, data = {}) { }, + function two (obj, data = {}) { } + ] + } +} +``` + +## Arguments + +All lifecycle methods will receive two parameters: + +- An object relevant to the lifecycle hook. See the [hooks API reference](/reference/hooks/) for details. - Data passed when the hook was registered (optional) + +## Notes + +Refer to the [hooks API reference](/reference/hooks/) for a list of all +available lifecycle hooks. + diff --git a/markdown/dev/guides/plugins/loading-build-time-plugins/en.md b/markdown/dev/guides/plugins/loading-build-time-plugins/en.md deleted file mode 100644 index 72417d16fc2..00000000000 --- a/markdown/dev/guides/plugins/loading-build-time-plugins/en.md +++ /dev/null @@ -1,26 +0,0 @@ ---- -title: Loading build-time plugins -order: 20 ---- - -Build-time plugins are loaded at build time, by passing them to -the [`freesewing.Design`](/reference/api/design) constructor: - -```js -import freesewing from "@freesewing/core" -import plugins from "@freesewing/plugin-bundle" -import config from "../config" - -const Pattern = new freesewing.Design(config, plugins) -``` - -If you have multiple plugins to load, you can pass them as an array: - -```js -import freesewing from "@freesewing/core" -import plugins from "@freesewing/plugin-bundle" -import gorePlugin from "@freesewing/plugin-gore" -import config from "../config" - -const Pattern = new freesewing.Design(config, [plugins, gorePlugin] ) -``` diff --git a/markdown/dev/guides/plugins/loading-run-time-plugins/en.md b/markdown/dev/guides/plugins/loading-run-time-plugins/en.md deleted file mode 100644 index 866831426ee..00000000000 --- a/markdown/dev/guides/plugins/loading-run-time-plugins/en.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Loading run-time plugins -order: 40 ---- - -Run-time plugin are loaded at run time, by passing them to the `use` method of -an instatiated pattern. That method is chainable, so if you have multiple plugins -you can just chain them together: - -```js -import Aaron from "@freesewing/aaron"; -import theme from "@freesewing/plugin-theme"; -import i18n from "@freesewing/plugin-i18n"; - -const myAaron = new Aaron() - .use(theme) - .use(i18n) -``` - - - -Plugins that use only hooks are typically run-time plugins - - diff --git a/markdown/dev/guides/plugins/loading/en.md b/markdown/dev/guides/plugins/loading/en.md new file mode 100644 index 00000000000..b6fb025af73 --- /dev/null +++ b/markdown/dev/guides/plugins/loading/en.md @@ -0,0 +1,12 @@ +--- +title: Loading plugins +order: 140 +--- + +Plugins can be loaded at build time and added to the desig. Or at run time and added to an instantiated pattern. + +To load a plugin at build time, it should be added to [the `plugins` key of the part configuration](/reference/api/part/config/plugins). + +To load a plugin at run time, it should be loaded with a call to [`Pattern.use()`](/reference/api/pattern/use). + +Please refer to the relevant documentation for more details. diff --git a/markdown/dev/guides/plugins/macros/en.md b/markdown/dev/guides/plugins/macros/en.md index f764262f43f..8f742f5757c 100644 --- a/markdown/dev/guides/plugins/macros/en.md +++ b/markdown/dev/guides/plugins/macros/en.md @@ -1,58 +1,37 @@ --- -title: Macros -order: 90 +title: Macro methods +order: 120 --- -Plugin structure for macros is similar, with a few changes: +FreeSewing plugins can provide macros, which is a way to automate multiple +steps into a single command. -- Rather than the hook name, you provide the macro name (that you choose yourself) -- The context (`this`) of a macro method is **always** a [Part](/reference/api/part) object. +## Signature -Apart from these, the structure is very similar: +To provide one or more macros, your plugin should have a `macros` property that +is an object where the keys are the macro name, and the value holds a method to +run when the macro is executed. -```js -import {name, version} from '../package.json'; - -export default { - name, - version, +```mjs +const myPlugin = { + name: 'example', + version: '0.0.1', macros: { - box: function(so) { - this.points.boxTopLeft = so.anchor; - this.points.boxTopRight = so.anchor.shift(0, so.size); - this.points.boxBottomRight = this.points.boxTopRight.shift(-90, so.size); - this.points.boxBottomLeft = new this.Point(so.anchor.x, this.points.boxBottomRight.y); - - this.paths.box = new this.Path() - .move(this.points.boxTopLeft) - .line(this.points.boxTopRight) - .line(this.points.boxBottomRight) - .line(this.points.boxBottomLeft) - .close() - .attr('class', 'box'); - } - } + example: function(so, { log }) { + log.info('Running the example macro') + } + } } ``` -Did you figure out what this plugin does? -It provides a `box` macro that draws a box on our pattern in a given location with a give size. +## Arguments -We can use it like this: +All macros receive two arguments: -```js -points.boxAnchor = new Point(100, 100); -macro('box', { - anchor: points.boxAnchor - size: 25 -}); -``` - -Obviously, you can expect to learn how to call a macro in its documentation, -rather than have to comb through its code. +- `so`: A plain object holding configuration object passed to the macro +- `props`: The same object as passed to the Part.draft()` method that you can destructure - ###### Macros take only 1 argument When writing a macro, keep in mind that all information that needs to be passed @@ -61,3 +40,7 @@ to a macro needs to be contained in a single argument. Typically, you use a single plain object to configure the macro. + +## Return value + +Macros do not need to return anything. If they do, it will be ignored. diff --git a/markdown/dev/guides/plugins/plugin-structure/en.md b/markdown/dev/guides/plugins/plugin-structure/en.md deleted file mode 100644 index f19f4bb5676..00000000000 --- a/markdown/dev/guides/plugins/plugin-structure/en.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Plugin structure -order: 50 ---- - -Plugins can do two things: - -- They can use hooks -- They can provide macros - -Your plugin should export an object with the following structure: - -```js -{ - name: 'myPlugin', - version: '1.0.0', - hooks: {}, - macros: {} -}; -``` - -The `name` and `version` attributes are self-explanatory. -The [hooks](/guides/plugins/hooks/) and [macros](/guides/plugins/macros/) sections -explain the `hooks` and `macros` properties. diff --git a/markdown/dev/guides/plugins/store/en.md b/markdown/dev/guides/plugins/store/en.md new file mode 100644 index 00000000000..a17c345d84b --- /dev/null +++ b/markdown/dev/guides/plugins/store/en.md @@ -0,0 +1,56 @@ +--- +title: Store methods +order: 130 +--- + +FreeSewing plugins can provide store methods, which facilitate data handling +within a pattern. + +## Signature + +To provide one or more store methods, your plugin should have a `macros` property that +is an array where each member is itself an array with two members: + +- The first member holds the key to attach the method to (in dot notation) +- The second member holds the method to attach + +```mjs +const myPlugin = { + name: 'example', + version: '0.0.1', + store: [ + [ + 'log.panic', + function(store, ...params) { + store.setIfUnset('logs.panic', new Array()) + store.push(...params) + } + ] + } +} +``` + +## Arguments + +All store methods receive at least two arguments: + +- `store`: The store object itself +- `...params`: All additional plugins that were passed to the store method + +## Overwriting store methods + +You are allowed to overwrite existing store methods. +As it happens, this is how you should implement a custom logging solution, but overwriting the logging methods under the store's `log` key, + +However, the following methods cannot be overwritten: + +- `extend` +- `get` +- `push` +- `set` +- `setIfUnset` +- `unset` + +## Return value + +Store methods do not need to return anything. If they do, it will be ignored. diff --git a/markdown/dev/guides/plugins/structure/en.md b/markdown/dev/guides/plugins/structure/en.md new file mode 100644 index 00000000000..86df5d753e7 --- /dev/null +++ b/markdown/dev/guides/plugins/structure/en.md @@ -0,0 +1,25 @@ +--- +title: Plugin structure +order: 100 +--- + +A FreeSewing plugin is a plain object with the following structure: + +```mjs +Object plugin = { + String name, + String version, + Object hooks, + Object macros, + Array store, +} +``` + +A plugin **must** have the `name` and `version` properties. +The other properties are optional, and they map to the three different functionalities macros can provide: + +- `hooks`: Holds an object with lifecycle hooks the plugin wants to hook into +- `macros`: Holds and object with macros the plugin provides +- `store`: Holds and Array with store methods the plugin provides. + +Click on the links above for more details on the structure of these properties. diff --git a/markdown/dev/guides/plugins/types-of-plugins/en.md b/markdown/dev/guides/plugins/types-of-plugins/en.md deleted file mode 100644 index 3a500ebd83a..00000000000 --- a/markdown/dev/guides/plugins/types-of-plugins/en.md +++ /dev/null @@ -1,36 +0,0 @@ ---- -title: Types of plugins -order: 10 ---- - -Plugins come in two flavours: - -- [Build-time plugins](#build-time-plugins) -- [Run-time plugins](#run-time-plugins) - -When writing a plugin, ask yourself whether it's a run-time or a build-time plugin. -And if the answer is both, please split them into two plugins. - -## Build-time plugins - -A plugin is a build-time plugin if it is required by the pattern at build-time. -In other words, the plugin is a dependency for the pattern, and if it's missing -the pattern won't load. - - - -Our [plugin bundle](/reference/plugins/bundle/) bundles build-time plugins that are used in many patterns. - - - -Plugins that provide a macro are typically build-time plugins - -## Run-time plugins - -A plugin is a run-time plugin if it can be added after instantiating your pattern. -Think of it as a plugin to be used in the front-end. - -Run-time plugins are not a dependecy of the pattern. They just _add something_ to it. - -Our [theme plugin](/reference/plugins/theme/) is a good example of a run-time plugin. -If it's missing, your pattern will still work, it just won't look pretty. diff --git a/markdown/dev/guides/plugins/using-hooks-more-than-once/en.md b/markdown/dev/guides/plugins/using-hooks-more-than-once/en.md deleted file mode 100644 index 2edc8d266c8..00000000000 --- a/markdown/dev/guides/plugins/using-hooks-more-than-once/en.md +++ /dev/null @@ -1,26 +0,0 @@ ---- -title: Using hooks more than once -order: 80 ---- - -What if you want to attach more than one method to a hook? -You could spread them over separate plugins, but there's a better way. - -Rather than assigning a method to your hook, assign an array of methods like this: - -```js -import myCoolMethod from './method-a'; -import myEvenCoolerMethod from './method-b'; -import {name, version} from '../package.json'; - -export default { - name, - version, - hooks: { - preRender: [ - myCoolMethod, - myEvenCoolerMethod - ] - } -} -``` diff --git a/markdown/dev/guides/plugins/using-hooks-without-plugin/en.md b/markdown/dev/guides/plugins/using-hooks-without-plugin/en.md deleted file mode 100644 index bdd452a33a1..00000000000 --- a/markdown/dev/guides/plugins/using-hooks-without-plugin/en.md +++ /dev/null @@ -1,19 +0,0 @@ ---- -title: Using hooks without a plugin -order: 85 ---- - -You can attach a method to a hook at run-time without the need for a plugin -using the [Pattern.on()](/reference/api/pattern/on) method. - -The method takes the hook name as its first argument, and the hook method as its second. - -Below is an example: - -```js -pattern.on('preRender', function(svg) { - svg.style += "svg { background: yellow;}"; -}); -``` - -Congratulations, you've just made your pattern yellow. diff --git a/markdown/dev/guides/plugins/using-hooks/en.md b/markdown/dev/guides/plugins/using-hooks/en.md deleted file mode 100644 index e0539f50e13..00000000000 --- a/markdown/dev/guides/plugins/using-hooks/en.md +++ /dev/null @@ -1,58 +0,0 @@ ---- -title: Using hooks -order: 70 ---- - -For each hook, your plugin should provide a method that takes the relevant data -as its first argument. If data was passed when the hook was loaded, you will receive -that as the second object. - -Remember that: - -- The `insertText` hook will receive a locale and string and you should return a string. -- All other hooks receive an object. You don't need to return anything, but rather modify the object you receive. - -Let's look at an example: - -```js -import myStyle from './style'; -import myDefs from './defs'; -import {name, version} from '../package.json'; - -export default { - name, - version, - hooks: { - preRender: function(svg) { - if (svg.attributes.get("freesewing:plugin-"+name) === false) { - svg.style += myStyle; - svg.defs += myDefs; - svg.attributes.add("freesewing:plugin-"+name, version); - } - }, - insertText: function(text) { - return text.toUpperCase(); - } - } -} -``` - -This is a complete plugin, ready to be published on NPM. It uses two hooks: - -- `preRender` : We add some style and defs to our SVG -- `insertText` : We transfer all text to UPPERCASE - - - -###### Note that we avoid running our hook twice - -As you can see, the last thing we do in the `preRender` hook is set an attribute on -the SVG tag with the name and version of our plugin. - -We check for this attribute when the `preRender` hook runs, thereby avoiding that -our styles and defs will be added twice. - -It is good practice to wrap you hook methods in a call like this, because you have -no guarantee the user won't render your pattern more than once. - - diff --git a/markdown/dev/guides/v3/migration/en.md b/markdown/dev/guides/v3/migration/en.md index da503610d0a..9abdfb01344 100644 --- a/markdown/dev/guides/v3/migration/en.md +++ b/markdown/dev/guides/v3/migration/en.md @@ -47,6 +47,24 @@ The following packages have been removed in v3: - **@freesewing/plugin-export-dxf**: DXF is kinda garbage, you deserve better - **@freesewing/plugin-validate** +### API changes + +#### Use log instead of raise + +The `raise` object that held methods for logging has been replaced by log: + +```mjs +// strikeout-start +raise.warning('This raise object no longer exists') +// strikeout-end +// highlight-start +raise.info('Use the log object instead') +// highlight-end +``` + +Note that `log` can be destructured in your draft method. +Refer to [the `Store.log` documentation](/reference/api/store/log) for all details. + ## Migrating designs ### Design configuration