Skip to content
GitLab
Menu
Projects
Groups
Snippets
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
Menu
Open sidebar
bonita
bonita-ui-designer-sdk
Commits
600da9ee
Commit
600da9ee
authored
Mar 25, 2021
by
Jerome Cambon
Browse files
Custom Widget Builder : first step to generate properties json file
parent
0aa7ecab
Changes
12
Expand all
Hide whitespace changes
Inline
Side-by-side
README.md
View file @
600da9ee
# bonita-ui-designer-sdk
\ No newline at end of file
# bonita-ui-designer-sdk
This sdk provides tooling to build custom widgets for UI Designer.
## Custom Widget Builder
Generates Bonita UI Designer custom widgets from web components.
custom-widget-builder/.gitignore
0 → 100644
View file @
600da9ee
## editors
/.idea
/.vscode
## system files
.DS_Store
## npm
/node_modules/
/npm-debug.log
## testing
/coverage/
## temp folders
/.tmp/
# build
/_site/
/build/
/dist/
/out-tsc/
custom-widget-builder/package-lock.json
0 → 100644
View file @
600da9ee
This diff is collapsed.
Click to expand it.
custom-widget-builder/package.json
0 → 100644
View file @
600da9ee
{
"name"
:
"poc-widget-builder"
,
"version"
:
"0.0.1"
,
"description"
:
""
,
"author"
:
"poc-widget-builder"
,
"license"
:
"GPLv2"
,
"type"
:
"module"
,
"scripts"
:
{},
"dependencies"
:
{
"@types/node"
:
"^14.14.35"
,
"typescript"
:
"^4.1.3"
,
"web-component-analyzer"
:
"^1.1.6"
}
}
custom-widget-builder/src/CustomWidgetBuilder.ts
0 → 100644
View file @
600da9ee
import
{
analyzeText
,
AnalyzeTextResult
,
transformAnalyzerResult
}
from
"
web-component-analyzer
"
;
import
fs
from
"
fs
"
;
import
{
PropertiesInfo
}
from
"
./PropertiesInfo.js
"
;
import
{
Property
}
from
"
./Property.js
"
;
export
class
CustomWidgetBuilder
{
private
wcFile
:
string
=
""
;
private
outputDir
:
string
=
""
;
public
generatePropertiesFile
(
wcFile
:
string
,
outputDir
:
string
)
{
this
.
wcFile
=
wcFile
;
this
.
outputDir
=
outputDir
;
this
.
createPropertiesFile
(
this
.
getPropertiesInfo
());
}
private
getPropertiesInfo
():
PropertiesInfo
{
let
info
=
JSON
.
parse
(
this
.
analyzeFile
()).
tags
[
0
];
let
wcName
=
info
.
name
;
let
id
=
CustomWidgetBuilder
.
toCamelCase
(
wcName
);
let
name
=
CustomWidgetBuilder
.
getDisplayName
(
wcName
);
let
type
=
"
widget
"
;
let
template
=
CustomWidgetBuilder
.
getTemplate
(
id
);
let
description
=
info
.
description
;
let
order
=
"
1
"
;
let
icon
=
CustomWidgetBuilder
.
generateIcon
();
let
properties
=
CustomWidgetBuilder
.
getProperties
(
info
.
properties
);
return
new
PropertiesInfo
(
id
,
name
,
type
,
template
,
description
,
order
,
icon
,
properties
);
}
private
createPropertiesFile
(
propertiesInfo
:
PropertiesInfo
)
{
//TODO replace console.log() by logging
let
output
=
JSON
.
stringify
(
propertiesInfo
,
null
,
2
)
console
.
log
(
output
);
let
filePath
=
`
${
this
.
outputDir
}
/
${
propertiesInfo
.
id
}
.json`
;
fs
.
writeFile
(
filePath
,
output
,
(
err
)
=>
{
if
(
err
)
{
console
.
log
(
`ERROR: Cannot write properties file
${
filePath
}
`
);
console
.
log
(
`[
${
err
.
message
}
]`
);
}
else
{
console
.
log
(
`
${
filePath
}
has been generated!`
);
}
});
}
private
analyzeFile
():
string
{
let
fileStr
=
fs
.
readFileSync
(
this
.
wcFile
,
"
utf8
"
).
toString
();
let
result
:
AnalyzeTextResult
=
analyzeText
(
fileStr
);
return
transformAnalyzerResult
(
"
json
"
,
result
.
results
,
result
.
program
,
{
visibility
:
"
public
"
});
}
private
static
getProperties
(
props
:
any
):
Array
<
Property
>
{
let
properties
:
Array
<
Property
>
=
[];
for
(
let
prop
of
props
)
{
if
(
prop
.
name
===
'
lang
'
)
{
// We don't want to expose the 'lang' property here
continue
;
}
let
label
=
CustomWidgetBuilder
.
getDisplayName
(
prop
.
name
);
let
name
=
prop
.
name
;
let
help
=
prop
.
description
;
let
type
=
CustomWidgetBuilder
.
getPropertyType
(
prop
.
type
);
let
defaultValue
=
prop
.
default
;
properties
.
push
(
new
Property
(
label
,
name
,
type
,
defaultValue
,
help
));
}
return
properties
;
}
private
static
toCamelCase
(
wcName
:
string
):
string
{
// e.g. pb-input -> pbInput
return
wcName
.
replace
(
/-
([
a-z
])
/g
,
(
g
)
=>
{
return
g
[
1
].
toUpperCase
()});
}
private
static
getDisplayName
(
wcName
:
string
):
string
{
// e.g. pb-input -> Input
// required -> Required
let
name
=
wcName
.
replace
(
/^
(
pb-
)
/
,
""
);
return
name
.
charAt
(
0
).
toUpperCase
()
+
name
.
slice
(
1
);
}
private
static
getTemplate
(
id
:
string
):
string
{
// e.g. pbInput -> @pbInput.tpl.html
return
`@
${
id
}
.tpl.html`
;
}
private
static
generateIcon
():
string
{
// TODO
return
""
;
}
private
static
getPropertyType
(
wcType
:
string
):
string
{
// Mapping from web component type to UID type
// number -> integer
// string -> text
// e,g, number | undefined -> number
wcType
=
wcType
.
replace
(
"
| undefined
"
,
""
);
if
(
wcType
===
'
number
'
)
{
return
'
integer
'
;
}
if
(
wcType
===
'
string
'
)
{
return
'
text
'
;
}
return
wcType
;
}
}
custom-widget-builder/src/PropertiesInfo.ts
0 → 100644
View file @
600da9ee
import
{
Property
}
from
"
./Property
"
;
export
class
PropertiesInfo
{
public
id
:
string
;
public
name
:
string
;
public
type
:
string
;
public
template
:
string
;
public
description
:
string
;
public
order
:
string
;
public
icon
:
string
;
public
properties
:
Array
<
Property
>
;
constructor
(
id
:
string
,
name
:
string
,
type
:
string
,
template
:
string
,
description
:
string
,
order
:
string
,
icon
:
string
,
properties
:
Array
<
Property
>
)
{
this
.
id
=
id
;
this
.
name
=
name
;
this
.
type
=
type
;
this
.
template
=
template
;
this
.
description
=
description
;
this
.
order
=
order
;
this
.
icon
=
icon
;
this
.
properties
=
properties
;
}
}
custom-widget-builder/src/Property.ts
0 → 100644
View file @
600da9ee
import
{
PropertyConstraint
}
from
"
./PropertyConstraint
"
;
import
{
PropertyChoice
}
from
"
./PropertyChoice
"
;
export
class
Property
{
public
label
:
string
;
public
name
:
string
;
public
help
:
string
|
undefined
;
public
type
:
string
;
public
defaultValue
:
string
;
public
constraints
:
PropertyConstraint
|
undefined
;
public
showFor
:
string
|
undefined
;
public
bond
:
string
|
undefined
;
public
choiceValues
:
PropertyChoice
|
undefined
;
constructor
(
label
:
string
,
name
:
string
,
type
:
string
,
defaultValue
:
string
,
help
?:
string
,
bond
?:
string
,
constraints
?:
PropertyConstraint
,
showFor
?:
string
,
choiceValues
?:
PropertyChoice
)
{
this
.
label
=
label
;
this
.
name
=
name
;
this
.
help
=
help
;
this
.
type
=
type
;
this
.
defaultValue
=
defaultValue
;
this
.
constraints
=
constraints
;
this
.
showFor
=
showFor
;
this
.
bond
=
bond
;
this
.
choiceValues
=
choiceValues
;
}
}
custom-widget-builder/src/PropertyChoice.ts
0 → 100644
View file @
600da9ee
export
class
PropertyChoice
{
public
choices
:
Array
<
String
>
;
constructor
(
choices
:
Array
<
String
>
)
{
this
.
choices
=
choices
;
}
}
custom-widget-builder/src/PropertyConstraint.ts
0 → 100644
View file @
600da9ee
export
class
PropertyConstraint
{
public
min
:
string
;
public
max
:
string
;
constructor
(
min
:
string
,
max
:
string
)
{
this
.
min
=
min
;
this
.
max
=
max
;
}
}
custom-widget-builder/src/starter.ts
0 → 100644
View file @
600da9ee
import
{
CustomWidgetBuilder
}
from
"
./CustomWidgetBuilder.js
"
;
let
wcFile
;
let
outputDir
;
for
(
let
param
of
process
.
argv
)
{
if
(
param
.
startsWith
(
"
wcFile
"
))
{
wcFile
=
getParameter
(
param
);
}
if
(
param
.
startsWith
(
"
outputDir
"
))
{
outputDir
=
getParameter
(
param
);
}
}
console
.
log
(
`Generating widget for
${
wcFile
}
...\n`
);
new
CustomWidgetBuilder
().
generatePropertiesFile
(
wcFile
,
outputDir
);
function
getParameter
(
param
:
string
):
any
{
return
param
.
substr
(
param
.
indexOf
(
'
=
'
)
+
1
);
}
custom-widget-builder/test/resources/pb-input.ts
0 → 100644
View file @
600da9ee
import
{
css
,
customElement
,
html
,
LitElement
,
property
}
from
'
lit-element
'
;
// @ts-ignore
import
bootstrapStyle
from
'
./style.scss
'
;
import
{
get
,
listenForLangChanged
,
registerTranslateConfig
,
use
}
from
"
lit-translate
"
;
import
*
as
i18n_en
from
"
./i18n/en.json
"
;
import
*
as
i18n_es
from
"
./i18n/es-ES.json
"
;
import
*
as
i18n_fr
from
"
./i18n/fr.json
"
;
import
*
as
i18n_ja
from
"
./i18n/ja.json
"
;
import
*
as
i18n_pt
from
"
./i18n/pt-BR.json
"
;
// Registers i18n loader
registerTranslateConfig
({
loader
:
(
lang
)
=>
Promise
.
resolve
(
PbInput
.
getCatalog
(
lang
))
});
/**
* Input field, optionally with a label, where the user can enter information
*/
@
customElement
(
'
pb-input
'
)
export
class
PbInput
extends
LitElement
{
private
name
=
"
pbInput
"
;
@
property
({
attribute
:
'
lang
'
,
type
:
String
,
reflect
:
true
})
lang
:
string
=
"
en
"
;
// Common properties below are handled by the div above pb-input:
// @property({ attribute: 'width', type: String, reflect: true })
// private width: string = "12";
//
// @property({ attribute: 'css-classes', type: String, reflect: true })
// private cssClasses: string = "";
//
// @property({ attribute: 'hidden', type: Boolean, reflect: true })
// private hidden: boolean = false;
@
property
({
attribute
:
'
id
'
,
type
:
String
,
reflect
:
true
})
id
:
string
=
""
;
@
property
({
attribute
:
'
required
'
,
type
:
Boolean
,
reflect
:
true
})
required
:
boolean
=
false
;
@
property
({
attribute
:
'
min-length
'
,
type
:
Number
,
reflect
:
true
})
minLength
:
number
|
undefined
;
@
property
({
attribute
:
'
max-length
'
,
type
:
Number
,
reflect
:
true
})
maxLength
:
number
|
undefined
;
@
property
({
attribute
:
'
readonly
'
,
type
:
Boolean
,
reflect
:
true
})
readOnly
:
boolean
=
false
;
@
property
({
attribute
:
'
label-hidden
'
,
type
:
Boolean
,
reflect
:
true
})
labelHidden
:
boolean
=
false
;
@
property
({
attribute
:
'
label
'
,
type
:
String
,
reflect
:
true
})
label
:
string
=
""
;
/**
* Position of the label
* @type {"left"|"top"}
*/
@
property
({
attribute
:
'
label-position
'
,
type
:
String
,
reflect
:
true
})
labelPosition
:
string
=
"
top
"
;
@
property
({
attribute
:
'
label-width
'
,
type
:
Number
,
reflect
:
true
})
labelWidth
:
number
=
4
;
@
property
({
attribute
:
'
placeholder
'
,
type
:
String
,
reflect
:
true
})
placeholder
:
string
=
""
;
@
property
({
attribute
:
'
value
'
,
type
:
String
,
reflect
:
true
})
value
:
string
=
""
;
@
property
({
attribute
:
'
type
'
,
type
:
String
,
reflect
:
true
})
type
:
string
=
"
text
"
;
@
property
({
attribute
:
'
min
'
,
type
:
Number
,
reflect
:
true
})
min
:
number
|
undefined
;
@
property
({
attribute
:
'
max
'
,
type
:
Number
,
reflect
:
true
})
max
:
number
|
undefined
;
@
property
({
attribute
:
'
step
'
,
type
:
Number
,
reflect
:
true
})
step
:
number
=
1
;
constructor
()
{
super
();
listenForLangChanged
(()
=>
{
if
(
this
.
label
===
""
)
{
this
.
label
=
get
(
"
defaultLabel
"
);
}
});
}
async
attributeChangedCallback
(
name
:
string
,
old
:
string
|
null
,
value
:
string
|
null
)
{
super
.
attributeChangedCallback
(
name
,
old
,
value
);
if
(
name
===
'
lang
'
)
{
use
(
this
.
lang
).
then
();
}
}
static
getCatalog
(
lang
:
string
)
{
switch
(
lang
)
{
case
"
es
"
:
case
"
es-ES
"
:
return
i18n_es
;
case
"
fr
"
:
return
i18n_fr
;
case
"
ja
"
:
return
i18n_ja
;
case
"
pt
"
:
case
"
pt-BR
"
:
return
i18n_pt
;
default
:
return
i18n_en
;
}
}
static
get
styles
()
{
return
css
`
:host {
display: block;
font-family: sans-serif;
text-align: left;
}
.input-elem {
font-size: 14px;
height: 20px;
}
.label-elem {
font-size: 14px;
font-weight: 700;
padding-left: 0
}
/* Add a red star after required inputs */
.label-required:after {
content: " *";
color: #C00;
}
.text-right {
text-align: right;
}
`
;
}
render
()
{
return
html
`
<style>
${
bootstrapStyle
}
</style>
<div id="
${
this
.
id
}
" class="container">
<div class="row">
${
this
.
getLabel
()}
<input
class="
${
this
.
getInputCssClass
()}
"
id="input"
name="
${
this
.
name
}
"
type="
${
this
.
type
}
"
min="
${
this
.
min
}
"
max="
${
this
.
max
}
"
step="
${
this
.
step
}
"
value="
${
this
.
value
}
"
@input=
${(
e
:
any
)
=>
this
.
valueChanged
(
e
.
target
.
value
)}
placeholder="
${
this
.
placeholder
}
"
minlength="
${
this
.
minLength
}
"
maxlength="
${
this
.
maxLength
}
"
?readonly="
${
this
.
readOnly
}
"
/>
</div>
</div>
`
;
}
private
getLabel
()
{
if
(
this
.
labelHidden
)
{
return
html
``
;
}
return
html
`
<label
class="
${
this
.
getLabelCssClass
()}
"
for="input"
>
${
this
.
label
}
</label>
`
}
private
getLabelCssClass
()
:
string
{
return
(
this
.
required
?
"
label-required
"
:
""
)
+
"
label-elem form-horizontal col-form-label
"
+
(
!
this
.
labelHidden
&&
this
.
labelPosition
===
'
left
'
?
"
col-
"
+
this
.
labelWidth
+
"
text-right
"
:
"
col-12
"
);
}
private
getInputCssClass
()
:
string
{
return
"
form-control input-elem col
"
;
}
private
valueChanged
(
value
:
string
)
{
this
.
dispatchEvent
(
new
CustomEvent
(
'
valueChange
'
,
{
detail
:
value
}));
}
}
custom-widget-builder/tsconfig.json
0 → 100644
View file @
600da9ee
{
"compilerOptions"
:
{
/*
Basic
Options
*/
"target"
:
"es2020"
/*
Specify
ECMAScript
target
version:
'ES
3
'
(default)
,
'ES
5
'
,
'ES
2015
'
,
'ES
2016
'
,
'ES
2017
'
,
'ES
2018
'
or
'ESNEXT'.
*/
,
"module"
:
"es2015"
/*
Specify
module
code
generation:
'none'
,
'commonjs'
,
'amd'
,
'system'
,
'umd'
,
'es
2015
'
,
or
'ESNext'.
*/
,
"lib"
:
[
"es2020"
],
/*
Specify
library
files
to
be
included
in
the
compilation.
*/
//
"allowJs"
:
true
,
/*
Allow
javascript
files
to
be
compiled.
*/
//
"checkJs"
:
true
,
/*
Report
errors
in
.js
files.
*/
//
"jsx"
:
"preserve"
,
/*
Specify
JSX
code
generation:
'preserve'
,
'react-native'
,
or
'react'.
*/
//
"declaration"
:
true
,
/*
Generates
corresponding
'.d.ts'
file.
*/
//
"declarationMap"
:
true
,
/*
Generates
a
sourcemap
for
each
corresponding
'.d.ts'
file.
*/
"sourceMap"
:
true
/*
Generates
corresponding
'.map'
file.
*/
,
//
"outFile"
:
"./"
,
/*
Concatenate
and
emit
output
to
single
file.
*/
"outDir"
:
"./build"
/*
Redirect
output
structure
to
the
directory.
*/
,
//
"rootDir"
:
"./"
,
/*
Specify
the
root
directory
of
input
files.
Use
to
control
the
output
directory
structure
with
--outDir.
*/
//
"composite"
:
true
,
/*
Enable
project
compilation
*/
//
"removeComments"
:
true
,
/*
Do
not
emit
comments
to
output.
*/
//
"noEmit"
:
true
,
/*
Do
not
emit
outputs.
*/
//
"importHelpers"
:
true
,
/*
Import
emit
helpers
from
'tslib'.
*/
//
"downlevelIteration"
:
true
,
/*
Provide
full
support
for
iterables
in
'for-of'
,
spread
,
and
destructuring
when
targeting
'ES
5
'
or
'ES
3
'.
*/
//
"isolatedModules"
:
true
,
/*
Transpile
each
file
as
a
separate
module
(similar
to
'ts.transpileModule').
*/
/*
Strict
Type-Checking
Options
*/
"strict"
:
true
/*
Enable
all
strict
type-checking
options.
*/
,
"noImplicitAny"
:
true
/*
Raise
error
on
expressions
and
declarations
with
an
implied
'any'
type.
*/
,
"strictNullChecks"
:
true
/*
Enable
strict
null
checks.
*/
,
//
"strictFunctionTypes"
:
true
,
/*
Enable
strict
checking
of
function
types.
*/
//
"strictBindCallApply"
:
true
,
/*
Enable
strict
'bind'
,
'call'
,
and
'apply'
methods
on
functions.
*/
//
"strictPropertyInitialization"
:
true
,
/*
Enable
strict
checking
of
property
initialization
in
classes.
*/
"noImplicitThis"
:
true
/*
Raise
error
on
'this'
expressions
with
an
implied
'any'
type.
*/
,
"alwaysStrict"
:
true
/*
Parse
in
strict
mode
and
emit
"use strict"
for
each
source
file.
*/
,
/*
Additional
Checks
*/
//
"noUnusedLocals"
:
true
,
/*
Report
errors
on
unused
locals.
*/
//
"noUnusedParameters"
:
true
,
/*
Report
errors
on
unused
parameters.
*/
//
"noImplicitReturns"
:
true
,
/*
Report
error
when
not
all
code
paths
in
function
return
a
value.
*/
//
"noFallthroughCasesInSwitch"
:
true
,
/*
Report
errors
for
fallthrough
cases
in
switch
statement.
*/
/*
Module
Resolution
Options
*/
"moduleResolution"
:
"node"
/*
Specify
module
resolution
strategy:
'node'
(Node.js)
or
'classic'
(TypeScript
pre
-1.6
).
*/
,
//
"baseUrl"
:
"./"
,
/*
Base
directory
to
resolve
non-absolute
module
names.
*/
//
"paths"
:
{},
/*
A
series
of
entries
which
re-map
imports
to
lookup
locations
relative
to
the
'baseUrl'.
*/
//
"rootDirs"
:
[],
/*
List
of
root
folders
whose
combined
content
represents
the
structure
of
the
project
at
runtime.
*/
//
"typeRoots"
:
[],
/*
List
of
folders
to
include
type
definitions
from.
*/
//
"types"
:
[],
/*
Type
declaration
files
to
be
included
in
compilation.
*/
//
"allowSyntheticDefaultImports"
:
true
,
/*
Allow
default
imports
from
modules
with
no
default
export.
This
does
not
affect
code
emit
,
just
typechecking.
*/
"esModuleInterop"
:
true
,
/*
Enables
emit
interoperability
between
CommonJS
and
ES
Modules
via
creation
of
namespace
objects
for
all
imports.
Implies
'allowSyntheticDefaultImports'.
*/
"skipLibCheck"
:
true
,
"forceConsistentCasingInFileNames"
:
true
//
"preserveSymlinks"
:
true
,
/*
Do
not
resolve
the
real
path
of
symlinks.
*/
/*
Source
Map
Options
*/
//
"sourceRoot"
:
""
,
/*
Specify
the
location
where
debugger
should
locate
TypeScript
files
instead
of
source
locations.
*/
//
"mapRoot"
:
""
,
/*
Specify
the
location
where
debugger
should
locate
map
files
instead
of
generated
locations.
*/
//
"inlineSourceMap"
:
true
,
/*
Emit
a
single
file
with
source
maps
instead
of
having
a
separate
file.
*/
//
"inlineSources"
:
true
,
/*
Emit
the
source
alongside
the
sourcemaps
within
a
single
file;
requires
'--inlineSourceMap'
or
'--sourceMap'
to
be
set.
*/
/*
Experimental
Options
*/
//
"experimentalDecorators"
:
true
,
/*
Enables
experimental
support
for
ES
7
decorators.
*/
//
"emitDecoratorMetadata"
:
true
,
/*
Enables
experimental
support
for
emitting
type
metadata
for
decorators.
*/
},
"include"
:
[
"./src/**/*"
,
"./test/**/*"
]
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment