feat: Support notes in title macro
This started out as a fix for #5753 after I felt the approach in #5760 was not the best way to handle this. The problem lies in the way the cutlist data is added to the pattern by the title plugin. The whole cutlist thing was added by a less experienced contributor, and they were a little too focussed on what they were trying to accomplish, rather than keeping an eye on the big picture. In this case, a bunch of points get added to the part to anchor all of the text for the cutlist. This causes problems when inheriting parts because points are added per material, and now we don't know what materials were used and how to remove these points. I appreciate @woutervdub effort to resolve this in #5760 but as I mentioned above, I don't love the approach. Rather than finding a clever way to remove these points, I am of the opinion that they should never have been added in the first place. Specifically, all we are doing is adding one or more lines of text. The idea that you need multiple points for this is an incorrect assumption, and therein lies the root of this problem. So, this refactor adds support to the title macro for 'notes'. These notes will be added below the title. The cutlist info is now just prepended to the notes. So it stops being something 'special' (which it never should have been) and instead just behaves like other text that is placed on the part by the title macro. The only flanking measure required to make this happen was to add support for nested arrays in handling text. This way, you can pass a nested array to concatenate strings in such a way that each part will still be individually translated. And as all text in FreeSewing, to force a linebreak, you just use "\n" as The Dark Lord intended.
This commit is contained in:
parent
9e5fbdf3ba
commit
c06a18e06e
4 changed files with 109 additions and 101 deletions
|
@ -1,6 +1,18 @@
|
||||||
Unreleased:
|
Unreleased:
|
||||||
Added:
|
Added:
|
||||||
|
|
||||||
|
Added:
|
||||||
|
plugin-annotations:
|
||||||
|
- The `title` macro now takes a `notes` and `classes.notes` as its config, allowing you to add notes
|
||||||
|
- The `classes.cutlist` config is removed from the title plugin, cutlist info is now included as notes
|
||||||
|
plugin-i18n:
|
||||||
|
- This plugin now supports translation of nested arrays of strings, giving you more flexibility to concatenate translated parts of strings
|
||||||
|
react-components:
|
||||||
|
- This Pattern component now supports translation of nested arrays of strings, giving you more flexibility to concatenate translated parts of strings
|
||||||
|
|
||||||
|
Removed:
|
||||||
|
- The `classes.cutlist` config is removed from the title plugin, cutlist info is now included as notes
|
||||||
|
|
||||||
3.1.0:
|
3.1.0:
|
||||||
date: 2023-12-26
|
date: 2023-12-26
|
||||||
Added:
|
Added:
|
||||||
|
|
|
@ -71,7 +71,8 @@ export const getId = ({
|
||||||
export const translateStrings = (t, list) => {
|
export const translateStrings = (t, list) => {
|
||||||
let translated = ''
|
let translated = ''
|
||||||
for (const string of list) {
|
for (const string of list) {
|
||||||
if (string) translated += t(string.toString()).replace(/"/g, '"') + ' '
|
if (Array.isArray(string)) translated += translateStrings(t, string)
|
||||||
|
else if (string) translated += t(string.toString()).replace(/"/g, '"') + ' '
|
||||||
}
|
}
|
||||||
|
|
||||||
return translated
|
return translated
|
||||||
|
|
|
@ -1,3 +1,6 @@
|
||||||
|
const capitalize = (string) =>
|
||||||
|
typeof string === 'string' ? string.charAt(0).toUpperCase() + string.slice(1) : ''
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Defaults for the title macro
|
* Defaults for the title macro
|
||||||
*/
|
*/
|
||||||
|
@ -12,10 +15,10 @@ const macroDefaults = {
|
||||||
rotation: 0,
|
rotation: 0,
|
||||||
scale: 1,
|
scale: 1,
|
||||||
title: 'plugin-annotations:noName',
|
title: 'plugin-annotations:noName',
|
||||||
|
notes: false,
|
||||||
classes: {
|
classes: {
|
||||||
cutlist: 'text-md fill-current',
|
notes: 'text-md fill-current',
|
||||||
date: 'text-sm fill-current',
|
date: 'text-sm fill-current',
|
||||||
for: 'fill-current font-bold',
|
|
||||||
name: 'fill-note',
|
name: 'fill-note',
|
||||||
nr: 'text-4xl fill-note font-bold',
|
nr: 'text-4xl fill-note font-bold',
|
||||||
title: 'text-lg fill-current font-bold',
|
title: 'text-lg fill-current font-bold',
|
||||||
|
@ -74,9 +77,8 @@ const title = function (config, { Point, points, scale, locale, store, part, log
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Get the list of IDs
|
* Get the list of IDs
|
||||||
* Initialize the verticle cadence
|
|
||||||
*/
|
*/
|
||||||
const ids = store.generateMacroIds(['cutlist', 'date', 'for', 'name', 'nr', 'title'], mc.id)
|
const ids = store.generateMacroIds(['nr', 'date', 'title', 'name', 'notes'], mc.id)
|
||||||
|
|
||||||
let shift = mc.dy
|
let shift = mc.dy
|
||||||
|
|
||||||
|
@ -93,6 +95,24 @@ const title = function (config, { Point, points, scale, locale, store, part, log
|
||||||
store.set(['partNumbers', part.name], mc.nr)
|
store.set(['partNumbers', part.name], mc.nr)
|
||||||
} else delete ids.nr
|
} else delete ids.nr
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Title: date
|
||||||
|
*/
|
||||||
|
points[ids.date] = mc.at
|
||||||
|
.shift(-90, shift / 2)
|
||||||
|
.addText(
|
||||||
|
new Date().toLocaleString(locale || 'en', {
|
||||||
|
weekday: 'long',
|
||||||
|
year: 'numeric',
|
||||||
|
month: 'short',
|
||||||
|
day: 'numeric',
|
||||||
|
}),
|
||||||
|
`${mc.classes.date} ${mc.align}`
|
||||||
|
)
|
||||||
|
.attr('data-text-transform', transform)
|
||||||
|
.attr('data-render-always', 1) // Render even when outside the part bounding box
|
||||||
|
shift += mc.dy
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Title: title
|
* Title: title
|
||||||
*/
|
*/
|
||||||
|
@ -100,18 +120,39 @@ const title = function (config, { Point, points, scale, locale, store, part, log
|
||||||
points[ids.title] = mc.at
|
points[ids.title] = mc.at
|
||||||
.clone()
|
.clone()
|
||||||
.shift(-90, shift)
|
.shift(-90, shift)
|
||||||
.attr('data-text', mc.title, mc.append ? false : true)
|
|
||||||
.attr('data-text-class', `${mc.classes.title} ${mc.align}`)
|
|
||||||
.attr('data-text-transform', transform)
|
.attr('data-text-transform', transform)
|
||||||
.attr('data-render-always', 1) // Render even when outside the part bounding box
|
.attr('data-render-always', 1) // Render even when outside the part bounding box
|
||||||
|
if (mc.append) points[ids.title].addText(mc.title, `${mc.classes.title} ${mc.align}`)
|
||||||
|
else points[ids.title].setText(mc.title, `${mc.classes.title} ${mc.align}`)
|
||||||
shift += mc.dy
|
shift += mc.dy
|
||||||
store.set(['partTitles', part.name], mc.title)
|
store.set(['partTitles', part.name], mc.title)
|
||||||
} else delete ids.title
|
} else delete ids.title
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Title: cutlist
|
* Title: name
|
||||||
*/
|
*/
|
||||||
|
points[ids.name] = mc.at
|
||||||
|
.clone()
|
||||||
|
.shift(-90, shift)
|
||||||
|
.addText(
|
||||||
|
`FreeSewing ${capitalize(
|
||||||
|
(store.data?.name || 'plugin-annotations:noName').replace('@freesewing/', '')
|
||||||
|
)} v${store.data?.version || 'plugin-annotations:noVersion'} ( `,
|
||||||
|
|
||||||
|
`${mc.classes.name} ${mc.align}`
|
||||||
|
)
|
||||||
|
.addText(store.data?.for ? store.data.for : 'ephemeral')
|
||||||
|
.addText(' )')
|
||||||
|
.attr('data-text-transform', transform)
|
||||||
|
.attr('data-render-always', 1) // Render even when outside the part bounding box
|
||||||
|
shift += mc.dy
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Title: notes
|
||||||
|
*/
|
||||||
|
const notes = []
|
||||||
if (mc.cutlist) {
|
if (mc.cutlist) {
|
||||||
|
points[ids.notes] = mc.at.clone().shift(-90, shift)
|
||||||
/*
|
/*
|
||||||
* Get cutlist instructions from the store, only proceed if the list is available
|
* Get cutlist instructions from the store, only proceed if the list is available
|
||||||
*/
|
*/
|
||||||
|
@ -123,92 +164,37 @@ const title = function (config, { Point, points, scale, locale, store, part, log
|
||||||
for (const [material, instructions] of Object.entries(partCutlist.materials)) {
|
for (const [material, instructions] of Object.entries(partCutlist.materials)) {
|
||||||
instructions.forEach(({ cut, identical, onBias, onFold }, c) => {
|
instructions.forEach(({ cut, identical, onBias, onFold }, c) => {
|
||||||
/*
|
/*
|
||||||
* Create point
|
* Concat line
|
||||||
*/
|
|
||||||
const id = `${ids.cutlist}_${material}_${c}`
|
|
||||||
ids[`cutlist_${material}_${c}`] = id
|
|
||||||
points[id] = mc.at
|
|
||||||
.clone()
|
|
||||||
.shift(-90, shift)
|
|
||||||
.attr('data-text', 'plugin-annotations:cut')
|
|
||||||
.attr('data-text-class', `${mc.classes.cutlist} ${mc.align}`)
|
|
||||||
.attr('data-text-transform', transform)
|
|
||||||
.attr('data-render-always', 1) // Render even when outside the part bounding box
|
|
||||||
.addText(cut)
|
|
||||||
shift += mc.dy
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Add instructions if parts are mirrored
|
|
||||||
*/
|
|
||||||
if (!identical && cut > 1) points[id].addText('plugin-annotations:mirrored')
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Add instructions if parts are cut on fold
|
|
||||||
*/
|
*/
|
||||||
|
notes.push('plugin-annotations:cut')
|
||||||
|
notes.push(cut)
|
||||||
|
if (!identical && cut > 1) notes.push('plugin-annotations:mirrored')
|
||||||
if (onFold)
|
if (onFold)
|
||||||
points[id].addText(
|
notes.push(onBias ? 'plugin-annotations:onFoldAndBias' : 'plugin-annotations:onFold')
|
||||||
onBias ? 'plugin-annotations:onFoldAndBias' : 'plugin-annotations:onFold'
|
else if (onBias) notes.push('plugin-annotations:onBias')
|
||||||
)
|
notes.push('plugin-annotations:from', 'plugin-annotations:' + material)
|
||||||
/*
|
/*
|
||||||
* Add instructions if parts on on bias
|
* Force a line break between materials
|
||||||
*/ else if (onBias) points[id].addText('plugin-annotations:onBias')
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Add 'from' (material) text
|
|
||||||
*/
|
*/
|
||||||
points[id].addText('plugin-annotations:from').addText('plugin-annotations:' + material)
|
notes.push('\n')
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else delete ids.cutlist
|
}
|
||||||
|
if (mc.notes) {
|
||||||
/*
|
if (Array.isArray(notes)) notes.push(...mc.notes)
|
||||||
* Title: Design name
|
else notes.push(mc.notes)
|
||||||
*/
|
}
|
||||||
points[ids.name] = mc.at
|
if (notes.length > 0) {
|
||||||
.clone()
|
/*
|
||||||
.shift(-90, shift)
|
* Add all text on a single point
|
||||||
.attr(
|
*/
|
||||||
'data-text',
|
points[ids.notes]
|
||||||
`${(store.data?.name || 'plugin-annotations:noName').replace('@freesewing/', '')} v${
|
.addText(notes, `${mc.classes.notes} ${mc.align}`)
|
||||||
store.data?.version || 'plugin-annotations:noVersion'
|
|
||||||
}`
|
|
||||||
)
|
|
||||||
.attr('data-text-class', `${mc.classes.name} ${mc.align}`)
|
|
||||||
.attr('data-text-transform', transform)
|
|
||||||
.attr('data-render-always', 1) // Render even when outside the part bounding box
|
|
||||||
shift += mc.dy
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Title: For (measurements set)
|
|
||||||
*/
|
|
||||||
if (store.data.for) {
|
|
||||||
points[ids.for] = mc.at
|
|
||||||
.shift(-90, shift)
|
|
||||||
.attr('data-text', `(${store.data.for})`)
|
|
||||||
.attr('data-text-class', `${mc.classes.for} ${mc.align}`)
|
|
||||||
.attr('data-text-transform', transform)
|
.attr('data-text-transform', transform)
|
||||||
.attr('data-render-always', 1) // Render even when outside the part bounding box
|
.attr('data-render-always', 1) // Render even when outside the part bounding box
|
||||||
shift += mc.dy
|
.attr('data-text-lineheight', mc.dy)
|
||||||
} else delete ids.for
|
} else delete ids.cutlist
|
||||||
|
|
||||||
/*
|
|
||||||
* Title: Date
|
|
||||||
*/
|
|
||||||
points[ids.date] = mc.at
|
|
||||||
.shift(-90, shift)
|
|
||||||
.attr(
|
|
||||||
'data-text',
|
|
||||||
new Date().toLocaleString(locale || 'en', {
|
|
||||||
weekday: 'long',
|
|
||||||
year: 'numeric',
|
|
||||||
month: 'short',
|
|
||||||
day: 'numeric',
|
|
||||||
})
|
|
||||||
)
|
|
||||||
.attr('data-text-class', `${mc.classes.date} ${mc.align}`)
|
|
||||||
.attr('data-text-transform', transform)
|
|
||||||
.attr('data-render-always', 1) // Render even when outside the part bounding box
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Store all IDs in the store so we can remove this macro with rmtitle
|
* Store all IDs in the store so we can remove this macro with rmtitle
|
||||||
|
|
|
@ -1,25 +1,34 @@
|
||||||
import { name, version } from '../data.mjs'
|
import { name, version } from '../data.mjs'
|
||||||
|
|
||||||
|
const translate = (locale, text, t, pattern) => {
|
||||||
|
let translated = ''
|
||||||
|
/*
|
||||||
|
* Call oneself recursively if text is an array
|
||||||
|
*/
|
||||||
|
if (Array.isArray(text))
|
||||||
|
return text.map((string) => translate(locale, string, t, pattern)).join(' ')
|
||||||
|
|
||||||
|
if (t instanceof Function) return t(text, locale)
|
||||||
|
else if (typeof t[locale] === 'object') return t[locale][text] || text
|
||||||
|
else {
|
||||||
|
const msg =
|
||||||
|
"No translation method or object was passed to the i18n plugin. This plugin won't do anything without that"
|
||||||
|
if (pattern?.store?.log?.warn) {
|
||||||
|
if (!pattern.store.get(['plugins', 'plugin-i18n', 'missingMethodWarning'])) {
|
||||||
|
pattern.store.set(['plugins', 'plugin-i18n', 'missingMethodWarning'], true)
|
||||||
|
pattern.store.log.warn(msg)
|
||||||
|
}
|
||||||
|
} else console.log(msg)
|
||||||
|
|
||||||
|
return text
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export const plugin = {
|
export const plugin = {
|
||||||
name,
|
name,
|
||||||
version,
|
version,
|
||||||
hooks: {
|
hooks: {
|
||||||
insertText: (locale, text, t, pattern) => {
|
insertText: (locale, text, t, pattern) => translate(locale, text, t, pattern),
|
||||||
if (t instanceof Function) return t(text, locale)
|
|
||||||
else if (typeof t[locale] === 'object') return t[locale][text] || text
|
|
||||||
else {
|
|
||||||
const msg =
|
|
||||||
"No translation method or object was passed to the i18n plugin. This plugin won't do anything without that"
|
|
||||||
if (pattern?.store?.log?.warn) {
|
|
||||||
if (!pattern.store.get(['plugins', 'plugin-i18n', 'missingMethodWarning'])) {
|
|
||||||
pattern.store.set(['plugins', 'plugin-i18n', 'missingMethodWarning'], true)
|
|
||||||
pattern.store.log.warn(msg)
|
|
||||||
}
|
|
||||||
} else console.log(msg)
|
|
||||||
|
|
||||||
return text
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue