Bugzilla – Attachment 187522 Details for
Bug 39320
Create a 'landing page' for ERM
Home
|
New
|
Browse
|
Search
|
[?]
|
Reports
|
Help
|
New Account
|
Log In
[x]
|
Forgot Password
Login:
[x]
[patch]
Bug 39320: Preparation: Add base and wrapper components
Bug-39320-Preparation-Add-base-and-wrapper-compone.patch (text/plain), 28.10 KB, created by
Pedro Amorim
on 2025-10-07 12:34:10 UTC
(
hide
)
Description:
Bug 39320: Preparation: Add base and wrapper components
Filename:
MIME Type:
Creator:
Pedro Amorim
Created:
2025-10-07 12:34:10 UTC
Size:
28.10 KB
patch
obsolete
>From 05c201c53e7f3a031a27c22de55c6153df811615 Mon Sep 17 00:00:00 2001 >From: Pedro Amorim <pedro.amorim@ptfs-europe.com> >Date: Tue, 25 Feb 2025 16:05:26 +0000 >Subject: [PATCH] Bug 39320: Preparation: Add base and wrapper components > >As well as wrapper support components >base-widget.js composable >--- > .../ModuleDashboard/ModuleDashboard.vue | 222 +++++++++++++++++ > .../WidgetDashboardWrapper.vue | 231 ++++++++++++++++++ > .../ModuleDashboard/WidgetPicker.vue | 59 +++++ > .../ModuleDashboard/WidgetPickerWrapper.vue | 97 ++++++++ > .../ModuleDashboard/WidgetWrapper.vue | 136 +++++++++++ > .../prog/js/vue/composables/base-widget.js | 97 ++++++++ > 6 files changed, 842 insertions(+) > create mode 100644 koha-tmpl/intranet-tmpl/prog/js/vue/components/ModuleDashboard/ModuleDashboard.vue > create mode 100644 koha-tmpl/intranet-tmpl/prog/js/vue/components/ModuleDashboard/WidgetDashboardWrapper.vue > create mode 100644 koha-tmpl/intranet-tmpl/prog/js/vue/components/ModuleDashboard/WidgetPicker.vue > create mode 100644 koha-tmpl/intranet-tmpl/prog/js/vue/components/ModuleDashboard/WidgetPickerWrapper.vue > create mode 100644 koha-tmpl/intranet-tmpl/prog/js/vue/components/ModuleDashboard/WidgetWrapper.vue > create mode 100644 koha-tmpl/intranet-tmpl/prog/js/vue/composables/base-widget.js > >diff --git a/koha-tmpl/intranet-tmpl/prog/js/vue/components/ModuleDashboard/ModuleDashboard.vue b/koha-tmpl/intranet-tmpl/prog/js/vue/components/ModuleDashboard/ModuleDashboard.vue >new file mode 100644 >index 00000000000..0ec96c92ffe >--- /dev/null >+++ b/koha-tmpl/intranet-tmpl/prog/js/vue/components/ModuleDashboard/ModuleDashboard.vue >@@ -0,0 +1,222 @@ >+<template> >+ <div> >+ <div id="dashboard-header"> >+ <p> >+ {{ $__("Customize your dashboard by adding widgets.") }} >+ <a href="#" id="open-widget-picker" @click="toggleWidgetPicker"> >+ {{ $__("Open Widget Picker") }} >+ </a> >+ </p> >+ </div> >+ </div> >+ <div class="row"> >+ <div class="col-md-6 dashboard-left-col"> >+ <draggable >+ :ghost="true" >+ @drag="handleDrag" >+ class="dragArea list-group w-full" >+ :list="selectedWidgetsLeft" >+ group="widgets" >+ > >+ <component >+ v-for="(widget, index) in selectedWidgetsLeft" >+ :key="widget.name" >+ :is="widget" >+ display="dashboard" >+ dashboardColumn="left" >+ @moveWidget="moveWidget" >+ @removed="removeWidget(widget)" >+ ></component> >+ </draggable> >+ </div> >+ <div class="col-md-6 dashboard-right-col"> >+ <draggable >+ :ghost="true" >+ @drag="handleDrag" >+ class="dragArea list-group w-full" >+ :list="selectedWidgetsRight" >+ group="widgets" >+ > >+ <component >+ v-for="(widget, index) in selectedWidgetsRight" >+ :key="widget.name" >+ :is="widget" >+ display="dashboard" >+ dashboardColumn="right" >+ @moveWidget="moveWidget" >+ @removed="removeWidget(widget)" >+ ></component> >+ </draggable> >+ </div> >+ </div> >+</template> >+ >+<script> >+import { onMounted, ref, watch, inject } from "vue"; >+import WidgetPicker from "./WidgetPicker.vue"; >+import VueCookies from "vue-cookies"; >+import { $__ } from "@koha-vue/i18n"; >+import { VueDraggableNext } from "vue-draggable-next"; >+ >+export default { >+ setup(props) { >+ const availableWidgets = props.availableWidgets; >+ const selectedWidgetsLeft = ref([]); >+ const selectedWidgetsRight = ref([]); >+ >+ const { setComponentDialog } = inject("mainStore"); >+ >+ const handleDrag = event => { >+ const dropContext = event.relatedContext; >+ if (dropContext) { >+ const dropElement = dropContext.element; >+ dropElement.style.border = "2px dotted #ccc"; >+ event.onDragEnd(() => { >+ dropElement.style.border = ""; >+ }); >+ } >+ }; >+ >+ function removeWidget(widget) { >+ const indexLeft = selectedWidgetsLeft.value.indexOf(widget); >+ const indexRight = selectedWidgetsRight.value.indexOf(widget); >+ if (indexLeft > -1) { >+ selectedWidgetsLeft.value.splice(indexLeft, 1); >+ } else if (indexRight > -1) { >+ selectedWidgetsRight.value.splice(indexRight, 1); >+ } >+ } >+ >+ function addWidget(widget) { >+ if ( >+ !selectedWidgetsLeft.value.includes(widget) && >+ !selectedWidgetsRight.value.includes(widget) >+ ) { >+ if ( >+ selectedWidgetsLeft.value.length <= >+ selectedWidgetsRight.value.length >+ ) { >+ selectedWidgetsLeft.value.unshift(widget); >+ } else { >+ selectedWidgetsRight.value.unshift(widget); >+ } >+ } >+ } >+ >+ function moveWidget(widget) { >+ if ( >+ selectedWidgetsLeft.value.some( >+ element => element.name === widget.componentName >+ ) >+ ) { >+ selectedWidgetsRight.value.unshift( >+ selectedWidgetsLeft.value.find( >+ element => element.name === widget.componentName >+ ) >+ ); >+ selectedWidgetsLeft.value = selectedWidgetsLeft.value.filter( >+ element => element.name !== widget.componentName >+ ); >+ } else if ( >+ selectedWidgetsRight.value.some( >+ element => element.name === widget.componentName >+ ) >+ ) { >+ selectedWidgetsLeft.value.unshift( >+ selectedWidgetsRight.value.find( >+ element => element.name === widget.componentName >+ ) >+ ); >+ selectedWidgetsRight.value = selectedWidgetsRight.value.filter( >+ element => element.name !== widget.componentName >+ ); >+ } >+ } >+ >+ const toggleWidgetPicker = e => { >+ setComponentDialog({ >+ title: $__("Customize your dashboard widgets"), >+ cancel_label: $__("Close"), >+ componentPath: >+ "@koha-vue/components/ModuleDashboard/WidgetPicker.vue", >+ componentProps: { >+ selectedWidgetsLeft: selectedWidgetsLeft.value, >+ selectedWidgetsRight: selectedWidgetsRight.value, >+ availableWidgets: availableWidgets, >+ }, >+ componentListeners: { >+ added: widget => addWidget(widget), >+ removed: widget => removeWidget(widget), >+ }, >+ }); >+ }; >+ >+ onMounted(() => { >+ const storedWidgets = VueCookies.get("dashboard-widgets"); >+ if (storedWidgets) { >+ const { left, right } = storedWidgets; >+ left.forEach(widgetName => { >+ const widget = availableWidgets.find( >+ widget => widget.name === widgetName >+ ); >+ if (widget) { >+ selectedWidgetsLeft.value.push(widget); >+ } >+ }); >+ right.forEach(widgetName => { >+ const widget = availableWidgets.find( >+ widget => widget.name === widgetName >+ ); >+ if (widget) { >+ selectedWidgetsRight.value.push(widget); >+ } >+ }); >+ } else { >+ availableWidgets.forEach(widget => addWidget(widget)); >+ } >+ }); >+ >+ watch( >+ [selectedWidgetsLeft, selectedWidgetsRight], >+ ([left, right]) => { >+ const leftWidgetNames = left.map(widget => widget.name); >+ const rightWidgetNames = right.map(widget => widget.name); >+ VueCookies.set( >+ "dashboard-widgets", >+ JSON.stringify({ >+ left: leftWidgetNames, >+ right: rightWidgetNames, >+ }), >+ "30d" >+ ); >+ }, >+ { deep: true } >+ ); >+ >+ return { >+ ...props, >+ selectedWidgetsLeft, >+ selectedWidgetsRight, >+ addWidget, >+ handleDrag, >+ removeWidget, >+ moveWidget, >+ toggleWidgetPicker, >+ }; >+ }, >+ components: { >+ WidgetPicker, >+ draggable: VueDraggableNext, >+ }, >+ name: "ModuleDashboard", >+}; >+</script> >+<style> >+.sortable-ghost { >+ height: 50px !important; >+} >+ >+.sortable-ghost.sortable-chosen > * { >+ visibility: hidden; >+} >+</style> >diff --git a/koha-tmpl/intranet-tmpl/prog/js/vue/components/ModuleDashboard/WidgetDashboardWrapper.vue b/koha-tmpl/intranet-tmpl/prog/js/vue/components/ModuleDashboard/WidgetDashboardWrapper.vue >new file mode 100644 >index 00000000000..539f507172c >--- /dev/null >+++ b/koha-tmpl/intranet-tmpl/prog/js/vue/components/ModuleDashboard/WidgetDashboardWrapper.vue >@@ -0,0 +1,231 @@ >+<template> >+ <div class="widget" :id="id"> >+ <div class="widget-header"> >+ <div class="row align-items-center"> >+ <div class="col-md-6"> >+ <h1 class="widget-title m-0"> >+ <i v-if="icon" :icon="icon" :class="icon"></i> >+ {{ name }} >+ </h1> >+ </div> >+ <div class="col-md-6 text-end"> >+ <div class="dropdown"> >+ <button >+ class="btn btn-default" >+ type="button" >+ id="dropdownMenuButton" >+ data-bs-toggle="dropdown" >+ aria-expanded="false" >+ > >+ <font-awesome-icon icon="ellipsis-vertical" /> >+ </button> >+ <ul >+ class="dropdown-menu" >+ aria-labelledby="dropdownMenuButton" >+ > >+ <li> >+ <button >+ v-if=" >+ Object.keys(settings_definitions).length >+ " >+ class="dropdown-item toggle-settings" >+ @mousedown="toggleSettings" >+ :title="$__('Settings')" >+ > >+ <font-awesome-icon icon="cog" /> >+ {{ $__("Settings") }} >+ </button> >+ </li> >+ <li v-if="dashboardColumn === 'left'"> >+ <button >+ class="dropdown-item move-right" >+ @mousedown="moveWidget('right')" >+ :title="$__('Move to right')" >+ > >+ <font-awesome-icon icon="arrow-right" /> >+ {{ $__("Move to right") }} >+ </button> >+ </li> >+ <li v-if="dashboardColumn === 'right'"> >+ <button >+ class="dropdown-item move-left" >+ @mousedown="moveWidget('left')" >+ :title="$__('Move to left')" >+ > >+ <font-awesome-icon icon="arrow-left" /> >+ {{ $__("Move to left") }} >+ </button> >+ </li> >+ <li> >+ <button >+ class="dropdown-item remove-widget" >+ @mousedown="removeWidget" >+ :title="$__('Remove')" >+ > >+ <font-awesome-icon icon="trash" /> >+ {{ $__("Remove") }} >+ </button> >+ </li> >+ </ul> >+ </div> >+ </div> >+ </div> >+ </div> >+ <div v-if="showSettings" class="widget-settings"> >+ <h3 class="widget-title">{{ $__("Settings") }}</h3> >+ <form> >+ <fieldset class="rows"> >+ <ol> >+ <li >+ v-for="(setting, index) in settings_definitions" >+ v-bind:key="index" >+ > >+ <FormElement >+ :resource="settings" >+ :attr="setting" >+ :index="index" >+ /> >+ </li> >+ </ol> >+ </fieldset> >+ </form> >+ <div class="text-end"> >+ <button >+ type="button" >+ class="btn btn-default" >+ @click="toggleSettings" >+ > >+ {{ $__("Close settings") }} >+ </button> >+ </div> >+ </div> >+ <div class="widget-content"> >+ <div v-if="loading" class="text-center"> >+ {{ $__("Loading...") }} >+ </div> >+ <slot v-else></slot> >+ </div> >+ </div> >+</template> >+ >+<script> >+import { ref } from "vue"; >+import FormElement from "../FormElement.vue"; >+export default { >+ props: { >+ id: { >+ type: String, >+ required: true, >+ }, >+ name: { >+ type: String, >+ required: true, >+ }, >+ icon: { >+ type: String, >+ required: false, >+ }, >+ loading: { >+ type: Boolean, >+ }, >+ settings: { >+ type: Object, >+ default: () => ({}), >+ }, >+ settings_definitions: { >+ type: Object, >+ default: () => ({}), >+ }, >+ dashboardColumn: { >+ type: String, >+ required: false, >+ }, >+ }, >+ setup(props, { emit }) { >+ const showSettings = ref(false); >+ >+ const removeWidget = () => { >+ emit("removed", props); >+ }; >+ >+ const moveWidget = column => { >+ emit("moveWidget"); >+ }; >+ >+ function toggleSettings() { >+ showSettings.value = !showSettings.value; >+ } >+ >+ return { >+ showSettings, >+ toggleSettings, >+ removeWidget, >+ moveWidget, >+ }; >+ }, >+ emits: ["removed", "moveWidget"], >+ components: { >+ FormElement, >+ }, >+}; >+</script> >+ >+<style scoped> >+.widget { >+ background-color: #fff; >+ border: 1px solid #ccc; >+ border-radius: 8px; >+ padding: 16px; >+ margin-bottom: 12px; >+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); >+} >+ >+.widget-header { >+ background-color: #f7f7f7; >+ border-bottom: 1px solid #ccc; >+ border-top-left-radius: 8px; >+ border-top-right-radius: 8px; >+} >+ >+.widget-settings { >+ border-bottom: 1px solid #ccc; >+ border-top-left-radius: 8px; >+ border-top-right-radius: 8px; >+} >+ >+.widget-settings fieldset.rows ol { >+ padding: 0; >+} >+ >+.widget-settings form:after { >+ content: ""; >+ display: table; >+ clear: both; >+} >+ >+:deep(.widget-settings form .v-select) { >+ display: inline-block; >+ background-color: white; >+ width: 60%; >+} >+ >+:deep(.v-select), >+:deep( >+ input:not([type="submit"]):not([type="search"]):not([type="button"]):not( >+ [type="checkbox"] >+ ):not([type="radio"]) >+), >+:deep(textarea) { >+ border-color: rgba(60, 60, 60, 0.26); >+ border-width: 1px; >+ border-radius: 4px; >+ min-width: 60%; >+} >+ >+.widget-content, >+.widget-settings, >+.widget-header { >+ padding: 6px 12px; >+ margin-bottom: 6px; >+} >+</style> >diff --git a/koha-tmpl/intranet-tmpl/prog/js/vue/components/ModuleDashboard/WidgetPicker.vue b/koha-tmpl/intranet-tmpl/prog/js/vue/components/ModuleDashboard/WidgetPicker.vue >new file mode 100644 >index 00000000000..1f5994faf41 >--- /dev/null >+++ b/koha-tmpl/intranet-tmpl/prog/js/vue/components/ModuleDashboard/WidgetPicker.vue >@@ -0,0 +1,59 @@ >+<template> >+ <div v-for="widget in availableWidgets" :key="widget" class="widget-item"> >+ <component >+ display="picker" >+ :is="widget" >+ :alreadyAdded="alreadyOnDashboard(widget)" >+ @removed="removeWidget(widget)" >+ @added="addWidget(widget)" >+ ></component> >+ </div> >+</template> >+ >+<script> >+export default { >+ props: { >+ availableWidgets: { >+ type: Array, >+ required: true, >+ }, >+ selectedWidgetsLeft: { >+ type: Array, >+ required: true, >+ }, >+ selectedWidgetsRight: { >+ type: Array, >+ required: true, >+ }, >+ }, >+ >+ setup(props, { emit }) { >+ const availableWidgets = props.availableWidgets; >+ const selectedWidgetsLeft = props.selectedWidgetsLeft; >+ const selectedWidgetsRight = props.selectedWidgetsRight; >+ >+ const removeWidget = widget => { >+ emit("removed", widget); >+ }; >+ const addWidget = widget => { >+ emit("added", widget); >+ }; >+ >+ const alreadyOnDashboard = widget => { >+ return ( >+ selectedWidgetsLeft.includes(widget) || >+ selectedWidgetsRight.includes(widget) >+ ); >+ }; >+ >+ return { >+ ...props, >+ addWidget, >+ alreadyOnDashboard, >+ removeWidget, >+ }; >+ }, >+ emits: ["added", "removed"], >+ name: "WidgetPicker", >+}; >+</script> >diff --git a/koha-tmpl/intranet-tmpl/prog/js/vue/components/ModuleDashboard/WidgetPickerWrapper.vue b/koha-tmpl/intranet-tmpl/prog/js/vue/components/ModuleDashboard/WidgetPickerWrapper.vue >new file mode 100644 >index 00000000000..8affc61bdcf >--- /dev/null >+++ b/koha-tmpl/intranet-tmpl/prog/js/vue/components/ModuleDashboard/WidgetPickerWrapper.vue >@@ -0,0 +1,97 @@ >+<template> >+ <div class="widget-details" :id="id"> >+ <div class="d-flex align-items-center widget-header"> >+ <h1 class="widget-title me-auto m-0"> >+ <i v-if="icon" :class="icon"></i> {{ name }} >+ </h1> >+ <div class="widget-actions"> >+ <button >+ v-if="!alreadyAdded" >+ class="btn btn-primary me-1 add-widget" >+ @click="addWidget" >+ > >+ <font-awesome-icon icon="plus" /> >+ {{ $__("Add") }} >+ </button> >+ <button >+ v-if="alreadyAdded" >+ class="btn btn-default remove-widget" >+ @click="removeWidget" >+ > >+ <font-awesome-icon icon="trash" /> >+ {{ $__("Remove") }} >+ </button> >+ </div> >+ </div> >+ <p class="widget-description"> >+ {{ description }} >+ </p> >+ </div> >+</template> >+ >+<script> >+import { toRefs } from "vue"; >+ >+export default { >+ name: "WidgetDetails", >+ props: { >+ id: { >+ type: String, >+ required: true, >+ }, >+ name: { >+ type: String, >+ required: true, >+ }, >+ icon: { >+ type: String, >+ required: false, >+ }, >+ description: { >+ type: String, >+ required: true, >+ }, >+ alreadyAdded: { >+ type: Boolean, >+ default: false, >+ }, >+ }, >+ emits: ["removed", "added"], >+ setup(props, { emit }) { >+ const { id, name, icon, description, alreadyAdded } = toRefs(props); >+ >+ function removeWidget() { >+ emit("removed", props); >+ } >+ >+ function addWidget() { >+ emit("added", props); >+ } >+ >+ return { >+ id, >+ name, >+ icon, >+ description, >+ alreadyAdded, >+ removeWidget, >+ addWidget, >+ }; >+ }, >+}; >+</script> >+ >+<style scoped> >+.widget-description { >+ padding: 12px; >+} >+ >+.widget-header { >+ background-color: #f7f7f7; >+ border-bottom: 1px solid #ccc; >+ border-top-left-radius: 8px; >+ border-top-right-radius: 8px; >+ padding: 6px 12px; >+ margin-bottom: 6px; >+} >+</style> >diff --git a/koha-tmpl/intranet-tmpl/prog/js/vue/components/ModuleDashboard/WidgetWrapper.vue b/koha-tmpl/intranet-tmpl/prog/js/vue/components/ModuleDashboard/WidgetWrapper.vue >new file mode 100644 >index 00000000000..a873ce6a276 >--- /dev/null >+++ b/koha-tmpl/intranet-tmpl/prog/js/vue/components/ModuleDashboard/WidgetWrapper.vue >@@ -0,0 +1,136 @@ >+<!-- WidgetWrapper.vue --> >+<template> >+ <template v-if="display === 'picker'"> >+ <WidgetPickerWrapper >+ :id="id" >+ :alreadyAdded="alreadyAdded" >+ @added="handleAddWidget" >+ @removed="handleRemoveWidget" >+ :name="name" >+ :icon="icon" >+ :description="description" >+ /> >+ </template> >+ >+ <template v-else-if="display === 'dashboard'"> >+ <WidgetDashboardWrapper >+ @removed="handleRemoveWidget" >+ @moveWidget="handleMoveWidget" >+ :id="id" >+ :settings="settings" >+ :settings_definitions="settings_definitions" >+ :loading="loading" >+ :dashboardColumn="dashboardColumn" >+ :name="name" >+ :icon="icon" >+ > >+ <slot /> >+ </WidgetDashboardWrapper> >+ </template> >+</template> >+ >+<script> >+import { inject, toRefs } from "vue"; >+import WidgetDashboardWrapper from "./WidgetDashboardWrapper.vue"; >+import WidgetPickerWrapper from "./WidgetPickerWrapper.vue"; >+ >+export default { >+ name: "WidgetWrapper", >+ components: { >+ WidgetDashboardWrapper, >+ WidgetPickerWrapper, >+ }, >+ props: { >+ display: { >+ type: String, >+ required: true, >+ }, >+ alreadyAdded: { >+ type: Boolean, >+ default: false, >+ }, >+ dashboardColumn: { >+ type: String, >+ }, >+ id: { >+ type: String, >+ required: true, >+ }, >+ name: { >+ type: String, >+ required: true, >+ }, >+ icon: { >+ type: String, >+ }, >+ description: { >+ type: String, >+ required: true, >+ }, >+ loading: { >+ type: Boolean, >+ }, >+ settings: { >+ type: Object, >+ }, >+ settings_definitions: { >+ type: Array, >+ }, >+ }, >+ setup(props) { >+ // Destructure props so they are all reactive refs >+ const { >+ display, >+ alreadyAdded, >+ dashboardColumn, >+ id, >+ name, >+ icon, >+ description, >+ loading, >+ settings, >+ settings_definitions, >+ } = toRefs(props); >+ >+ const addWidget = inject("addWidget"); >+ const removeWidget = inject("removeWidget"); >+ const moveWidget = inject("moveWidget"); >+ >+ const handleAddWidget = () => { >+ if (addWidget) addWidget(); >+ }; >+ >+ const handleRemoveWidget = () => { >+ if (removeWidget) removeWidget(); >+ }; >+ >+ const handleMoveWidget = () => { >+ if (moveWidget) moveWidget(); >+ }; >+ >+ return { >+ display, >+ alreadyAdded, >+ dashboardColumn, >+ id, >+ name, >+ icon, >+ description, >+ loading, >+ settings, >+ settings_definitions, >+ handleAddWidget, >+ handleRemoveWidget, >+ handleMoveWidget, >+ }; >+ }, >+}; >+</script> >+ >+<style> >+.widget-title { >+ margin-top: 0; >+ font-weight: bold; >+ font-size: 1.2rem; >+} >+</style> >diff --git a/koha-tmpl/intranet-tmpl/prog/js/vue/composables/base-widget.js b/koha-tmpl/intranet-tmpl/prog/js/vue/composables/base-widget.js >new file mode 100644 >index 00000000000..4b52776d5ca >--- /dev/null >+++ b/koha-tmpl/intranet-tmpl/prog/js/vue/composables/base-widget.js >@@ -0,0 +1,97 @@ >+// composables/useBaseWidget.js >+import { ref, computed, watch, provide, onMounted, onBeforeMount } from "vue"; >+import VueCookies from "vue-cookies"; >+ >+export function useBaseWidget(widgetConfig, emit) { >+ const loading = ref( >+ widgetConfig.loading !== undefined ? widgetConfig.loading : true >+ ); >+ const settings = ref(widgetConfig.settings || {}); >+ const settings_definitions = ref(widgetConfig.settings_definitions || []); >+ const removeWidget = () => emit("removed", widgetConfig); >+ const addWidget = () => emit("added", widgetConfig); >+ const moveWidget = () => { >+ emit("moveWidget", { >+ componentName: widgetConfig.id, >+ currentColumn: widgetConfig.dashboardColumn, >+ }); >+ }; >+ >+ provide("removeWidget", removeWidget); >+ provide("addWidget", addWidget); >+ provide("moveWidget", moveWidget); >+ >+ const widgetWrapperProps = computed(() => ({ >+ id: widgetConfig.id, >+ display: widgetConfig.display, >+ alreadyAdded: widgetConfig.alreadyAdded, >+ dashboardColumn: widgetConfig.dashboardColumn, >+ name: widgetConfig.name, >+ icon: widgetConfig.icon, >+ loading: loading.value, >+ description: widgetConfig.description, >+ settings: settings.value, >+ settings_definitions: settings_definitions.value, >+ })); >+ >+ const getWidgetSavedSettings = () => { >+ const cookie = VueCookies.get( >+ "widget-" + widgetConfig.id + "-settings" >+ ); >+ return cookie || null; >+ }; >+ >+ onBeforeMount(() => { >+ if (widgetConfig.display === "dashboard") { >+ const cookie = getWidgetSavedSettings(); >+ if (cookie) { >+ settings.value = cookie; >+ } >+ } >+ }); >+ >+ /** >+ * Lifecycle method that runs when the widget is mounted on the dashboard. >+ * Use this to e.g. fetch data >+ * >+ * Sample usage from ERMCounts.vue: >+ * >+ * baseWidget.onDashboardMounted(() => { >+ * getCounts(); >+ * }); >+ */ >+ function onDashboardMounted(callback) { >+ onMounted(() => { >+ if (widgetConfig.display === "dashboard") { >+ callback?.(); >+ } >+ }); >+ } >+ >+ watch( >+ settings, >+ newSettings => { >+ if (widgetConfig.display === "dashboard" && newSettings !== null) { >+ VueCookies.set( >+ "widget-" + widgetConfig.id + "-settings", >+ JSON.stringify(newSettings), >+ "30d" >+ ); >+ } >+ }, >+ { deep: true } >+ ); >+ >+ return { >+ ...widgetConfig, >+ loading, >+ display: widgetConfig.display, >+ settings, >+ settings_definitions, >+ widgetWrapperProps, >+ removeWidget, >+ addWidget, >+ moveWidget, >+ onDashboardMounted, >+ }; >+} >-- >2.39.5
You cannot view the attachment while viewing its details because your browser does not support IFRAMEs.
View the attachment on a separate page
.
View Attachment As Diff
View Attachment As Raw
Actions:
View
|
Diff
|
Splinter Review
Attachments on
bug 39320
:
187513
|
187514
|
187515
|
187516
|
187517
|
187518
|
187519
|
187520
|
187521
|
187522
|
187563
|
187568
|
187635
|
187636
|
187637
|
187638
|
187639
|
187640
|
187641
|
187642
|
187643
|
187644
|
187645
|
188094
|
188095
|
188096
|
188097
|
188098
|
188099
|
188100
|
188101
|
188102
|
188103
|
188104
|
188151
|
188279
|
188280
|
188281
|
188282
|
188283
|
188284
|
188285
|
188312
|
188315
|
188372
|
188375
|
188376
|
188637
|
188638
|
188639
|
188640
|
188641
|
188830
|
188831
|
188832
|
188833
|
188834
|
188835
|
188836
|
188837
|
188838
|
188839
|
188840
|
188841
|
188842
|
188843
|
188844
|
188845
|
188846
|
188847
|
188848
|
188849
|
188850
|
188851
|
188852
|
188853
|
188854
|
188855
|
188856
|
188857
|
188858
|
188859
|
188860
|
188861
|
188862
|
188863
|
188864