iobroker.mywebui 1.164.0 → 1.170.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (244) hide show
  1. package/README.md +30 -0
  2. package/default-controls/controls/ReportSelector.control +1 -1
  3. package/default-controls/controls/analog_points/scada_point_panel.control +1 -1
  4. package/default-controls/controls/navigation/app-bar.control +1 -1
  5. package/default-controls/controls/navigation/menu-item.control +1 -1
  6. package/default-controls/controls/navigation/sidebar.control +1 -1
  7. package/default-controls/controls/txtviewer/ReportSelector.control +1 -1
  8. package/dist/backend/3d-editor-integration.js +1 -1
  9. package/dist/backend/main.js +1 -1
  10. package/dist/backend/reportCsv.js +1 -1
  11. package/dist/backend/reportGenerate.js +1 -1
  12. package/dist/backend/reportOutput.js +1 -1
  13. package/dist/backend/reportPdf.js +1 -1
  14. package/dist/backend/reportRenderer.js +1 -1
  15. package/dist/backend/reportSandbox.js +1 -1
  16. package/dist/backend/reportSandboxWorker.js +1 -1
  17. package/docs/README.md +1 -0
  18. package/docs/bindings.md +64 -2
  19. package/docs/permissions.md +2 -0
  20. package/docs/screens.md +30 -0
  21. package/docs/status-indicators.az.md +80 -0
  22. package/docs/status-indicators.md +80 -0
  23. package/docs/translations.md +11 -0
  24. package/io-package.json +1 -1
  25. package/native/mywebui_scada_engine.win32-x64-msvc.node +0 -0
  26. package/package.json +1 -1
  27. package/www/3d-editor/js/Animation.js +1 -1
  28. package/www/3d-editor/js/AnimationResizer.js +1 -1
  29. package/www/3d-editor/js/Command.js +1 -1
  30. package/www/3d-editor/js/Config.js +1 -1
  31. package/www/3d-editor/js/Editor.js +1 -1
  32. package/www/3d-editor/js/EditorControls.js +1 -1
  33. package/www/3d-editor/js/GLTFImportDialog.js +1 -1
  34. package/www/3d-editor/js/History.js +1 -1
  35. package/www/3d-editor/js/Loader.js +1 -1
  36. package/www/3d-editor/js/LoaderUtils.js +1 -1
  37. package/www/3d-editor/js/Menubar.Add.js +1 -1
  38. package/www/3d-editor/js/Menubar.Edit.js +1 -1
  39. package/www/3d-editor/js/Menubar.File.js +1 -1
  40. package/www/3d-editor/js/Menubar.Help.js +1 -1
  41. package/www/3d-editor/js/Menubar.Render.js +1 -1
  42. package/www/3d-editor/js/Menubar.Status.js +1 -1
  43. package/www/3d-editor/js/Menubar.View.js +1 -1
  44. package/www/3d-editor/js/Menubar.js +1 -1
  45. package/www/3d-editor/js/Player.js +1 -1
  46. package/www/3d-editor/js/Resizer.js +1 -1
  47. package/www/3d-editor/js/Script.js +1 -1
  48. package/www/3d-editor/js/Selector.js +1 -1
  49. package/www/3d-editor/js/Sidebar.Geometry.BoxGeometry.js +1 -1
  50. package/www/3d-editor/js/Sidebar.Geometry.BufferGeometry.js +1 -1
  51. package/www/3d-editor/js/Sidebar.Geometry.CapsuleGeometry.js +1 -1
  52. package/www/3d-editor/js/Sidebar.Geometry.CircleGeometry.js +1 -1
  53. package/www/3d-editor/js/Sidebar.Geometry.CylinderGeometry.js +1 -1
  54. package/www/3d-editor/js/Sidebar.Geometry.DodecahedronGeometry.js +1 -1
  55. package/www/3d-editor/js/Sidebar.Geometry.ExtrudeGeometry.js +1 -1
  56. package/www/3d-editor/js/Sidebar.Geometry.IcosahedronGeometry.js +1 -1
  57. package/www/3d-editor/js/Sidebar.Geometry.LatheGeometry.js +1 -1
  58. package/www/3d-editor/js/Sidebar.Geometry.Modifiers.js +1 -1
  59. package/www/3d-editor/js/Sidebar.Geometry.OctahedronGeometry.js +1 -1
  60. package/www/3d-editor/js/Sidebar.Geometry.PlaneGeometry.js +1 -1
  61. package/www/3d-editor/js/Sidebar.Geometry.RingGeometry.js +1 -1
  62. package/www/3d-editor/js/Sidebar.Geometry.ShapeGeometry.js +1 -1
  63. package/www/3d-editor/js/Sidebar.Geometry.SphereGeometry.js +1 -1
  64. package/www/3d-editor/js/Sidebar.Geometry.TetrahedronGeometry.js +1 -1
  65. package/www/3d-editor/js/Sidebar.Geometry.TextGeometry.js +1 -1
  66. package/www/3d-editor/js/Sidebar.Geometry.TorusGeometry.js +1 -1
  67. package/www/3d-editor/js/Sidebar.Geometry.TorusKnotGeometry.js +1 -1
  68. package/www/3d-editor/js/Sidebar.Geometry.TubeGeometry.js +1 -1
  69. package/www/3d-editor/js/Sidebar.Geometry.js +1 -1
  70. package/www/3d-editor/js/Sidebar.Material.BooleanProperty.js +1 -1
  71. package/www/3d-editor/js/Sidebar.Material.ColorProperty.js +1 -1
  72. package/www/3d-editor/js/Sidebar.Material.ConstantProperty.js +1 -1
  73. package/www/3d-editor/js/Sidebar.Material.MapProperty.js +1 -1
  74. package/www/3d-editor/js/Sidebar.Material.NumberProperty.js +1 -1
  75. package/www/3d-editor/js/Sidebar.Material.Program.js +1 -1
  76. package/www/3d-editor/js/Sidebar.Material.RangeValueProperty.js +1 -1
  77. package/www/3d-editor/js/Sidebar.Material.js +1 -1
  78. package/www/3d-editor/js/Sidebar.Object.js +1 -1
  79. package/www/3d-editor/js/Sidebar.Project.App.js +1 -1
  80. package/www/3d-editor/js/Sidebar.Project.Materials.js +1 -1
  81. package/www/3d-editor/js/Sidebar.Project.Renderer.js +1 -1
  82. package/www/3d-editor/js/Sidebar.Project.Resources.js +1 -1
  83. package/www/3d-editor/js/Sidebar.Project.js +1 -1
  84. package/www/3d-editor/js/Sidebar.Properties.js +1 -1
  85. package/www/3d-editor/js/Sidebar.Scene.js +1 -1
  86. package/www/3d-editor/js/Sidebar.Script.js +1 -1
  87. package/www/3d-editor/js/Sidebar.Settings.History.js +1 -1
  88. package/www/3d-editor/js/Sidebar.Settings.Shortcuts.js +1 -1
  89. package/www/3d-editor/js/Sidebar.Settings.js +1 -1
  90. package/www/3d-editor/js/Sidebar.js +1 -1
  91. package/www/3d-editor/js/Storage.js +1 -1
  92. package/www/3d-editor/js/Strings.js +1 -1
  93. package/www/3d-editor/js/TextureParametersDialog.js +1 -1
  94. package/www/3d-editor/js/Toolbar.js +1 -1
  95. package/www/3d-editor/js/Viewport.Controls.js +1 -1
  96. package/www/3d-editor/js/Viewport.Info.js +1 -1
  97. package/www/3d-editor/js/Viewport.Pathtracer.js +1 -1
  98. package/www/3d-editor/js/Viewport.ViewHelper.js +1 -1
  99. package/www/3d-editor/js/Viewport.XR.js +1 -1
  100. package/www/3d-editor/js/Viewport.js +1 -1
  101. package/www/3d-editor/js/commands/AddObjectCommand.js +1 -1
  102. package/www/3d-editor/js/commands/AddScriptCommand.js +1 -1
  103. package/www/3d-editor/js/commands/Commands.js +1 -1
  104. package/www/3d-editor/js/commands/MoveObjectCommand.js +1 -1
  105. package/www/3d-editor/js/commands/MultiCmdsCommand.js +1 -1
  106. package/www/3d-editor/js/commands/RemoveObjectCommand.js +1 -1
  107. package/www/3d-editor/js/commands/RemoveScriptCommand.js +1 -1
  108. package/www/3d-editor/js/commands/SetColorCommand.js +1 -1
  109. package/www/3d-editor/js/commands/SetGeometryCommand.js +1 -1
  110. package/www/3d-editor/js/commands/SetGeometryValueCommand.js +1 -1
  111. package/www/3d-editor/js/commands/SetMaterialColorCommand.js +1 -1
  112. package/www/3d-editor/js/commands/SetMaterialCommand.js +1 -1
  113. package/www/3d-editor/js/commands/SetMaterialMapCommand.js +1 -1
  114. package/www/3d-editor/js/commands/SetMaterialRangeCommand.js +1 -1
  115. package/www/3d-editor/js/commands/SetMaterialValueCommand.js +1 -1
  116. package/www/3d-editor/js/commands/SetMaterialVectorCommand.js +1 -1
  117. package/www/3d-editor/js/commands/SetPositionCommand.js +1 -1
  118. package/www/3d-editor/js/commands/SetRotationCommand.js +1 -1
  119. package/www/3d-editor/js/commands/SetScaleCommand.js +1 -1
  120. package/www/3d-editor/js/commands/SetSceneCommand.js +1 -1
  121. package/www/3d-editor/js/commands/SetScriptValueCommand.js +1 -1
  122. package/www/3d-editor/js/commands/SetShadowValueCommand.js +1 -1
  123. package/www/3d-editor/js/commands/SetTextureParametersCommand.js +1 -1
  124. package/www/3d-editor/js/commands/SetUuidCommand.js +1 -1
  125. package/www/3d-editor/js/commands/SetValueCommand.js +1 -1
  126. package/www/dist/frontend/bundle/HistoryDirectives-SZNC2EXZ.js +1 -0
  127. package/www/dist/frontend/bundle/ReportViewer-BDBHZVT7.js +197 -0
  128. package/www/dist/frontend/bundle/Sha256-BSH7CIPW.js +1 -1
  129. package/www/dist/frontend/bundle/chunk-G2T4KGPO.js +21 -0
  130. package/www/dist/frontend/bundle/chunk-KVPT4J7H.js +1 -1
  131. package/www/dist/frontend/bundle/chunk-TDFN5CKC.js +1 -0
  132. package/www/dist/frontend/bundle/common/IobrokerHandler.js +1 -1
  133. package/www/dist/frontend/bundle/runtime/CustomControls.js +1 -1
  134. package/www/dist/frontend/bundle/runtime/ScreenViewer.js +1 -1
  135. package/www/dist/frontend/bundle/runtime/controls.js +4 -4
  136. package/www/dist/frontend/bundle/runtime/init.js +3 -3
  137. package/www/dist/frontend/common/BindingExpression.js +1 -1
  138. package/www/dist/frontend/common/BuiltinTranslations.js +1 -0
  139. package/www/dist/frontend/common/Common.globals.js +1 -1
  140. package/www/dist/frontend/common/EChartsOption.js +1 -1
  141. package/www/dist/frontend/common/HistoryDirective.js +1 -1
  142. package/www/dist/frontend/common/HistoryOptions.js +1 -1
  143. package/www/dist/frontend/common/HistoryQuery.js +1 -1
  144. package/www/dist/frontend/common/HistoryQueryViews.js +1 -1
  145. package/www/dist/frontend/common/IobrokerHandler.js +1 -1
  146. package/www/dist/frontend/common/PlantPaxResolve.js +1 -1
  147. package/www/dist/frontend/common/ReadQuality.js +1 -0
  148. package/www/dist/frontend/common/ReportAllowlist.js +1 -1
  149. package/www/dist/frontend/common/ReportSchedule.js +1 -1
  150. package/www/dist/frontend/common/ReportTheme.js +1 -1
  151. package/www/dist/frontend/common/Runtime.js +1 -1
  152. package/www/dist/frontend/common/Sha256.js +1 -1
  153. package/www/dist/frontend/common/StateDiagnostics.js +1 -0
  154. package/www/dist/frontend/common/StateQuality.js +1 -0
  155. package/www/dist/frontend/common/WriteFeedback.js +1 -1
  156. package/www/dist/frontend/config/CommandHandling.js +1 -1
  157. package/www/dist/frontend/config/ConfigureWebcomponentDesigner.js +1 -1
  158. package/www/dist/frontend/config/DockHelper.js +1 -1
  159. package/www/dist/frontend/config/HistoryQueryWindow.js +1 -1
  160. package/www/dist/frontend/config/IobrokerWebui3DDriveEngine.js +1 -1
  161. package/www/dist/frontend/config/IobrokerWebui3DScreenEditor.js +3 -3
  162. package/www/dist/frontend/config/IobrokerWebui3DScreenPropertiesPanel.js +3 -3
  163. package/www/dist/frontend/config/IobrokerWebui3DScreenViewer.js +2 -2
  164. package/www/dist/frontend/config/IobrokerWebuiAppShell.js +3 -3
  165. package/www/dist/frontend/config/IobrokerWebuiBindingsEditor.js +1 -1
  166. package/www/dist/frontend/config/IobrokerWebuiConfirmationWrapper.js +3 -3
  167. package/www/dist/frontend/config/IobrokerWebuiControlPropertiesEditor.js +3 -3
  168. package/www/dist/frontend/config/IobrokerWebuiDynamicPropsEditor.js +1 -1
  169. package/www/dist/frontend/config/IobrokerWebuiEventAssignment.js +1 -1
  170. package/www/dist/frontend/config/IobrokerWebuiIconsView.js +3 -3
  171. package/www/dist/frontend/config/IobrokerWebuiMonacoEditor.js +3 -3
  172. package/www/dist/frontend/config/IobrokerWebuiPlantPaxTypesEditor.js +1 -1
  173. package/www/dist/frontend/config/IobrokerWebuiPropertyGrid.js +1 -1
  174. package/www/dist/frontend/config/IobrokerWebuiScreenEditor.js +1 -1
  175. package/www/dist/frontend/config/IobrokerWebuiScreensView.js +3 -3
  176. package/www/dist/frontend/config/IobrokerWebuiSecurityEditor.js +1 -1
  177. package/www/dist/frontend/config/IobrokerWebuiSignalPropertyEditor.js +1 -1
  178. package/www/dist/frontend/config/IobrokerWebuiSolutionExplorer.js +3 -3
  179. package/www/dist/frontend/config/IobrokerWebuiTranslationEditor.js +1 -1
  180. package/www/dist/frontend/config/IobrokerWebuiWidgetGallery.js +3 -3
  181. package/www/dist/frontend/config/RightDockStrip.js +1 -1
  182. package/www/dist/frontend/config/UprightOverlays.js +1 -1
  183. package/www/dist/frontend/helper/DialogHelper.js +2 -2
  184. package/www/dist/frontend/helper/EditorOfDesignItem.js +1 -1
  185. package/www/dist/frontend/helper/EsprimaHelper.js +1 -1
  186. package/www/dist/frontend/helper/Helper.js +1 -1
  187. package/www/dist/frontend/helper/LayoutHelper.js +1 -1
  188. package/www/dist/frontend/helper/XmlHelper.js +1 -1
  189. package/www/dist/frontend/helper/ZipHelper.js +1 -1
  190. package/www/dist/frontend/runtime/AnimationService.js +1 -1
  191. package/www/dist/frontend/runtime/CustomControls.js +1 -1
  192. package/www/dist/frontend/runtime/DynamicElementProperties.js +1 -1
  193. package/www/dist/frontend/runtime/EChart.js +1 -1
  194. package/www/dist/frontend/runtime/HabPanelLikeMenu.js +3 -3
  195. package/www/dist/frontend/runtime/HistoryDirectives.js +1 -1
  196. package/www/dist/frontend/runtime/NavigationShell.js +1 -1
  197. package/www/dist/frontend/runtime/ReportViewer.js +3 -3
  198. package/www/dist/frontend/runtime/RuntimeTheme.js +1 -1
  199. package/www/dist/frontend/runtime/ScreenViewer.js +3 -3
  200. package/www/dist/frontend/runtime/TranslateableText.js +2 -2
  201. package/www/dist/frontend/runtime/VisibilityService.js +1 -1
  202. package/www/dist/frontend/runtime/controls.js +1 -1
  203. package/www/dist/frontend/runtime/init.js +1 -1
  204. package/www/dist/frontend/scripting/IobrokerWebuiScriptSystem.js +1 -1
  205. package/www/dist/frontend/scripting/blockly/OpenScreen.js +1 -1
  206. package/www/dist/frontend/scripting/blockly/webuiBlocklyToolbox.js +1 -1
  207. package/www/dist/frontend/services/DynamicPropertiesHelper.js +1 -1
  208. package/www/dist/frontend/services/IobrokerWebuiBindableLocalObjectsService.js +1 -1
  209. package/www/dist/frontend/services/IobrokerWebuiBindableObjectDragDropService.js +1 -1
  210. package/www/dist/frontend/services/IobrokerWebuiBindableObjectsForPropertiesService.js +1 -1
  211. package/www/dist/frontend/services/IobrokerWebuiBindableObjectsService.js +1 -1
  212. package/www/dist/frontend/services/IobrokerWebuiConfigButtonProvider.js +1 -1
  213. package/www/dist/frontend/services/IobrokerWebuiCopyPasteService.js +1 -1
  214. package/www/dist/frontend/services/IobrokerWebuiCustomControlEventsService.js +1 -1
  215. package/www/dist/frontend/services/IobrokerWebuiCustomElementContextMenu.js +1 -1
  216. package/www/dist/frontend/services/IobrokerWebuiDemoProviderService.js +1 -1
  217. package/www/dist/frontend/services/IobrokerWebuiDynamicPropertiesService.js +1 -1
  218. package/www/dist/frontend/services/IobrokerWebuiEChartPropertiesService.js +1 -1
  219. package/www/dist/frontend/services/IobrokerWebuiEventsService.js +1 -1
  220. package/www/dist/frontend/services/IobrokerWebuiExternalDragDropService.js +1 -1
  221. package/www/dist/frontend/services/IobrokerWebuiLitPropertiesService.js +1 -1
  222. package/www/dist/frontend/services/IobrokerWebuiModelCommandService.js +1 -1
  223. package/www/dist/frontend/services/IobrokerWebuiPropertiesService.js +1 -1
  224. package/www/dist/frontend/services/IobrokerWebuiPropertyGroupsService.js +1 -1
  225. package/www/dist/frontend/services/IobrokerWebuiRefactorService.js +1 -1
  226. package/www/dist/frontend/services/IobrokerWebuiReportContextMenu.js +1 -1
  227. package/www/dist/frontend/services/IobrokerWebuiScreenContextMenu.js +1 -1
  228. package/www/dist/frontend/services/IobrokerWebuiSpecialPropertiesService.js +1 -1
  229. package/www/dist/frontend/services/IobrokerWebuiSpectrumEditorService.js +1 -1
  230. package/www/dist/frontend/services/IobrokerWebuiVisibilityPropertiesService.js +1 -1
  231. package/www/dist/frontend/services/LayoutComponentsPropertiesService.js +1 -1
  232. package/www/dist/frontend/widgets/OrthoFlowLineTool.js +1 -1
  233. package/www/dist/frontend/widgets/ReportToolbox.js +3 -3
  234. package/www/dist/frontend/widgets/customElementsObserver.js +1 -1
  235. package/www/dist/frontend/widgets/importWidgetFiles.js +1 -1
  236. package/www/dist/frontend/widgets/importWidgetFilesRuntime.js +1 -1
  237. package/www/dist/frontend/widgets/layout-components.js +1 -1
  238. package/www/dist/frontend/widgets/layout-placement-services.js +1 -1
  239. package/www/dist/frontend/widgets/testElement.js +1 -1
  240. package/www/libs/@gokturk413/web-component-designer-visualization-addons/dist/helpers/BindingsHelper.js +7 -0
  241. package/www/dist/frontend/bundle/HistoryDirectives-UWOJOPXL.js +0 -1
  242. package/www/dist/frontend/bundle/ReportViewer-DOJWNDU3.js +0 -197
  243. package/www/dist/frontend/bundle/chunk-5A724DQA.js +0 -1
  244. package/www/dist/frontend/bundle/chunk-YMYCSI72.js +0 -21
package/README.md CHANGED
@@ -302,6 +302,36 @@ runtime.html?screenName=screen2
302
302
  Placeholder for next versions:
303
303
  ### __WORK IN PROGRESS__
304
304
  -->
305
+ ### 1.170.0 (2026-10-01)
306
+ * (gokturk413) feat(runtime): state details panel for administrators (click a badge / Alt+click a control: every bound state with read and write status); runtime texts (write feedback, read quality, quality codes, the panel) in az/en/ru/de/tr, overridable by project translations
307
+
308
+ ### 1.169.0 (2026-10-01)
309
+ * (gokturk413) feat(navigation): Start as — the menu opens full, icons only, hidden, or as this browser left it last time
310
+
311
+ ### 1.168.2 (2026-10-01)
312
+ * (gokturk413) fix(navigation): with the sidebar on the right the ☰ sits in the app bar's right corner, and the app bar keeps room for it there
313
+
314
+ ### 1.168.1 (2026-10-01)
315
+ * (gokturk413) fix(designer): Settings splits Navigation into "this screen" and "all screens" (position, layout, theme… are global); a rebuilt sidebar lets go of its listeners and badge subscriptions
316
+
317
+ ### 1.168.0 (2026-10-01)
318
+ * (gokturk413) feat(navigation): position left/right/top/bottom (top/bottom as a bar with drop-down groups), layout push/overlay, a badge per entry from a state (summed on groups); fix: a control updated live no longer throws in disconnectedCallback
319
+
320
+ ### 1.167.1 (2026-10-01)
321
+ * (gokturk413) fix(designer): Settings → Navigation → Groups follows Group items at once
322
+
323
+ ### 1.167.0 (2026-10-01)
324
+ * (gokturk413) feat(navigation): menu groups get their own title (translatable), icon and order (Settings → Navigation → Groups); with icons only, a group opens its entries in a panel beside the sidebar; read-quality and write-feedback layers sit under the navigation
325
+
326
+ ### 1.166.0 (2026-10-01)
327
+ * (gokturk413) feat(navigation): menu groups by folder or by a group per screen (open/close, nested), SVG menu icons in the theme colour, the screen moves and scales with the sidebar (easing, no jump)
328
+
329
+ ### 1.165.1 (2026-10-01)
330
+ * (gokturk413) fix(runtime): read quality frames the whole control (content bigger than the host) and watches custom-control properties of type signal (input_with_button); scada_point_panel and ReportSelector no longer overflow their own box
331
+
332
+ ### 1.165.0 (2026-10-01)
333
+ * (gokturk413) feat(runtime): read quality — every bound control shows when its value cannot be trusted (quality code, driver down, stale, missing); write feedback names the quality code and no longer counts substitute values as errors; docs/status-indicators.md (en + az)
334
+
305
335
  ### 1.164.0 (2026-10-01)
