From e532ca176b319663c7b191b5c419dd4da1f406aa Mon Sep 17 00:00:00 2001 From: nathan Date: Tue, 21 Feb 2023 10:31:38 -0700 Subject: [PATCH] Add Et2TabsMobile that has special rendering for tabs on mobile --- api/etemplate.php | 25 ---- api/js/etemplate/Et2Widget/Et2Widget.ts | 12 +- api/js/etemplate/Layout/Et2Tabs/Et2Tabs.ts | 7 +- .../etemplate/Layout/Et2Tabs/Et2TabsMobile.ts | 108 ++++++++++++++++++ api/js/etemplate/etemplate2.ts | 1 + preferences/templates/mobile/settings.xet | 15 ++- 6 files changed, 134 insertions(+), 34 deletions(-) create mode 100644 api/js/etemplate/Layout/Et2Tabs/Et2TabsMobile.ts diff --git a/api/etemplate.php b/api/etemplate.php index 76b5a94cee..11d16b803f 100644 --- a/api/etemplate.php +++ b/api/etemplate.php @@ -413,31 +413,6 @@ function send_template() if ($template === 'mobile') { - // replace tabs in mobile template with details widgets - $str = preg_replace_callback('#^(\s+)]*)>\n\s*\n(.*)\n\s*\n\s*\n(.*)\n\s*\n\s*#ms', - static function($matches) - { - $indent = $matches[1]; - $tabbox_attrs = parseAttrs($matches[2]); - unset($tabbox_attrs['align_tabs']); - if (preg_match_all('##', $matches[3], $tabs) !== - preg_match_all('##', $matches[4], $panels)) - { - throw Exception("Error parsing tabbox for mobile template into details"); - } - $details = []; - foreach($tabs[1] as $n => $tab) - { - $tab_attrs = parseAttrs($tab); - $tab_attrs['id'] = $tab_attrs['id'] ?? $tabbox_attrs['id'].$n; - $tab_attrs['summary'] = $tab_attrs['label']; - $tab_attrs['title'] = $tab_attrs['statustext']; - unset($tab_attrs['label'], $tab_attrs['statustext']); - $details[] = $indent."\t".''."\n$indent\t\t".$panels[0][$n]."\n$indent\t"; - } - unset($tabbox_attrs['id']); - return $indent.'\n".implode("\n", $details)."\n$indent"; - }, $str); } // ^^^^^^^^^^^^^^^^ above widgets get transformed independent of legacy="true" set in overlay ^^^^^^^^^^^^^^^^^^ diff --git a/api/js/etemplate/Et2Widget/Et2Widget.ts b/api/js/etemplate/Et2Widget/Et2Widget.ts index 5be3aa8c52..6c5cfaa1e3 100644 --- a/api/js/etemplate/Et2Widget/Et2Widget.ts +++ b/api/js/etemplate/Et2Widget/Et2Widget.ts @@ -1352,12 +1352,21 @@ export function loadWebComponent(_nodeName : string, _template_node : Element|{[ } // Try to find the class for the given node + let mobile = (typeof egwIsMobile != "undefined" && egwIsMobile()); + if(mobile && typeof window.customElements.get(_nodeName + "_mobile") != "undefined") + { + _nodeName += "_mobile"; + } + let widget_class = window.customElements.get(_nodeName); if(!widget_class) { // Given node has no registered class. Try some of our special things (remove type, fallback to actual node) let tries = [_nodeName.split('-')[0]]; - if (_template_node.nodeName) tries = tries.concat(_template_node.nodeName.toLowerCase()); + if(_template_node.nodeName) + { + tries = tries.concat(_template_node.nodeName.toLowerCase()); + } for(let i = 0; i < tries.length && !window.customElements.get(_nodeName); i++) { _nodeName = tries[i]; @@ -1365,6 +1374,7 @@ export function loadWebComponent(_nodeName : string, _template_node : Element|{[ widget_class = window.customElements.get(_nodeName); if(!widget_class) { + debugger; throw Error("Unknown or unregistered WebComponent '" + _nodeName + "', could not find class. Also checked for " + tries.join(',')); } } diff --git a/api/js/etemplate/Layout/Et2Tabs/Et2Tabs.ts b/api/js/etemplate/Layout/Et2Tabs/Et2Tabs.ts index c2aefd06c8..11b8e6dac9 100644 --- a/api/js/etemplate/Layout/Et2Tabs/Et2Tabs.ts +++ b/api/js/etemplate/Layout/Et2Tabs/Et2Tabs.ts @@ -315,8 +315,11 @@ export class Et2Tabs extends Et2InputWidget(SlTabGroup) implements et2_IResizeab if(changedProperties.has("tabHeight")) { const body = this.shadowRoot.querySelector(".tab-group__body"); - body.style.setProperty("height", this.tabHeight == parseInt(this.tabHeight) + "" ? this.tabHeight + "px" : this.tabHeight); - body.classList.toggle("tab-group__body-fixed-height", this.tabHeight !== ''); + if(body) + { + body.style.setProperty("height", this.tabHeight == parseInt(this.tabHeight) + "" ? this.tabHeight + "px" : this.tabHeight); + body.classList.toggle("tab-group__body-fixed-height", this.tabHeight !== ''); + } } } diff --git a/api/js/etemplate/Layout/Et2Tabs/Et2TabsMobile.ts b/api/js/etemplate/Layout/Et2Tabs/Et2TabsMobile.ts new file mode 100644 index 0000000000..609d205d58 --- /dev/null +++ b/api/js/etemplate/Layout/Et2Tabs/Et2TabsMobile.ts @@ -0,0 +1,108 @@ +import {Et2Tabs} from "./Et2Tabs"; +import {classMap, html, repeat, TemplateResult} from "@lion/core"; +import {et2_createWidget} from "../../et2_core_widget"; +import {et2_template} from "../../et2_widget_template"; +import {Et2Details} from "../Et2Details/Et2Details"; +import {SlTab, SlTabPanel} from "@shoelace-style/shoelace"; + +/** + * Widget to render tabs in a mobile-friendly way + * + * We render tabs as a series of details instead of normal tabs. + * loadWebComponent() will load this component instead of Et2Tabs on mobile browsers + */ +export class Et2TabsMobile extends Et2Tabs +{ + connectedCallback() + { + super.connectedCallback(); + this.nav = this.shadowRoot.querySelector("et2-vbox"); + } + + protected createTabs(tabData) + { + // "Tabs" are created in render() + this.tabData = tabData; + + // Create tab panels here though + tabData.forEach((tab, index) => + { + let panel = this.createPanel(tab, true); + panel.slot = tab.id; + }); + } + + getAllTabs(includeDisabled = false) + { + const slot = this.shadowRoot.querySelectorAll('et2-details'); + const tabNames = ["et2-details"]; + + // It's really not a list of SlTab... + return [...slot].filter((el) => + { + return includeDisabled ? tabNames.indexOf(el.tagName.toLowerCase()) != -1 : tabNames.indexOf(el.tagName.toLowerCase()) !== -1 && !el.disabled; + }); + } + + getAllPanels() + { + const slot = this.querySelector('slot')!; + return <[SlTabPanel]>[...this.querySelectorAll('et2-tab-panel')] + } + + syncIndicator() + { + // Don't have an indicator to sync + } + + protected tabTemplate(tab, index : number) : TemplateResult + { + if(tab.XMLNode) + { + // Just read the XMLNode + let tabContent = this.createElementFromNode(tab.XMLNode); + tabContent.getDOMNode().slot = tab.id; + } + else + { + let template = et2_createWidget('template', tab.widget_options, this); + template.getDOMNode().slot = tab.id; + } + return html` + + + ` + } + + render() + { + return html` + + ${repeat(this.tabData, this.tabTemplate.bind(this))} + + + `; + } +} + +if(typeof customElements.get("et2-tabbox_mobile") == "undefined") +{ + customElements.define("et2-tabbox_mobile", Et2TabsMobile); +} \ No newline at end of file diff --git a/api/js/etemplate/etemplate2.ts b/api/js/etemplate/etemplate2.ts index 504d280e8a..e423e1d7cc 100644 --- a/api/js/etemplate/etemplate2.ts +++ b/api/js/etemplate/etemplate2.ts @@ -27,6 +27,7 @@ import './Layout/Et2Details/Et2Details'; import './Layout/Et2Tabs/Et2Tab'; import './Layout/Et2Tabs/Et2Tabs'; import './Layout/Et2Tabs/Et2TabPanel'; +import './Layout/Et2Tabs/Et2TabsMobile'; import './Et2Avatar/Et2Avatar'; import './Et2Avatar/Et2AvatarGroup'; import './Et2Button/Et2Button'; diff --git a/preferences/templates/mobile/settings.xet b/preferences/templates/mobile/settings.xet index 6a81f664fb..23228bca57 100644 --- a/preferences/templates/mobile/settings.xet +++ b/preferences/templates/mobile/settings.xet @@ -43,12 +43,15 @@ - - -