From d7ae6036af65d234cc48d91a44bc76f209e5f2f6 Mon Sep 17 00:00:00 2001 From: Yannick Schaus Date: Sun, 23 Feb 2020 21:28:18 +0100 Subject: [PATCH] New Pages section & sitemap editor (#192) This adds a new Pages section as discussed in https://github.com/openhab/openhab-webui/issues/155#issuecomment-586711180 with an editor for "managed" sitemaps stored as UI components in the system:sitemap namespace. A code view supporting the current sitemap DSL is provided. Sitemaps made with the UI don't support visibility or color rules yet. Signed-off-by: Yannick Schaus --- .../web/src/assets/i18n/en/empty-states.json | 11 +- .../web/src/assets/sitemap-lexer.nearley | 111 +++++ .../org.openhab.ui/web/src/components/app.vue | 15 +- .../pagedesigner/sitemap/dslUtil.js | 50 +++ .../pagedesigner/sitemap/mapping-details.vue | 51 +++ .../pagedesigner/sitemap/sitemap-code.vue | 76 ++++ .../pagedesigner/sitemap/treeview-item.vue | 77 ++++ .../pagedesigner/sitemap/widget-details.vue | 125 ++++++ bundles/org.openhab.ui/web/src/js/app.js | 6 +- bundles/org.openhab.ui/web/src/js/routes.js | 38 +- .../src/pages/settings/pages/pages-list.vue | 211 ++++++++++ .../settings/pages/sitemap/sitemap-edit.vue | 378 ++++++++++++++++++ .../src/pages/settings/rules/rules-list.vue | 2 +- .../web/src/pages/settings/settings-menu.vue | 27 +- .../org.openhab.ui/web/src/pages/sitemap.vue | 12 +- 15 files changed, 1141 insertions(+), 49 deletions(-) create mode 100644 bundles/org.openhab.ui/web/src/assets/sitemap-lexer.nearley create mode 100644 bundles/org.openhab.ui/web/src/components/pagedesigner/sitemap/dslUtil.js create mode 100644 bundles/org.openhab.ui/web/src/components/pagedesigner/sitemap/mapping-details.vue create mode 100644 bundles/org.openhab.ui/web/src/components/pagedesigner/sitemap/sitemap-code.vue create mode 100644 bundles/org.openhab.ui/web/src/components/pagedesigner/sitemap/treeview-item.vue create mode 100644 bundles/org.openhab.ui/web/src/components/pagedesigner/sitemap/widget-details.vue create mode 100644 bundles/org.openhab.ui/web/src/pages/settings/pages/pages-list.vue create mode 100644 bundles/org.openhab.ui/web/src/pages/settings/pages/sitemap/sitemap-edit.vue diff --git a/bundles/org.openhab.ui/web/src/assets/i18n/en/empty-states.json b/bundles/org.openhab.ui/web/src/assets/i18n/en/empty-states.json index 1f0bed22f..7261e027d 100644 --- a/bundles/org.openhab.ui/web/src/assets/i18n/en/empty-states.json +++ b/bundles/org.openhab.ui/web/src/assets/i18n/en/empty-states.json @@ -6,19 +6,22 @@ "things.text": "Things are the devices and services connected to openHAB, they are provided by binding add-ons.

Installed bindings which support auto-discovery will add thing candidates to your Inbox. You can also start a scan for a certain binding or add your first thing manually with the button below.", "things.nobindings.title": "No bindings installed", - "things.nobindings.text": "You need to install binding add-ons to add things they support to your system. Click the button below to install some.", + "things.nobindings.text": "You need to install binding add-ons to be able to add things to your system.", + + "model.title": "Start modelling your home", + "model.text": "Build a model from your items to organize them and relate them to each other semantically.

Begin with a hierarchy of locations: buildings, outside areas, floors and rooms, as needed. Then, insert equipments and points from your things (or manually).", "items.title": "No items yet", "items.text": "Items represent the functional side of your home - you can link them to the channels defined by your things. Start with the Model view to create a clean initial structure.

You can also define items with configuration files, or with the button below.", - "model.title": "Start modelling your home", - "model.text": "Build a model from your items to organize them and relate them to each other semantically.

Begin with a hierarchy of locations: buildings, outside areas, floors and rooms, as needed. Then, insert equipments and points from your things (or manually).", + "pages.title": "No pages yet", + "pages.text": "Design pages to display information in various ways and interact with your items. You can create several kinds of pages: charts, sitemaps, floor plans...

Click the button below to create your first page.", "rules.title": "No rules yet", "rules.text": "Rules are the basic building blocks to automate your home - they define which actions to perform when certain events occur.

Create your first rule with the button below; for more advanced scenarios, you can also write script files in your configuration folder.", "schedule.title": "Nothing in the schedule", - "schedule.text": "The schedule displays up to 30 days of times when rules specifically tagged \"Schedule\" are expected to run.

Click the button below to create your first scheduled rule.", + "schedule.text": "The schedule displays when rules specifically tagged \"Schedule\" are expected to run, up to 30 days.

Click the button below to create your first scheduled rule.", "rules.missingengine.title": "Rule engine not installed", "rules.missingengine.text": "The rule engine must be installed before rules can be created.", diff --git a/bundles/org.openhab.ui/web/src/assets/sitemap-lexer.nearley b/bundles/org.openhab.ui/web/src/assets/sitemap-lexer.nearley new file mode 100644 index 000000000..b413cd31f --- /dev/null +++ b/bundles/org.openhab.ui/web/src/assets/sitemap-lexer.nearley @@ -0,0 +1,111 @@ +@{% + const moo = require('moo') + + let lexer = moo.compile({ + WS: /[ \t]+/, + comment: /\/\/.*?$/, + number: /\-?[0-9]+(?:\.[0-9]*)?/, + string: { match: /"(?:\\["\\]|[^\n"\\])*"/, value: x => x.slice(1, -1) }, + sitemap: 'sitemap ', + name: 'name=', + label: 'label=', + item: 'item=', + icon: 'icon=', + widgetattr: ['url=', 'refresh=', 'service=', 'refresh=', 'period=', 'legend=', 'height=', 'frequency=', 'sendFrequency=', + 'switchEnabled=', 'mappings=', 'minValue=', 'maxValue=', 'step=', 'separator=', 'encoding='], + nlwidget: ['Switch ', 'Selection ', 'Slider ', 'List ', 'Setpoint ', 'Video ', 'Chart ', 'Webview ', 'Colorpicker ', 'Mapview ', 'Default '], + lwidget: ['Text', 'Group', 'Image', 'Frame'], + identifier: /[A-Za-z0-9_]+/, + lparen: '(', + rparen: ')', + colon: ':', + lbrace: '{', + rbrace: '}', + lbracket: '[', + rbracket: ']', + lt: '<', + gt: '>', + equals: '=', + comma: ',', + NL: { match: /\n/, lineBreaks: true }, + }) + + function getSitemap(d) { + return { + "uid": d[2][0].text, + "component": "Sitemap", + "config": d[4], + "slots": { + "widgets": d[8] + } + } + } + + function getWidget(d,l,r) { + let widget = { + component: d[0].text.trim(), + config: {} + } + if (d[2] && d[2][0]) { + for (let a of d[2][0]) { + widget.config[a[0].replace('=', '')] = a[1] + } + } + if (d[6]) { + widget.slots = { + widgets: d[6] + } + } + + return widget + } + +%} + +@lexer lexer + + +Main -> _ Sitemap _ {% (d) => d[1] %} +Sitemap -> %sitemap _ SitemapName __ SitemapLabel __ %lbrace _ Widgets _ %rbrace {% getSitemap %} + +SitemapName -> %identifier +SitemapLabel -> null {% (d) => { return {} } %} + | %label %string {% (d) => { return { "label": d[1].value } } %} + +Widgets -> Widget {% (d) => [d[0]] %} + | Widgets _ Widget {% (d) => d[0].concat([d[2]]) %} + +Widget -> %nlwidget _ WidgetAttrs:* {% getWidget %} + | %lwidget _ WidgetAttrs:* {% getWidget %} + | %lwidget _ WidgetAttrs:* __ %lbrace __ Widgets __ %rbrace {% getWidget %} + +WidgetAttrs -> WidgetAttr {% (d) => [d[0]] %} + | WidgetAttrs _ WidgetAttr {% (d) => d[0].concat([d[2]]) %} +WidgetAttr -> WidgetAttrName WidgetAttrValue {% (d) => [d[0][0].value, d[1]] %} +WidgetAttrName -> %item | %label | %icon | %widgetattr +WidgetAttrValue -> %string {% (d) => d[0].value %} + | %identifier {% (d) => d[0].value %} + | %number {% (d) => { return parseFloat(d[0].value) } %} + | %lbracket _ Mappings _ %rbracket {% (d) => d[2] %} + +Mappings -> Mapping {% (d) => [d[0]] %} + | Mappings _ %comma _ Mapping {% (d) => d[0].concat([d[4]]) %} +Mapping -> MappingCommand %equals MappingLabel {% (d) => d[0][0].value.toString() + '=' + d[2][0].value.toString() %} +MappingCommand -> %number | %identifier | %string +MappingLabel -> %number | %identifier | %string + + +_ -> null {% () => null %} + | _ %WS {% () => null %} + | _ %NL {% () => null %} +# | _ %comment {% () => null %} + +__ -> %WS {% () => null %} + | %NL {% () => null %} + | %comment {% () => null %} + | __ %WS {% () => null %} + | __ %NL {% () => null %} + | __ %comment {% () => null %} + +NL -> %NL {% () => null %} + | _ %NL {% () => null %} diff --git a/bundles/org.openhab.ui/web/src/components/app.vue b/bundles/org.openhab.ui/web/src/components/app.vue index e41c947c1..5b929e459 100644 --- a/bundles/org.openhab.ui/web/src/components/app.vue +++ b/bundles/org.openhab.ui/web/src/components/app.vue @@ -28,11 +28,14 @@ + + + - - + + @@ -217,7 +220,7 @@ export default { } }, methods: { - loadSitemaps () { + loadSidebarPages () { this.$oh.api.get('/rest/sitemaps').then((data) => { this.sitemaps = data }) @@ -272,13 +275,17 @@ export default { this.loggedIn = true } - this.loadSitemaps() + this.loadSidebarPages() this.$f7.on('pageBeforeIn', (page) => { if (page.route && page.route.url) { this.showAdministrationMenu = page.route.url.indexOf('/settings/') === 0 } }) + + this.$f7.on('sidebarRefresh', () => { + this.loadSidebarPages() + }) }) } } diff --git a/bundles/org.openhab.ui/web/src/components/pagedesigner/sitemap/dslUtil.js b/bundles/org.openhab.ui/web/src/components/pagedesigner/sitemap/dslUtil.js new file mode 100644 index 000000000..8c15d4ef8 --- /dev/null +++ b/bundles/org.openhab.ui/web/src/components/pagedesigner/sitemap/dslUtil.js @@ -0,0 +1,50 @@ +function writeWidget (widget, indent) { + let dsl = ' '.repeat(indent) + dsl += widget.component + if (widget.config) { + for (let key in widget.config) { + if (!widget.config[key]) continue + dsl += ` ${key}=` + if (key === 'item' || Number.isFinite(widget.config[key])) { + dsl += widget.config[key] + } else if (key === 'mappings') { + dsl += '[' + const mappingsDsl = widget.config.mappings.map((m) => + `${m.split('=')[0]}="${m.substring(m.indexOf('=') + 1)}"` + ) + dsl += mappingsDsl.join(',') + dsl += ']' + } else { + dsl += '"' + widget.config[key] + '"' + } + } + } + if (widget.slots) { + dsl += ' {\n' + widget.slots.widgets.forEach((w) => { + dsl += writeWidget(w, indent + 4) + }) + dsl += ' '.repeat(indent) + '}' + } + dsl += '\n' + + return dsl +} + +export default { + toDsl (sitemap) { + let dsl = 'sitemap ' + sitemap.uid + if (sitemap.config && sitemap.config.label) { + dsl += ` label="${sitemap.config.label}"` + } + dsl += ' {\n' + if (sitemap.slots && sitemap.slots.widgets) { + sitemap.slots.widgets.forEach((w) => { + dsl += writeWidget(w, 4) + }) + } + dsl += '}\n' + + return dsl + } +} diff --git a/bundles/org.openhab.ui/web/src/components/pagedesigner/sitemap/mapping-details.vue b/bundles/org.openhab.ui/web/src/components/pagedesigner/sitemap/mapping-details.vue new file mode 100644 index 000000000..1624a9aa9 --- /dev/null +++ b/bundles/org.openhab.ui/web/src/components/pagedesigner/sitemap/mapping-details.vue @@ -0,0 +1,51 @@ + + + diff --git a/bundles/org.openhab.ui/web/src/components/pagedesigner/sitemap/sitemap-code.vue b/bundles/org.openhab.ui/web/src/components/pagedesigner/sitemap/sitemap-code.vue new file mode 100644 index 000000000..1ccf0d7ce --- /dev/null +++ b/bundles/org.openhab.ui/web/src/components/pagedesigner/sitemap/sitemap-code.vue @@ -0,0 +1,76 @@ + + + + + diff --git a/bundles/org.openhab.ui/web/src/components/pagedesigner/sitemap/treeview-item.vue b/bundles/org.openhab.ui/web/src/components/pagedesigner/sitemap/treeview-item.vue new file mode 100644 index 000000000..b9b64a433 --- /dev/null +++ b/bundles/org.openhab.ui/web/src/components/pagedesigner/sitemap/treeview-item.vue @@ -0,0 +1,77 @@ + + + diff --git a/bundles/org.openhab.ui/web/src/components/pagedesigner/sitemap/widget-details.vue b/bundles/org.openhab.ui/web/src/components/pagedesigner/sitemap/widget-details.vue new file mode 100644 index 000000000..c18c38038 --- /dev/null +++ b/bundles/org.openhab.ui/web/src/components/pagedesigner/sitemap/widget-details.vue @@ -0,0 +1,125 @@ + + + + + diff --git a/bundles/org.openhab.ui/web/src/js/app.js b/bundles/org.openhab.ui/web/src/js/app.js index 38abc03b0..010379a13 100644 --- a/bundles/org.openhab.ui/web/src/js/app.js +++ b/bundles/org.openhab.ui/web/src/js/app.js @@ -3,7 +3,8 @@ import Vue from 'vue' import SitemapWidgetGeneric from '../components/sitemap/widget-generic.vue' import OHIconComponent from '../components/oh-icon.vue' -import TreeviewItem from '../components/model/treeview-item.vue' +import ModelTreeviewItem from '../components/model/treeview-item.vue' +import SitemapTreeviewItem from '../components/pagedesigner/sitemap/treeview-item.vue' import EmptyStatePlaceholder from '../components/empty-state-placeholder.vue' // Import Framework7 @@ -47,5 +48,6 @@ const app = new Vue({ Vue.component('sitemap-widget-generic', SitemapWidgetGeneric) Vue.component('oh-icon', OHIconComponent) -Vue.component('model-treeview-item', TreeviewItem) +Vue.component('model-treeview-item', ModelTreeviewItem) +Vue.component('sitemap-treeview-item', SitemapTreeviewItem) Vue.component('empty-state-placeholder', EmptyStatePlaceholder) diff --git a/bundles/org.openhab.ui/web/src/js/routes.js b/bundles/org.openhab.ui/web/src/js/routes.js index 002c014b8..9011548aa 100644 --- a/bundles/org.openhab.ui/web/src/js/routes.js +++ b/bundles/org.openhab.ui/web/src/js/routes.js @@ -31,6 +31,9 @@ import RulesListPage from '../pages/settings/rules/rules-list.vue' import RuleEditPage from '../pages/settings/rules/rule-edit.vue' import RuleConfigureModulePage from '../pages/settings/rules/rule-configure-module.vue' +import PagesListPage from '../pages/settings/pages/pages-list.vue' +import SitemapEditPage from '../pages/settings/pages/sitemap/sitemap-edit.vue' + // import SchedulePage from '../pages/settings/schedule/schedule.vue' import Analyzer from '../pages/analyzer/analyzer.vue' @@ -135,22 +138,25 @@ export default [ } ] }, - // { - // path: 'items-virtual', - // component: ItemsVirtualListPage, - // routes: [ - // { - // path: ':itemName', - // component: ItemDetailsPage, - // routes: [ - // { - // path: 'edit', - // component: ItemEditPage - // } - // ] - // } - // ] - // }, + { + path: 'pages', + component: PagesListPage, + routes: [ + { + path: 'sitemap/add', + component: SitemapEditPage, + options: { + props: { + createMode: true + } + } + }, + { + path: 'sitemap/:uid', + component: SitemapEditPage + } + ] + }, { path: 'things/', component: ThingsListPage, diff --git a/bundles/org.openhab.ui/web/src/pages/settings/pages/pages-list.vue b/bundles/org.openhab.ui/web/src/pages/settings/pages/pages-list.vue new file mode 100644 index 000000000..429772bd0 --- /dev/null +++ b/bundles/org.openhab.ui/web/src/pages/settings/pages/pages-list.vue @@ -0,0 +1,211 @@ + + + diff --git a/bundles/org.openhab.ui/web/src/pages/settings/pages/sitemap/sitemap-edit.vue b/bundles/org.openhab.ui/web/src/pages/settings/pages/sitemap/sitemap-edit.vue new file mode 100644 index 000000000..4e8ac2906 --- /dev/null +++ b/bundles/org.openhab.ui/web/src/pages/settings/pages/sitemap/sitemap-edit.vue @@ -0,0 +1,378 @@ + + + + + diff --git a/bundles/org.openhab.ui/web/src/pages/settings/rules/rules-list.vue b/bundles/org.openhab.ui/web/src/pages/settings/rules/rules-list.vue index 8286a9ea7..4bfe02754 100644 --- a/bundles/org.openhab.ui/web/src/pages/settings/rules/rules-list.vue +++ b/bundles/org.openhab.ui/web/src/pages/settings/rules/rules-list.vue @@ -57,7 +57,7 @@ - {{rules.length}} rules + {{rules.length}} rules + + + - + :footer="objectsSubtitles.pages"> + - {{sitemap.title}} {{sitemap.title}} + + Warning: sitemaps are not functional. Please use Basic UI. + @@ -30,12 +33,8 @@