306
336
  * (gokturk413) feat(designer): layout toolbar, upright overlays on rotated controls; incremental npm upload
307
337
 
@@ -1 +1 @@
1
- {"html":"<div id=\"reportSelectorContainer\" style=\"width:100%;height:100%;display:flex;align-items:center;gap:12px;padding:16px;border-radius:8px;\">\n <button id=\"btn1\" class=\"report-btn\" style=\"flex:1;cursor:pointer;border-radius:8px;font-size:15px;font-weight:600;box-shadow:0 2px 4px rgba(0,0,0,0.1);transition:0.3s;border:2px solid;padding:14px 20px;\">SnapShot Report</button>\n <button id=\"btn2\" class=\"report-btn\" style=\"flex:1;cursor:pointer;border-radius:8px;font-size:15px;font-weight:600;box-shadow:0 2px 4px rgba(0,0,0,0.1);transition:0.3s;border:2px solid;padding:14px 20px;\">Daily Report</button>\n <button id=\"btn3\" class=\"report-btn\" style=\"flex:1;cursor:pointer;border-radius:8px;font-size:15px;font-weight:600;box-shadow:0 2px 4px rgba(0,0,0,0.1);transition:0.3s;border:2px solid;padding:14px 20px;\">Hourly Report</button>\n <button id=\"btn4\" class=\"report-btn\" style=\"flex:1;cursor:pointer;border-radius:8px;font-size:15px;font-weight:600;box-shadow:0 2px 4px rgba(0,0,0,0.1);transition:0.3s;border:2px solid;padding:14px 20px;\">Prover Report</button>\n</div>\n","style":":host {\n display: block;\n width: 100%;\n}\n\n.report-btn:hover:not(.active) {\n transform: translateY(-2px);\n box-shadow: 0 4px 12px rgba(0,0,0,0.15);\n}\n\n.report-btn:active {\n transform: scale(0.98);\n}\n\n.report-btn.active {\n transform: translateY(-2px) scale(1.02);\n box-shadow: 0 6px 20px rgba(0,0,0,0.25);\n font-weight: 700;\n}\n","script":"/**\n * @param {BaseScreenViewerAndControl} instance\n */\nexport function init(instance) {\n const btn1 = instance._getDomElement('btn1');\n const btn2 = instance._getDomElement('btn2');\n const btn3 = instance._getDomElement('btn3');\n const btn4 = instance._getDomElement('btn4');\n const container = instance._getDomElement('reportSelectorContainer');\n\n const buttons = [btn1, btn2, btn3, btn4];\n let currentIndex = parseInt(instance.activeButton) || 1;\n let currentTheme = instance.theme || 'default';\n\n // Button mətnlərini yeniləyirik\n if (instance.button1Text) btn1.textContent = instance.button1Text;\n if (instance.button2Text) btn2.textContent = instance.button2Text;\n if (instance.button3Text) btn3.textContent = instance.button3Text;\n if (instance.button4Text) btn4.textContent = instance.button4Text;\n\n // İlk theming və active button\n applyTheming(instance, currentTheme);\n setActiveButton(currentIndex);\n loadReport(currentIndex);\n\n // Button click event-ləri\n buttons.forEach((btn, index) => {\n btn.addEventListener('click', () => {\n const btnIndex = index + 1;\n setActiveButton(btnIndex);\n loadReport(btnIndex);\n });\n });\n\n function applyTheming(inst, theme) {\n const themes = {\n 'default': {\n containerBg: '#f5f5f5',\n btnBg: '#ffffff',\n btnText: '#2d3436',\n btnBorder: '#dfe6e9',\n activeBg: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',\n activeText: '#ffffff',\n activeBorder: '#667eea'\n },\n 'dark': {\n containerBg: '#2d3436',\n btnBg: '#636e72',\n btnText: '#dfe6e9',\n btnBorder: '#000000',\n activeBg: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',\n activeText: '#ffffff',\n activeBorder: '#667eea'\n },\n 'light': {\n containerBg: '#ffffff',\n btnBg: '#f8f9fa',\n btnText: '#212529',\n btnBorder: '#dee2e6',\n activeBg: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',\n activeText: '#ffffff',\n activeBorder: '#667eea'\n },\n 'blue': {\n containerBg: '#dfe6e9',\n btnBg: '#ffffff',\n btnText: '#0984e3',\n btnBorder: '#74b9ff',\n activeBg: 'linear-gradient(135deg, #0984e3 0%, #0652DD 100%)',\n activeText: '#ffffff',\n activeBorder: '#0984e3'\n },\n 'green': {\n containerBg: '#d4f1e8',\n btnBg: '#ffffff',\n btnText: '#00b894',\n btnBorder: '#55efc4',\n activeBg: 'linear-gradient(135deg, #00b894 0%, #00a878 100%)',\n activeText: '#ffffff',\n activeBorder: '#00b894'\n },\n 'red': {\n containerBg: '#ffcccc',\n btnBg: '#ffffff',\n btnText: '#d63031',\n btnBorder: '#ff7675',\n activeBg: 'linear-gradient(135deg, #d63031 0%, #c0392b 100%)',\n activeText: '#ffffff',\n activeBorder: '#d63031'\n },\n 'purple': {\n containerBg: '#e5dbff',\n btnBg: '#ffffff',\n btnText: '#6c5ce7',\n btnBorder: '#a29bfe',\n activeBg: 'linear-gradient(135deg, #6c5ce7 0%, #5f27cd 100%)',\n activeText: '#ffffff',\n activeBorder: '#6c5ce7'\n }\n };\n\n const colors = themes[theme] || themes['default'];\n\n // Container\n if (container) {\n container.style.background = colors.containerBg;\n }\n\n // Buttons - inactive state\n buttons.forEach(btn => {\n if (btn && !btn.classList.contains('active')) {\n btn.style.background = colors.btnBg;\n btn.style.color = colors.btnText;\n btn.style.borderColor = colors.btnBorder;\n }\n });\n\n // Store colors for active state\n inst._themeColors = colors;\n\n // Re-apply active button styling\n setActiveButton(currentIndex);\n }\n\n function setActiveButton(index) {\n currentIndex = index;\n const colors = instance._themeColors || {\n btnBg: '#ffffff',\n btnText: '#2d3436',\n btnBorder: '#dfe6e9',\n activeBg: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',\n activeText: '#ffffff',\n activeBorder: '#667eea'\n };\n\n buttons.forEach((btn, i) => {\n if (i + 1 === index) {\n btn.classList.add('active');\n btn.style.background = colors.activeBg;\n btn.style.color = colors.activeText;\n btn.style.borderColor = colors.activeBorder;\n } else {\n btn.classList.remove('active');\n btn.style.background = colors.btnBg;\n btn.style.color = colors.btnText;\n btn.style.borderColor = colors.btnBorder;\n }\n });\n }\n\n function loadReport(index) {\n const reportStates = [instance.reportState1, instance.reportState2, instance.reportState3, instance.reportState4];\n const outputStates = [instance.btn1Output, instance.btn2Output, instance.btn3Output, instance.btn4Output];\n const sourceState = reportStates[index - 1];\n const targetState = outputStates[index - 1];\n if (!sourceState || !targetState) {\n console.warn('ReportSelector: Source or target state not configured');\n return;\n }\n window.IOB.getState(sourceState).then(state => {\n if (state && state.val !== null && state.val !== undefined) {\n window.IOB.setState(targetState, state.val); console.log('ReportSelector: Written to', targetState);\n }\n else {\n console.warn('ReportSelector: No data in source state:', sourceState);\n window.IOB.setState(targetState, 'No report data available');\n }\n }).catch(err => {\n console.error('ReportSelector: Error loading report:', err);\n window.IOB.setState(targetState, 'Error: ' + err.message);\n });\n }\n\n // Source state-dən məlumatı oxuyuruq\n window.IOB.getState(sourceState).then(state => {\n if (state && state.val !== null && state.val !== undefined) {\n const data = state.val;\n\n // Hər 4 target state-ə yazırıq\n targetStates.forEach((targetState, i) => {\n if (targetState) {\n window.IOB.setState(targetState, data);\n console.log(`ReportSelector: Written to output ${i + 1}:`, targetState);\n }\n });\n\n // Əgər outputState (legacy) varsa ona da yazırıq\n if (instance.outputState) {\n window.IOB.setState(instance.outputState, data);\n }\n } else {\n console.warn('ReportSelector: No data in source state:', sourceState);\n // Boş məlumat da yazırıq\n targetStates.forEach(targetState => {\n if (targetState) {\n window.IOB.setState(targetState, 'No report data available');\n }\n });\n }\n }).catch(err => {\n console.error('ReportSelector: Error loading report:', err);\n targetStates.forEach(targetState => {\n if (targetState) {\n window.IOB.setState(targetState, 'Error loading report: ' + err.message);\n }\n });\n });\n\n // Property dəyişiklikləri üçün event-lər\ninstance._assignEvent('button1-text-changed', () => {\n if (btn1) btn1.textContent = instance.button1Text || 'Report 1';\n});\n\ninstance._assignEvent('button2-text-changed', () => {\n if (btn2) btn2.textContent = instance.button2Text || 'Report 2';\n});\n\ninstance._assignEvent('button3-text-changed', () => {\n if (btn3) btn3.textContent = instance.button3Text || 'Report 3';\n});\n\ninstance._assignEvent('button4-text-changed', () => {\n if (btn4) btn4.textContent = instance.button4Text || 'Report 4';\n});\n\ninstance._assignEvent('active-button-changed', () => {\n const newIndex = parseInt(instance.activeButton) || 1;\n setActiveButton(newIndex);\n loadReport(newIndex);\n});\n\ninstance._assignEvent('theme-changed', () => {\n currentTheme = instance.theme || 'default';\n applyTheming(instance, currentTheme);\n});\n}\n\n\n/**\n * @param {BaseScreenViewerAndControl} instance\n */\nexport function disconnectedCallback(instance) {\n // Cleanup if needed\n}\n","properties":{"reportState1":{"type":"ioBrokerObject"},"reportState2":{"type":"ioBrokerObject"},"reportState3":{"type":"ioBrokerObject"},"reportState4":{"type":"ioBrokerObject"},"btn1Output":{"type":"ioBrokerObject"},"btn2Output":{"type":"ioBrokerObject"},"btn3Output":{"type":"ioBrokerObject"},"btn4Output":{"type":"ioBrokerObject"},"button1Text":{"type":"string","default":"SnapShot Report"},"button2Text":{"type":"string","default":"Daily Report"},"button3Text":{"type":"string","default":"Hourly Report"},"button4Text":{"type":"string","default":"Prover Report"},"activeButton":{"type":"string","default":"1"},"theme":{"type":"enum","default":"default","values":["default","dark","light","blue","green","red","purple"]}},"settings":{"width":"auto","height":"60px","visibilityEnabled":"false","visibilityGroups":"","visibilityAction":"hide","visibilityRedirectScreen":""}}
1
+ {"html":"<div id=\"reportSelectorContainer\" style=\"box-sizing:border-box;width:100%;height:100%;display:flex;align-items:center;gap:12px;padding:16px;border-radius:8px;\">\n <button id=\"btn1\" class=\"report-btn\" style=\"flex:1;cursor:pointer;border-radius:8px;font-size:15px;font-weight:600;box-shadow:0 2px 4px rgba(0,0,0,0.1);transition:0.3s;border:2px solid;padding:14px 20px;\">SnapShot Report</button>\n <button id=\"btn2\" class=\"report-btn\" style=\"flex:1;cursor:pointer;border-radius:8px;font-size:15px;font-weight:600;box-shadow:0 2px 4px rgba(0,0,0,0.1);transition:0.3s;border:2px solid;padding:14px 20px;\">Daily Report</button>\n <button id=\"btn3\" class=\"report-btn\" style=\"flex:1;cursor:pointer;border-radius:8px;font-size:15px;font-weight:600;box-shadow:0 2px 4px rgba(0,0,0,0.1);transition:0.3s;border:2px solid;padding:14px 20px;\">Hourly Report</button>\n <button id=\"btn4\" class=\"report-btn\" style=\"flex:1;cursor:pointer;border-radius:8px;font-size:15px;font-weight:600;box-shadow:0 2px 4px rgba(0,0,0,0.1);transition:0.3s;border:2px solid;padding:14px 20px;\">Prover Report</button>\n</div>\n","style":":host {\n display: block;\n width: 100%;\n}\n\n.report-btn:hover:not(.active) {\n transform: translateY(-2px);\n box-shadow: 0 4px 12px rgba(0,0,0,0.15);\n}\n\n.report-btn:active {\n transform: scale(0.98);\n}\n\n.report-btn.active {\n transform: translateY(-2px) scale(1.02);\n box-shadow: 0 6px 20px rgba(0,0,0,0.25);\n font-weight: 700;\n}\n","script":"/**\n * @param {BaseScreenViewerAndControl} instance\n */\nexport function init(instance) {\n const btn1 = instance._getDomElement('btn1');\n const btn2 = instance._getDomElement('btn2');\n const btn3 = instance._getDomElement('btn3');\n const btn4 = instance._getDomElement('btn4');\n const container = instance._getDomElement('reportSelectorContainer');\n\n const buttons = [btn1, btn2, btn3, btn4];\n let currentIndex = parseInt(instance.activeButton) || 1;\n let currentTheme = instance.theme || 'default';\n\n // Button mətnlərini yeniləyirik\n if (instance.button1Text) btn1.textContent = instance.button1Text;\n if (instance.button2Text) btn2.textContent = instance.button2Text;\n if (instance.button3Text) btn3.textContent = instance.button3Text;\n if (instance.button4Text) btn4.textContent = instance.button4Text;\n\n // İlk theming və active button\n applyTheming(instance, currentTheme);\n setActiveButton(currentIndex);\n loadReport(currentIndex);\n\n // Button click event-ləri\n buttons.forEach((btn, index) => {\n btn.addEventListener('click', () => {\n const btnIndex = index + 1;\n setActiveButton(btnIndex);\n loadReport(btnIndex);\n });\n });\n\n function applyTheming(inst, theme) {\n const themes = {\n 'default': {\n containerBg: '#f5f5f5',\n btnBg: '#ffffff',\n btnText: '#2d3436',\n btnBorder: '#dfe6e9',\n activeBg: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',\n activeText: '#ffffff',\n activeBorder: '#667eea'\n },\n 'dark': {\n containerBg: '#2d3436',\n btnBg: '#636e72',\n btnText: '#dfe6e9',\n btnBorder: '#000000',\n activeBg: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',\n activeText: '#ffffff',\n activeBorder: '#667eea'\n },\n 'light': {\n containerBg: '#ffffff',\n btnBg: '#f8f9fa',\n btnText: '#212529',\n btnBorder: '#dee2e6',\n activeBg: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',\n activeText: '#ffffff',\n activeBorder: '#667eea'\n },\n 'blue': {\n containerBg: '#dfe6e9',\n btnBg: '#ffffff',\n btnText: '#0984e3',\n btnBorder: '#74b9ff',\n activeBg: 'linear-gradient(135deg, #0984e3 0%, #0652DD 100%)',\n activeText: '#ffffff',\n activeBorder: '#0984e3'\n },\n 'green': {\n containerBg: '#d4f1e8',\n btnBg: '#ffffff',\n btnText: '#00b894',\n btnBorder: '#55efc4',\n activeBg: 'linear-gradient(135deg, #00b894 0%, #00a878 100%)',\n activeText: '#ffffff',\n activeBorder: '#00b894'\n },\n 'red': {\n containerBg: '#ffcccc',\n btnBg: '#ffffff',\n btnText: '#d63031',\n btnBorder: '#ff7675',\n activeBg: 'linear-gradient(135deg, #d63031 0%, #c0392b 100%)',\n activeText: '#ffffff',\n activeBorder: '#d63031'\n },\n 'purple': {\n containerBg: '#e5dbff',\n btnBg: '#ffffff',\n btnText: '#6c5ce7',\n btnBorder: '#a29bfe',\n activeBg: 'linear-gradient(135deg, #6c5ce7 0%, #5f27cd 100%)',\n activeText: '#ffffff',\n activeBorder: '#6c5ce7'\n }\n };\n\n const colors = themes[theme] || themes['default'];\n\n // Container\n if (container) {\n container.style.background = colors.containerBg;\n }\n\n // Buttons - inactive state\n buttons.forEach(btn => {\n if (btn && !btn.classList.contains('active')) {\n btn.style.background = colors.btnBg;\n btn.style.color = colors.btnText;\n btn.style.borderColor = colors.btnBorder;\n }\n });\n\n // Store colors for active state\n inst._themeColors = colors;\n\n // Re-apply active button styling\n setActiveButton(currentIndex);\n }\n\n function setActiveButton(index) {\n currentIndex = index;\n const colors = instance._themeColors || {\n btnBg: '#ffffff',\n btnText: '#2d3436',\n btnBorder: '#dfe6e9',\n activeBg: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',\n activeText: '#ffffff',\n activeBorder: '#667eea'\n };\n\n buttons.forEach((btn, i) => {\n if (i + 1 === index) {\n btn.classList.add('active');\n btn.style.background = colors.activeBg;\n btn.style.color = colors.activeText;\n btn.style.borderColor = colors.activeBorder;\n } else {\n btn.classList.remove('active');\n btn.style.background = colors.btnBg;\n btn.style.color = colors.btnText;\n btn.style.borderColor = colors.btnBorder;\n }\n });\n }\n\n function loadReport(index) {\n const reportStates = [instance.reportState1, instance.reportState2, instance.reportState3, instance.reportState4];\n const outputStates = [instance.btn1Output, instance.btn2Output, instance.btn3Output, instance.btn4Output];\n const sourceState = reportStates[index - 1];\n const targetState = outputStates[index - 1];\n if (!sourceState || !targetState) {\n console.warn('ReportSelector: Source or target state not configured');\n return;\n }\n window.IOB.getState(sourceState).then(state => {\n if (state && state.val !== null && state.val !== undefined) {\n window.IOB.setState(targetState, state.val); console.log('ReportSelector: Written to', targetState);\n }\n else {\n console.warn('ReportSelector: No data in source state:', sourceState);\n window.IOB.setState(targetState, 'No report data available');\n }\n }).catch(err => {\n console.error('ReportSelector: Error loading report:', err);\n window.IOB.setState(targetState, 'Error: ' + err.message);\n });\n }\n\n // Source state-dən məlumatı oxuyuruq\n window.IOB.getState(sourceState).then(state => {\n if (state && state.val !== null && state.val !== undefined) {\n const data = state.val;\n\n // Hər 4 target state-ə yazırıq\n targetStates.forEach((targetState, i) => {\n if (targetState) {\n window.IOB.setState(targetState, data);\n console.log(`ReportSelector: Written to output ${i + 1}:`, targetState);\n }\n });\n\n // Əgər outputState (legacy) varsa ona da yazırıq\n if (instance.outputState) {\n window.IOB.setState(instance.outputState, data);\n }\n } else {\n console.warn('ReportSelector: No data in source state:', sourceState);\n // Boş məlumat da yazırıq\n targetStates.forEach(targetState => {\n if (targetState) {\n window.IOB.setState(targetState, 'No report data available');\n }\n });\n }\n }).catch(err => {\n console.error('ReportSelector: Error loading report:', err);\n targetStates.forEach(targetState => {\n if (targetState) {\n window.IOB.setState(targetState, 'Error loading report: ' + err.message);\n }\n });\n });\n\n // Property dəyişiklikləri üçün event-lər\ninstance._assignEvent('button1-text-changed', () => {\n if (btn1) btn1.textContent = instance.button1Text || 'Report 1';\n});\n\ninstance._assignEvent('button2-text-changed', () => {\n if (btn2) btn2.textContent = instance.button2Text || 'Report 2';\n});\n\ninstance._assignEvent('button3-text-changed', () => {\n if (btn3) btn3.textContent = instance.button3Text || 'Report 3';\n});\n\ninstance._assignEvent('button4-text-changed', () => {\n if (btn4) btn4.textContent = instance.button4Text || 'Report 4';\n});\n\ninstance._assignEvent('active-button-changed', () => {\n const newIndex = parseInt(instance.activeButton) || 1;\n setActiveButton(newIndex);\n loadReport(newIndex);\n});\n\ninstance._assignEvent('theme-changed', () => {\n currentTheme = instance.theme || 'default';\n applyTheming(instance, currentTheme);\n});\n}\n\n\n/**\n * @param {BaseScreenViewerAndControl} instance\n */\nexport function disconnectedCallback(instance) {\n // Cleanup if needed\n}\n","properties":{"reportState1":{"type":"ioBrokerObject"},"reportState2":{"type":"ioBrokerObject"},"reportState3":{"type":"ioBrokerObject"},"reportState4":{"type":"ioBrokerObject"},"btn1Output":{"type":"ioBrokerObject"},"btn2Output":{"type":"ioBrokerObject"},"btn3Output":{"type":"ioBrokerObject"},"btn4Output":{"type":"ioBrokerObject"},"button1Text":{"type":"string","default":"SnapShot Report"},"button2Text":{"type":"string","default":"Daily Report"},"button3Text":{"type":"string","default":"Hourly Report"},"button4Text":{"type":"string","default":"Prover Report"},"activeButton":{"type":"string","default":"1"},"theme":{"type":"enum","default":"default","values":["default","dark","light","blue","green","red","purple"]}},"settings":{"width":"auto","height":"60px","visibilityEnabled":"false","visibilityGroups":"","visibilityAction":"hide","visibilityRedirectScreen":""}}
@@ -1 +1 @@
1
- {"html": "<div id=\"panel-container\" style=\"width:100%;height:100%;border:2px solid var(--ui-border,rgb(58, 90, 90));border-radius:4px;padding:8px;display:flex;flex-direction:column;font:&quot;Times New Roman&quot;, serif;-webkit-backdrop-filter:var(--ui-backdrop,none);backdrop-filter:var(--ui-backdrop,none);background:var(--ui-surface,rgb(90, 122, 122));\">\n <div style=\"display:flex;justify-content:space-between;align-items:center;margin-bottom:6px;flex-shrink:0;\">\n <div id=\"point-name\" style=\"color:var(--ui-text,#fff);text-align:center;flex:1;font:bold 14px &quot;Times New Roman&quot;, serif;\">Point</div>\n <svg id=\"trend-icon\" width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" title=\"Show Trend\" style=\"cursor:pointer;fill:none;stroke:#fff;stroke-width:2;margin-left:4px;flex-shrink:0;\">\n <polyline points=\"3 17 9 11 13 15 21 7\"></polyline>\n <polyline points=\"14 7 21 7 21 14\"></polyline>\n </svg>\n </div>\n <div style=\"display:grid;grid-template-columns:auto 1fr auto;gap:4px 8px;align-items:center;flex:1;\">\n <div id=\"label1\" style=\"color:var(--ui-text,#e0e0e0);font:italic 12px &quot;Times New Roman&quot;, serif;\"></div>\n <div id=\"value1\" bind-content:text=\"??value1_value\" style=\"color:var(--ui-text,#fff);text-align:center;font:bold 16px &quot;Times New Roman&quot;, serif;\">NaN</div>\n <div id=\"unit1\" style=\"color:var(--ui-text,#e0e0e0);font:italic 11px &quot;Times New Roman&quot;, serif;\"></div>\n <div id=\"label2\" style=\"color:var(--ui-text,#e0e0e0);display:none;font:italic 12px &quot;Times New Roman&quot;, serif;\"></div>\n <div id=\"value2\" style=\"color:var(--ui-text,#fff);text-align:center;display:none;font:bold 16px &quot;Times New Roman&quot;, serif;\"></div>\n <div id=\"unit2\" style=\"color:var(--ui-text,#e0e0e0);display:none;font:italic 11px &quot;Times New Roman&quot;, serif;\"></div>\n </div>\n <div id=\"action-button\" style=\"margin-top:8px;text-align:center;display:none;flex-shrink:0;\">\n<button id=\"action-btn\" style=\"border:1px solid var(--ui-border,rgb(58, 90, 90));color:var(--ui-text,#fff);border-radius:3px;cursor:pointer;background:rgb(74, 106, 106);font:11px &quot;Times New Roman&quot;, serif;padding:4px 12px;\">Button</button>\n </div>\n</div>\n<dialog id=\"trend-dialog\" bind-css:width=\"??grafana_dialogwidth\" bind-css:height=\"??grafana_dialogheight\" style=\"overflow:hidden;border:2px solid var(--ui-border,rgb(58, 90, 90));border-radius:8px;padding:0px;background:var(--ui-input,rgb(255, 255, 255));\">\n <div id=\"dialog-header\" style=\"color:var(--ui-text,#fff);padding:12px;display:flex;justify-content:space-between;align-items:center;background:var(--ui-surface,rgb(90, 122, 122));\">\n <h3 id=\"dialog-title\" style=\"margin:0px;font:16px &quot;Times New Roman&quot;, serif;\">Trend</h3>\n <button id=\"close-dialog\" style=\"border:initial none initial;color:var(--ui-text,#fff);cursor:pointer;padding:0px;width:30px;height:30px;background:transparent;font:24px &quot;Times New Roman&quot;, serif;\">×</button>\n </div>\n <div id=\"iframecontainerdiv1\" style=\"padding:0px;width:100%;height:91%;\">\n<iframe id=\"grafana-iframe\" frameborder=\"0\" style=\"width:100%;height:100%;border:initial none initial;\"></iframe>\n </div>\n</dialog>\n", "style": "#trend-dialog{\r\n width: 600px;\r\n height: 600px;\r\n}\r\n", "script": "let protocol = window.location.protocol;\nlet ip = window.location.hostname;\nlet port = window.location.port;\nlet baseaddress = protocol+\"//\"+ip+\":\"+port;\nfunction formatValue(value, options) {\n if (value === null || value === undefined || value === '' || isNaN(value)) {\n return 'NaN';\n }\n\n let numValue = Number(value);\n\n // Apply scaling\n if (options.scale && options.scale !== 1) {\n numValue = numValue * Number(options.scale);\n }\n\n // Apply offset\n if (options.offset && options.offset !== 0) {\n numValue = numValue + Number(options.offset);\n }\n\n // Apply min/max limits\n if (options.minLimit !== undefined && options.minLimit !== null && options.minLimit !== '') {\n const min = Number(options.minLimit);\n if (numValue < min) numValue = min;\n }\n if (options.maxLimit !== undefined && options.maxLimit !== null && options.maxLimit !== '') {\n const max = Number(options.maxLimit);\n if (numValue > max) numValue = max;\n }\n\n // Apply rounding\n let formattedValue;\n const decimals = options.decimals !== undefined ? Number(options.decimals) : 2;\n\n switch (options.roundMode) {\n case 'floor':\n formattedValue = Math.floor(numValue * Math.pow(10, decimals)) / Math.pow(10, decimals);\n break;\n case 'ceil':\n formattedValue = Math.ceil(numValue * Math.pow(10, decimals)) / Math.pow(10, decimals);\n break;\n case 'round':\n default:\n formattedValue = Math.round(numValue * Math.pow(10, decimals)) / Math.pow(10, decimals);\n break;\n }\n\n // Format to fixed decimals\n let result = formattedValue.toFixed(decimals);\n\n // Add thousand separator if enabled\n if (options.thousandSep) {\n const parts = result.split('.');\n parts[0] = parts[0].replace(/\\B(?=(\\d{3})+(?!\\d))/g, options.thousandSep);\n result = parts.join('.');\n }\n\n // Add prefix/suffix\n if (options.prefix) result = options.prefix + result;\n if (options.suffix) result = result + options.suffix;\n\n return result;\n}\nexport async function init(instance) {\n instance._bindingsRefresh?.();\n\n applyTheming(instance);\n updatePanel(instance);\n\n instance._assignEvent('point-name-changed', () => updatePanel(instance));\n instance._assignEvent('value1_label-changed', () => updatePanel(instance));\n instance._assignEvent('value1_value-changed', () => updatePanel(instance));\n instance._assignEvent('value1_unit-changed', () => updatePanel(instance));\n instance._assignEvent('value2_label-changed', () => updatePanel(instance));\n instance._assignEvent('value2_value-changed', () => updatePanel(instance));\n instance._assignEvent('value2_unit-changed', () => updatePanel(instance));\n instance._assignEvent('theme-changed', () => applyTheming(instance));\n instance._assignEvent('grafana_url-changed', () => { });\n\n instance._assignEvent('show-button-changed', () => updatePanel(instance));\n instance._assignEvent('button-text-changed', () => updatePanel(instance));\n\n // SCADA functions event listeners\n instance._assignEvent('value1_decimals-changed', () => updatePanel(instance));\n instance._assignEvent('value1_roundmode-changed', () => updatePanel(instance));\n instance._assignEvent('value1_scale-changed', () => updatePanel(instance));\n instance._assignEvent('value1_offset-changed', () => updatePanel(instance));\n instance._assignEvent('value1_minlimit-changed', () => updatePanel(instance));\n instance._assignEvent('value1_maxlimit-changed', () => updatePanel(instance));\n instance._assignEvent('value1_prefix-changed', () => updatePanel(instance));\n instance._assignEvent('value1_suffix-changed', () => updatePanel(instance));\n instance._assignEvent('value1_thousandsep-changed', () => updatePanel(instance));\n instance._assignEvent('value2_decimals-changed', () => updatePanel(instance));\n instance._assignEvent('value2_roundmode-changed', () => updatePanel(instance));\n instance._assignEvent('value2_scale-changed', () => updatePanel(instance));\n instance._assignEvent('value2_offset-changed', () => updatePanel(instance));\n instance._assignEvent('value2_minlimit-changed', () => updatePanel(instance));\n instance._assignEvent('value2_maxlimit-changed', () => updatePanel(instance));\n instance._assignEvent('value2_prefix-changed', () => updatePanel(instance));\n instance._assignEvent('value2_suffix-changed', () => updatePanel(instance));\n instance._assignEvent('value2_thousandsep-changed', () => updatePanel(instance));\n\n const trendIcon = instance._getDomElement('trend-icon');\n const dialog = instance._getDomElement('trend-dialog');\n const closeBtn = instance._getDomElement('close-dialog');\n const iframe = instance._getDomElement('grafana-iframe');\n const dialogTitle = instance._getDomElement('dialog-title');\n\n /*if (!instance.grafanaUrl || instance.grafanaUrl.trim() === \"\") {\n console.log(\"Grafana URL boşdur!\");\n } else {\n // URL boş deyilsə, parametrləri əlavə et\n const params = new URLSearchParams({\n \"var-selected_state\": instance.grafana_selected_state_name1,\n \"var-selected_state2\": instance.grafana_selected_state_name2,\n \"var-tag_name\": instance.pointName,\n kiosk: \"\" // kiosk parametri əlavə olunur\n });\n\n\n\n const finalUrl = `${instance.grafanaUrl}?${params.toString()}`;\n // instance.grafanaUrl = finalUrl;\n }*/\n\n\n\n\n\n if (trendIcon && dialog) {\n trendIcon.addEventListener('click', async() => {\n if (!instance.grafana_url || instance.grafana_url.trim() === \"\") {\n console.log(\"Grafana URL boşdur!\");\n } else {\n // URL obyektini qururuq\n const url = new URL(instance.grafana_url, baseaddress); // baza hostu əlavə et\n\n // Mövcud parametrləri götürürük\n const params = url.searchParams;\n\n // İstədiyin parametrlərə dəyər ver\n params.set(\"var-selected_state\", instance.grafana_selected_state_name1);\n params.set(\"var-selected_state2\", instance.grafana_selected_state_name2);\n params.set(\"var-tag_name\", instance.pointName);\n\n\n // Yenilənmiş URL\n const finalUrl = `${url.href}&kiosk`;\n //console.log(finalUrl);\n\n //const grafanaUrl = instance.grafanaUrl || 'about:blank';\n // ioBroker-dən cari useri al\n iframe.src = finalUrl;\n dialogTitle.textContent = (instance.pointName || 'Trend') + ' - Trend';\n dialog.showModal();\n \n\n /*const pointName = instance.pointName || 'Trend';\n iframe.src = finalUrl;\n dialogTitle.textContent = pointName + ' - Trend';\n dialog.showModal();*/\n }\n });\n }\n\n if (closeBtn && dialog) {\n closeBtn.addEventListener('click', () => {\n dialog.close();\n iframe.src = 'about:blank';\n });\n }\n\n if (dialog) {\n dialog.addEventListener('click', (e) => {\n const rect = dialog.getBoundingClientRect();\n if (e.clientX < rect.left || e.clientX > rect.right || e.clientY < rect.top || e.clientY > rect.bottom) {\n dialog.close();\n iframe.src = 'about:blank';\n }\n });\n }\n\n const actionBtn = instance._getDomElement('action-btn');\n if (actionBtn) {\n actionBtn.addEventListener('click', () => {\n const event = new CustomEvent('scada-point-button-clicked', {\n detail: { pointName: instance.pointName },\n bubbles: true\n });\n instance.dispatchEvent(event);\n });\n }\n}\n\nfunction updatePanel(instance) {\n const nameEl = instance._getDomElement('point-name');\n if (nameEl) nameEl.textContent = instance.pointName || 'Point';\n\n const label1El = instance._getDomElement('label1');\n const value1El = instance._getDomElement('value1');\n const unit1El = instance._getDomElement('unit1');\n if (label1El) label1El.textContent = instance.value1_label || '';\n\n // Format value1 with SCADA functions\n if (value1El) {\n const value1Options = {\n decimals: instance.value1_decimals,\n roundMode: instance.value1_roundmode,\n scale: instance.value1_scale,\n offset: instance.value1_offset,\n minLimit: instance.value1_minlimit,\n maxLimit: instance.value1_maxlimit,\n prefix: instance.value1_prefix,\n suffix: instance.value1_suffix,\n thousandSep: instance.value1_thousandsep\n };\n value1El.textContent = formatValue(instance.value1_value, value1Options);\n }\n\n if (unit1El) unit1El.textContent = instance.value1_unit || '';\n\n const label2El = instance._getDomElement('label2');\n const value2El = instance._getDomElement('value2');\n const unit2El = instance._getDomElement('unit2');\n const hasRow2 = instance.value2_label || instance.value2_value || instance.value2_unit;\n\n if (label2El) {\n label2El.textContent = instance.value2_label || '';\n label2El.style.display = hasRow2 ? 'block' : 'none';\n }\n if (value2El) {\n const value2Options = {\n decimals: instance.value2_decimals,\n roundMode: instance.value2_roundmode,\n scale: instance.value2_scale,\n offset: instance.value2_offset,\n minLimit: instance.value2_minlimit,\n maxLimit: instance.value2_maxlimit,\n prefix: instance.value2_prefix,\n suffix: instance.value2_suffix,\n thousandSep: instance.value2_thousandsep\n };\n value2El.textContent = formatValue(instance.value2_value, value2Options);\n value2El.style.display = hasRow2 ? 'block' : 'none';\n }\n if (unit2El) {\n unit2El.textContent = instance.value2_unit || '';\n unit2El.style.display = hasRow2 ? 'block' : 'none';\n }\n\n const buttonEl = instance._getDomElement('action-button');\n const buttonTextEl = instance._getDomElement('action-btn');\n if (buttonEl && buttonTextEl) {\n const showButton = instance.showButton === true || instance.showButton === 'true';\n buttonEl.style.display = showButton ? 'block' : 'none';\n buttonTextEl.textContent = instance.buttonText || 'Button';\n }\n}\n\nfunction applyTheming(instance) {\n const theme = instance.theme || 'default';\n const container = instance._getDomElement('panel-container');\n if (!container) return;\n\n const themes = {\n 'default': { bg: 'var(--ui-surface,#5a7a7a)', border: 'var(--ui-border,#3a5a5a)', text: 'var(--ui-text,#fff)', label: 'var(--ui-dim,#e0e0e0)', btnBg: 'var(--ui-surface-2,#4a6a6a)' },\n 'dark': { bg: '#2d3436', border: '#000', text: '#fff', label: '#dfe6e9', btnBg: '#636e72' },\n 'light': { bg: '#dfe6e9', border: '#b2bec3', text: '#2d3436', label: '#636e72', btnBg: '#b2bec3' },\n 'blue': { bg: '#0984e3', border: '#0652DD', text: '#fff', label: '#dfe6e9', btnBg: '#74b9ff' },\n 'green': { bg: '#00b894', border: '#00a383', text: '#fff', label: '#d4f1e8', btnBg: '#55efc4' },\n 'red': { bg: '#d63031', border: '#b71c1c', text: '#fff', label: '#ffcccc', btnBg: '#ff7675' },\n 'purple': { bg: '#6c5ce7', border: '#5f3dc4', text: '#fff', label: '#e5dbff', btnBg: '#a29bfe' }\n };\n\n const colors = themes[theme] || themes['default'];\n container.style.background = colors.bg;\n container.style.borderColor = colors.border;\n\n const nameEl = instance._getDomElement('point-name');\n const value1El = instance._getDomElement('value1');\n const value2El = instance._getDomElement('value2');\n if (nameEl) nameEl.style.color = colors.text;\n if (value1El) value1El.style.color = colors.text;\n if (value2El) value2El.style.color = colors.text;\n\n const label1El = instance._getDomElement('label1');\n const label2El = instance._getDomElement('label2');\n const unit1El = instance._getDomElement('unit1');\n const unit2El = instance._getDomElement('unit2');\n if (label1El) label1El.style.color = colors.label;\n if (label2El) label2El.style.color = colors.label;\n if (unit1El) unit1El.style.color = colors.label;\n if (unit2El) unit2El.style.color = colors.label;\n\n const trendIcon = instance._getDomElement('trend-icon');\n if (trendIcon) trendIcon.style.stroke = colors.text;\n\n const actionBtn = instance._getDomElement('action-btn');\n if (actionBtn) {\n actionBtn.style.background = colors.btnBg;\n actionBtn.style.borderColor = colors.border;\n actionBtn.style.color = colors.text;\n }\n\n const dialog = instance._getDomElement('trend-dialog');\n if (dialog) {\n dialog.style.borderColor = colors.border;\n const header = instance._getDomElement('dialog-header');\n if (header) {\n header.style.background = colors.bg;\n header.style.color = colors.text;\n }\n }\n}\n\nexport function connectedCallback(instance)\n{\n \n}\n\nfunction getResolvedSignalId() {\n // bind-prop:value1 atributundan ham pattern-i oxu\n const raw = element.getAttribute('bind-prop:value1');\n if (!raw) return null;\n\n // {??propName} → screen[propName] ilə replace et\n return raw.replace(/\\{(\\?\\?([^}]+))\\}/g, (_, __, propName) => {\n return screen?.[propName] ?? '';\n });\n }", "properties": {"pointName": {"type": "string", "default": "DPT 010"}, "value1_value": {"type": "number"}, "value1_label": {"type": "string"}, "value1_unit": {"type": "string", "default": "kPa"}, "value2_label": {"type": "string"}, "value2_value": {"type": "number"}, "theme": {"type": "enum", "default": "default", "values": ["default", "dark", "light", "blue", "green", "red", "purple"]}, "value2_unit": {"type": "string"}, "grafana_url": {"type": "string"}, "grafana_selected_state_name1": {"type": "string"}, "grafana_selected_state_name2": {"type": "string"}, "showButton": {"type": "boolean"}, "buttonText": {"type": "string", "default": "Button"}, "grafana_dialogheight": {"type": "string"}, "grafana_dialogwidth": {"type": "string"}, "value1_decimals": {"type": "number", "default": "2"}, "value1_roundmode": {"type": "enum", "default": "round", "values": ["round", "floor", "ceil"]}, "value1_scale": {"type": "number", "default": "1"}, "value1_offset": {"type": "number"}, "value1_maxlimit": {"type": "number"}, "value1_minlimit": {"type": "number"}, "value1_prefix": {"type": "string"}, "value1_suffix": {"type": "string"}, "value1_thousandsep": {"type": "string"}, "value2_decimals": {"type": "number", "default": "2"}, "value2_roundmode": {"type": "enum", "default": "round", "values": ["round", "floor", "ceil"]}, "value2_scale": {"type": "number", "default": "1"}, "value2_offset": {"type": "number"}, "value2_minlimit": {"type": "number"}, "value2_maxlimit": {"type": "number"}, "value2_prefix": {"type": "string"}, "value2_suffix": {"type": "string"}, "value2_thousandsep": {"type": "string"}}, "settings": {"visibilityEnabled": "false", "visibilityGroups": "", "visibilityAction": "hide", "visibilityRedirectScreen": ""}}
1
+ {"html": "<div id=\"panel-container\" style=\"box-sizing:border-box;width:100%;height:100%;border:2px solid var(--ui-border,rgb(58, 90, 90));border-radius:4px;padding:8px;display:flex;flex-direction:column;font:&quot;Times New Roman&quot;, serif;-webkit-backdrop-filter:var(--ui-backdrop,none);backdrop-filter:var(--ui-backdrop,none);background:var(--ui-surface,rgb(90, 122, 122));\">\n <div style=\"display:flex;justify-content:space-between;align-items:center;margin-bottom:6px;flex-shrink:0;\">\n <div id=\"point-name\" style=\"color:var(--ui-text,#fff);text-align:center;flex:1;font:bold 14px &quot;Times New Roman&quot;, serif;\">Point</div>\n <svg id=\"trend-icon\" width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" title=\"Show Trend\" style=\"cursor:pointer;fill:none;stroke:#fff;stroke-width:2;margin-left:4px;flex-shrink:0;\">\n <polyline points=\"3 17 9 11 13 15 21 7\"></polyline>\n <polyline points=\"14 7 21 7 21 14\"></polyline>\n </svg>\n </div>\n <div style=\"display:grid;grid-template-columns:auto 1fr auto;gap:4px 8px;align-items:center;flex:1;\">\n <div id=\"label1\" style=\"color:var(--ui-text,#e0e0e0);font:italic 12px &quot;Times New Roman&quot;, serif;\"></div>\n <div id=\"value1\" bind-content:text=\"??value1_value\" style=\"color:var(--ui-text,#fff);text-align:center;font:bold 16px &quot;Times New Roman&quot;, serif;\">NaN</div>\n <div id=\"unit1\" style=\"color:var(--ui-text,#e0e0e0);font:italic 11px &quot;Times New Roman&quot;, serif;\"></div>\n <div id=\"label2\" style=\"color:var(--ui-text,#e0e0e0);display:none;font:italic 12px &quot;Times New Roman&quot;, serif;\"></div>\n <div id=\"value2\" style=\"color:var(--ui-text,#fff);text-align:center;display:none;font:bold 16px &quot;Times New Roman&quot;, serif;\"></div>\n <div id=\"unit2\" style=\"color:var(--ui-text,#e0e0e0);display:none;font:italic 11px &quot;Times New Roman&quot;, serif;\"></div>\n </div>\n <div id=\"action-button\" style=\"margin-top:8px;text-align:center;display:none;flex-shrink:0;\">\n<button id=\"action-btn\" style=\"border:1px solid var(--ui-border,rgb(58, 90, 90));color:var(--ui-text,#fff);border-radius:3px;cursor:pointer;background:rgb(74, 106, 106);font:11px &quot;Times New Roman&quot;, serif;padding:4px 12px;\">Button</button>\n </div>\n</div>\n<dialog id=\"trend-dialog\" bind-css:width=\"??grafana_dialogwidth\" bind-css:height=\"??grafana_dialogheight\" style=\"overflow:hidden;border:2px solid var(--ui-border,rgb(58, 90, 90));border-radius:8px;padding:0px;background:var(--ui-input,rgb(255, 255, 255));\">\n <div id=\"dialog-header\" style=\"color:var(--ui-text,#fff);padding:12px;display:flex;justify-content:space-between;align-items:center;background:var(--ui-surface,rgb(90, 122, 122));\">\n <h3 id=\"dialog-title\" style=\"margin:0px;font:16px &quot;Times New Roman&quot;, serif;\">Trend</h3>\n <button id=\"close-dialog\" style=\"border:initial none initial;color:var(--ui-text,#fff);cursor:pointer;padding:0px;width:30px;height:30px;background:transparent;font:24px &quot;Times New Roman&quot;, serif;\">×</button>\n </div>\n <div id=\"iframecontainerdiv1\" style=\"padding:0px;width:100%;height:91%;\">\n<iframe id=\"grafana-iframe\" frameborder=\"0\" style=\"width:100%;height:100%;border:initial none initial;\"></iframe>\n </div>\n</dialog>\n", "style": "#trend-dialog{\r\n width: 600px;\r\n height: 600px;\r\n}\r\n", "script": "let protocol = window.location.protocol;\nlet ip = window.location.hostname;\nlet port = window.location.port;\nlet baseaddress = protocol+\"//\"+ip+\":\"+port;\nfunction formatValue(value, options) {\n if (value === null || value === undefined || value === '' || isNaN(value)) {\n return 'NaN';\n }\n\n let numValue = Number(value);\n\n // Apply scaling\n if (options.scale && options.scale !== 1) {\n numValue = numValue * Number(options.scale);\n }\n\n // Apply offset\n if (options.offset && options.offset !== 0) {\n numValue = numValue + Number(options.offset);\n }\n\n // Apply min/max limits\n if (options.minLimit !== undefined && options.minLimit !== null && options.minLimit !== '') {\n const min = Number(options.minLimit);\n if (numValue < min) numValue = min;\n }\n if (options.maxLimit !== undefined && options.maxLimit !== null && options.maxLimit !== '') {\n const max = Number(options.maxLimit);\n if (numValue > max) numValue = max;\n }\n\n // Apply rounding\n let formattedValue;\n const decimals = options.decimals !== undefined ? Number(options.decimals) : 2;\n\n switch (options.roundMode) {\n case 'floor':\n formattedValue = Math.floor(numValue * Math.pow(10, decimals)) / Math.pow(10, decimals);\n break;\n case 'ceil':\n formattedValue = Math.ceil(numValue * Math.pow(10, decimals)) / Math.pow(10, decimals);\n break;\n case 'round':\n default:\n formattedValue = Math.round(numValue * Math.pow(10, decimals)) / Math.pow(10, decimals);\n break;\n }\n\n // Format to fixed decimals\n let result = formattedValue.toFixed(decimals);\n\n // Add thousand separator if enabled\n if (options.thousandSep) {\n const parts = result.split('.');\n parts[0] = parts[0].replace(/\\B(?=(\\d{3})+(?!\\d))/g, options.thousandSep);\n result = parts.join('.');\n }\n\n // Add prefix/suffix\n if (options.prefix) result = options.prefix + result;\n if (options.suffix) result = result + options.suffix;\n\n return result;\n}\nexport async function init(instance) {\n instance._bindingsRefresh?.();\n\n applyTheming(instance);\n updatePanel(instance);\n\n instance._assignEvent('point-name-changed', () => updatePanel(instance));\n instance._assignEvent('value1_label-changed', () => updatePanel(instance));\n instance._assignEvent('value1_value-changed', () => updatePanel(instance));\n instance._assignEvent('value1_unit-changed', () => updatePanel(instance));\n instance._assignEvent('value2_label-changed', () => updatePanel(instance));\n instance._assignEvent('value2_value-changed', () => updatePanel(instance));\n instance._assignEvent('value2_unit-changed', () => updatePanel(instance));\n instance._assignEvent('theme-changed', () => applyTheming(instance));\n instance._assignEvent('grafana_url-changed', () => { });\n\n instance._assignEvent('show-button-changed', () => updatePanel(instance));\n instance._assignEvent('button-text-changed', () => updatePanel(instance));\n\n // SCADA functions event listeners\n instance._assignEvent('value1_decimals-changed', () => updatePanel(instance));\n instance._assignEvent('value1_roundmode-changed', () => updatePanel(instance));\n instance._assignEvent('value1_scale-changed', () => updatePanel(instance));\n instance._assignEvent('value1_offset-changed', () => updatePanel(instance));\n instance._assignEvent('value1_minlimit-changed', () => updatePanel(instance));\n instance._assignEvent('value1_maxlimit-changed', () => updatePanel(instance));\n instance._assignEvent('value1_prefix-changed', () => updatePanel(instance));\n instance._assignEvent('value1_suffix-changed', () => updatePanel(instance));\n instance._assignEvent('value1_thousandsep-changed', () => updatePanel(instance));\n instance._assignEvent('value2_decimals-changed', () => updatePanel(instance));\n instance._assignEvent('value2_roundmode-changed', () => updatePanel(instance));\n instance._assignEvent('value2_scale-changed', () => updatePanel(instance));\n instance._assignEvent('value2_offset-changed', () => updatePanel(instance));\n instance._assignEvent('value2_minlimit-changed', () => updatePanel(instance));\n instance._assignEvent('value2_maxlimit-changed', () => updatePanel(instance));\n instance._assignEvent('value2_prefix-changed', () => updatePanel(instance));\n instance._assignEvent('value2_suffix-changed', () => updatePanel(instance));\n instance._assignEvent('value2_thousandsep-changed', () => updatePanel(instance));\n\n const trendIcon = instance._getDomElement('trend-icon');\n const dialog = instance._getDomElement('trend-dialog');\n const closeBtn = instance._getDomElement('close-dialog');\n const iframe = instance._getDomElement('grafana-iframe');\n const dialogTitle = instance._getDomElement('dialog-title');\n\n /*if (!instance.grafanaUrl || instance.grafanaUrl.trim() === \"\") {\n console.log(\"Grafana URL boşdur!\");\n } else {\n // URL boş deyilsə, parametrləri əlavə et\n const params = new URLSearchParams({\n \"var-selected_state\": instance.grafana_selected_state_name1,\n \"var-selected_state2\": instance.grafana_selected_state_name2,\n \"var-tag_name\": instance.pointName,\n kiosk: \"\" // kiosk parametri əlavə olunur\n });\n\n\n\n const finalUrl = `${instance.grafanaUrl}?${params.toString()}`;\n // instance.grafanaUrl = finalUrl;\n }*/\n\n\n\n\n\n if (trendIcon && dialog) {\n trendIcon.addEventListener('click', async() => {\n if (!instance.grafana_url || instance.grafana_url.trim() === \"\") {\n console.log(\"Grafana URL boşdur!\");\n } else {\n // URL obyektini qururuq\n const url = new URL(instance.grafana_url, baseaddress); // baza hostu əlavə et\n\n // Mövcud parametrləri götürürük\n const params = url.searchParams;\n\n // İstədiyin parametrlərə dəyər ver\n params.set(\"var-selected_state\", instance.grafana_selected_state_name1);\n params.set(\"var-selected_state2\", instance.grafana_selected_state_name2);\n params.set(\"var-tag_name\", instance.pointName);\n\n\n // Yenilənmiş URL\n const finalUrl = `${url.href}&kiosk`;\n //console.log(finalUrl);\n\n //const grafanaUrl = instance.grafanaUrl || 'about:blank';\n // ioBroker-dən cari useri al\n iframe.src = finalUrl;\n dialogTitle.textContent = (instance.pointName || 'Trend') + ' - Trend';\n dialog.showModal();\n \n\n /*const pointName = instance.pointName || 'Trend';\n iframe.src = finalUrl;\n dialogTitle.textContent = pointName + ' - Trend';\n dialog.showModal();*/\n }\n });\n }\n\n if (closeBtn && dialog) {\n closeBtn.addEventListener('click', () => {\n dialog.close();\n iframe.src = 'about:blank';\n });\n }\n\n if (dialog) {\n dialog.addEventListener('click', (e) => {\n const rect = dialog.getBoundingClientRect();\n if (e.clientX < rect.left || e.clientX > rect.right || e.clientY < rect.top || e.clientY > rect.bottom) {\n dialog.close();\n iframe.src = 'about:blank';\n }\n });\n }\n\n const actionBtn = instance._getDomElement('action-btn');\n if (actionBtn) {\n actionBtn.addEventListener('click', () => {\n const event = new CustomEvent('scada-point-button-clicked', {\n detail: { pointName: instance.pointName },\n bubbles: true\n });\n instance.dispatchEvent(event);\n });\n }\n}\n\nfunction updatePanel(instance) {\n const nameEl = instance._getDomElement('point-name');\n if (nameEl) nameEl.textContent = instance.pointName || 'Point';\n\n const label1El = instance._getDomElement('label1');\n const value1El = instance._getDomElement('value1');\n const unit1El = instance._getDomElement('unit1');\n if (label1El) label1El.textContent = instance.value1_label || '';\n\n // Format value1 with SCADA functions\n if (value1El) {\n const value1Options = {\n decimals: instance.value1_decimals,\n roundMode: instance.value1_roundmode,\n scale: instance.value1_scale,\n offset: instance.value1_offset,\n minLimit: instance.value1_minlimit,\n maxLimit: instance.value1_maxlimit,\n prefix: instance.value1_prefix,\n suffix: instance.value1_suffix,\n thousandSep: instance.value1_thousandsep\n };\n value1El.textContent = formatValue(instance.value1_value, value1Options);\n }\n\n if (unit1El) unit1El.textContent = instance.value1_unit || '';\n\n const label2El = instance._getDomElement('label2');\n const value2El = instance._getDomElement('value2');\n const unit2El = instance._getDomElement('unit2');\n const hasRow2 = instance.value2_label || instance.value2_value || instance.value2_unit;\n\n if (label2El) {\n label2El.textContent = instance.value2_label || '';\n label2El.style.display = hasRow2 ? 'block' : 'none';\n }\n if (value2El) {\n const value2Options = {\n decimals: instance.value2_decimals,\n roundMode: instance.value2_roundmode,\n scale: instance.value2_scale,\n offset: instance.value2_offset,\n minLimit: instance.value2_minlimit,\n maxLimit: instance.value2_maxlimit,\n prefix: instance.value2_prefix,\n suffix: instance.value2_suffix,\n thousandSep: instance.value2_thousandsep\n };\n value2El.textContent = formatValue(instance.value2_value, value2Options);\n value2El.style.display = hasRow2 ? 'block' : 'none';\n }\n if (unit2El) {\n unit2El.textContent = instance.value2_unit || '';\n unit2El.style.display = hasRow2 ? 'block' : 'none';\n }\n\n const buttonEl = instance._getDomElement('action-button');\n const buttonTextEl = instance._getDomElement('action-btn');\n if (buttonEl && buttonTextEl) {\n const showButton = instance.showButton === true || instance.showButton === 'true';\n buttonEl.style.display = showButton ? 'block' : 'none';\n buttonTextEl.textContent = instance.buttonText || 'Button';\n }\n}\n\nfunction applyTheming(instance) {\n const theme = instance.theme || 'default';\n const container = instance._getDomElement('panel-container');\n if (!container) return;\n\n const themes = {\n 'default': { bg: 'var(--ui-surface,#5a7a7a)', border: 'var(--ui-border,#3a5a5a)', text: 'var(--ui-text,#fff)', label: 'var(--ui-dim,#e0e0e0)', btnBg: 'var(--ui-surface-2,#4a6a6a)' },\n 'dark': { bg: '#2d3436', border: '#000', text: '#fff', label: '#dfe6e9', btnBg: '#636e72' },\n 'light': { bg: '#dfe6e9', border: '#b2bec3', text: '#2d3436', label: '#636e72', btnBg: '#b2bec3' },\n 'blue': { bg: '#0984e3', border: '#0652DD', text: '#fff', label: '#dfe6e9', btnBg: '#74b9ff' },\n 'green': { bg: '#00b894', border: '#00a383', text: '#fff', label: '#d4f1e8', btnBg: '#55efc4' },\n 'red': { bg: '#d63031', border: '#b71c1c', text: '#fff', label: '#ffcccc', btnBg: '#ff7675' },\n 'purple': { bg: '#6c5ce7', border: '#5f3dc4', text: '#fff', label: '#e5dbff', btnBg: '#a29bfe' }\n };\n\n const colors = themes[theme] || themes['default'];\n container.style.background = colors.bg;\n container.style.borderColor = colors.border;\n\n const nameEl = instance._getDomElement('point-name');\n const value1El = instance._getDomElement('value1');\n const value2El = instance._getDomElement('value2');\n if (nameEl) nameEl.style.color = colors.text;\n if (value1El) value1El.style.color = colors.text;\n if (value2El) value2El.style.color = colors.text;\n\n const label1El = instance._getDomElement('label1');\n const label2El = instance._getDomElement('label2');\n const unit1El = instance._getDomElement('unit1');\n const unit2El = instance._getDomElement('unit2');\n if (label1El) label1El.style.color = colors.label;\n if (label2El) label2El.style.color = colors.label;\n if (unit1El) unit1El.style.color = colors.label;\n if (unit2El) unit2El.style.color = colors.label;\n\n const trendIcon = instance._getDomElement('trend-icon');\n if (trendIcon) trendIcon.style.stroke = colors.text;\n\n const actionBtn = instance._getDomElement('action-btn');\n if (actionBtn) {\n actionBtn.style.background = colors.btnBg;\n actionBtn.style.borderColor = colors.border;\n actionBtn.style.color = colors.text;\n }\n\n const dialog = instance._getDomElement('trend-dialog');\n if (dialog) {\n dialog.style.borderColor = colors.border;\n const header = instance._getDomElement('dialog-header');\n if (header) {\n header.style.background = colors.bg;\n header.style.color = colors.text;\n }\n }\n}\n\nexport function connectedCallback(instance)\n{\n \n}\n\nfunction getResolvedSignalId() {\n // bind-prop:value1 atributundan ham pattern-i oxu\n const raw = element.getAttribute('bind-prop:value1');\n if (!raw) return null;\n\n // {??propName} → screen[propName] ilə replace et\n return raw.replace(/\\{(\\?\\?([^}]+))\\}/g, (_, __, propName) => {\n return screen?.[propName] ?? '';\n });\n }", "properties": {"pointName": {"type": "string", "default": "DPT 010"}, "value1_value": {"type": "number"}, "value1_label": {"type": "string"}, "value1_unit": {"type": "string", "default": "kPa"}, "value2_label": {"type": "string"}, "value2_value": {"type": "number"}, "theme": {"type": "enum", "default": "default", "values": ["default", "dark", "light", "blue", "green", "red", "purple"]}, "value2_unit": {"type": "string"}, "grafana_url": {"type": "string"}, "grafana_selected_state_name1": {"type": "string"}, "grafana_selected_state_name2": {"type": "string"}, "showButton": {"type": "boolean"}, "buttonText": {"type": "string", "default": "Button"}, "grafana_dialogheight": {"type": "string"}, "grafana_dialogwidth": {"type": "string"}, "value1_decimals": {"type": "number", "default": "2"}, "value1_roundmode": {"type": "enum", "default": "round", "values": ["round", "floor", "ceil"]}, "value1_scale": {"type": "number", "default": "1"}, "value1_offset": {"type": "number"}, "value1_maxlimit": {"type": "number"}, "value1_minlimit": {"type": "number"}, "value1_prefix": {"type": "string"}, "value1_suffix": {"type": "string"}, "value1_thousandsep": {"type": "string"}, "value2_decimals": {"type": "number", "default": "2"}, "value2_roundmode": {"type": "enum", "default": "round", "values": ["round", "floor", "ceil"]}, "value2_scale": {"type": "number", "default": "1"}, "value2_offset": {"type": "number"}, "value2_minlimit": {"type": "number"}, "value2_maxlimit": {"type": "number"}, "value2_prefix": {"type": "string"}, "value2_suffix": {"type": "string"}, "value2_thousandsep": {"type": "string"}}, "settings": {"visibilityEnabled": "false", "visibilityGroups": "", "visibilityAction": "hide", "visibilityRedirectScreen": ""}}
@@ -1 +1 @@
1
- {"properties": {"caption": {"type": "string", "default": ""}, "icon": {"type": "string", "default": ""}, "bgColor": {"type": "color", "default": ""}}, "settings": {"width": "100%", "height": "48px"}, "html": "<div id=\"bar\" class=\"bar\"><div id=\"slotLeft\" class=\"bar-slot\"></div><img id=\"barIcon\" class=\"bar-icon\" alt=\"\" style=\"display:none;\"/><span id=\"barCap\" class=\"bar-cap\"></span><div id=\"slotCenter\" class=\"bar-slot bar-grow\" style=\"justify-content:center;\"></div><div id=\"slotRight\" class=\"bar-slot\"></div></div>", "style": ":host{display:block;width:100%;height:100%;}\n.bar{display:flex;align-items:center;gap:12px;height:100%;padding:0 16px 0 60px;color:var(--bar-fg,#fff);box-sizing:border-box;background:var(--bar-bg,linear-gradient(90deg,#1e3c72 0%,#2a5298 100%));box-shadow:0 2px 12px rgba(0,0,0,.25);-webkit-backdrop-filter:var(--ui-backdrop,none);backdrop-filter:var(--ui-backdrop,none);}\n.bar-icon{height:30px;width:auto;filter:drop-shadow(0 1px 2px rgba(0,0,0,.3));}\n.bar-cap{font-size:17px;font-weight:600;letter-spacing:.3px;}\n.bar-slot{display:flex;align-items:center;gap:8px;}\n.bar-grow{flex:1;min-width:0;}\n:host([data-theme=\"dark\"]){--bar-bg:linear-gradient(90deg,#0a0e27 0%,#1a1f3a 100%);}\n:host([data-theme=\"light\"]){--bar-bg:linear-gradient(90deg,#ffffff 0%,#eceff1 100%);}\n:host([data-theme=\"purple\"]){--bar-bg:linear-gradient(90deg,#4a1d6e 0%,#7b2d8e 100%);}\n:host([data-theme=\"green\"]){--bar-bg:linear-gradient(90deg,#1b5e20 0%,#2e7d32 100%);}\n:host([data-theme=\"orange\"]){--bar-bg:linear-gradient(90deg,#bf360c 0%,#e64a19 100%);}\n:host([data-theme=\"teal\"]){--bar-bg:linear-gradient(90deg,#004d40 0%,#00695c 100%);}\n:host([data-theme=\"auto\"]){--bar-bg:var(--ui-surface,#252b38);--bar-fg:var(--ui-text,#e2e6ec);}", "script": "export function init(instance){\n instance._bindingsRefresh?.();\n const resolveImg = (img) => { if(!img) return ''; if(/^(https?:|data:|blob:|\\/)/.test(img)) return img; try{ return (window.IOB&&window.IOB.imagePrefix?window.IOB.imagePrefix:'')+img; }catch(e){ return img; } };\n const cfg = (window.IOB && window.IOB.config && window.IOB.config.appBar) || {};\n const navCfg = (window.IOB && window.IOB.config && window.IOB.config.navigation) || {};\n instance.setAttribute('data-theme', navCfg.theme || 'default');\n const isDesigner = !!document.querySelector('iobroker-webui-app-shell');\n if(typeof isDesigner!=='undefined' && isDesigner){ try{ document.querySelectorAll('.nav-slot-delchip').forEach(n=>n.remove()); }catch(e){} }\n function htmlFromDef(def){\n let attrs='';\n if(def.defaultAttributes) for(const a in def.defaultAttributes){ const v=def.defaultAttributes[a]; attrs+=' '+a+'=\"'+String(typeof v==='object'?JSON.stringify(v):v).replace(/\"/g,'&quot;')+'\"'; }\n let style='';\n if(def.defaultStyles) for(const k in def.defaultStyles) style+=k+':'+def.defaultStyles[k]+';';\n if(def.defaultWidth) style+='width:'+def.defaultWidth+';';\n if(def.defaultHeight) style+='height:'+def.defaultHeight+';';\n return '<'+def.tag+attrs+(style?' style=\"'+style+'\"':'')+'>'+(def.defaultContent||'')+'</'+def.tag+'>';\n }\n function wireSlot(zone, cfgRoot, key){\n if(!zone) return;\n zone.innerHTML = (cfgRoot && cfgRoot.slots && cfgRoot.slots[key]) || '';\n if(!isDesigner) return;\n if(!zone.innerHTML.trim()){ zone.dataset.empty='1'; zone.style.minHeight='30px'; zone.style.outline='1px dashed rgba(255,255,255,.28)'; zone.style.borderRadius='8px'; zone.style.margin='4px 8px'; }\n (function(){\n let chip=null,target=null,hideT=null;\n const saveFromDom=()=>{ if(!cfgRoot.slots) cfgRoot.slots={}; cfgRoot.slots[key]=Array.from(zone.children).map(c=>c.outerHTML).join(''); if(window.IOB&&window.IOB.saveConfig) window.IOB.saveConfig(); };\n const hideChip=()=>{ if(chip) chip.style.display='none'; target=null; };\n const ensureChip=()=>{ if(chip) return chip; chip=document.createElement('div'); chip.className='nav-slot-delchip'; chip.textContent='✕'; chip.title='Delete'; chip.style.cssText='position:fixed;z-index:2147483647;width:18px;height:18px;line-height:18px;text-align:center;font:700 12px/18px sans-serif;background:#e74c3c;color:#fff;border-radius:50%;cursor:pointer;box-shadow:0 1px 5px rgba(0,0,0,.45);user-select:none;display:none;'; chip.addEventListener('mouseenter',()=>{ if(hideT){clearTimeout(hideT);hideT=null;} }); chip.addEventListener('mouseleave',hideChip); chip.addEventListener('click',e=>{ e.preventDefault(); e.stopPropagation(); if(target&&target.parentNode===zone){ target.remove(); saveFromDom(); } hideChip(); }); document.body.appendChild(chip); return chip; };\n const showChip=(el)=>{ if(hideT){clearTimeout(hideT);hideT=null;} target=el; const c=ensureChip(); const r=el.getBoundingClientRect(); c.style.left=(r.right-9)+'px'; c.style.top=(r.top-9)+'px'; c.style.display='block'; };\n zone.addEventListener('mouseover', e=>{ let el=e.target; while(el && el!==zone && el.parentNode!==zone) el=el.parentNode; if(el && el.parentNode===zone && el.nodeType===1) showChip(el); });\n zone.addEventListener('mouseleave', ()=>{ hideT=setTimeout(()=>{ if(chip && chip.style.display!=='none' && !chip.matches(':hover')) hideChip(); }, 160); });\n })();\n zone.addEventListener('dragover', e=>{ if(e.dataTransfer && e.dataTransfer.types && e.dataTransfer.types.indexOf('text/json/elementdefintion')>=0){ e.preventDefault(); zone.style.background='rgba(255,255,255,.18)'; } });\n zone.addEventListener('dragleave', ()=>{ zone.style.background=''; });\n zone.addEventListener('drop', e=>{ e.preventDefault(); e.stopPropagation(); zone.style.background='';\n try{ const td=e.dataTransfer.getData('text/json/elementdefintion'); if(!td) return; const def=JSON.parse(td); const html=htmlFromDef(def);\n if(!cfgRoot.slots) cfgRoot.slots={}; cfgRoot.slots[key]=(cfgRoot.slots[key]||'')+html;\n if(window.IOB && window.IOB.saveConfig) window.IOB.saveConfig();\n }catch(err){ console.warn('[nav] slot drop failed', err); }\n });\n }\n const upd = () => {\n const bar=instance._getDomElement('bar'), ic=instance._getDomElement('barIcon'), cap=instance._getDomElement('barCap');\n const bg = instance.bgColor || cfg.bgColor;\n if(bg) instance.style.setProperty('--bar-bg', bg);\n const icon = instance.icon || cfg.icon;\n if(ic){ if(icon){ ic.src=resolveImg(icon); ic.style.display=''; } else ic.style.display='none'; }\n if(cap) cap.textContent = instance.caption || cfg.caption || '';\n };\n instance._assignEvent('caption-changed', upd);\n instance._assignEvent('icon-changed', upd);\n instance._assignEvent('bg-color-changed', upd);\n upd();\n wireSlot(instance._getDomElement('slotLeft'), cfg, 'left');\n wireSlot(instance._getDomElement('slotCenter'), cfg, 'center');\n wireSlot(instance._getDomElement('slotRight'), cfg, 'right');\n}"}
1
+ {"properties": {"caption": {"type": "string", "default": ""}, "icon": {"type": "string", "default": ""}, "bgColor": {"type": "color", "default": ""}}, "settings": {"width": "100%", "height": "48px"}, "html": "<div id=\"bar\" class=\"bar\"><div id=\"slotLeft\" class=\"bar-slot\"></div><img id=\"barIcon\" class=\"bar-icon\" alt=\"\" style=\"display:none;\"/><span id=\"barCap\" class=\"bar-cap\"></span><div id=\"slotCenter\" class=\"bar-slot bar-grow\" style=\"justify-content:center;\"></div><div id=\"slotRight\" class=\"bar-slot\"></div></div>", "style": ":host{display:block;width:100%;height:100%;}\n.bar{display:flex;align-items:center;gap:12px;height:100%;padding:var(--bar-pad,0 16px 0 60px);color:var(--bar-fg,#fff);box-sizing:border-box;background:var(--bar-bg,linear-gradient(90deg,#1e3c72 0%,#2a5298 100%));box-shadow:0 2px 12px rgba(0,0,0,.25);-webkit-backdrop-filter:var(--ui-backdrop,none);backdrop-filter:var(--ui-backdrop,none);}\n.bar-icon{height:30px;width:auto;filter:drop-shadow(0 1px 2px rgba(0,0,0,.3));}\n.bar-cap{font-size:17px;font-weight:600;letter-spacing:.3px;}\n.bar-slot{display:flex;align-items:center;gap:8px;}\n.bar-grow{flex:1;min-width:0;}\n:host([data-theme=\"dark\"]){--bar-bg:linear-gradient(90deg,#0a0e27 0%,#1a1f3a 100%);}\n:host([data-theme=\"light\"]){--bar-bg:linear-gradient(90deg,#ffffff 0%,#eceff1 100%);}\n:host([data-theme=\"purple\"]){--bar-bg:linear-gradient(90deg,#4a1d6e 0%,#7b2d8e 100%);}\n:host([data-theme=\"green\"]){--bar-bg:linear-gradient(90deg,#1b5e20 0%,#2e7d32 100%);}\n:host([data-theme=\"orange\"]){--bar-bg:linear-gradient(90deg,#bf360c 0%,#e64a19 100%);}\n:host([data-theme=\"teal\"]){--bar-bg:linear-gradient(90deg,#004d40 0%,#00695c 100%);}\n:host([data-theme=\"auto\"]){--bar-bg:var(--ui-surface,#252b38);--bar-fg:var(--ui-text,#e2e6ec);}", "script": "export function init(instance){\n instance._bindingsRefresh?.();\n const resolveImg = (img) => { if(!img) return ''; if(/^(https?:|data:|blob:|\\/)/.test(img)) return img; try{ return (window.IOB&&window.IOB.imagePrefix?window.IOB.imagePrefix:'')+img; }catch(e){ return img; } };\n const cfg = (window.IOB && window.IOB.config && window.IOB.config.appBar) || {};\n const navCfg = (window.IOB && window.IOB.config && window.IOB.config.navigation) || {};\n instance.setAttribute('data-theme', navCfg.theme || 'default');\n const isDesigner = !!document.querySelector('iobroker-webui-app-shell');\n if(typeof isDesigner!=='undefined' && isDesigner){ try{ document.querySelectorAll('.nav-slot-delchip').forEach(n=>n.remove()); }catch(e){} }\n function htmlFromDef(def){\n let attrs='';\n if(def.defaultAttributes) for(const a in def.defaultAttributes){ const v=def.defaultAttributes[a]; attrs+=' '+a+'=\"'+String(typeof v==='object'?JSON.stringify(v):v).replace(/\"/g,'&quot;')+'\"'; }\n let style='';\n if(def.defaultStyles) for(const k in def.defaultStyles) style+=k+':'+def.defaultStyles[k]+';';\n if(def.defaultWidth) style+='width:'+def.defaultWidth+';';\n if(def.defaultHeight) style+='height:'+def.defaultHeight+';';\n return '<'+def.tag+attrs+(style?' style=\"'+style+'\"':'')+'>'+(def.defaultContent||'')+'</'+def.tag+'>';\n }\n function wireSlot(zone, cfgRoot, key){\n if(!zone) return;\n zone.innerHTML = (cfgRoot && cfgRoot.slots && cfgRoot.slots[key]) || '';\n if(!isDesigner) return;\n if(!zone.innerHTML.trim()){ zone.dataset.empty='1'; zone.style.minHeight='30px'; zone.style.outline='1px dashed rgba(255,255,255,.28)'; zone.style.borderRadius='8px'; zone.style.margin='4px 8px'; }\n (function(){\n let chip=null,target=null,hideT=null;\n const saveFromDom=()=>{ if(!cfgRoot.slots) cfgRoot.slots={}; cfgRoot.slots[key]=Array.from(zone.children).map(c=>c.outerHTML).join(''); if(window.IOB&&window.IOB.saveConfig) window.IOB.saveConfig(); };\n const hideChip=()=>{ if(chip) chip.style.display='none'; target=null; };\n const ensureChip=()=>{ if(chip) return chip; chip=document.createElement('div'); chip.className='nav-slot-delchip'; chip.textContent='✕'; chip.title='Delete'; chip.style.cssText='position:fixed;z-index:2147483647;width:18px;height:18px;line-height:18px;text-align:center;font:700 12px/18px sans-serif;background:#e74c3c;color:#fff;border-radius:50%;cursor:pointer;box-shadow:0 1px 5px rgba(0,0,0,.45);user-select:none;display:none;'; chip.addEventListener('mouseenter',()=>{ if(hideT){clearTimeout(hideT);hideT=null;} }); chip.addEventListener('mouseleave',hideChip); chip.addEventListener('click',e=>{ e.preventDefault(); e.stopPropagation(); if(target&&target.parentNode===zone){ target.remove(); saveFromDom(); } hideChip(); }); document.body.appendChild(chip); return chip; };\n const showChip=(el)=>{ if(hideT){clearTimeout(hideT);hideT=null;} target=el; const c=ensureChip(); const r=el.getBoundingClientRect(); c.style.left=(r.right-9)+'px'; c.style.top=(r.top-9)+'px'; c.style.display='block'; };\n zone.addEventListener('mouseover', e=>{ let el=e.target; while(el && el!==zone && el.parentNode!==zone) el=el.parentNode; if(el && el.parentNode===zone && el.nodeType===1) showChip(el); });\n zone.addEventListener('mouseleave', ()=>{ hideT=setTimeout(()=>{ if(chip && chip.style.display!=='none' && !chip.matches(':hover')) hideChip(); }, 160); });\n })();\n zone.addEventListener('dragover', e=>{ if(e.dataTransfer && e.dataTransfer.types && e.dataTransfer.types.indexOf('text/json/elementdefintion')>=0){ e.preventDefault(); zone.style.background='rgba(255,255,255,.18)'; } });\n zone.addEventListener('dragleave', ()=>{ zone.style.background=''; });\n zone.addEventListener('drop', e=>{ e.preventDefault(); e.stopPropagation(); zone.style.background='';\n try{ const td=e.dataTransfer.getData('text/json/elementdefintion'); if(!td) return; const def=JSON.parse(td); const html=htmlFromDef(def);\n if(!cfgRoot.slots) cfgRoot.slots={}; cfgRoot.slots[key]=(cfgRoot.slots[key]||'')+html;\n if(window.IOB && window.IOB.saveConfig) window.IOB.saveConfig();\n }catch(err){ console.warn('[nav] slot drop failed', err); }\n });\n }\n const upd = () => {\n const bar=instance._getDomElement('bar'), ic=instance._getDomElement('barIcon'), cap=instance._getDomElement('barCap');\n const bg = instance.bgColor || cfg.bgColor;\n if(bg) instance.style.setProperty('--bar-bg', bg);\n const icon = instance.icon || cfg.icon;\n if(ic){ if(icon){ ic.src=resolveImg(icon); ic.style.display=''; } else ic.style.display='none'; }\n if(cap) cap.textContent = instance.caption || cfg.caption || '';\n };\n instance._assignEvent('caption-changed', upd);\n instance._assignEvent('icon-changed', upd);\n instance._assignEvent('bg-color-changed', upd);\n upd();\n wireSlot(instance._getDomElement('slotLeft'), cfg, 'left');\n wireSlot(instance._getDomElement('slotCenter'), cfg, 'center');\n wireSlot(instance._getDomElement('slotRight'), cfg, 'right');\n}"}
@@ -1 +1 @@
1
- {"properties": {"screenName": {"type": "string", "default": ""}, "title": {"type": "string", "default": "Item"}, "image": {"type": "string", "default": ""}, "selected": {"type": "boolean", "default": "false"}, "compact": {"type": "boolean", "default": "false"}}, "settings": {"width": "220px", "height": "48px"}, "html": "<div id=\"mi\" class=\"mi\"><span id=\"miIcon\" class=\"mi-icon\">▸</span><img id=\"miImg\" class=\"mi-img\" alt=\"\" style=\"display:none;\"/><span id=\"miLabel\" class=\"mi-label\"></span></div>", "style": ":host{display:block;color:inherit;}\n.mi{display:flex;align-items:center;gap:13px;margin:4px 10px;padding:10px 13px;cursor:pointer;white-space:nowrap;overflow:hidden;border-radius:10px;border:1px solid transparent;background:var(--mi-bg,rgba(255,255,255,.06));color:var(--mi-fg,rgba(255,255,255,.9));transition:background .18s,border-color .18s,transform .12s,box-shadow .18s;box-sizing:border-box;}\n.mi:hover{background:var(--mi-hover,rgba(255,255,255,.14));border-color:rgba(255,255,255,.18);transform:translateX(2px);}\n.mi.selected{background:var(--mi-active,linear-gradient(135deg,rgba(33,150,243,.38),rgba(33,150,243,.16)));border-color:var(--mi-active-border,rgba(33,150,243,.55));box-shadow:0 2px 10px rgba(33,150,243,.22);}\n.mi.selected .mi-label{color:var(--mi-active-fg,#fff);}\n.mi.selected .mi-icon{background:var(--mi-active-fg,rgba(255,255,255,.9));color:var(--mi-active-icon,#1976d2);}\n.mi-img{width:24px;height:24px;object-fit:contain;flex-shrink:0;filter:drop-shadow(0 1px 2px rgba(0,0,0,.35));}\n.mi-icon{width:26px;height:26px;flex-shrink:0;display:inline-flex;align-items:center;justify-content:center;border-radius:8px;background:rgba(255,255,255,.13);font-size:13px;line-height:1;}\n.mi-label{overflow:hidden;text-overflow:ellipsis;font-size:14px;font-weight:500;letter-spacing:.2px;}\n.mi.compact{margin:5px auto;padding:9px 0;justify-content:center;gap:0;width:44px;}\n.mi.compact .mi-label{display:none;}", "script": "export function init(instance){\n instance._bindingsRefresh?.();\n const resolveImg = (img) => { if(!img) return ''; if(/^(https?:|data:|blob:|\\/)/.test(img)) return img; try{ return (window.IOB&&window.IOB.imagePrefix?window.IOB.imagePrefix:'')+img; }catch(e){ return img; } };\n const upd = () => {\n const img=instance._getDomElement('miImg'), ic=instance._getDomElement('miIcon'), lbl=instance._getDomElement('miLabel'), mi=instance._getDomElement('mi');\n const hasImg = !!instance.image;\n if(img){ if(hasImg){ img.src=resolveImg(instance.image); img.style.display=''; } else img.style.display='none'; }\n if(ic){ ic.style.display = hasImg ? 'none' : ''; ic.textContent = (instance.title||instance.screenName||'?').trim().charAt(0).toUpperCase() || '▸'; }\n if(lbl) lbl.textContent = instance.title || instance.screenName || '';\n if(mi){ mi.classList.toggle('selected', instance.selected===true||instance.selected==='true'); mi.classList.toggle('compact', instance.compact===true||instance.compact==='true'); }\n };\n instance._assignEvent('title-changed', upd);\n instance._assignEvent('image-changed', upd);\n instance._assignEvent('screen-name-changed', upd);\n instance._assignEvent('selected-changed', upd);\n instance._assignEvent('compact-changed', upd);\n const mi=instance._getDomElement('mi');\n if(mi) mi.addEventListener('click', () => {\n if(instance.screenName && window.IOB && window.IOB.changeView) window.IOB.changeView.emit(instance.screenName);\n instance.dispatchEvent(new CustomEvent('menuitemclick',{bubbles:true,composed:true,detail:{screenName:instance.screenName}}));\n });\n upd();\n}"}
1
+ {"properties":{"screenName":{"type":"string","default":""},"title":{"type":"string","default":"Item"},"image":{"type":"string","default":""},"selected":{"type":"boolean","default":"false"},"compact":{"type":"boolean","default":"false"},"kind":{"type":"enum","values":["item","group"],"default":"item"},"expanded":{"type":"boolean","default":"false"},"level":{"type":"number","default":0},"tint":{"type":"boolean","default":"true"},"badge":{"type":"string","default":""}},"settings":{"width":"220px","height":"48px"},"html":"<div id=\"mi\" class=\"mi\"><span id=\"miChev\" class=\"mi-chev\">▸</span><span id=\"miIcon\" class=\"mi-icon\">▸</span><span id=\"miMask\" class=\"mi-mask\" style=\"display:none;\"></span><img id=\"miImg\" class=\"mi-img\" alt=\"\" style=\"display:none;\"/><span id=\"miLabel\" class=\"mi-label\"></span><span id=\"miBadge\" class=\"mi-badge\" style=\"display:none;\"></span></div>","style":":host{display:block;color:inherit;}\r\n.mi{display:flex;align-items:center;gap:13px;margin:4px 10px;padding:10px 13px;cursor:pointer;white-space:nowrap;overflow:hidden;border-radius:10px;border:1px solid transparent;background:var(--mi-bg,rgba(255,255,255,.06));color:var(--mi-fg,rgba(255,255,255,.9));transition:background .18s,border-color .18s,transform .12s,box-shadow .18s;box-sizing:border-box;}\r\n.mi:hover{background:var(--mi-hover,rgba(255,255,255,.14));border-color:rgba(255,255,255,.18);transform:translateX(2px);}\r\n.mi.selected{background:var(--mi-active,linear-gradient(135deg,rgba(33,150,243,.38),rgba(33,150,243,.16)));border-color:var(--mi-active-border,rgba(33,150,243,.55));box-shadow:0 2px 10px rgba(33,150,243,.22);}\r\n.mi.selected .mi-label{color:var(--mi-active-fg,#fff);}\r\n.mi.selected .mi-icon{background:var(--mi-active-fg,rgba(255,255,255,.9));color:var(--mi-active-icon,#1976d2);}\r\n.mi-img{width:24px;height:24px;object-fit:contain;flex-shrink:0;filter:drop-shadow(0 1px 2px rgba(0,0,0,.35));}\r\n.mi-icon{width:26px;height:26px;flex-shrink:0;display:inline-flex;align-items:center;justify-content:center;border-radius:8px;background:rgba(255,255,255,.13);font-size:13px;line-height:1;}\r\n.mi-label{overflow:hidden;text-overflow:ellipsis;font-size:14px;font-weight:500;letter-spacing:.2px;}\r\n.mi.compact{margin:5px auto;padding:9px 0;justify-content:center;gap:0;width:44px;}\r\n.mi.compact .mi-label{display:none;}.mi{margin-left:calc(10px + var(--mi-level,0) * 14px);}\r\n.mi.compact{margin-left:auto;}\r\n.mi-mask{width:24px;height:24px;flex-shrink:0;background:currentColor;-webkit-mask:var(--mi-mask) center/contain no-repeat;mask:var(--mi-mask) center/contain no-repeat;}\r\n.mi.selected .mi-mask{color:var(--mi-active-fg,#fff);}\r\n.mi-chev{display:none;width:10px;flex-shrink:0;margin-right:-6px;font-size:10px;opacity:.75;transition:transform .2s ease;}\r\n.mi.group .mi-chev{display:inline-block;}\r\n.mi.group.expanded .mi-chev{transform:rotate(90deg);}\r\n.mi.group .mi-label{font-weight:600;}\r\n.mi.compact .mi-chev{display:none;}\r\n@media (prefers-reduced-motion:reduce){.mi,.mi-chev{transition:none;}}\r\n\r\n.mi-badge{margin-left:auto;min-width:18px;height:18px;padding:0 5px;border-radius:9px;background:var(--ui-error,#ff4d6d);color:#fff;font:600 11px/18px sans-serif;text-align:center;flex-shrink:0;box-sizing:border-box;}\r\n.mi-badge.dot{min-width:10px;width:10px;height:10px;padding:0;}\r\n.mi.compact{position:relative;overflow:visible;}\r\n.mi.compact .mi-badge{position:absolute;top:-3px;right:-3px;margin:0;}\r\n:host([data-h]) .mi{margin:4px 3px;padding:8px 12px;position:relative;overflow:visible;}\r\n:host([data-h]) .mi.compact{width:40px;margin:4px 2px;padding:7px 0;}\r\n:host([data-h]) .mi.group .mi-chev{transform:rotate(90deg);}\r\n:host([data-h]) .mi .mi-badge{margin-left:4px;}\r\n:host([data-h]) .mi.compact .mi-badge{position:absolute;top:-3px;right:-3px;margin:0;}\r\n","script":"export function init(instance){\r\n instance._bindingsRefresh?.();\r\n const resolveImg = (img) => { if(!img) return ''; if(/^(https?:|data:|blob:|\\/)/.test(img)) return img; try{ return (window.IOB&&window.IOB.imagePrefix?window.IOB.imagePrefix:'')+img; }catch(e){ return img; } };\r\n // An SVG icon (icons-material-svg, icons-mfd-svg, …) is drawn as a mask in the text colour,\r\n // so it follows the menu theme; other images keep their own colours.\r\n const isSvg = (u) => /\\.svg(\\?|#|$)/i.test(u) || /^data:image\\/svg/i.test(u);\r\n const FOLDER = \"data:image/svg+xml,\" + encodeURIComponent('<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\"><path d=\"M3 6.5A1.5 1.5 0 0 1 4.5 5h4.6l2 2.2h8.4A1.5 1.5 0 0 1 21 8.7v9.8a1.5 1.5 0 0 1-1.5 1.5h-15A1.5 1.5 0 0 1 3 18.5z\"/></svg>');\r\n const FOLDER_OPEN = \"data:image/svg+xml,\" + encodeURIComponent('<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\"><path d=\"M3 6.5A1.5 1.5 0 0 1 4.5 5h4.6l2 2.2h8.4A1.5 1.5 0 0 1 21 8.7V10H7.2a1.5 1.5 0 0 0-1.4 1L3 18.6z\"/><path d=\"M6.6 11.6a1 1 0 0 1 .95-.7H22a.8.8 0 0 1 .76 1.05l-2.4 7.35a1 1 0 0 1-.95.7H3.6z\"/></svg>');\r\n const yes = (v) => v === true || v === 'true';\r\n const upd = () => {\r\n const badge=instance._getDomElement('miBadge');\r\n if(badge){\r\n // a number (99+ above 99), a dot for true, a short text; nothing for 0 / false / empty\r\n const b=instance.badge; const n=Number(b);\r\n const dot=(b===true||b==='true'), txt=dot?'':(b===''||b==null||b===false||b==='false'||(!isNaN(n)&&n<=0))?'':(!isNaN(n)?(n>99?'99+':String(n)):String(b));\r\n badge.textContent=txt; badge.classList.toggle('dot',dot); badge.style.display=(dot||txt)?'':'none';\r\n }\r\n const img=instance._getDomElement('miImg'), ic=instance._getDomElement('miIcon'), mask=instance._getDomElement('miMask'), lbl=instance._getDomElement('miLabel'), mi=instance._getDomElement('mi');\r\n const group = instance.kind === 'group';\r\n const expanded = yes(instance.expanded);\r\n let src = resolveImg(instance.image);\r\n if (!src && group) src = expanded ? FOLDER_OPEN : FOLDER;\r\n const asMask = !!src && (group && !instance.image || (instance.tint !== false && instance.tint !== 'false' && isSvg(src)));\r\n if(mask){ if(asMask){ mask.style.setProperty('--mi-mask', 'url(\"' + src.replace(/\"/g, '%22') + '\")'); mask.style.display=''; } else mask.style.display='none'; }\r\n if(img){ if(src && !asMask){ img.src=src; img.style.display=''; } else img.style.display='none'; }\r\n if(ic){ ic.style.display = src ? 'none' : ''; ic.textContent = (instance.title||instance.screenName||'?').trim().charAt(0).toUpperCase() || '▸'; }\r\n if(lbl) lbl.textContent = instance.title || instance.screenName || '';\r\n if(mi){\r\n mi.classList.toggle('selected', yes(instance.selected));\r\n mi.classList.toggle('compact', yes(instance.compact));\r\n mi.classList.toggle('group', group);\r\n mi.classList.toggle('expanded', expanded);\r\n mi.style.setProperty('--mi-level', String(Number(instance.level) || 0));\r\n mi.title = yes(instance.compact) ? (instance.title || instance.screenName || '') : '';\r\n }\r\n };\r\n instance._assignEvent('title-changed', upd);\r\n instance._assignEvent('image-changed', upd);\r\n instance._assignEvent('screen-name-changed', upd);\r\n instance._assignEvent('selected-changed', upd);\r\n instance._assignEvent('compact-changed', upd);\r\n instance._assignEvent('kind-changed', upd);\r\n instance._assignEvent('expanded-changed', upd);\r\n instance._assignEvent('level-changed', upd);\r\n instance._assignEvent('tint-changed', upd);\r\n instance._assignEvent('badge-changed', upd);\r\n const mi=instance._getDomElement('mi');\r\n if(mi) mi.addEventListener('click', () => {\r\n if (instance.kind === 'group') {\r\n instance.dispatchEvent(new CustomEvent('menugroupclick',{bubbles:true,composed:true,detail:{title:instance.title}}));\r\n return;\r\n }\r\n // on its own the item opens its screen; inside the sidebar (data-managed) the sidebar does,\r\n // it knows whether the entry is a screen or a report\r\n if(instance.dataset.managed !== '1' && instance.screenName && window.IOB && window.IOB.changeView) window.IOB.changeView.emit(instance.screenName);\r\n instance.dispatchEvent(new CustomEvent('menuitemclick',{bubbles:true,composed:true,detail:{screenName:instance.screenName}}));\r\n });\r\n upd();\r\n}\r\n"}
@@ -1 +1 @@
1
- {"properties":{},"settings":{"width":"220px","height":"100%"},"html":"<div id=\"sb\" class=\"sb\"><div id=\"sbHdr\" class=\"sb-hdr\"></div><div id=\"slotTop\" class=\"sb-slot\"></div><div id=\"sbItems\" class=\"sb-items\"></div><div id=\"slotBottom\" class=\"sb-slot\"></div></div>","style":":host{display:block;width:100%;height:100%;box-sizing:border-box;}\n.sb{display:flex;flex-direction:column;height:100%;width:100%;background:var(--sb-bg,linear-gradient(180deg,#1e3c72 0%,#2a5298 100%));color:var(--sb-fg,rgba(255,255,255,.9));box-shadow:2px 0 18px rgba(0,0,0,.28);-webkit-backdrop-filter:var(--ui-backdrop,none);backdrop-filter:var(--ui-backdrop,none);box-sizing:border-box;}\n.sb-hdr{padding:18px 16px 14px;font-weight:700;font-size:16px;letter-spacing:.5px;color:var(--sb-hdr-fg,#fff);border-bottom:1px solid rgba(255,255,255,.12);white-space:nowrap;overflow:hidden;}\n.sb-items{flex:1;overflow-y:auto;overflow-x:hidden;padding:8px 0;}\n.sb-items::-webkit-scrollbar{width:6px;}\n.sb-items::-webkit-scrollbar-thumb{background:rgba(255,255,255,.3);border-radius:3px;}\n.sb-slot{display:block;}\n:host([data-theme=\"dark\"]){--sb-bg:linear-gradient(180deg,#0a0e27 0%,#1a1f3a 100%);--sb-fg:rgba(255,255,255,.88);--sb-hdr-fg:#fff;--mi-bg:rgba(255,255,255,.04);--mi-hover:rgba(255,255,255,.1);--mi-active:linear-gradient(135deg,rgba(74,158,255,.35),rgba(74,158,255,.14));--mi-active-fg:#fff;--mi-active-icon:#4a9eff;}\n:host([data-theme=\"light\"]){--sb-bg:linear-gradient(180deg,#f5f7fa 0%,#e4e8f0 100%);--sb-fg:#37474f;--sb-hdr-fg:#1a237e;--mi-bg:rgba(0,0,0,.04);--mi-hover:rgba(33,150,243,.1);--mi-active:linear-gradient(135deg,rgba(33,150,243,.22),rgba(33,150,243,.1));--mi-active-fg:#1565c0;--mi-active-icon:#1976d2;}\n:host([data-theme=\"purple\"]){--sb-bg:linear-gradient(180deg,#4a1d6e 0%,#7b2d8e 100%);--sb-fg:rgba(255,255,255,.9);--sb-hdr-fg:#fff;--mi-bg:rgba(255,255,255,.07);--mi-hover:rgba(255,255,255,.16);--mi-active:linear-gradient(135deg,rgba(224,64,251,.35),rgba(224,64,251,.15));--mi-active-fg:#fff;--mi-active-icon:#8e24aa;}\n:host([data-theme=\"green\"]){--sb-bg:linear-gradient(180deg,#1b5e20 0%,#2e7d32 100%);--sb-fg:rgba(255,255,255,.9);--sb-hdr-fg:#fff;--mi-bg:rgba(255,255,255,.07);--mi-hover:rgba(255,255,255,.16);--mi-active:linear-gradient(135deg,rgba(105,240,174,.3),rgba(105,240,174,.12));--mi-active-fg:#fff;--mi-active-icon:#2e7d32;}\n:host([data-theme=\"orange\"]){--sb-bg:linear-gradient(180deg,#bf360c 0%,#e64a19 100%);--sb-fg:rgba(255,255,255,.92);--sb-hdr-fg:#fff;--mi-bg:rgba(255,255,255,.08);--mi-hover:rgba(255,255,255,.18);--mi-active:linear-gradient(135deg,rgba(255,171,64,.32),rgba(255,171,64,.14));--mi-active-fg:#fff;--mi-active-icon:#e64a19;}\n:host([data-theme=\"teal\"]){--sb-bg:linear-gradient(180deg,#004d40 0%,#00695c 100%);--sb-fg:rgba(255,255,255,.9);--sb-hdr-fg:#fff;--mi-bg:rgba(255,255,255,.07);--mi-hover:rgba(255,255,255,.16);--mi-active:linear-gradient(135deg,rgba(29,233,182,.3),rgba(29,233,182,.12));--mi-active-fg:#fff;--mi-active-icon:#00897b;}\n:host([data-theme=\"auto\"]){--sb-bg:var(--ui-surface,#252b38);--sb-fg:var(--ui-text,#e2e6ec);--sb-hdr-fg:var(--ui-text,#fff);--mi-bg:color-mix(in srgb,var(--ui-text,#fff) 6%,transparent);--mi-fg:var(--ui-text,#e2e6ec);--mi-hover:color-mix(in srgb,var(--ui-text,#fff) 13%,transparent);--mi-active:color-mix(in srgb,var(--accent,#2680eb) 26%,transparent);--mi-active-border:color-mix(in srgb,var(--accent,#2680eb) 55%,transparent);--mi-active-fg:var(--ui-text,#fff);--mi-active-icon:var(--accent,#2680eb);}","script":"export function init(instance){\n instance._bindingsRefresh?.();\n const IOB = window.IOB;\n const cfg = (IOB && IOB.config && IOB.config.navigation) || {};\n instance.setAttribute('data-theme', cfg.theme || 'default');\n function htmlFromDef(def){\n let attrs='';\n if(def.defaultAttributes) for(const a in def.defaultAttributes){ const v=def.defaultAttributes[a]; attrs+=' '+a+'=\"'+String(typeof v==='object'?JSON.stringify(v):v).replace(/\"/g,'&quot;')+'\"'; }\n let style='';\n if(def.defaultStyles) for(const k in def.defaultStyles) style+=k+':'+def.defaultStyles[k]+';';\n if(def.defaultWidth) style+='width:'+def.defaultWidth+';';\n if(def.defaultHeight) style+='height:'+def.defaultHeight+';';\n return '<'+def.tag+attrs+(style?' style=\"'+style+'\"':'')+'>'+(def.defaultContent||'')+'</'+def.tag+'>';\n }\n function wireSlot(zone, cfgRoot, key){\n if(!zone) return;\n zone.innerHTML = (cfgRoot && cfgRoot.slots && cfgRoot.slots[key]) || '';\n if(!isDesigner) return;\n if(!zone.innerHTML.trim()){ zone.dataset.empty='1'; zone.style.minHeight='30px'; zone.style.outline='1px dashed rgba(255,255,255,.28)'; zone.style.borderRadius='8px'; zone.style.margin='4px 8px'; }\n (function(){\n let chip=null,target=null,hideT=null;\n const saveFromDom=()=>{ if(!cfgRoot.slots) cfgRoot.slots={}; cfgRoot.slots[key]=Array.from(zone.children).map(c=>c.outerHTML).join(''); if(window.IOB&&window.IOB.saveConfig) window.IOB.saveConfig(); };\n const hideChip=()=>{ if(chip) chip.style.display='none'; target=null; };\n const ensureChip=()=>{ if(chip) return chip; chip=document.createElement('div'); chip.className='nav-slot-delchip'; chip.textContent='✕'; chip.title='Delete'; chip.style.cssText='position:fixed;z-index:2147483647;width:18px;height:18px;line-height:18px;text-align:center;font:700 12px/18px sans-serif;background:#e74c3c;color:#fff;border-radius:50%;cursor:pointer;box-shadow:0 1px 5px rgba(0,0,0,.45);user-select:none;display:none;'; chip.addEventListener('mouseenter',()=>{ if(hideT){clearTimeout(hideT);hideT=null;} }); chip.addEventListener('mouseleave',hideChip); chip.addEventListener('click',e=>{ e.preventDefault(); e.stopPropagation(); if(target&&target.parentNode===zone){ target.remove(); saveFromDom(); } hideChip(); }); document.body.appendChild(chip); return chip; };\n const showChip=(el)=>{ if(hideT){clearTimeout(hideT);hideT=null;} target=el; const c=ensureChip(); const r=el.getBoundingClientRect(); c.style.left=(r.right-9)+'px'; c.style.top=(r.top-9)+'px'; c.style.display='block'; };\n zone.addEventListener('mouseover', e=>{ let el=e.target; while(el && el!==zone && el.parentNode!==zone) el=el.parentNode; if(el && el.parentNode===zone && el.nodeType===1) showChip(el); });\n zone.addEventListener('mouseleave', ()=>{ hideT=setTimeout(()=>{ if(chip && chip.style.display!=='none' && !chip.matches(':hover')) hideChip(); }, 160); });\n })();\n zone.addEventListener('dragover', e=>{ if(e.dataTransfer && e.dataTransfer.types && e.dataTransfer.types.indexOf('text/json/elementdefintion')>=0){ e.preventDefault(); zone.style.background='rgba(255,255,255,.18)'; } });\n zone.addEventListener('dragleave', ()=>{ zone.style.background=''; });\n zone.addEventListener('drop', e=>{ e.preventDefault(); e.stopPropagation(); zone.style.background='';\n try{ const td=e.dataTransfer.getData('text/json/elementdefintion'); if(!td) return; const def=JSON.parse(td); const html=htmlFromDef(def);\n if(!cfgRoot.slots) cfgRoot.slots={}; cfgRoot.slots[key]=(cfgRoot.slots[key]||'')+html;\n if(window.IOB && window.IOB.saveConfig) window.IOB.saveConfig();\n }catch(err){ console.warn('[nav] slot drop failed', err); }\n });\n }\n const isDesigner = !!document.querySelector('iobroker-webui-app-shell');\n if(typeof isDesigner!=='undefined' && isDesigner){ try{ document.querySelectorAll('.nav-slot-delchip').forEach(n=>n.remove()); }catch(e){} }\n const hdr=instance._getDomElement('sbHdr'), items=instance._getDomElement('sbItems');\n if(cfg.bgColor) instance.style.setProperty('--sb-bg', cfg.bgColor);\n if(cfg.textColor){ instance.style.setProperty('--sb-fg', cfg.textColor); instance.style.setProperty('--mi-fg', cfg.textColor); }\n if(cfg.menuHeaderTextColor) instance.style.setProperty('--sb-hdr-fg', cfg.menuHeaderTextColor);\n if(cfg.bgColorSelected){ instance.style.setProperty('--mi-active', cfg.bgColorSelected); instance.style.setProperty('--mi-active-icon', cfg.bgColorSelected); }\n if(cfg.textColorSelected) instance.style.setProperty('--mi-active-fg', cfg.textColorSelected);\n if(hdr){ hdr.textContent=cfg.menuHeaderText||''; hdr.style.display=cfg.menuHeaderText?'':'none'; }\n let state='expanded'; const ICONS_W=64; let burger=null; let hasItems=false;\n const navWidth = cfg.menuWidth ? (/^\\d+$/.test(String(cfg.menuWidth))?cfg.menuWidth+'px':String(cfg.menuWidth)) : '220px';\n const setActive=(name)=>{ if(items) items.querySelectorAll('webui-navigation-menu-item').forEach(mi=>{ mi.selected=(mi.screenName===name); }); };\n const navTo=(name,kind)=>{ if(IOB&&IOB.changeView) IOB.changeView.emit(kind==='report'?{name:name,type:'report'}:name); setActive(name); if(cfg.hideAfterSelection) setState('hidden'); };\n async function loadEntries(){\n if(!IOB||!IOB.getAllNames) return [];\n // Reports carry every screen setting, navigation among them, so they belong in the\n // menu exactly as screens do — the entry remembers which kind it is so the runtime opens\n // a .report in the report viewer rather than looking for a screen of that name.\n // Screens and reports are listed at the same time and every file is read at the same\n // time — each listing is a round trip to the server, so waiting for one before the next\n // is what made the menu slow.\n const kinds=await Promise.all(['screen','report'].map(async k=>[k,await IOB.getAllNames(k).catch(()=>[])]));\n const all=await Promise.all(kinds.flatMap(([kind,names])=>names.map(async n=>{ const o=await IOB.getWebuiObject(kind,n).catch(()=>null); const nv=o&&o.settings&&o.settings.navigation; return nv&&nv.show?{name:n,kind:kind,title:nv.title||n,order:parseFloat(nv.order)||0,image:nv.image||''}:null; })));\n const list=all.filter(Boolean);\n list.sort((a,b)=>a.order-b.order); return list;\n }\n // The menu this browser saw last is drawn at once; the fresh list replaces it when it differs.\n const menuCacheKey='webui.nav.menu.'+((IOB&&IOB.configPath)||'');\n async function render(){\n let cached=null;\n if(!isDesigner){ try{ cached=JSON.parse(localStorage.getItem(menuCacheKey)||'null'); }catch(e){} if(Array.isArray(cached)) draw(cached); }\n const list=await loadEntries();\n const fresh=JSON.stringify(list);\n if(!Array.isArray(cached)||JSON.stringify(cached)!==fresh) draw(list);\n if(!isDesigner){ try{ localStorage.setItem(menuCacheKey,fresh); }catch(e){} }\n }\n function draw(list){\n hasItems=list.length>0;\n if(items){ items.innerHTML='';\n for(const e of list){ const mi=document.createElement('webui-navigation-menu-item'); mi.screenName=e.name; mi.title=e.title; mi.image=e.image; mi.addEventListener('menuitemclick',()=>navTo(e.name,e.kind)); items.appendChild(mi); }\n }\n if(!isDesigner){ if(hasItems && !cfg.hideMenu) applyRuntimeLayout(); else { const v=document.getElementById('viewer'); if(v){ v.style.left='0'; v.style.width='100%'; } instance.style.display='none'; } }\n }\n function applyRuntimeLayout(){\n const ab=document.getElementById('__nav-appbar-host'); const top=ab?ab.offsetHeight:0;\n instance.style.position='fixed'; instance.style.left='0'; instance.style.bottom='0'; instance.style.top=top+'px';\n instance.style.zIndex='99999'; instance.style.overflow='hidden'; instance.style.transition='width .2s ease,transform .2s ease';\n if(!burger){ burger=document.createElement('div'); burger.id='__nav-burger'; burger.textContent='☰'; const inBar=top>0; burger.style.cssText='position:fixed;top:'+(inBar?0:8)+'px;left:'+(inBar?0:8)+'px;z-index:100002;width:'+(inBar?top:42)+'px;height:'+(inBar?top:42)+'px;display:flex;align-items:center;justify-content:center;font-size:22px;cursor:pointer;'+(inBar?'':'border-radius:8px;')+'background:'+(cfg.showButtonBg||(inBar?'transparent':'rgba(0,0,0,.45)'))+';color:'+(cfg.showButtonColor||'#fff')+';transition:background .15s;'; burger.onclick=cycle; document.body.appendChild(burger); }\n setState(state);\n }\n function setState(s){\n state=s; const v=document.getElementById('viewer'); const top=parseInt(instance.style.top)||0; let left='0px';\n const compact=(state==='icons');\n if(items) items.querySelectorAll('webui-navigation-menu-item').forEach(mi=>mi.compact=compact);\n if(state==='expanded'){ instance.style.width=navWidth; instance.style.transform='translateX(0)'; if(hdr)hdr.style.display=cfg.menuHeaderText?'':'none'; left=navWidth; }\n else if(state==='icons'){ instance.style.width=ICONS_W+'px'; instance.style.transform='translateX(0)'; if(hdr)hdr.style.display='none'; left=ICONS_W+'px'; }\n else { instance.style.transform='translateX(-110%)'; left='0px'; }\n if(v){ v.style.left=left; v.style.width='calc(100% - '+left+')'; v.style.top=top+'px'; v.style.height='calc(100% - '+top+'px)'; }\n }\n function cycle(){ const o=['expanded','icons','hidden']; let n=o[(o.indexOf(state)+1)%o.length]; if(n==='hidden'&&cfg.doNotHideCompletely)n='icons'; setState(n); }\n wireSlot(instance._getDomElement('slotTop'), cfg, 'top');\n wireSlot(instance._getDomElement('slotBottom'), cfg, 'bottom');\n render();\n if(IOB&&IOB.changeView) IOB.changeView.on(v=>setActive(v));\n const sv=document.getElementById('viewer'); if(sv&&sv.screenName) setActive(sv.screenName);\n}"}
1
+ {"properties":{},"settings":{"width":"220px","height":"100%"},"html":"<div id=\"sb\" class=\"sb\"><div id=\"sbHdr\" class=\"sb-hdr\"></div><div id=\"slotTop\" class=\"sb-slot\"></div><div id=\"sbItems\" class=\"sb-items\"></div><div id=\"slotBottom\" class=\"sb-slot\"></div></div>","style":":host{display:block;width:100%;height:100%;box-sizing:border-box;}\r\n.sb{display:flex;flex-direction:column;height:100%;width:100%;background:var(--sb-bg,linear-gradient(180deg,#1e3c72 0%,#2a5298 100%));color:var(--sb-fg,rgba(255,255,255,.9));box-shadow:2px 0 18px rgba(0,0,0,.28);-webkit-backdrop-filter:var(--ui-backdrop,none);backdrop-filter:var(--ui-backdrop,none);box-sizing:border-box;}\r\n.sb-hdr{padding:18px 16px 14px;font-weight:700;font-size:16px;letter-spacing:.5px;color:var(--sb-hdr-fg,#fff);border-bottom:1px solid rgba(255,255,255,.12);white-space:nowrap;overflow:hidden;}\r\n.sb-items{flex:1;overflow-y:auto;overflow-x:hidden;padding:8px 0;}\r\n.sb-items::-webkit-scrollbar{width:6px;}\r\n.sb-items::-webkit-scrollbar-thumb{background:rgba(255,255,255,.3);border-radius:3px;}\r\n.sb-slot{display:block;}\r\n:host([data-theme=\"dark\"]){--sb-bg:linear-gradient(180deg,#0a0e27 0%,#1a1f3a 100%);--sb-fg:rgba(255,255,255,.88);--sb-hdr-fg:#fff;--mi-bg:rgba(255,255,255,.04);--mi-hover:rgba(255,255,255,.1);--mi-active:linear-gradient(135deg,rgba(74,158,255,.35),rgba(74,158,255,.14));--mi-active-fg:#fff;--mi-active-icon:#4a9eff;}\r\n:host([data-theme=\"light\"]){--sb-bg:linear-gradient(180deg,#f5f7fa 0%,#e4e8f0 100%);--sb-fg:#37474f;--sb-hdr-fg:#1a237e;--mi-bg:rgba(0,0,0,.04);--mi-hover:rgba(33,150,243,.1);--mi-active:linear-gradient(135deg,rgba(33,150,243,.22),rgba(33,150,243,.1));--mi-active-fg:#1565c0;--mi-active-icon:#1976d2;}\r\n:host([data-theme=\"purple\"]){--sb-bg:linear-gradient(180deg,#4a1d6e 0%,#7b2d8e 100%);--sb-fg:rgba(255,255,255,.9);--sb-hdr-fg:#fff;--mi-bg:rgba(255,255,255,.07);--mi-hover:rgba(255,255,255,.16);--mi-active:linear-gradient(135deg,rgba(224,64,251,.35),rgba(224,64,251,.15));--mi-active-fg:#fff;--mi-active-icon:#8e24aa;}\r\n:host([data-theme=\"green\"]){--sb-bg:linear-gradient(180deg,#1b5e20 0%,#2e7d32 100%);--sb-fg:rgba(255,255,255,.9);--sb-hdr-fg:#fff;--mi-bg:rgba(255,255,255,.07);--mi-hover:rgba(255,255,255,.16);--mi-active:linear-gradient(135deg,rgba(105,240,174,.3),rgba(105,240,174,.12));--mi-active-fg:#fff;--mi-active-icon:#2e7d32;}\r\n:host([data-theme=\"orange\"]){--sb-bg:linear-gradient(180deg,#bf360c 0%,#e64a19 100%);--sb-fg:rgba(255,255,255,.92);--sb-hdr-fg:#fff;--mi-bg:rgba(255,255,255,.08);--mi-hover:rgba(255,255,255,.18);--mi-active:linear-gradient(135deg,rgba(255,171,64,.32),rgba(255,171,64,.14));--mi-active-fg:#fff;--mi-active-icon:#e64a19;}\r\n:host([data-theme=\"teal\"]){--sb-bg:linear-gradient(180deg,#004d40 0%,#00695c 100%);--sb-fg:rgba(255,255,255,.9);--sb-hdr-fg:#fff;--mi-bg:rgba(255,255,255,.07);--mi-hover:rgba(255,255,255,.16);--mi-active:linear-gradient(135deg,rgba(29,233,182,.3),rgba(29,233,182,.12));--mi-active-fg:#fff;--mi-active-icon:#00897b;}\r\n:host([data-theme=\"auto\"]){--sb-bg:var(--ui-surface,#252b38);--sb-fg:var(--ui-text,#e2e6ec);--sb-hdr-fg:var(--ui-text,#fff);--mi-bg:color-mix(in srgb,var(--ui-text,#fff) 6%,transparent);--mi-fg:var(--ui-text,#e2e6ec);--mi-hover:color-mix(in srgb,var(--ui-text,#fff) 13%,transparent);--mi-active:color-mix(in srgb,var(--accent,#2680eb) 26%,transparent);--mi-active-border:color-mix(in srgb,var(--accent,#2680eb) 55%,transparent);--mi-active-fg:var(--ui-text,#fff);--mi-active-icon:var(--accent,#2680eb);}\r\n:host([data-orient=\"h\"]) .sb{flex-direction:row;align-items:center;box-shadow:0 2px 18px rgba(0,0,0,.28);}\r\n:host([data-orient=\"h\"]) .sb-hdr{border-bottom:0;border-right:1px solid rgba(255,255,255,.12);padding:0 16px;align-self:stretch;display:flex;align-items:center;}\r\n:host([data-orient=\"h\"]) .sb-items{display:flex;flex-direction:row;align-items:center;overflow-x:auto;overflow-y:hidden;padding:0 6px;height:100%;}\r\n:host([data-orient=\"h\"]) .sb-items::-webkit-scrollbar{height:4px;width:auto;}\r\n:host([data-orient=\"h\"]) .sb-slot{display:flex;align-items:center;gap:6px;}\r\n","script":"export function init(instance){\r\n instance._bindingsRefresh?.();\r\n const IOB = window.IOB;\r\n const cfg = (IOB && IOB.config && IOB.config.navigation) || {};\r\n instance.setAttribute('data-theme', cfg.theme || 'default');\r\n function htmlFromDef(def){\r\n let attrs='';\r\n if(def.defaultAttributes) for(const a in def.defaultAttributes){ const v=def.defaultAttributes[a]; attrs+=' '+a+'=\"'+String(typeof v==='object'?JSON.stringify(v):v).replace(/\"/g,'&quot;')+'\"'; }\r\n let style='';\r\n if(def.defaultStyles) for(const k in def.defaultStyles) style+=k+':'+def.defaultStyles[k]+';';\r\n if(def.defaultWidth) style+='width:'+def.defaultWidth+';';\r\n if(def.defaultHeight) style+='height:'+def.defaultHeight+';';\r\n return '<'+def.tag+attrs+(style?' style=\"'+style+'\"':'')+'>'+(def.defaultContent||'')+'</'+def.tag+'>';\r\n }\r\n function wireSlot(zone, cfgRoot, key){\r\n if(!zone) return;\r\n zone.innerHTML = (cfgRoot && cfgRoot.slots && cfgRoot.slots[key]) || '';\r\n if(!isDesigner) return;\r\n if(!zone.innerHTML.trim()){ zone.dataset.empty='1'; zone.style.minHeight='30px'; zone.style.outline='1px dashed rgba(255,255,255,.28)'; zone.style.borderRadius='8px'; zone.style.margin='4px 8px'; }\r\n (function(){\r\n let chip=null,target=null,hideT=null;\r\n const saveFromDom=()=>{ if(!cfgRoot.slots) cfgRoot.slots={}; cfgRoot.slots[key]=Array.from(zone.children).map(c=>c.outerHTML).join(''); if(window.IOB&&window.IOB.saveConfig) window.IOB.saveConfig(); };\r\n const hideChip=()=>{ if(chip) chip.style.display='none'; target=null; };\r\n const ensureChip=()=>{ if(chip) return chip; chip=document.createElement('div'); chip.className='nav-slot-delchip'; chip.textContent='✕'; chip.title='Delete'; chip.style.cssText='position:fixed;z-index:2147483647;width:18px;height:18px;line-height:18px;text-align:center;font:700 12px/18px sans-serif;background:#e74c3c;color:#fff;border-radius:50%;cursor:pointer;box-shadow:0 1px 5px rgba(0,0,0,.45);user-select:none;display:none;'; chip.addEventListener('mouseenter',()=>{ if(hideT){clearTimeout(hideT);hideT=null;} }); chip.addEventListener('mouseleave',hideChip); chip.addEventListener('click',e=>{ e.preventDefault(); e.stopPropagation(); if(target&&target.parentNode===zone){ target.remove(); saveFromDom(); } hideChip(); }); document.body.appendChild(chip); return chip; };\r\n const showChip=(el)=>{ if(hideT){clearTimeout(hideT);hideT=null;} target=el; const c=ensureChip(); const r=el.getBoundingClientRect(); c.style.left=(r.right-9)+'px'; c.style.top=(r.top-9)+'px'; c.style.display='block'; };\r\n zone.addEventListener('mouseover', e=>{ let el=e.target; while(el && el!==zone && el.parentNode!==zone) el=el.parentNode; if(el && el.parentNode===zone && el.nodeType===1) showChip(el); });\r\n zone.addEventListener('mouseleave', ()=>{ hideT=setTimeout(()=>{ if(chip && chip.style.display!=='none' && !chip.matches(':hover')) hideChip(); }, 160); });\r\n })();\r\n zone.addEventListener('dragover', e=>{ if(e.dataTransfer && e.dataTransfer.types && e.dataTransfer.types.indexOf('text/json/elementdefintion')>=0){ e.preventDefault(); zone.style.background='rgba(255,255,255,.18)'; } });\r\n zone.addEventListener('dragleave', ()=>{ zone.style.background=''; });\r\n zone.addEventListener('drop', e=>{ e.preventDefault(); e.stopPropagation(); zone.style.background='';\r\n try{ const td=e.dataTransfer.getData('text/json/elementdefintion'); if(!td) return; const def=JSON.parse(td); const html=htmlFromDef(def);\r\n if(!cfgRoot.slots) cfgRoot.slots={}; cfgRoot.slots[key]=(cfgRoot.slots[key]||'')+html;\r\n if(window.IOB && window.IOB.saveConfig) window.IOB.saveConfig();\r\n }catch(err){ console.warn('[nav] slot drop failed', err); }\r\n });\r\n }\r\n const isDesigner = !!document.querySelector('iobroker-webui-app-shell');\r\n if(typeof isDesigner!=='undefined' && isDesigner){ try{ document.querySelectorAll('.nav-slot-delchip').forEach(n=>n.remove()); }catch(e){} }\r\n const hdr=instance._getDomElement('sbHdr'), items=instance._getDomElement('sbItems');\r\n if(cfg.bgColor) instance.style.setProperty('--sb-bg', cfg.bgColor);\r\n if(cfg.textColor){ instance.style.setProperty('--sb-fg', cfg.textColor); instance.style.setProperty('--mi-fg', cfg.textColor); }\r\n if(cfg.menuHeaderTextColor) instance.style.setProperty('--sb-hdr-fg', cfg.menuHeaderTextColor);\r\n if(cfg.bgColorSelected){ instance.style.setProperty('--mi-active', cfg.bgColorSelected); instance.style.setProperty('--mi-active-icon', cfg.bgColorSelected); }\r\n if(cfg.textColorSelected) instance.style.setProperty('--mi-active-fg', cfg.textColorSelected);\r\n if(hdr){ hdr.textContent=cfg.menuHeaderText||''; hdr.style.display=cfg.menuHeaderText?'':'none'; }\r\n // how the menu opens: full, icons only, hidden, or as this browser left it last time\r\n const stateKey='webui.nav.state.'+((IOB&&IOB.configPath)||'');\r\n const startAs=['expanded','icons','hidden','last'].includes(cfg.startState)?cfg.startState:'expanded';\r\n let state=startAs;\r\n if(startAs==='last'){ try{ state=localStorage.getItem(stateKey)||'expanded'; }catch(e){ state='expanded'; } }\r\n if(!['expanded','icons','hidden'].includes(state)) state='expanded';\r\n if(state==='hidden'&&cfg.doNotHideCompletely) state='icons';\r\n const ICONS_W=64; let burger=null; let hasItems=false;\r\n const navWidth = cfg.menuWidth ? (/^\\d+$/.test(String(cfg.menuWidth))?cfg.menuWidth+'px':String(cfg.menuWidth)) : '220px';\r\n // Groups: none (a flat list), 'folder' (the screen's folder: tests/pump → tests) or 'group'\r\n // (the screen's own Navigation → Group). A group opens and closes; a \"/\" nests it.\r\n const groupBy = cfg.groupBy || 'none';\r\n const groupsKey='webui.nav.groups.'+((IOB&&IOB.configPath)||'');\r\n let openGroups={}; try{ openGroups=JSON.parse(localStorage.getItem(groupsKey)||'{}')||{}; }catch(e){}\r\n const saveGroups=()=>{ try{ localStorage.setItem(groupsKey, JSON.stringify(openGroups)); }catch(e){} };\r\n const groupOf=(e)=> groupBy==='folder' ? (e.name.includes('/') ? e.name.slice(0, e.name.lastIndexOf('/')) : '') : groupBy==='group' ? String(e.group||'').replace(/^\\/+|\\/+$/g,'') : '';\r\n const setActive=(name)=>{\r\n if(!items) return;\r\n items.querySelectorAll('webui-navigation-menu-item').forEach(mi=>{ if(mi.kind!=='group') mi.selected=(mi.screenName===name); });\r\n // the group holding the open screen is opened\r\n const own=items.querySelector('webui-navigation-menu-item[data-entry=\"'+CSS.escape(String(name))+'\"]');\r\n for(let g=own&&own.parentElement; g&&g!==items; g=g.parentElement){ if(g.dataset.group!=null && g.hidden){ g.hidden=false; const head=g.previousElementSibling; if(head) head.expanded=true; } }\r\n };\r\n const navTo=(name,kind)=>{ if(IOB&&IOB.changeView) IOB.changeView.emit(kind==='report'?{name:name,type:'report'}:name); setActive(name); if(cfg.hideAfterSelection) setState('hidden'); };\r\n async function loadEntries(){\r\n if(!IOB||!IOB.getAllNames) return [];\r\n // Reports carry every screen setting, navigation among them, so they belong in the\r\n // menu exactly as screens do — the entry remembers which kind it is so the runtime opens\r\n // a .report in the report viewer rather than looking for a screen of that name.\r\n // Screens and reports are listed at the same time and every file is read at the same\r\n // time — each listing is a round trip to the server, so waiting for one before the next\r\n // is what made the menu slow.\r\n const kinds=await Promise.all(['screen','report'].map(async k=>[k,await IOB.getAllNames(k).catch(()=>[])]));\r\n const all=await Promise.all(kinds.flatMap(([kind,names])=>names.map(async n=>{ const o=await IOB.getWebuiObject(kind,n).catch(()=>null); const nv=o&&o.settings&&o.settings.navigation; return nv&&nv.show?{name:n,kind:kind,title:nv.title||n,order:parseFloat(nv.order)||0,image:nv.image||'',group:nv.group||'',badge:nv.badge||''}:null; })));\r\n const list=all.filter(Boolean);\r\n list.sort((a,b)=>a.order-b.order); return list;\r\n }\r\n // The menu this browser saw last is drawn at once; the fresh list replaces it when it differs.\r\n const menuCacheKey='webui.nav.menu.'+((IOB&&IOB.configPath)||'');\r\n async function render(){\r\n // items made before the menu item is defined lose what is set on them (level, image):\r\n // wait for the definition, at most 3 s\r\n if(window.customElements&&!customElements.get('webui-navigation-menu-item')) await Promise.race([customElements.whenDefined('webui-navigation-menu-item'), new Promise(r=>setTimeout(r,3000))]);\r\n let cached=null;\r\n if(!isDesigner){ try{ cached=JSON.parse(localStorage.getItem(menuCacheKey)||'null'); }catch(e){} if(Array.isArray(cached)) draw(cached); }\r\n const list=await loadEntries();\r\n const fresh=JSON.stringify(list);\r\n if(!Array.isArray(cached)||JSON.stringify(cached)!==fresh) draw(list);\r\n if(!isDesigner){ try{ localStorage.setItem(menuCacheKey,fresh); }catch(e){} }\r\n }\r\n function itemFor(e, level){\r\n const mi=document.createElement('webui-navigation-menu-item');\r\n mi.dataset.managed='1'; mi.dataset.entry=e.name;\r\n // inside its folder an entry without its own title shows its name without the folder\r\n mi.screenName=e.name; mi.title=(groupBy==='folder'&&e.title===e.name&&e.name.includes('/'))?e.name.slice(e.name.lastIndexOf('/')+1):e.title; mi.image=e.image; mi.level=level; mi.tint=cfg.tintIcons!==false; mi.compact=(state==='icons');\r\n mi.badge=badgeVals.has(e.name)?badgeVals.get(e.name):'';\r\n mi.addEventListener('menuitemclick',()=>navTo(e.name,e.kind));\r\n return mi;\r\n }\r\n // entries → a tree of groups; a group sits where its first entry (by order) sits\r\n function tree(list){\r\n const root={sub:new Map(),rows:[]};\r\n for(const e of list){\r\n let node=root, path='';\r\n for(const seg of groupOf(e).split('/').filter(Boolean)){\r\n path=path?path+'/'+seg:seg;\r\n if(!node.sub.has(seg)){ const g={title:seg,path:path,sub:new Map(),rows:[]}; node.sub.set(seg,g); node.rows.push({group:g,order:e.order}); }\r\n node=node.sub.get(seg);\r\n }\r\n node.rows.push({entry:e,order:e.order});\r\n }\r\n // a group with its own order (Settings → Navigation → Groups) moves there\r\n (function sortNode(n){ for(const r of n.rows) if(r.group){ const o=parseFloat(groupCfg(r.group.path).order); if(!isNaN(o)) r.order=o; } n.rows.sort((a,b)=>a.order-b.order); for(const g of n.sub.values()) sortNode(g); })(root);\r\n return root;\r\n }\r\n // Settings → Navigation → Groups: own title (a translation key works too), icon and order\r\n const groupCfg=(path)=>(cfg.groups&&cfg.groups[path])||{};\r\n const groupTitle=(g)=>{ const t=groupCfg(g.path).title||g.title; try{ const tr=IOB&&IOB.t?IOB.t(t):t; return tr&&tr!==t?tr:t; }catch(e){ return t; } };\r\n // Icons only: a group opens its entries in a panel beside the sidebar (hover or click).\r\n // The panel lives in the page (the sidebar clips what is inside it) and takes the menu's colours.\r\n let fly=null, flyT=0;\r\n const VARS=['--sb-bg','--sb-fg','--mi-bg','--mi-fg','--mi-hover','--mi-active','--mi-active-border','--mi-active-fg','--mi-active-icon'];\r\n function closeFlyout(){ clearTimeout(flyT); if(fly){ fly.remove(); fly=null; } }\r\n function closeSoon(){ clearTimeout(flyT); flyT=setTimeout(closeFlyout,280); }\r\n function openFlyout(head, g){\r\n clearTimeout(flyT);\r\n if(fly&&fly._g===g) return;\r\n closeFlyout();\r\n fly=document.createElement('div'); fly._g=g; fly.className='nav-flyout';\r\n const cs=getComputedStyle(instance); for(const v of VARS){ const x=cs.getPropertyValue(v); if(x&&x.trim()) fly.style.setProperty(v,x.trim()); }\r\n const r=head.getBoundingClientRect(), sb=instance.getBoundingClientRect(), top=Math.max(8,r.top-8);\r\n // beside a vertical menu (on its inner side), under a top bar, over a bottom bar\r\n const place=horiz?(pos==='top'?{left:Math.max(6,r.left)+'px',top:(sb.bottom+6)+'px',maxHeight:(innerHeight-sb.bottom-18)+'px'}:{left:Math.max(6,r.left)+'px',bottom:(innerHeight-sb.top+6)+'px',maxHeight:(sb.top-18)+'px'})\r\n :(pos==='right'?{right:(innerWidth-sb.left+6)+'px',top:top+'px',maxHeight:(innerHeight-top-12)+'px'}:{left:((state==='icons'?sb.left+ICONS_W:sb.right)+6)+'px',top:top+'px',maxHeight:(innerHeight-top-12)+'px'});\r\n Object.assign(fly.style,place,{position:'fixed',zIndex:'100001',minWidth:'210px',maxWidth:'320px',overflow:'auto',padding:'6px 0 8px',borderRadius:'12px',background:'var(--sb-bg,linear-gradient(180deg,#1e3c72 0%,#2a5298 100%))',color:'var(--sb-fg,rgba(255,255,255,.9))',boxShadow:'0 10px 30px rgba(0,0,0,.38)'});\r\n const h=document.createElement('div'); h.textContent=groupTitle(g); Object.assign(h.style,{padding:'8px 16px 6px',fontWeight:'700',fontSize:'13px',opacity:'.85',whiteSpace:'nowrap'}); fly.appendChild(h);\r\n const current=(document.getElementById('viewer')||{}).screenName;\r\n (function add(node, level){ for(const row of node.rows){\r\n if(row.entry){ const mi=itemFor(row.entry, level); mi.compact=false; mi.selected=(row.entry.name===current); mi.addEventListener('menuitemclick',closeFlyout); fly.appendChild(mi); }\r\n else { const sub=document.createElement('webui-navigation-menu-item'); sub.dataset.managed='1'; sub.kind='group'; sub.expanded=true; sub.title=groupTitle(row.group); sub.image=groupCfg(row.group.path).image||''; sub.level=level; sub.tint=cfg.tintIcons!==false; fly.appendChild(sub); add(row.group, level+1); }\r\n } })(g, 0);\r\n fly.addEventListener('mouseenter',()=>clearTimeout(flyT)); fly.addEventListener('mouseleave',closeSoon);\r\n document.body.appendChild(fly);\r\n }\r\n if(!isDesigner){\r\n // the runtime rebuilds the sidebar when the settings change: a removed sidebar lets go\r\n const onDown=e=>{ if(!instance.isConnected){ document.removeEventListener('pointerdown',onDown,true); closeFlyout(); return; } if(fly&&!e.composedPath().some(n=>n===fly||n===instance)) closeFlyout(); };\r\n const onKey=e=>{ if(!instance.isConnected){ document.removeEventListener('keydown',onKey); return; } if(e.key==='Escape') closeFlyout(); };\r\n document.addEventListener('pointerdown',onDown,true);\r\n document.addEventListener('keydown',onKey);\r\n }\r\n function drawNode(node, parent, level){\r\n for(const r of node.rows){\r\n if(r.entry){ parent.appendChild(itemFor(r.entry, level)); continue; }\r\n const g=r.group, head=document.createElement('webui-navigation-menu-item'), box=document.createElement('div');\r\n head.dataset.managed='1'; head.kind='group'; head.title=groupTitle(g); head.image=groupCfg(g.path).image||''; head.level=level; head.tint=cfg.tintIcons!==false; head.compact=(state==='icons');\r\n head.expanded=!!openGroups[g.path];\r\n box.dataset.group=g.path; box.hidden=!openGroups[g.path];\r\n head.addEventListener('menugroupclick',()=>{ if(state==='icons'||horiz){ fly&&fly._g===g?closeFlyout():openFlyout(head,g); return; } box.hidden=!box.hidden; head.expanded=!box.hidden; openGroups[g.path]=!box.hidden; saveGroups(); });\r\n head.addEventListener('mouseenter',()=>{ if(state==='icons'||horiz) openFlyout(head,g); });\r\n head.addEventListener('mouseleave',()=>{ if(state==='icons'||horiz) closeSoon(); });\r\n head._entries=(function all(n){ return n.rows.flatMap(x=>x.entry?[x.entry.name]:all(x.group)); })(g);\r\n parent.appendChild(head); parent.appendChild(box);\r\n drawNode(g, box, level+1);\r\n }\r\n }\r\n // Badge: a screen's Navigation → Badge names a state; its value shows on the entry (a number,\r\n // or a dot for true) and adds up on the group that holds it — an alarm count, for example.\r\n const badgeVals=new Map(); let badgeSubs=[];\r\n const badgeOf=(v)=>{ if(v===true||v==='true') return true; const n=Number(v); if(v!==''&&v!=null&&!isNaN(n)) return n>0?n:''; return (v==null||v===false||v==='false')?'':String(v); };\r\n function showBadge(name){\r\n const v=badgeVals.has(name)?badgeVals.get(name):'';\r\n const sel='webui-navigation-menu-item[data-entry=\"'+CSS.escape(String(name))+'\"]';\r\n for(const root of [items,fly]) if(root) root.querySelectorAll(sel).forEach(mi=>{ mi.badge=v; });\r\n // groups: the sum of their numbers, or a dot when only dots\r\n const heads=[]; for(const root of [items,fly]) if(root) root.querySelectorAll('webui-navigation-menu-item').forEach(h=>{ if(h.kind==='group'&&h._entries) heads.push(h); });\r\n for(const h of heads){ let sum=0, dot=false; for(const n of h._entries){ const x=badgeVals.get(n); if(typeof x==='number') sum+=x; else if(x) dot=true; } h.badge=sum>0?sum:(dot?true:''); }\r\n }\r\n function watchBadges(list){\r\n if(isDesigner||!IOB||!IOB.subscribeState) return;\r\n for(const [id,cb] of badgeSubs) try{ IOB.unsubscribeState(id,cb); }catch(e){}\r\n badgeSubs=[];\r\n for(const e of list){\r\n if(!e.badge) continue;\r\n const cb=(_id,st)=>{ if(!instance.isConnected){ for(const [i,c] of badgeSubs) try{ IOB.unsubscribeState(i,c); }catch(x){} badgeSubs=[]; closeFlyout(); return; } badgeVals.set(e.name,badgeOf(st?st.val:'')); showBadge(e.name); };\r\n IOB.subscribeState(e.badge,cb); badgeSubs.push([e.badge,cb]);\r\n Promise.resolve(IOB.getState(e.badge)).then(st=>cb(e.badge,st)).catch(()=>{});\r\n }\r\n }\r\n function draw(list){\r\n hasItems=list.length>0;\r\n if(items){ items.innerHTML='';\r\n if(groupBy==='none') for(const e of list) items.appendChild(itemFor(e, 0));\r\n else drawNode(tree(list), items, 0);\r\n const sv=document.getElementById('viewer'); if(sv&&sv.screenName) setActive(sv.screenName);\r\n }\r\n watchBadges(list);\r\n if(!isDesigner){ if(hasItems && !cfg.hideMenu) applyRuntimeLayout(); else { const v=document.getElementById('viewer'); if(v){ v.style.left='0'; v.style.width='100%'; } instance.style.display='none'; } }\r\n }\r\n // Position (left | right | top | bottom) and layout (push: the screen makes room; overlay: the\r\n // menu opens over the screen, which keeps its size). An older \"horizontal\" orientation\r\n // without a position means top. Top and bottom are a bar: groups open in a panel.\r\n const pos=['left','right','top','bottom'].includes(cfg.position)?cfg.position:(cfg.orientation==='horizontal'?'top':'left');\r\n const horiz=(pos==='top'||pos==='bottom');\r\n const overlay=(cfg.layout==='overlay');\r\n const BAR_H=56, BAR_ICONS_H=44;\r\n if(horiz) instance.setAttribute('data-orient','h'); else instance.removeAttribute('data-orient');\r\n let barTop=0;\r\n function applyRuntimeLayout(){\r\n const ab=document.getElementById('__nav-appbar-host'); barTop=ab?ab.offsetHeight:0;\r\n const st=instance.style; st.position='fixed'; st.zIndex='99999'; st.overflow='hidden';\r\n st.left=st.right=st.top=st.bottom='';\r\n if(pos==='left'){ st.left='0'; st.top=barTop+'px'; st.bottom='0'; }\r\n else if(pos==='right'){ st.right='0'; st.top=barTop+'px'; st.bottom='0'; }\r\n else if(pos==='top'){ st.left='0'; st.right='0'; st.top=barTop+'px'; }\r\n else { st.left='0'; st.right='0'; st.bottom='0'; }\r\n if(!burger){\r\n burger=document.createElement('div'); burger.id='__nav-burger'; burger.textContent='☰'; const inBar=barTop>0;\r\n // the corner on the menu's side: in the app bar (a right menu: its right corner), or\r\n // without an app bar over the page\r\n const corner=inBar?(pos==='right'?'top:0;right:0;':'top:0;left:0;'):(pos==='right'?'top:8px;right:8px;':pos==='bottom'?'bottom:6px;left:8px;':'top:'+(pos==='top'?7:8)+'px;left:8px;');\r\n burger.style.cssText='position:fixed;'+corner+'z-index:100002;width:'+(inBar?barTop:42)+'px;height:'+(inBar?barTop:42)+'px;display:flex;align-items:center;justify-content:center;font-size:22px;cursor:pointer;'+(inBar?'':'border-radius:8px;')+'background:'+(cfg.showButtonBg||(inBar?'transparent':'rgba(0,0,0,.45)'))+';color:'+(cfg.showButtonColor||'#fff')+';transition:background .15s;';\r\n burger.onclick=cycle; document.body.appendChild(burger);\r\n }\r\n // the app bar keeps room for the ☰ on the side it sits on\r\n const abEl=ab&&ab.querySelector('webui-navigation-app-bar');\r\n if(abEl) abEl.style.setProperty('--bar-pad', pos==='right'?'0 '+(barTop+12)+'px 0 16px':'0 16px 0 '+(barTop+12)+'px');\r\n // a bar without an app bar: the floating ☰ needs room at its start\r\n st.paddingLeft=(horiz&&!barTop)?'56px':'';\r\n setState(state);\r\n }\r\n // The sidebar and the screen move together, with the same easing: the screen's size\r\n // changes every frame, so a stretched screen scales smoothly instead of jumping. The first\r\n // placement is not animated.\r\n const EASE='.32s cubic-bezier(.22,.8,.24,1)';\r\n const still=window.matchMedia&&window.matchMedia('(prefers-reduced-motion: reduce)').matches;\r\n let placed=false;\r\n function setState(s){\r\n closeFlyout();\r\n state=s;\r\n if(startAs==='last'&&!isDesigner){ try{ localStorage.setItem(stateKey,s); }catch(e){} } const v=document.getElementById('viewer');\r\n const anim=placed&&!still; placed=true;\r\n instance.style.transition=anim?(horiz?'height ':'width ')+EASE+',transform '+EASE:'none';\r\n if(v) v.style.transition=anim?['left','top','width','height'].map(p=>p+' '+EASE).join(','):'none';\r\n const compact=(state==='icons');\r\n if(items) items.querySelectorAll('webui-navigation-menu-item').forEach(mi=>{ mi.compact=compact; mi.toggleAttribute('data-h',horiz); });\r\n // icons only, and any bar: a group's entries open in the panel, not under it\r\n if(items) items.querySelectorAll('div[data-group]').forEach(box=>{ box.style.display=(compact||horiz)?'none':''; });\r\n const hidden=(state==='hidden');\r\n let size='0px';\r\n if(state==='expanded'){ size=horiz?BAR_H+'px':navWidth; if(hdr)hdr.style.display=cfg.menuHeaderText?'':'none'; }\r\n else if(state==='icons'){ size=(horiz?BAR_ICONS_H:ICONS_W)+'px'; if(hdr)hdr.style.display='none'; }\r\n if(horiz){ instance.style.width=''; instance.style.height=size; instance.style.transform=''; }\r\n else { instance.style.height=''; if(!hidden) instance.style.width=size; instance.style.transform=hidden?(pos==='right'?'translateX(110%)':'translateX(-110%)'):'translateX(0)'; }\r\n if(v){\r\n const room=(overlay||hidden)?'0px':size;\r\n let l='0px', t=barTop+'px', w='100%', h='calc(100% - '+barTop+'px)';\r\n if(pos==='left'){ l=room; w='calc(100% - '+room+')'; }\r\n else if(pos==='right'){ w='calc(100% - '+room+')'; }\r\n else if(pos==='top'){ t='calc('+barTop+'px + '+room+')'; h='calc(100% - '+barTop+'px - '+room+')'; }\r\n else { h='calc(100% - '+barTop+'px - '+room+')'; }\r\n v.style.left=l; v.style.top=t; v.style.width=w; v.style.height=h;\r\n }\r\n }\r\n function cycle(){ const o=['expanded','icons','hidden']; let n=o[(o.indexOf(state)+1)%o.length]; if(n==='hidden'&&cfg.doNotHideCompletely)n='icons'; setState(n); }\r\n wireSlot(instance._getDomElement('slotTop'), cfg, 'top');\r\n wireSlot(instance._getDomElement('slotBottom'), cfg, 'bottom');\r\n render();\r\n if(IOB&&IOB.changeView) IOB.changeView.on(v=>setActive(v));\r\n const sv=document.getElementById('viewer'); if(sv&&sv.screenName) setActive(sv.screenName);\r\n}"}
@@ -1 +1 @@
1
- {"html":"<div id=\"reportSelectorContainer\" style=\"width:100%;height:100%;display:flex;align-items:center;gap:12px;padding:16px;border-radius:8px;\">\n <button id=\"btn1\" class=\"report-btn\" @click='{\"commands\":[{\"type\":\"SetSignalValue\",\"signal\":{\"source\":\"property\",\"name\":\"snapshot\"},\"value\":true}]}' style=\"flex:1;padding:14px 20px 14px 20px;cursor:pointer;border:2px solid initial;border-radius:8px;font-size:15px;font-weight:600;transition:all 0.3s ease;box-shadow:0 2px 4px rgba(0,0,0,0.1);\">SnapShot Report</button>\n <button id=\"btn2\" class=\"report-btn\" @click='{\"commands\":[{\"type\":\"SetSignalValue\",\"signal\":{\"source\":\"property\",\"name\":\"daily\"},\"value\":true}]}' style=\"flex:1;padding:14px 20px 14px 20px;cursor:pointer;border:2px solid initial;border-radius:8px;font-size:15px;font-weight:600;transition:all 0.3s ease;box-shadow:0 2px 4px rgba(0,0,0,0.1);\">Daily Report</button>\n <button id=\"btn3\" class=\"report-btn\" @click='{\"commands\":[{\"type\":\"SetSignalValue\",\"value\":true,\"signal\":{\"source\":\"property\",\"name\":\"hourly\"}}]}' style=\"flex:1;padding:14px 20px 14px 20px;cursor:pointer;border:2px solid initial;border-radius:8px;font-size:15px;font-weight:600;transition:all 0.3s ease;box-shadow:0 2px 4px rgba(0,0,0,0.1);\">Hourly Report</button>\n <button id=\"btn4\" class=\"report-btn\" @click='{\"commands\":[{\"type\":\"SetSignalValue\",\"signal\":{\"source\":\"property\",\"name\":\"prover\"},\"value\":true}]}' data-visibility-enabled=\"true\" data-visibility-condition=\"==\" data-visibility-action=\"disable\" data-visibility-groups=\"Administrator\" style=\"flex:1;padding:14px 20px 14px 20px;cursor:pointer;border:2px solid initial;border-radius:8px;font-size:15px;font-weight:600;transition:all 0.3s ease;box-shadow:0 2px 4px rgba(0,0,0,0.1);\">Prover Report</button>\n</div>\n","style":":host {\n display: block;\n width: 100%;\n}\n\n.report-btn:hover:not(.active) {\n transform: translateY(-2px);\n box-shadow: 0 4px 12px rgba(0,0,0,0.15);\n}\n\n.report-btn:active {\n transform: scale(0.98);\n}\n\n.report-btn.active {\n transform: translateY(-2px) scale(1.02);\n box-shadow: 0 6px 20px rgba(0,0,0,0.25);\n font-weight: 700;\n}\n","script":"/**\n * @param {BaseScreenViewerAndControl} instance\n */\nexport function init(instance) {\n const btn1 = instance._getDomElement('btn1');\n const btn2 = instance._getDomElement('btn2');\n const btn3 = instance._getDomElement('btn3');\n const btn4 = instance._getDomElement('btn4');\n const container = instance._getDomElement('reportSelectorContainer');\n \n const buttons = [btn1, btn2, btn3, btn4];\n let currentIndex = parseInt(instance.activeButton) || 1;\n let currentTheme = instance.theme || 'default';\n \n // Button mətnlərini yeniləyirik\n if (instance.button1Text) btn1.textContent = instance.button1Text;\n if (instance.button2Text) btn2.textContent = instance.button2Text;\n if (instance.button3Text) btn3.textContent = instance.button3Text;\n if (instance.button4Text) btn4.textContent = instance.button4Text;\n // Property dəyişiklikləri üçün event-lər\n instance._assignEvent('button1-text-changed', () => {\n if (btn1) btn1.textContent = instance.button1Text || 'Report 1';\n });\n \n instance._assignEvent('button2-text-changed', () => {\n if (btn2) btn2.textContent = instance.button2Text || 'Report 2';\n });\n \n instance._assignEvent('button3-text-changed', () => {\n if (btn3) btn3.textContent = instance.button3Text || 'Report 3';\n });\n \n instance._assignEvent('button4-text-changed', () => {\n if (btn4) btn4.textContent = instance.button4Text || 'Report 4';\n });\n \n instance._assignEvent('active-button-changed', () => {\n const newIndex = parseInt(instance.activeButton) || 1;\n setActiveButton(newIndex);\n //loadReport(newIndex);\n });\n \n instance._assignEvent('theme-changed', () => {\n currentTheme = instance.theme || 'default';\n applyTheming(instance, currentTheme);\n });\n\n // İlk theming və active button\n applyTheming(instance, currentTheme);\n setActiveButton(currentIndex);\n //loadReport(currentIndex);\n \n // Button click event-ləri\n buttons.forEach((btn, index) => {\n btn.addEventListener('click', () => {\n const btnIndex = index + 1;\n setActiveButton(btnIndex);\n //loadReport(btnIndex);\n });\n });\n \n function applyTheming(inst, theme) {\n const themes = {\n 'default': {\n containerBg: '#f5f5f5',\n btnBg: '#ffffff',\n btnText: '#2d3436',\n btnBorder: '#dfe6e9',\n activeBg: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',\n activeText: '#ffffff',\n activeBorder: '#667eea'\n },\n 'dark': {\n containerBg: '#2d3436',\n btnBg: '#636e72',\n btnText: '#dfe6e9',\n btnBorder: '#000000',\n activeBg: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',\n activeText: '#ffffff',\n activeBorder: '#667eea'\n },\n 'light': {\n containerBg: '#ffffff',\n btnBg: '#f8f9fa',\n btnText: '#212529',\n btnBorder: '#dee2e6',\n activeBg: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',\n activeText: '#ffffff',\n activeBorder: '#667eea'\n },\n 'blue': {\n containerBg: '#dfe6e9',\n btnBg: '#ffffff',\n btnText: '#0984e3',\n btnBorder: '#74b9ff',\n activeBg: 'linear-gradient(135deg, #0984e3 0%, #0652DD 100%)',\n activeText: '#ffffff',\n activeBorder: '#0984e3'\n },\n 'green': {\n containerBg: '#d4f1e8',\n btnBg: '#ffffff',\n btnText: '#00b894',\n btnBorder: '#55efc4',\n activeBg: 'linear-gradient(135deg, #00b894 0%, #00a878 100%)',\n activeText: '#ffffff',\n activeBorder: '#00b894'\n },\n 'red': {\n containerBg: '#ffcccc',\n btnBg: '#ffffff',\n btnText: '#d63031',\n btnBorder: '#ff7675',\n activeBg: 'linear-gradient(135deg, #d63031 0%, #c0392b 100%)',\n activeText: '#ffffff',\n activeBorder: '#d63031'\n },\n 'purple': {\n containerBg: '#e5dbff',\n btnBg: '#ffffff',\n btnText: '#6c5ce7',\n btnBorder: '#a29bfe',\n activeBg: 'linear-gradient(135deg, #6c5ce7 0%, #5f27cd 100%)',\n activeText: '#ffffff',\n activeBorder: '#6c5ce7'\n }\n };\n \n const colors = themes[theme] || themes['default'];\n \n // Container\n if (container) {\n container.style.background = colors.containerBg;\n }\n \n // Buttons - inactive state\n buttons.forEach(btn => {\n if (btn && !btn.classList.contains('active')) {\n btn.style.background = colors.btnBg;\n btn.style.color = colors.btnText;\n btn.style.borderColor = colors.btnBorder;\n }\n });\n \n // Store colors for active state\n inst._themeColors = colors;\n \n // Re-apply active button styling\n setActiveButton(currentIndex);\n }\n \n function setActiveButton(index) {\n currentIndex = index;\n const colors = instance._themeColors || {\n btnBg: '#ffffff',\n btnText: '#2d3436',\n btnBorder: '#dfe6e9',\n activeBg: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',\n activeText: '#ffffff',\n activeBorder: '#667eea'\n };\n \n buttons.forEach((btn, i) => {\n if (i + 1 === index) {\n btn.classList.add('active');\n btn.style.background = colors.activeBg;\n btn.style.color = colors.activeText;\n btn.style.borderColor = colors.activeBorder;\n } else {\n btn.classList.remove('active');\n btn.style.background = colors.btnBg;\n btn.style.color = colors.btnText;\n btn.style.borderColor = colors.btnBorder;\n }\n });\n }\n \n \n}\n\n/**\n * @param {BaseScreenViewerAndControl} instance\n */\nexport function disconnectedCallback(instance) {\n // Cleanup if needed\n}\n","properties":{"button1Text":{"type":"string","default":"SnapShot Report"},"button2Text":{"type":"string","default":"Daily Report"},"button3Text":{"type":"string","default":"Hourly Report"},"button4Text":{"type":"string","default":"Prover Report"},"activeButton":{"type":"string","default":"1"},"theme":{"type":"enum","default":"default","values":["default","dark","light","blue","green","red","purple"]},"snapshot":{"type":"signal"},"daily":{"type":"signal"},"hourly":{"type":"signal"},"prover":{"type":"signal"}},"settings":{"width":"auto","height":"60px","visibilityEnabled":"false","visibilityGroups":"","visibilityAction":"hide","visibilityRedirectScreen":""}}
1
+ {"html":"<div id=\"reportSelectorContainer\" style=\"box-sizing:border-box;width:100%;height:100%;display:flex;align-items:center;gap:12px;padding:16px;border-radius:8px;\">\n <button id=\"btn1\" class=\"report-btn\" @click='{\"commands\":[{\"type\":\"SetSignalValue\",\"signal\":{\"source\":\"property\",\"name\":\"snapshot\"},\"value\":true}]}' style=\"flex:1;padding:14px 20px 14px 20px;cursor:pointer;border:2px solid initial;border-radius:8px;font-size:15px;font-weight:600;transition:all 0.3s ease;box-shadow:0 2px 4px rgba(0,0,0,0.1);\">SnapShot Report</button>\n <button id=\"btn2\" class=\"report-btn\" @click='{\"commands\":[{\"type\":\"SetSignalValue\",\"signal\":{\"source\":\"property\",\"name\":\"daily\"},\"value\":true}]}' style=\"flex:1;padding:14px 20px 14px 20px;cursor:pointer;border:2px solid initial;border-radius:8px;font-size:15px;font-weight:600;transition:all 0.3s ease;box-shadow:0 2px 4px rgba(0,0,0,0.1);\">Daily Report</button>\n <button id=\"btn3\" class=\"report-btn\" @click='{\"commands\":[{\"type\":\"SetSignalValue\",\"value\":true,\"signal\":{\"source\":\"property\",\"name\":\"hourly\"}}]}' style=\"flex:1;padding:14px 20px 14px 20px;cursor:pointer;border:2px solid initial;border-radius:8px;font-size:15px;font-weight:600;transition:all 0.3s ease;box-shadow:0 2px 4px rgba(0,0,0,0.1);\">Hourly Report</button>\n <button id=\"btn4\" class=\"report-btn\" @click='{\"commands\":[{\"type\":\"SetSignalValue\",\"signal\":{\"source\":\"property\",\"name\":\"prover\"},\"value\":true}]}' data-visibility-enabled=\"true\" data-visibility-condition=\"==\" data-visibility-action=\"disable\" data-visibility-groups=\"Administrator\" style=\"flex:1;padding:14px 20px 14px 20px;cursor:pointer;border:2px solid initial;border-radius:8px;font-size:15px;font-weight:600;transition:all 0.3s ease;box-shadow:0 2px 4px rgba(0,0,0,0.1);\">Prover Report</button>\n</div>\n","style":":host {\n display: block;\n width: 100%;\n}\n\n.report-btn:hover:not(.active) {\n transform: translateY(-2px);\n box-shadow: 0 4px 12px rgba(0,0,0,0.15);\n}\n\n.report-btn:active {\n transform: scale(0.98);\n}\n\n.report-btn.active {\n transform: translateY(-2px) scale(1.02);\n box-shadow: 0 6px 20px rgba(0,0,0,0.25);\n font-weight: 700;\n}\n","script":"/**\n * @param {BaseScreenViewerAndControl} instance\n */\nexport function init(instance) {\n const btn1 = instance._getDomElement('btn1');\n const btn2 = instance._getDomElement('btn2');\n const btn3 = instance._getDomElement('btn3');\n const btn4 = instance._getDomElement('btn4');\n const container = instance._getDomElement('reportSelectorContainer');\n \n const buttons = [btn1, btn2, btn3, btn4];\n let currentIndex = parseInt(instance.activeButton) || 1;\n let currentTheme = instance.theme || 'default';\n \n // Button mətnlərini yeniləyirik\n if (instance.button1Text) btn1.textContent = instance.button1Text;\n if (instance.button2Text) btn2.textContent = instance.button2Text;\n if (instance.button3Text) btn3.textContent = instance.button3Text;\n if (instance.button4Text) btn4.textContent = instance.button4Text;\n // Property dəyişiklikləri üçün event-lər\n instance._assignEvent('button1-text-changed', () => {\n if (btn1) btn1.textContent = instance.button1Text || 'Report 1';\n });\n \n instance._assignEvent('button2-text-changed', () => {\n if (btn2) btn2.textContent = instance.button2Text || 'Report 2';\n });\n \n instance._assignEvent('button3-text-changed', () => {\n if (btn3) btn3.textContent = instance.button3Text || 'Report 3';\n });\n \n instance._assignEvent('button4-text-changed', () => {\n if (btn4) btn4.textContent = instance.button4Text || 'Report 4';\n });\n \n instance._assignEvent('active-button-changed', () => {\n const newIndex = parseInt(instance.activeButton) || 1;\n setActiveButton(newIndex);\n //loadReport(newIndex);\n });\n \n instance._assignEvent('theme-changed', () => {\n currentTheme = instance.theme || 'default';\n applyTheming(instance, currentTheme);\n });\n\n // İlk theming və active button\n applyTheming(instance, currentTheme);\n setActiveButton(currentIndex);\n //loadReport(currentIndex);\n \n // Button click event-ləri\n buttons.forEach((btn, index) => {\n btn.addEventListener('click', () => {\n const btnIndex = index + 1;\n setActiveButton(btnIndex);\n //loadReport(btnIndex);\n });\n });\n \n function applyTheming(inst, theme) {\n const themes = {\n 'default': {\n containerBg: '#f5f5f5',\n btnBg: '#ffffff',\n btnText: '#2d3436',\n btnBorder: '#dfe6e9',\n activeBg: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',\n activeText: '#ffffff',\n activeBorder: '#667eea'\n },\n 'dark': {\n containerBg: '#2d3436',\n btnBg: '#636e72',\n btnText: '#dfe6e9',\n btnBorder: '#000000',\n activeBg: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',\n activeText: '#ffffff',\n activeBorder: '#667eea'\n },\n 'light': {\n containerBg: '#ffffff',\n btnBg: '#f8f9fa',\n btnText: '#212529',\n btnBorder: '#dee2e6',\n activeBg: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',\n activeText: '#ffffff',\n activeBorder: '#667eea'\n },\n 'blue': {\n containerBg: '#dfe6e9',\n btnBg: '#ffffff',\n btnText: '#0984e3',\n btnBorder: '#74b9ff',\n activeBg: 'linear-gradient(135deg, #0984e3 0%, #0652DD 100%)',\n activeText: '#ffffff',\n activeBorder: '#0984e3'\n },\n 'green': {\n containerBg: '#d4f1e8',\n btnBg: '#ffffff',\n btnText: '#00b894',\n btnBorder: '#55efc4',\n activeBg: 'linear-gradient(135deg, #00b894 0%, #00a878 100%)',\n activeText: '#ffffff',\n activeBorder: '#00b894'\n },\n 'red': {\n containerBg: '#ffcccc',\n btnBg: '#ffffff',\n btnText: '#d63031',\n btnBorder: '#ff7675',\n activeBg: 'linear-gradient(135deg, #d63031 0%, #c0392b 100%)',\n activeText: '#ffffff',\n activeBorder: '#d63031'\n },\n 'purple': {\n containerBg: '#e5dbff',\n btnBg: '#ffffff',\n btnText: '#6c5ce7',\n btnBorder: '#a29bfe',\n activeBg: 'linear-gradient(135deg, #6c5ce7 0%, #5f27cd 100%)',\n activeText: '#ffffff',\n activeBorder: '#6c5ce7'\n }\n };\n \n const colors = themes[theme] || themes['default'];\n \n // Container\n if (container) {\n container.style.background = colors.containerBg;\n }\n \n // Buttons - inactive state\n buttons.forEach(btn => {\n if (btn && !btn.classList.contains('active')) {\n btn.style.background = colors.btnBg;\n btn.style.color = colors.btnText;\n btn.style.borderColor = colors.btnBorder;\n }\n });\n \n // Store colors for active state\n inst._themeColors = colors;\n \n // Re-apply active button styling\n setActiveButton(currentIndex);\n }\n \n function setActiveButton(index) {\n currentIndex = index;\n const colors = instance._themeColors || {\n btnBg: '#ffffff',\n btnText: '#2d3436',\n btnBorder: '#dfe6e9',\n activeBg: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',\n activeText: '#ffffff',\n activeBorder: '#667eea'\n };\n \n buttons.forEach((btn, i) => {\n if (i + 1 === index) {\n btn.classList.add('active');\n btn.style.background = colors.activeBg;\n btn.style.color = colors.activeText;\n btn.style.borderColor = colors.activeBorder;\n } else {\n btn.classList.remove('active');\n btn.style.background = colors.btnBg;\n btn.style.color = colors.btnText;\n btn.style.borderColor = colors.btnBorder;\n }\n });\n }\n \n \n}\n\n/**\n * @param {BaseScreenViewerAndControl} instance\n */\nexport function disconnectedCallback(instance) {\n // Cleanup if needed\n}\n","properties":{"button1Text":{"type":"string","default":"SnapShot Report"},"button2Text":{"type":"string","default":"Daily Report"},"button3Text":{"type":"string","default":"Hourly Report"},"button4Text":{"type":"string","default":"Prover Report"},"activeButton":{"type":"string","default":"1"},"theme":{"type":"enum","default":"default","values":["default","dark","light","blue","green","red","purple"]},"snapshot":{"type":"signal"},"daily":{"type":"signal"},"hourly":{"type":"signal"},"prover":{"type":"signal"}},"settings":{"width":"auto","height":"60px","visibilityEnabled":"false","visibilityGroups":"","visibilityAction":"hide","visibilityRedirectScreen":""}}