437 lines
22 KiB
HTML
437 lines
22 KiB
HTML
<script type="text/javascript">
|
|
(async function () {
|
|
// convert to i18 text
|
|
function c_(x) {
|
|
return RED._('@flowfuse/node-red-dashboard/ui-template:ui-template.' + x)
|
|
}
|
|
// TODO: move this to util.js for reuse & unit testing
|
|
function hasProperty(obj, prop) {
|
|
return Object.prototype.hasOwnProperty.call(obj, prop)
|
|
}
|
|
|
|
const template = []
|
|
|
|
template.push('<template>')
|
|
template.push(' <div>')
|
|
template.push(' <h2>Counter</h2>')
|
|
template.push(' <p>Current Count: {{ count }}</p>')
|
|
template.push(' <p class="my-class">Formatted Count: {{ formattedCount }}</p>')
|
|
template.push(' <v-btn @click="increase()">Increment</v-btn>')
|
|
template.push(' </div>')
|
|
template.push('</template>')
|
|
template.push('')
|
|
template.push('<script>')
|
|
template.push(' export default {')
|
|
template.push(' data() {')
|
|
template.push(' // define variables available component-wide')
|
|
template.push(' // (in <template> and component functions)')
|
|
template.push(' return {')
|
|
template.push(' count: 0')
|
|
template.push(' }')
|
|
template.push(' },')
|
|
template.push(' watch: {')
|
|
template.push(' // watch for any changes of "count"')
|
|
template.push(' count: function () {')
|
|
template.push(' if (this.count % 5 === 0) {')
|
|
template.push(' this.send({payload: \'Multiple of 5\'})')
|
|
template.push(' }')
|
|
template.push(' }')
|
|
template.push(' },')
|
|
template.push(' computed: {')
|
|
template.push(' // automatically compute this variable')
|
|
template.push(' // whenever VueJS deems appropriate')
|
|
template.push(' formattedCount: function () {')
|
|
template.push(' return this.count + \'Apples\'')
|
|
template.push(' }')
|
|
template.push(' },')
|
|
template.push(' methods: {')
|
|
template.push(' // expose a method to our <template> and Vue Application')
|
|
template.push(' increase: function () {')
|
|
template.push(' this.count++')
|
|
template.push(' }')
|
|
template.push(' },')
|
|
template.push(' mounted() {')
|
|
template.push(' // code here when the component is first loaded')
|
|
template.push(' },')
|
|
template.push(' unmounted() {')
|
|
template.push(' // code here when the component is removed from the Dashboard')
|
|
template.push(' // i.e. when the user navigates away from the page')
|
|
template.push(' }')
|
|
template.push(' }')
|
|
// eslint complaingin about unterminated string literal - need to do this oddity to get around it
|
|
template.push('</' + 'script>')
|
|
template.push('<style>')
|
|
template.push(' /* define any styles here - supports raw CSS */')
|
|
template.push(' .my-class {')
|
|
template.push(' color: red;')
|
|
template.push(' }')
|
|
template.push('</style>')
|
|
|
|
const defaultTemplate = template.join('\n')
|
|
|
|
RED.nodes.registerType('ui-template', {
|
|
category: RED._('@flowfuse/node-red-dashboard/ui-base:ui-base.label.category'),
|
|
color: RED._('@flowfuse/node-red-dashboard/ui-base:ui-base.colors.dark'),
|
|
defaults: {
|
|
group: {
|
|
type: 'ui-group',
|
|
validate: function () {
|
|
const tempScope = ($('#node-input-templateScope').val() !== undefined) ? $('#node-input-templateScope').val() : this.templateScope
|
|
const groupVal = ($('#node-input-group').val() !== undefined) ? $('#node-input-group').val() : this.group
|
|
|
|
if (tempScope !== 'local') {
|
|
return true
|
|
}
|
|
|
|
if (tempScope === 'local') {
|
|
if (!!groupVal && groupVal !== '_ADD_') {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
}, // for when template is scoped to 'local' (default)
|
|
page: {
|
|
type: 'ui-page',
|
|
validate: function () {
|
|
const tempScope = ($('#node-input-templateScope').val() !== undefined) ? $('#node-input-templateScope').val() : this.templateScope
|
|
const pageVal = ($('#node-input-page').val() !== undefined) ? $('#node-input-page').val() : this.page
|
|
|
|
if (tempScope !== 'widget:page' && tempScope !== 'page:style') {
|
|
return true
|
|
}
|
|
if (tempScope === 'widget:page' || tempScope === 'page:style') {
|
|
if (!!pageVal && pageVal !== '_ADD_') {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
}, // for when template is scoped to 'page'
|
|
ui: {
|
|
type: 'ui-base',
|
|
validate: function () {
|
|
const tempScope = ($('#node-input-templateScope').val() !== undefined) ? $('#node-input-templateScope').val() : this.templateScope
|
|
const uiVal = ($('#node-input-ui').val() !== undefined) ? $('#node-input-ui').val() : this.ui
|
|
|
|
if (tempScope !== 'widget:ui' && tempScope !== 'site:style') {
|
|
return true
|
|
}
|
|
if (tempScope === 'widget:ui' || tempScope === 'site:style') {
|
|
if (!!uiVal && uiVal !== '_ADD_') {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
}, // for when template is scoped to 'site'
|
|
name: { value: '' },
|
|
order: { value: 0 },
|
|
width: {
|
|
value: 0,
|
|
validate: function (v) {
|
|
let valid = true
|
|
if (this.templateScope !== 'global') {
|
|
const width = v || 0
|
|
const currentGroup = $('#node-input-group').val() || this.group
|
|
const groupNode = RED.nodes.node(currentGroup)
|
|
valid = !groupNode || +width <= +groupNode.width
|
|
$('#node-input-size').toggleClass('input-error', !valid)
|
|
}
|
|
return valid
|
|
}
|
|
},
|
|
height: { value: 0 },
|
|
head: { value: '' },
|
|
format: { value: defaultTemplate },
|
|
storeOutMessages: { value: true },
|
|
passthru: { value: true },
|
|
resendOnRefresh: { value: true },
|
|
templateScope: { value: 'local' },
|
|
className: { value: '' }
|
|
},
|
|
inputs: 1,
|
|
outputs: 1,
|
|
icon: 'font-awesome/fa-code',
|
|
paletteLabel: 'template',
|
|
label: function () {
|
|
if (this.name) { return this.name }
|
|
const knownLabels = {
|
|
local: 'template', // widget:group - kept as local for backward compatability
|
|
'widget:page': 'template',
|
|
'widget:ui': 'template',
|
|
'site:style': 'site style',
|
|
'page:style': 'page style'
|
|
}
|
|
return knownLabels[this.templateScope] || 'template'
|
|
},
|
|
labelStyle: function () { return this.name ? 'node_label_italic' : '' },
|
|
oneditprepare: function () {
|
|
if (RED.editor.__debug === true) {
|
|
console.log('ui-template: oneditprepare') // useful for locating this code in the browser debugger
|
|
}
|
|
if (hasProperty(RED.editor, 'editText') && typeof RED.editor.editText === 'function') {
|
|
$('#node-template-expand-editor').show()
|
|
} else {
|
|
$('#node-template-expand-editor').hide()
|
|
}
|
|
const that = this
|
|
const $templateScope = $('#node-input-templateScope')
|
|
|
|
// if this groups parent is a subflow template, set the node-config-input-width and node-config-input-height up
|
|
// as typedInputs and hide the elementSizer (as it doesn't make sense for subflow templates)
|
|
if (RED.nodes.subflow(this.z)) {
|
|
// change inputs from hidden to text & display them
|
|
$('#node-input-width').attr('type', 'text')
|
|
$('#node-input-height').attr('type', 'text')
|
|
$('div.form-row.nr-db-ui-element-sizer-row').hide()
|
|
$('div.form-row.nr-db-ui-manual-size-row').show()
|
|
} else {
|
|
// not in a subflow, use the elementSizer
|
|
$('div.form-row.nr-db-ui-element-sizer-row').show()
|
|
$('div.form-row.nr-db-ui-manual-size-row').hide()
|
|
$('#node-input-size').elementSizer({
|
|
width: '#node-input-width',
|
|
height: '#node-input-height',
|
|
group: '#node-input-group'
|
|
})
|
|
}
|
|
|
|
if (typeof this.storeOutMessages === 'undefined') {
|
|
this.storeOutMessages = true
|
|
$('#node-input-storeOutMessages').prop('checked', true)
|
|
}
|
|
|
|
if (typeof this.passthru === 'undefined') {
|
|
this.passthru = true
|
|
$('#node-input-passthru').prop('checked', true)
|
|
}
|
|
|
|
if (typeof this.templateScope === 'undefined') {
|
|
this.templateScope = 'local'
|
|
$templateScope.val(this.templateScope)
|
|
}
|
|
|
|
$templateScope.on('change', function () {
|
|
$('#template-row-group, #template-row-page, #template-row-ui, #template-row-class').hide()
|
|
|
|
switch ($templateScope.val()) {
|
|
case 'site:style':
|
|
$('#template-row-ui').show()
|
|
that.editor.getSession().setMode('ace/mode/css')
|
|
break
|
|
case 'page:style':
|
|
$('#template-row-page').show()
|
|
that.editor.getSession().setMode('ace/mode/css')
|
|
break
|
|
case 'site:script':
|
|
$('#template-row-ui').show()
|
|
that.editor.getSession().setMode('ace/mode/javascript')
|
|
break
|
|
case 'page:script':
|
|
$('#template-row-page').show()
|
|
that.editor.getSession().setMode('ace/mode/javascript')
|
|
break
|
|
case 'widget:ui':
|
|
$('#template-row-ui').show()
|
|
that.editor.getSession().setMode('ace/mode/html')
|
|
break
|
|
case 'widget:page':
|
|
$('#template-row-page').show()
|
|
that.editor.getSession().setMode('ace/mode/html')
|
|
break
|
|
default:
|
|
$('#template-row-group, #template-row-class').show()
|
|
that.editor.getSession().setMode('ace/mode/html')
|
|
break
|
|
}
|
|
resize.call(that)
|
|
})
|
|
|
|
this.editor = RED.editor.createEditor({
|
|
id: 'node-input-format-editor',
|
|
mode: 'ace/mode/html',
|
|
value: $('#node-input-format').val()
|
|
})
|
|
|
|
RED.library.create({
|
|
url: 'uitemplates', // where to get the data from
|
|
type: 'ui-template', // the type of object the library is for
|
|
editor: this.editor, // the field name the main text body goes to
|
|
mode: 'ace/mode/html',
|
|
fields: ['name']
|
|
})
|
|
|
|
this.editor.focus()
|
|
|
|
RED.popover.tooltip($('#node-template-expand-editor'), c_('label.expand'))
|
|
|
|
$('#node-template-expand-editor').on('click', function (e) {
|
|
e.preventDefault()
|
|
const value = that.editor.getValue()
|
|
RED.editor.editText({
|
|
mode: $templateScope.val() === 'global' ? 'css' : 'html',
|
|
value,
|
|
width: 'Infinity',
|
|
cursor: that.editor.getCursorPosition(),
|
|
complete: function (v, cursor) {
|
|
that.editor.setValue(v, -1)
|
|
that.editor.gotoLine(cursor.row + 1, cursor.column, false)
|
|
setTimeout(function () { that.editor.focus() }, 300)
|
|
}
|
|
})
|
|
})
|
|
|
|
// use jQuery UI tooltip to convert the plain old title attribute to a nice tooltip
|
|
$('.ui-node-popover-title').tooltip({
|
|
show: {
|
|
effect: 'slideDown',
|
|
delay: 150
|
|
}
|
|
})
|
|
|
|
$templateScope.trigger('change') // trigger the change event to hide/show the group/dashboard row
|
|
},
|
|
oneditsave: function () {
|
|
const annot = this.editor.getSession().getAnnotations()
|
|
this.noerr = 0
|
|
$('#node-input-noerr').val(0)
|
|
for (let k = 0; k < annot.length; k++) {
|
|
if (annot[k].type === 'error') {
|
|
$('#node-input-noerr').val(annot.length)
|
|
this.noerr = annot.length
|
|
}
|
|
}
|
|
$('#node-input-format').val(this.editor.getValue())
|
|
this.editor.destroy()
|
|
delete this.editor
|
|
|
|
// ensure we only have one of group/page/dashboard defined
|
|
const scope = $('#node-input-templateScope').val()
|
|
if (scope === 'local') {
|
|
$('#node-input-ui').val('_ADD_')
|
|
$('#node-input-page').val('_ADD_')
|
|
} else if (scope === 'widget:page') {
|
|
$('#node-input-ui').val('_ADD_')
|
|
$('#node-input-group').val('_ADD_')
|
|
} else if (scope === 'widget:ui') {
|
|
$('#node-input-page').val('_ADD_')
|
|
$('#node-input-group').val('_ADD_')
|
|
} else if (scope === 'site:style') {
|
|
$('#node-input-page').val('_ADD_')
|
|
$('#node-input-group').val('_ADD_')
|
|
} else if (scope === 'page:style') {
|
|
$('#node-input-ui').val('_ADD_')
|
|
$('#node-input-group').val('_ADD_')
|
|
}
|
|
},
|
|
oneditcancel: function () {
|
|
this.editor.destroy()
|
|
delete this.editor
|
|
},
|
|
oneditresize: resize.bind(this)
|
|
})
|
|
|
|
function resize(size) {
|
|
const rows = $('#dialog-form>div:not(.node-text-editor-row)')
|
|
let fixRowsHeight = 0
|
|
for (let i = 0; i < rows.size(); i++) {
|
|
fixRowsHeight += $(rows[i]).height()
|
|
}
|
|
const dialogHeight = $('#dialog-form').height()
|
|
const height = dialogHeight - fixRowsHeight
|
|
$('.node-text-editor').css('height', height + 'px')
|
|
if (RED.editor.__debug === true) {
|
|
console.log('dialogHeight', dialogHeight, 'fixRowsHeight', fixRowsHeight, 'height', height, 'editorRow')
|
|
}
|
|
this.editor && this.editor.resize()
|
|
}
|
|
})()
|
|
</script>
|
|
|
|
<script type="text/html" data-template-name="ui-template">
|
|
<div class="form-row">
|
|
<label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="node-red:common.label.name"></span></label>
|
|
<div style="display:inline-block; width:calc(100% - 105px)">
|
|
<input type="text" id="node-input-name" data-i18n="[placeholder]node-red:common.label.name">
|
|
</div>
|
|
</div>
|
|
<div class="form-row">
|
|
<label for="node-input-temlplateScope"><i class="fa fa-dot-circle-o"></i> <span data-i18n="ui-template.label.scope"></span></label>
|
|
<select style="width:76%" id="node-input-templateScope">
|
|
<option value="local" data-i18n="ui-template.label.widget-group"></option>
|
|
<option value="widget:page" data-i18n="ui-template.label.widget-page"></option>
|
|
<option value="widget:ui" data-i18n="ui-template.label.widget-ui"></option>
|
|
<option value="site:style" data-i18n="ui-template.label.site-style"></option>
|
|
<option value="page:style" data-i18n="ui-template.label.page-style"></option>
|
|
<!-- FUTURE? <option value="site:script" data-i18n="ui-template.label.site-script"></option>
|
|
<option value="page:script" data-i18n="ui-template.label.page-script"></option> -->
|
|
</select>
|
|
</div>
|
|
<div id="template-row-ui" class="form-row">
|
|
<label for="node-input-ui"><i class="fa fa-bookmark"></i> <span data-i18n="ui-template.label.ui"></label>
|
|
<input type="text" id="node-input-ui">
|
|
</div>
|
|
<div id="template-row-page" class="form-row">
|
|
<label for="node-input-page"><i class="fa fa-bookmark"></i> <span data-i18n="ui-template.label.page"></label>
|
|
<input type="text" id="node-input-page">
|
|
</div>
|
|
<div id="template-row-group" class="form-row">
|
|
<label for="node-input-group"><i class="fa fa-table"></i> <span data-i18n="ui-template.label.group"></label>
|
|
<input type="text" id="node-input-group">
|
|
</div>
|
|
<div class="form-row nr-db-ui-element-sizer-row">
|
|
<label><i class="fa fa-object-group"></i> <span data-i18n="ui-template.label.size">Size</label>
|
|
<button class="editor-button" id="node-input-size"></button>
|
|
</div>
|
|
<div class="form-row nr-db-ui-manual-size-row">
|
|
<label><i class="fa fa-arrows-h"></i> <span data-i18n="ui-template.label.width">Width</label>
|
|
<input type="hidden" id="node-input-width">
|
|
</div>
|
|
<div class="form-row nr-db-ui-manual-size-row">
|
|
<label><i class="fa fa-arrows-v"></i> <span data-i18n="ui-template.label.height">Height</label>
|
|
<input type="hidden" id="node-input-height">
|
|
</div>
|
|
<!--<div class="form-row" id="template-row-size">
|
|
<label><i class="fa fa-object-group"></i> <span data-i18n="ui-template.label.size"></span></label>
|
|
<input type="hidden" id="node-input-width">
|
|
<input type="hidden" id="node-input-height">
|
|
<button class="editor-button" id="node-input-size"></button>
|
|
</div>-->
|
|
<div class="form-row">
|
|
<label for="node-input-className"><i class="fa fa-code"></i> <span data-i18n="ui-template.label.className"></label>
|
|
<div style="display: inline;">
|
|
<input style="width: 70%;" type="text" id="node-input-className" data-i18n="[placeholder]ui-template.label.classNamePlaceholder" style="flex-grow: 1;">
|
|
<a
|
|
data-html="true"
|
|
title="Dynamic Property: Send msg.class to append new classes to this widget. NOTE: classes set at runtime will be applied in addition to any class(es) set in the nodes class field."
|
|
class="red-ui-button ui-node-popover-title"
|
|
style="margin-left: 4px; cursor: help; font-size: 0.625rem; border-radius: 50%; width: 24px; height: 24px; display: inline-flex; justify-content: center; align-items: center;">
|
|
<i style="font-family: ui-serif;">fx</i>
|
|
</a>
|
|
</div>
|
|
</div>
|
|
<div class="form-row" style="margin-bottom:0px;">
|
|
<label for="node-input-format" style="width:110px;"><i class="fa fa-copy"></i> <span data-i18n="ui-template.label.template"></span></label>
|
|
<input type="hidden" id="node-input-format">
|
|
<button id="node-template-expand-editor" class="red-ui-button red-ui-button-small" style="float:right"><i class="fa fa-expand"></i></button>
|
|
</div>
|
|
<div class="form-row node-text-editor-row" style="display: block;">
|
|
<div style="height:250px; min-height:100px" class="node-text-editor" id="node-input-format-editor" ></div>
|
|
</div>
|
|
<div id="template-pass-store">
|
|
<div class="form-row" style="margin-bottom:0px;">
|
|
<input type="checkbox" id="node-input-passthru" style="display:inline-block; margin-left:8px; width:auto; vertical-align:top;">
|
|
<label for="node-input-passthru" style="width:70%;"> <span data-i18n="ui-template.label.pass-through"></span></label>
|
|
</div>
|
|
</div>
|
|
<!--<div class="form-row" style="margin-bottom:0px;">
|
|
<input type="checkbox" id="node-input-storeOutMessages" style="display:inline-block; margin-left:8px; width:auto; vertical-align:top;">
|
|
<label for="node-input-storeOutMessages" style="width:70%;"> <span data-i18n="ui-template.label.store-state"></span></label>
|
|
</div>
|
|
<div class="form-row" style="margin-bottom:0px;">
|
|
<input type="checkbox" id="node-input-resendOnRefresh" style="display:inline-block; margin-left:8px; width:auto; vertical-align:top;">
|
|
<label for="node-input-resendOnRefresh" style="width:70%;"> <span data-i18n="ui-template.label.resend"></span></label>
|
|
</div>
|
|
</div> -->
|
|
</script> |