feat(shared): Added support for flags
This commit is contained in:
parent
7f55b3fb2a
commit
4528e1bc88
14 changed files with 232 additions and 24 deletions
|
@ -4,6 +4,10 @@
|
||||||
"cutOnFold": "Cut on fold",
|
"cutOnFold": "Cut on fold",
|
||||||
"cutOnFoldAndGrainline": "Cut on fold / Grainline",
|
"cutOnFoldAndGrainline": "Cut on fold / Grainline",
|
||||||
"fabric": "main fabric",
|
"fabric": "main fabric",
|
||||||
|
"altFabric1": "alternative fabric",
|
||||||
|
"altFabric2": "alternative fabric #2",
|
||||||
|
"altFabric3": "alternative fabric #3",
|
||||||
|
"altFabric4": "alternative fabric #4",
|
||||||
"facing": "facing",
|
"facing": "facing",
|
||||||
"from": "from",
|
"from": "from",
|
||||||
"fusible": "fusible interfacing",
|
"fusible": "fusible interfacing",
|
||||||
|
|
|
@ -30,7 +30,11 @@ export const cutlistHooks = {
|
||||||
* @param {boolean} so.ignoreOnFold should these cutting instructions ignore any cutOnFold information set by the part
|
* @param {boolean} so.ignoreOnFold should these cutting instructions ignore any cutOnFold information set by the part
|
||||||
*/
|
*/
|
||||||
function addCut(store, so = {}) {
|
function addCut(store, so = {}) {
|
||||||
const { cut = 2, material = 'fabric', identical = false, bias = false, ignoreOnFold = false } = so
|
const { cut = 2, identical = false, bias = false, ignoreOnFold = false } = so
|
||||||
|
// Make 'from' an alias for material
|
||||||
|
let { material = 'fabric' } = so
|
||||||
|
if (so.from) material = so.from
|
||||||
|
|
||||||
const partName = store.get('activePart')
|
const partName = store.get('activePart')
|
||||||
if (cut === false) {
|
if (cut === false) {
|
||||||
if (material === false) store.unset(['cutlist', partName, 'materials'])
|
if (material === false) store.unset(['cutlist', partName, 'materials'])
|
||||||
|
|
|
@ -12,6 +12,7 @@ const macroDefaults = {
|
||||||
grainline: false,
|
grainline: false,
|
||||||
margin: 0.05,
|
margin: 0.05,
|
||||||
offset: 15,
|
offset: 15,
|
||||||
|
reverse: false,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Export defs
|
// Export defs
|
||||||
|
@ -90,15 +91,13 @@ const cutonfold = function (config, { points, paths, Path, complete, store, scal
|
||||||
/*
|
/*
|
||||||
* Draw the path
|
* Draw the path
|
||||||
*/
|
*/
|
||||||
const from = mc.from.shiftFractionTowards(mc.to, mc.margin / 100)
|
const from = mc.from.shiftFractionTowards(mc.to, mc.margin)
|
||||||
const to = mc.to.shiftFractionTowards(mc.from, mc.margin / 100)
|
const to = mc.to.shiftFractionTowards(mc.from, mc.margin)
|
||||||
const via1 = from.shiftTowards(mc.from, mc.offset * scale).rotate(-90, from)
|
const via1 = from.shiftTowards(mc.from, mc.offset * scale).rotate(-90, from)
|
||||||
const via2 = to.shiftTowards(mc.to, mc.offset * scale).rotate(90, to)
|
const via2 = to.shiftTowards(mc.to, mc.offset * scale).rotate(90, to)
|
||||||
paths[ids.line] = new Path()
|
paths[ids.line] = new Path().move(from).line(via1).line(via2).line(to)
|
||||||
.move(from)
|
if (mc.reverse) paths[ids.line] = paths[ids.line].reverse()
|
||||||
.line(via1)
|
paths[ids.line] = paths[ids.line]
|
||||||
.line(via2)
|
|
||||||
.line(to)
|
|
||||||
.attr('class', mc.classes.line)
|
.attr('class', mc.classes.line)
|
||||||
.attr('marker-start', 'url(#cutonfoldFrom)')
|
.attr('marker-start', 'url(#cutonfoldFrom)')
|
||||||
.attr('marker-end', 'url(#cutonfoldTo)')
|
.attr('marker-end', 'url(#cutonfoldTo)')
|
||||||
|
|
76
plugins/plugin-annotations/src/flag.mjs
Normal file
76
plugins/plugin-annotations/src/flag.mjs
Normal file
|
@ -0,0 +1,76 @@
|
||||||
|
const storeRoot = ['plugins', 'plugin-annotations', 'flags']
|
||||||
|
|
||||||
|
// This is also the order in which they will be displayed
|
||||||
|
export const flagTypes = ['error', 'warn', 'note', 'info', 'tip', 'fixme']
|
||||||
|
|
||||||
|
export const flagStores = [
|
||||||
|
['flag.info', (store, data) => flag('info', store, data)],
|
||||||
|
['flag.tip', (store, data) => flag('tip', store, data)],
|
||||||
|
['flag.note', (store, data) => flag('note', store, data)],
|
||||||
|
['flag.warn', (store, data) => flag('warn', store, data)],
|
||||||
|
['flag.fixme', (store, data) => flag('fixme', store, data)],
|
||||||
|
['flag.error', (store, data) => flag('error', store, data)],
|
||||||
|
['flag.preset', (store, preset) => flag('preset', store, preset)],
|
||||||
|
['unflag.info', (store, id) => unflag('info', store, id)],
|
||||||
|
['unflag.tip', (store, id) => unflag('tip', store, id)],
|
||||||
|
['unflag.note', (store, id) => unflag('note', store, id)],
|
||||||
|
['unflag.warn', (store, id) => unflag('warn', store, id)],
|
||||||
|
['unflag.fixme', (store, id) => unflag('fixme', store, id)],
|
||||||
|
['unflag.error', (store, id) => unflag('error', store, id)],
|
||||||
|
['unflag.preset', (store, preset) => unflag('preset', store, preset)],
|
||||||
|
]
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Method that adds a flag to the store
|
||||||
|
*
|
||||||
|
* @param {type} string - The type of flag
|
||||||
|
* @param {store} object - The pattern store
|
||||||
|
* @param {data} object - The flag data
|
||||||
|
*/
|
||||||
|
function flag(type, store, data) {
|
||||||
|
// Load presets
|
||||||
|
if (type === 'preset' && presets[data]) {
|
||||||
|
data = presets[data]
|
||||||
|
type = data.type
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!data.id && !data.msg) {
|
||||||
|
store.log.warning(`store.flag.${type} called without an id or msg property`)
|
||||||
|
console.log(data)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
store.set([...storeRoot, type, data.id ? data.id : data.msg], data)
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Method that removes a flag from the store
|
||||||
|
*
|
||||||
|
* @param {type} string - The type of flag
|
||||||
|
* @param {store} object - The pattern store
|
||||||
|
* @param {id} string - The flag id to remove
|
||||||
|
*/
|
||||||
|
function unflag(type, store, id) {
|
||||||
|
if (type === 'preset' && presets[preset]) {
|
||||||
|
id = presets[preset].id || presets[preset].msg
|
||||||
|
type = presets[preset].type
|
||||||
|
}
|
||||||
|
store.unset([...storeRoot, type, id])
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Available flag presets
|
||||||
|
*/
|
||||||
|
const presets = {
|
||||||
|
expand: {
|
||||||
|
type: 'tip',
|
||||||
|
msg: 'flag:expandIsOff',
|
||||||
|
suggest: {
|
||||||
|
text: 'flag:enable',
|
||||||
|
icon: 'expand',
|
||||||
|
update: {
|
||||||
|
settings: ['expand', 1],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
|
@ -19,6 +19,8 @@ import { dimensionsMacros, dimensionsDefs } from './dimensions.mjs'
|
||||||
import { grainlineMacros, grainlineDefs } from './grainline.mjs'
|
import { grainlineMacros, grainlineDefs } from './grainline.mjs'
|
||||||
import { pleatMacros, pleatDefs } from './pleat.mjs'
|
import { pleatMacros, pleatDefs } from './pleat.mjs'
|
||||||
import { sewtogetherMacros, sewtogetherDefs } from './sewtogether.mjs'
|
import { sewtogetherMacros, sewtogetherDefs } from './sewtogether.mjs'
|
||||||
|
// Only stores
|
||||||
|
import { flagStores } from './flag.mjs'
|
||||||
|
|
||||||
export const plugin = {
|
export const plugin = {
|
||||||
name,
|
name,
|
||||||
|
@ -59,7 +61,7 @@ export const plugin = {
|
||||||
...sewtogetherMacros,
|
...sewtogetherMacros,
|
||||||
...titleMacros,
|
...titleMacros,
|
||||||
},
|
},
|
||||||
store: [...cutlistStores],
|
store: [...cutlistStores, ...flagStores],
|
||||||
}
|
}
|
||||||
|
|
||||||
export const annotationsPlugin = plugin
|
export const annotationsPlugin = plugin
|
||||||
|
|
|
@ -71,7 +71,10 @@ const removeScaleAnnotation = function (id = false, { paths, points, store, part
|
||||||
/*
|
/*
|
||||||
* The scalebox macro
|
* The scalebox macro
|
||||||
*/
|
*/
|
||||||
const scalebox = function (config, { store, points, paths, scale, Point, Path, complete, part }) {
|
const scalebox = function (
|
||||||
|
config,
|
||||||
|
{ store, points, paths, scale, Point, Path, complete, log, part }
|
||||||
|
) {
|
||||||
/*
|
/*
|
||||||
* Don't add a title when complete is false, unless force is true
|
* Don't add a title when complete is false, unless force is true
|
||||||
*/
|
*/
|
||||||
|
@ -103,7 +106,7 @@ const scalebox = function (config, { store, points, paths, scale, Point, Path, c
|
||||||
*/
|
*/
|
||||||
if (!mc.at || typeof mc.at.attr !== 'function') {
|
if (!mc.at || typeof mc.at.attr !== 'function') {
|
||||||
log.warn(`Scalebox macro called without a valid at point. Using (0,0) for at.`)
|
log.warn(`Scalebox macro called without a valid at point. Using (0,0) for at.`)
|
||||||
mc.from = new Point(0, 0)
|
mc.at = new Point(0, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -262,7 +265,10 @@ const scalebox = function (config, { store, points, paths, scale, Point, Path, c
|
||||||
/*
|
/*
|
||||||
* The miniscale macro
|
* The miniscale macro
|
||||||
*/
|
*/
|
||||||
const miniscale = function (config, { points, paths, scale, Point, Path, part, complete, store }) {
|
const miniscale = function (
|
||||||
|
config,
|
||||||
|
{ points, paths, scale, Point, Path, part, complete, log, store }
|
||||||
|
) {
|
||||||
/*
|
/*
|
||||||
* Don't add a title when complete is false, unless force is true
|
* Don't add a title when complete is false, unless force is true
|
||||||
*/
|
*/
|
||||||
|
@ -293,7 +299,7 @@ const miniscale = function (config, { points, paths, scale, Point, Path, part, c
|
||||||
*/
|
*/
|
||||||
if (!mc.at || typeof mc.at.attr !== 'function') {
|
if (!mc.at || typeof mc.at.attr !== 'function') {
|
||||||
log.warn(`Scalebox macro called without a valid at point. Using (0,0) for at.`)
|
log.warn(`Scalebox macro called without a valid at point. Using (0,0) for at.`)
|
||||||
mc.from = new Point(0, 0)
|
mc.at = new Point(0, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
|
@ -199,6 +199,7 @@ const addTitleMacro = function (
|
||||||
.shift(-90, shift)
|
.shift(-90, shift)
|
||||||
.attr('data-text', `(${store.data.for})`)
|
.attr('data-text', `(${store.data.for})`)
|
||||||
.attr('data-text-class', `${mc.classes.for} ${mc.align}`)
|
.attr('data-text-class', `${mc.classes.for} ${mc.align}`)
|
||||||
|
.attr('data-text-transform', transform)
|
||||||
shift += mc.dy
|
shift += mc.dy
|
||||||
} else delete ids.for
|
} else delete ids.for
|
||||||
|
|
||||||
|
@ -217,6 +218,7 @@ const addTitleMacro = function (
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
.attr('data-text-class', `${mc.classes.date} ${mc.align}`)
|
.attr('data-text-class', `${mc.classes.date} ${mc.align}`)
|
||||||
|
.attr('data-text-transform', transform)
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* 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
|
||||||
|
|
|
@ -29,6 +29,7 @@ import { MeasiesView, ns as measiesNs } from 'shared/components/workbench/views/
|
||||||
export const ns = [
|
export const ns = [
|
||||||
'account',
|
'account',
|
||||||
'workbench',
|
'workbench',
|
||||||
|
'flag',
|
||||||
...draftNs,
|
...draftNs,
|
||||||
...saveNs,
|
...saveNs,
|
||||||
...printNs,
|
...printNs,
|
||||||
|
|
|
@ -74,7 +74,7 @@ const ZoomButtons = ({ t, zoomFunctions, zoomed }) => {
|
||||||
|
|
||||||
const Spacer = () => <span className="opacity-50">|</span>
|
const Spacer = () => <span className="opacity-50">|</span>
|
||||||
|
|
||||||
export const ViewHeader = ({ update, settings, ui, control, account, design, setSettings }) => {
|
export const DraftHeader = ({ update, settings, ui, control, account, design, setSettings }) => {
|
||||||
const { t, i18n } = useTranslation(ns)
|
const { t, i18n } = useTranslation(ns)
|
||||||
const { zoomFunctions, zoomed } = useContext(PanZoomContext)
|
const { zoomFunctions, zoomed } = useContext(PanZoomContext)
|
||||||
const backend = useBackend()
|
const backend = useBackend()
|
|
@ -1,8 +1,9 @@
|
||||||
import { PanZoomPattern as ShowPattern } from 'shared/components/workbench/pan-zoom-pattern.mjs'
|
import { PanZoomPattern as ShowPattern } from 'shared/components/workbench/pan-zoom-pattern.mjs'
|
||||||
import { DraftMenu, ns as menuNs } from './menu.mjs'
|
import { DraftMenu, ns as menuNs } from './menu.mjs'
|
||||||
import { PatternWithMenu, ns as wrapperNs } from '../pattern-with-menu.mjs'
|
import { PatternWithMenu } from '../pattern-with-menu.mjs'
|
||||||
|
import { DraftHeader, ns as headerNs } from './header.mjs'
|
||||||
|
|
||||||
export const ns = [...menuNs, ...wrapperNs]
|
export const ns = [...menuNs, ...headerNs]
|
||||||
|
|
||||||
export const DraftView = ({
|
export const DraftView = ({
|
||||||
design,
|
design,
|
||||||
|
@ -47,6 +48,8 @@ export const DraftView = ({
|
||||||
design,
|
design,
|
||||||
pattern: output,
|
pattern: output,
|
||||||
setSettings,
|
setSettings,
|
||||||
|
Header: DraftHeader,
|
||||||
|
flags: pattern.setStores?.[0]?.plugins?.['plugin-annotations']?.flags,
|
||||||
menu: (
|
menu: (
|
||||||
<DraftMenu
|
<DraftMenu
|
||||||
{...{
|
{...{
|
||||||
|
|
105
sites/shared/components/workbench/views/flags.mjs
Normal file
105
sites/shared/components/workbench/views/flags.mjs
Normal file
|
@ -0,0 +1,105 @@
|
||||||
|
// Dependencies
|
||||||
|
import { flagTypes } from 'plugins/plugin-annotations/src/flag.mjs'
|
||||||
|
import mustache from 'mustache'
|
||||||
|
import { nsMerge } from 'shared/utils.mjs'
|
||||||
|
// Hooks
|
||||||
|
import { useTranslation } from 'next-i18next'
|
||||||
|
import { useState } from 'react'
|
||||||
|
// Components
|
||||||
|
import {
|
||||||
|
CloseIcon,
|
||||||
|
ChatIcon,
|
||||||
|
TipIcon,
|
||||||
|
WarningIcon,
|
||||||
|
ErrorIcon,
|
||||||
|
FixmeIcon,
|
||||||
|
ExpandIcon,
|
||||||
|
} from 'shared/components/icons.mjs'
|
||||||
|
|
||||||
|
const flagColors = {
|
||||||
|
note: 'primary',
|
||||||
|
tip: 'accent',
|
||||||
|
warn: 'error',
|
||||||
|
error: 'error',
|
||||||
|
fixme: 'warning',
|
||||||
|
}
|
||||||
|
|
||||||
|
const flagIcons = {
|
||||||
|
note: ChatIcon,
|
||||||
|
tip: TipIcon,
|
||||||
|
warn: WarningIcon,
|
||||||
|
error: ErrorIcon,
|
||||||
|
fixme: FixmeIcon,
|
||||||
|
// Used in content
|
||||||
|
expand: ExpandIcon,
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Flags = ({ flags, update }) => {
|
||||||
|
const handleUpdate = (config) => {
|
||||||
|
if (config.settings) update.settings(...config.settings)
|
||||||
|
if (config.ui) update.ui(...config.settings)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="p-4 flex flex-row overflow-x-auto w-full gap-4 max-w-screen">
|
||||||
|
{flagTypes.map((type) =>
|
||||||
|
flags[type]
|
||||||
|
? Object.values(flags[type]).map((flag) => (
|
||||||
|
<Flag type={type} data={flag} handleUpdate={handleUpdate} key={flag.id} />
|
||||||
|
))
|
||||||
|
: null
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Flag = ({ type, data, handleUpdate }) => {
|
||||||
|
const { t } = useTranslation(nsMerge('flag', data.ns, data.msg.split(':').shift()))
|
||||||
|
const [hide, setHide] = useState(false)
|
||||||
|
|
||||||
|
if (hide || !data.msg) return null
|
||||||
|
|
||||||
|
const color = flagColors[type]
|
||||||
|
const Icon = flagIcons[type]
|
||||||
|
const BtnIcon = data.suggest?.icon ? flagIcons[data.suggest.icon] : false
|
||||||
|
|
||||||
|
const button =
|
||||||
|
data.suggest?.text && data.suggest?.update ? (
|
||||||
|
<button
|
||||||
|
className={`btn btn-sm sm:btn-normal btn-neutral btn-outline grow flex flex-row items-center justify-between sm:grow-0 shrink-0 z-10 ${
|
||||||
|
BtnIcon ? 'gap-6' : ''
|
||||||
|
}`}
|
||||||
|
onClick={() => handleUpdate(data.suggest.update)}
|
||||||
|
>
|
||||||
|
{BtnIcon && <BtnIcon className="w-5 h-6 sm:w-6 h-6" />}
|
||||||
|
{t(data.suggest.text)}
|
||||||
|
</button>
|
||||||
|
) : null
|
||||||
|
|
||||||
|
const msg = data.replace ? mustache.render(t(data.msg), data.replace) : t(data.msg)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="w-4/5 max-w-md shrink-0">
|
||||||
|
<div className={`relative bg-${color} bg-opacity-10`}>
|
||||||
|
<div className="p-3 rounded-lg shadow text-base">
|
||||||
|
<div className="flex flex-row flex-wrap sm:flex-nowrap gap-2 items-start z-10">
|
||||||
|
<div
|
||||||
|
className="first:mt-0 popout-content grow z-10"
|
||||||
|
dangerouslySetInnerHTML={{ __html: msg }}
|
||||||
|
></div>
|
||||||
|
<div className="flex flex-row justify-between sm:flex-col gap-2 shrink-0 z-10 w-full sm:w-auto">
|
||||||
|
{button}
|
||||||
|
<button
|
||||||
|
className="w-1/2 sm:w-full btn btn-ghost btn-sm z-10 flex flex-row items-center justify-between w-full"
|
||||||
|
onClick={() => setHide(true)}
|
||||||
|
>
|
||||||
|
{t('flag:dismiss')}
|
||||||
|
<Icon className="w-5 h-6 sm:w-6 h-6" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
|
@ -1,10 +1,10 @@
|
||||||
import { PanZoomContextProvider } from 'shared/components/workbench/pattern/pan-zoom-context.mjs'
|
import { PanZoomContextProvider } from 'shared/components/workbench/pattern/pan-zoom-context.mjs'
|
||||||
import { ViewHeader, ns as headerNs } from './view-header.mjs'
|
|
||||||
import { MenuWrapper } from 'shared/components/workbench/menus/shared/menu-wrapper.mjs'
|
import { MenuWrapper } from 'shared/components/workbench/menus/shared/menu-wrapper.mjs'
|
||||||
|
import { Flags } from './flags.mjs'
|
||||||
|
|
||||||
export const ns = headerNs
|
export const ns = ['common', 'core-settings', 'ui-settings']
|
||||||
|
|
||||||
/** a layout for views that include a drafted pattern, a sidebar menu, and the draft view header */
|
/** a layout for views that include a drafted pattern, a sidebar menu, and a header you pass it */
|
||||||
export const PatternWithMenu = ({
|
export const PatternWithMenu = ({
|
||||||
settings,
|
settings,
|
||||||
ui,
|
ui,
|
||||||
|
@ -16,12 +16,13 @@ export const PatternWithMenu = ({
|
||||||
pattern,
|
pattern,
|
||||||
menu,
|
menu,
|
||||||
setSettings,
|
setSettings,
|
||||||
noHeader = false,
|
Header = false,
|
||||||
|
flags = false,
|
||||||
}) => (
|
}) => (
|
||||||
<PanZoomContextProvider>
|
<PanZoomContextProvider>
|
||||||
<div className="flex flex-col h-full">
|
<div className="flex flex-col h-full">
|
||||||
{noHeader ? null : (
|
{Header ? (
|
||||||
<ViewHeader
|
<Header
|
||||||
{...{
|
{...{
|
||||||
settings,
|
settings,
|
||||||
ui,
|
ui,
|
||||||
|
@ -32,7 +33,8 @@ export const PatternWithMenu = ({
|
||||||
setSettings,
|
setSettings,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
) : null}
|
||||||
|
{flags ? <Flags {...{ update, control, flags }} /> : null}
|
||||||
<div className="flex lg:flex-row grow lg:max-h-[90vh] max-h-[calc(100vh-3rem)] h-full py-4 lg:mt-6">
|
<div className="flex lg:flex-row grow lg:max-h-[90vh] max-h-[calc(100vh-3rem)] h-full py-4 lg:mt-6">
|
||||||
<div className="lg:w-2/3 flex flex-col h-full grow px-4">
|
<div className="lg:w-2/3 flex flex-col h-full grow px-4">
|
||||||
{title}
|
{title}
|
||||||
|
|
4
sites/shared/i18n/flag/en.yaml
Normal file
4
sites/shared/i18n/flag/en.yaml
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
dismiss: Dismiss
|
||||||
|
expandIsOff: The <b>expand</b> core setting is disabled. Some parts are not fully drawn. Enable it to see them.
|
||||||
|
enable: Enable
|
||||||
|
show: Show
|
|
@ -259,7 +259,7 @@ export const nsMerge = (...args) => {
|
||||||
if (typeof arg === 'string') ns.add(arg)
|
if (typeof arg === 'string') ns.add(arg)
|
||||||
else if (Array.isArray(arg)) {
|
else if (Array.isArray(arg)) {
|
||||||
for (const el of nsMerge(...arg)) ns.add(el)
|
for (const el of nsMerge(...arg)) ns.add(el)
|
||||||
} else console.log('Unexpected namespect type:', { arg })
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return [...ns]
|
return [...ns]
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue