+
{{
miniWidget.name.replace(/([a-z])([A-Z])/g, '$1 $2').replace(/^./, (str) => str.toUpperCase()) ||
- 'Very Generic Indicator'
+ 'Very generic indicator'
}}
+
+
diff --git a/src/components/MiniWidgetContainer.vue b/src/components/MiniWidgetContainer.vue
index ff481712c..dc099e915 100644
--- a/src/components/MiniWidgetContainer.vue
+++ b/src/components/MiniWidgetContainer.vue
@@ -25,7 +25,7 @@
@mouseover="widgetStore.miniWidgetManagerVars(miniWidget.hash).highlighted = allowEditing"
@mouseleave="widgetStore.miniWidgetManagerVars(miniWidget.hash).highlighted = false"
>
-
@@ -51,7 +51,7 @@
@add="handleDeleteWidget"
>
-
diff --git a/src/components/MiniWidgetInstantiator.vue b/src/components/MiniWidgetInstantiator.vue
index 0b57d8274..d7ae11b71 100644
--- a/src/components/MiniWidgetInstantiator.vue
+++ b/src/components/MiniWidgetInstantiator.vue
@@ -3,28 +3,40 @@
diff --git a/src/components/WidgetHugger.vue b/src/components/WidgetHugger.vue
index a45a7246f..d99488ac2 100644
--- a/src/components/WidgetHugger.vue
+++ b/src/components/WidgetHugger.vue
@@ -87,7 +87,7 @@ const hoveringWidgetOrOverlay = computed(() => hoveringOverlay.value || hovering
// Put the widget into highlighted state when in edit-mode and hovering over it
watch([hoveringWidgetOrOverlay, allowMoving], () => {
- widgetStore.widgetManagerVars(widget.value.hash).highlighted = hoveringWidgetOrOverlay.value && allowMoving.value
+ widgetStore.widgetManagerVars(widget.value.hash).highlighted = hoveringWidgetOrOverlay.value
})
const draggingWidget = ref(false)
diff --git a/src/components/custom-widget-elements/Button.vue b/src/components/custom-widget-elements/Button.vue
new file mode 100644
index 000000000..837f9990d
--- /dev/null
+++ b/src/components/custom-widget-elements/Button.vue
@@ -0,0 +1,85 @@
+
+
+
+
+ {{ miniWidget.options.layout?.label || 'Button' }}
+
+
+
+
+
+
+
+
diff --git a/src/components/custom-widget-elements/Checkbox.vue b/src/components/custom-widget-elements/Checkbox.vue
new file mode 100644
index 000000000..08310ed49
--- /dev/null
+++ b/src/components/custom-widget-elements/Checkbox.vue
@@ -0,0 +1,103 @@
+
+
+
+ {{ miniWidget.options.layout?.label }}
+
+
+
+
+
+
+
+
diff --git a/src/components/custom-widget-elements/Dial.vue b/src/components/custom-widget-elements/Dial.vue
new file mode 100644
index 000000000..a59ad69e7
--- /dev/null
+++ b/src/components/custom-widget-elements/Dial.vue
@@ -0,0 +1,243 @@
+
+
+
+
+ {{ Math.round(potentiometerValue) || 0 }}
+
+
+
+
+
+
+
diff --git a/src/components/custom-widget-elements/Dropdown.vue b/src/components/custom-widget-elements/Dropdown.vue
new file mode 100644
index 000000000..510457d88
--- /dev/null
+++ b/src/components/custom-widget-elements/Dropdown.vue
@@ -0,0 +1,139 @@
+
+
+
+
+
+
+
diff --git a/src/components/custom-widget-elements/Label.vue b/src/components/custom-widget-elements/Label.vue
new file mode 100644
index 000000000..90dc482af
--- /dev/null
+++ b/src/components/custom-widget-elements/Label.vue
@@ -0,0 +1,101 @@
+
+
+
+ {{ miniWidget.options.text || 'Label' }}
+
+
+
+
+
+
+
diff --git a/src/components/custom-widget-elements/Slider.vue b/src/components/custom-widget-elements/Slider.vue
new file mode 100644
index 000000000..f7297a377
--- /dev/null
+++ b/src/components/custom-widget-elements/Slider.vue
@@ -0,0 +1,142 @@
+
+
+
+
+ {{ miniWidget.options.layout?.label }}
+
+
+
+
+
+
+
+
+
diff --git a/src/components/custom-widget-elements/Switch.vue b/src/components/custom-widget-elements/Switch.vue
new file mode 100644
index 000000000..e3ca0ca41
--- /dev/null
+++ b/src/components/custom-widget-elements/Switch.vue
@@ -0,0 +1,100 @@
+
+
+
+
{{ miniWidget.options.layout?.label }}
+
+
+
+
+
+
diff --git a/src/components/widgets/CustomWidgetBase.vue b/src/components/widgets/CustomWidgetBase.vue
new file mode 100644
index 000000000..d8881dcb3
--- /dev/null
+++ b/src/components/widgets/CustomWidgetBase.vue
@@ -0,0 +1,426 @@
+
+
+
+
+
+ mdi-drag
+
+
+
+
+
+
{{ widget.name }}
+
+
+
+
+
+
+
+
+
+ Options
+
+
+ Save
+
+
+ Load
+
+
+
+
+
+
{{ widget.name }}
+
+
+
+
widgetAdded(e, container.name)"
+ >
+
+
+
+
+
+
+
+
+
+
+
widgetAdded(e, container.name)"
+ >
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Custom Widget options
+
+ mdi-close
+
+
+
Name:
+
+
Columns:
+
+
Background color
+
+
Background opacity:
+
+
Background blur:
+
+
+
+
+
+
+
+
+
+
diff --git a/src/stores/widgetManager.ts b/src/stores/widgetManager.ts
index 6f0220b01..709cd221b 100644
--- a/src/stores/widgetManager.ts
+++ b/src/stores/widgetManager.ts
@@ -7,6 +7,7 @@ import { v4 as uuid4 } from 'uuid'
import { computed, onBeforeMount, onBeforeUnmount, Ref, ref, watch } from 'vue'
import {
+ defaultCustomWidgetContainers,
defaultMiniWidgetManagerVars,
defaultProfileVehicleCorrespondency,
defaultWidgetManagerVars,
@@ -16,6 +17,7 @@ import { miniWidgetsProfile } from '@/assets/defaults'
import { useInteractionDialog } from '@/composables/interactionDialog'
import { resetJustMadeKey, useBlueOsStorage } from '@/composables/settingsSyncer'
import { openSnackbar } from '@/composables/snackbar'
+import { useSnackbar } from '@/composables/snackbar'
import { MavType } from '@/libs/connection/m2r/messages/mavlink2rest-enum'
import * as Words from '@/libs/funny-name/words'
import {
@@ -32,6 +34,8 @@ import {
type Profile,
type View,
type Widget,
+ CustomWidgetElement,
+ CustomWidgetElementContainer,
MiniWidgetManagerVars,
validateProfile,
validateView,
@@ -40,6 +44,7 @@ import {
} from '@/types/widgets'
const { showDialog } = useInteractionDialog()
+const { showSnackbar } = useSnackbar()
export const savedProfilesKey = 'cockpit-saved-profiles-v8'
@@ -60,6 +65,167 @@ export const useWidgetManagerStore = defineStore('widget-manager', () => {
)
const _widgetManagerVars: Ref
> = ref({})
const _miniWidgetManagerVars: Ref> = ref({})
+ const isElementsPropsDrawerVisible = ref(false)
+ const elementToShowOnDrawer = ref()
+ const widgetToEdit = ref()
+
+ const editWidgetByHash = (hash: string): Widget | undefined => {
+ widgetToEdit.value = currentProfile.value.views
+ .flatMap((view) => view.widgets)
+ .find((widget) => widget.hash === hash)
+ return widgetToEdit.value
+ }
+
+ const getElementByHash = (hash: string): CustomWidgetElement | undefined => {
+ let customWidgetElement = currentProfile.value.views
+ .flatMap((view) => view.widgets)
+ .filter((widget) => widget.component === WidgetType.CustomWidgetBase)
+ .flatMap((widget) => widget.options.elementContainers)
+ .flatMap((container) => container.elements)
+ .find((element) => element.hash === hash)
+
+ if (customWidgetElement) {
+ return customWidgetElement
+ }
+
+ customWidgetElement = currentProfile.value.views
+ .flatMap((view) => view.miniWidgetContainers || [])
+ .flatMap((container) => container.widgets) // Get all widgets in mini-widget containers
+ .find((miniWidget) => miniWidget.hash === hash) // Look for the mini-widget by its hash
+
+ // Return the found mini-widget or undefined if not found
+ return customWidgetElement
+ }
+
+ const showElementPropsDrawer = (customWidgetElementHash: string): void => {
+ const customWidgetElement = getElementByHash(customWidgetElementHash)
+ if (!customWidgetElement) {
+ alert('element not found')
+ showSnackbar({ variant: 'error', message: 'Could not find element with the given hash.', duration: 3000 })
+ return
+ }
+ elementToShowOnDrawer.value = customWidgetElement
+ isElementsPropsDrawerVisible.value = true
+ }
+
+ const removeElementFromCustomWidget = (elementHash: string): void => {
+ const customWidgetElement = getElementByHash(elementHash)
+ if (!customWidgetElement) {
+ showSnackbar({ variant: 'error', message: 'Could not find element with the given hash.', duration: 3000 })
+ return
+ }
+
+ const customWidget = currentProfile.value.views
+ .flatMap((view) => view.widgets)
+ .filter((widget) => widget.component === WidgetType.CustomWidgetBase)
+ .find((widget) =>
+ widget.options.elementContainers.some((container: CustomWidgetElementContainer) =>
+ container.elements.includes(customWidgetElement)
+ )
+ )
+
+ if (!customWidget) {
+ showSnackbar({
+ variant: 'error',
+ message: 'Could not find the custom widget containing the element.',
+ duration: 3000,
+ })
+ return
+ }
+
+ const customWidgetContainer = customWidget.options.elementContainers.find(
+ (container: CustomWidgetElementContainer) => container.elements.includes(customWidgetElement)
+ )
+ if (!customWidgetContainer) {
+ showSnackbar({
+ variant: 'error',
+ message: 'Could not find the container containing the element.',
+ duration: 3000,
+ })
+ return
+ }
+
+ customWidgetContainer.elements = customWidgetContainer.elements.filter(
+ (element: CustomWidgetElement) => element.hash !== elementHash
+ )
+ }
+
+ const downloadWidgetAsBrw = (widget: Widget): void => {
+ const blob = new Blob([JSON.stringify(widget)], { type: 'application/json;charset=utf-8' })
+ const fileName = `${widget.name}.brw`
+ saveAs(blob, fileName)
+ }
+
+ const loadWidgetFromFile = (widgetHash: string, loadedWidget: Widget): void => {
+ const currentViewWidgets = currentProfile.value.views[currentViewIndex.value].widgets
+ const widgetIndex = currentViewWidgets.findIndex((widget) => widget.hash === widgetHash)
+
+ if (widgetIndex === -1) {
+ showSnackbar({ variant: 'error', message: 'Widget not found with the given hash.', duration: 3000 })
+ return
+ }
+
+ const currentPosition = currentViewWidgets[widgetIndex].position
+
+ reassignHashesToWidget(loadedWidget)
+
+ loadedWidget.position = currentPosition
+ currentViewWidgets[widgetIndex] = loadedWidget
+
+ showSnackbar({ variant: 'success', message: 'Widget loaded successfully with new hash.', duration: 3000 })
+ }
+
+ const reassignHashesToWidget = (widget: Widget): void => {
+ const oldToNewHashMap = new Map()
+
+ const oldWidgetHash = widget.hash
+ widget.hash = uuid4()
+ oldToNewHashMap.set(oldWidgetHash, widget.hash)
+
+ for (const container of widget.options.elementContainers) {
+ for (const element of container.elements) {
+ reassignHashesToElement(element, oldToNewHashMap)
+ }
+ }
+ }
+
+ const reassignHashesToElement = (element: CustomWidgetElement, hashMap: Map): void => {
+ const oldHash = element.hash
+ element.hash = uuid4()
+ hashMap.set(oldHash, element.hash)
+
+ if (element.options && element.options.actionVariable) {
+ const actionVariable = element.options.actionVariable
+ actionVariable.id = `${actionVariable.id}_new`
+ actionVariable.name = `${actionVariable.name}_new`
+ }
+ }
+
+ /**
+ * Updates the options of a custom widget element by its hash.
+ * @param {string} elementHash - The unique identifier of the element.
+ * @param {Record} newOptions - The new options to merge with the existing ones.
+ */
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ const updateElementOptions = (elementHash: string, newOptions: Record): void => {
+ const element = getElementByHash(elementHash)
+ if (element) {
+ element.options = {
+ ...element.options,
+ ...newOptions,
+ }
+ }
+ }
+
+ const allowMovingAndResizing = (widgetHash: string, forcedState: boolean): void => {
+ currentProfile.value.views.forEach((view) => {
+ view.widgets.forEach((widget) => {
+ if (widget.hash === widgetHash) {
+ widgetManagerVars(widgetHash).allowMoving = forcedState
+ }
+ })
+ })
+ }
const widgetManagerVars = (widgetHash: string): WidgetManagerVars => {
if (!_widgetManagerVars.value[widgetHash]) {
@@ -422,8 +588,22 @@ export const useWidgetManagerStore = defineStore('widget-manager', () => {
options: {},
}
+ if (widgetType === WidgetType.CustomWidgetBase) {
+ widget.options = {
+ elementContainers: defaultCustomWidgetContainers,
+ columns: 1,
+ leftColumnWidth: 50,
+ backgroundOpacity: 0.2,
+ backgroundColor: '#FFFFFF',
+ backgroundBlur: 25,
+ }
+ }
+
view.widgets.unshift(widget)
- Object.assign(widgetManagerVars(widget.hash), { ...defaultWidgetManagerVars, ...{ allowMoving: true } })
+ Object.assign(widgetManagerVars(widget.hash), {
+ ...defaultWidgetManagerVars,
+ ...{ allowMoving: true },
+ })
}
/**
@@ -434,6 +614,7 @@ export const useWidgetManagerStore = defineStore('widget-manager', () => {
const view = viewFromWidget(widget)
const index = view.widgets.indexOf(widget)
view.widgets.splice(index, 1)
+ elementToShowOnDrawer.value = undefined
}
/**
@@ -682,6 +863,7 @@ export const useWidgetManagerStore = defineStore('widget-manager', () => {
currentMiniWidgetsProfile,
savedProfiles,
vehicleTypeProfileCorrespondency,
+ allowMovingAndResizing,
loadProfile,
saveProfile,
resetSavedProfiles,
@@ -713,5 +895,14 @@ export const useWidgetManagerStore = defineStore('widget-manager', () => {
visibleAreaMinClearancePixels,
currentTopBarHeightPixels,
currentBottomBarHeightPixels,
+ showElementPropsDrawer,
+ isElementsPropsDrawerVisible,
+ elementToShowOnDrawer,
+ updateElementOptions,
+ removeElementFromCustomWidget,
+ downloadWidgetAsBrw,
+ loadWidgetFromFile,
+ widgetToEdit,
+ editWidgetByHash,
}
})
diff --git a/src/types/widgets.ts b/src/types/widgets.ts
index 23e2f704e..d933562f3 100644
--- a/src/types/widgets.ts
+++ b/src/types/widgets.ts
@@ -1,3 +1,5 @@
+import { CockpitAction } from '@/libs/joystick/protocols/cockpit-actions'
+
import type { Point2D, SizeRect2D } from './general'
/**
@@ -7,8 +9,9 @@ import type { Point2D, SizeRect2D } from './general'
export enum WidgetType {
Attitude = 'Attitude',
Compass = 'Compass',
- DepthHUD = 'DepthHUD',
CompassHUD = 'CompassHUD',
+ CustomWidgetBase = 'CustomWidgetBase',
+ DepthHUD = 'DepthHUD',
IFrame = 'IFrame',
ImageView = 'ImageView',
Map = 'Map',
@@ -41,6 +44,531 @@ export enum MiniWidgetType {
ViewSelector = 'ViewSelector',
}
+/**
+ * Available elements to be used in the Custom Widget creator.
+ * The enum value is equal to the component's filename, without the '.vue' extension
+ */
+export enum CustomWidgetElementType {
+ Button = 'Button',
+ Checkbox = 'Checkbox',
+ Dial = 'Dial',
+ Dropdown = 'Dropdown',
+ Label = 'Label',
+ Slider = 'Slider',
+ Switch = 'Switch',
+}
+
+/**
+ * Available variables to be used in the Custom Widget creator.
+ */
+export enum CustomWidgetType {
+ Button = 'boolean',
+ Checkbox = 'boolean',
+ Dial = 'number',
+ Dropdown = 'string',
+ Label = 'string',
+ Slider = 'number',
+ Switch = 'boolean',
+}
+
+/**
+ * Available containers to be used in the Custom Widget creator.
+ */
+export enum CustomWidgetElementContainers {
+ Left0 = '0-left',
+ Left1 = '1-left',
+ Left2 = '2-left',
+ Left3 = '3-left',
+ Left4 = '4-left',
+ Left5 = '5-left',
+ Left6 = '6-left',
+ Left7 = '7-left',
+ Left8 = '8-left',
+ Left9 = '9-left',
+ Right0 = '0-right',
+ Right1 = '1-right',
+ Right2 = '2-right',
+ Right3 = '3-right',
+ Right4 = '4-right',
+ Right5 = '5-right',
+ Right6 = '6-right',
+ Right7 = '7-right',
+ Right8 = '8-right',
+ Right9 = '9-right',
+}
+
+export type SelectorOption = {
+ /**
+ * The name of the option
+ */
+ name: string
+ /**
+ * The value of the option
+ */
+ value: string
+}
+
+/**
+ * Options for the Cockpit Actions parameters
+ */
+export interface CockpitActionVariable {
+ /**
+ * Parameter ID, equals to initial name of the parameter
+ */
+ id: string
+ /**
+ * Parameter name
+ */
+ name: string
+ /**
+ * Parameter type
+ */
+ type: 'string' | 'boolean' | 'number'
+ /**
+ * Parameter description
+ */
+ description?: string
+}
+
+/**
+ * Options for the Custom Widgets inner elements
+ */
+export type CustomWidgetElementOptions = {
+ /**
+ * Custom widget element - Label
+ */
+ [CustomWidgetElementType.Label]: {
+ /**
+ * Element hash
+ */
+ hash: string
+ /**
+ * Element name
+ */
+ name: string
+ /**
+ * Mark as custom mini widget
+ */
+ isCustomElement: boolean
+ /**
+ * Element options
+ */
+ options: {
+ /**
+ * Last known value of the element
+ */
+ lastValue: string | number | boolean | undefined
+ /**
+ * Variable type
+ */
+ variableType: string
+ /**
+ * Action parameter
+ */
+ actionVariable: CockpitActionVariable
+ /**
+ * The label text
+ */
+ text: string
+ /**
+ * Layout options
+ */
+ layout: {
+ /**
+ * The size of the label's font (in pixels)
+ */
+ textSize: number
+ /**
+ * Alignment of the element
+ */
+ align: 'start' | 'center' | 'end'
+ /**
+ * The weight of the label's font
+ */
+ weight: 'normal' | 'bold' | 'bolder' | 'lighter'
+ /**
+ * The decoration for the label's text
+ */
+ decoration: 'none' | 'underline' | 'line-through' | 'overline'
+ /**
+ * The color of the label's text
+ */
+ color: string
+ }
+ }
+ }
+ /**
+ * Custom widget element - Button
+ */
+ [CustomWidgetElementType.Button]: {
+ /**
+ * Element hash
+ */
+ hash: string
+ /**
+ * Element name
+ */
+ name: string
+ /**
+ * Mark as custom mini widget
+ */
+ isCustomElement: boolean
+ /**
+ * Element options
+ */
+ options: {
+ /**
+ * Last known value of the element
+ */
+ lastValue: string | number | boolean | undefined
+ /**
+ * Variable type
+ */
+ variableType: string
+ /**
+ * Action parameter
+ */
+ cockpitAction: CockpitAction
+ /**
+ * Layout options
+ */
+ layout: {
+ /**
+ * Alignment of the element
+ */
+ align: 'start' | 'center' | 'end'
+ /**
+ * The label of the button
+ */
+ label: string
+ /**
+ * The size of the button
+ */
+ buttonSize: 'small' | 'default' | 'large'
+ /**
+ * The color of the button
+ */
+ backgroundColor: string
+ /**
+ * The color of the button's text
+ */
+ textColor: string
+ /**
+ * The variant of the button
+ */
+ variant: 'text' | 'outlined' | 'flat' | 'elevated' | 'tonal' | 'plain'
+ }
+ }
+ }
+ /**
+ * Custom widget element - Checkbox
+ */
+ [CustomWidgetElementType.Checkbox]: {
+ /**
+ * Element hash
+ */
+ hash: string
+ /**
+ * Element name
+ */
+ name: string
+ /**
+ * Mark as custom mini widget
+ */
+ isCustomElement: boolean
+ /**
+ * Element value
+ */
+ checked: boolean
+ /**
+ * Element options
+ */
+ options: {
+ /**
+ * Last known value of the element
+ */
+ lastValue: string | number | boolean | undefined
+ /**
+ * Variable type
+ */
+ variableType: string
+ /**
+ * Action parameter
+ */
+ actionVariable: CockpitActionVariable
+ /**
+ * Layout props for the element
+ */
+ layout: {
+ /**
+ * Alignment of the element
+ */
+ align: 'start' | 'center' | 'end'
+ /**
+ * The size of the checkbox
+ */
+ color: string
+ /**
+ * The label of the checkbox
+ */
+ label: string
+ }
+ }
+ }
+ /**
+ * Custom widget element - Dial
+ */
+ [CustomWidgetElementType.Dial]: {
+ /**
+ * Element hash
+ */
+ hash: string
+ /**
+ * Element name
+ */
+ name: string
+ /**
+ * Mark as custom mini widget
+ */
+ isCustomElement: boolean
+ /**
+ * Element options
+ */
+ options: {
+ /**
+ * Last known value of the element
+ */
+ lastValue: string | number | boolean | undefined
+ /**
+ * Variable type
+ */
+ variableType: string
+ /**
+ * Action parameter
+ */
+ actionVariable: CockpitActionVariable
+ /**
+ * Layout options
+ */
+ layout: {
+ /**
+ * Alignment of the element
+ */
+ align: 'start' | 'center' | 'end'
+ /**
+ * The size of the dial
+ */
+ size: 'small' | 'medium' | 'large'
+ /**
+ * The color of the dial
+ */
+ knobColor: string
+ /**
+ * The color of the knob's notch
+ */
+ notchColor: string
+ /**
+ * The minimum value of the dial
+ */
+ minValue: number
+ /**
+ * The maximum value of the dial
+ */
+ maxValue: number
+ /**
+ * The step value of the dial
+ */
+ showValue: boolean
+ }
+ }
+ }
+ /**
+ * Custom widget element - Dropdown
+ */
+ [CustomWidgetElementType.Dropdown]: {
+ /**
+ * Element hash
+ */
+ hash: string
+ /**
+ * Element name
+ */
+ name: string
+ /**
+ * Mark as custom mini widget
+ */
+ isCustomElement: boolean
+ /**
+ * Element options
+ */
+ options: {
+ /**
+ * Last known value of the element
+ */
+ lastValue: string | number | boolean | undefined
+ /**
+ * Variable type
+ */
+ variableType: string
+ /**
+ * Action parameter
+ */
+ actionVariable: CockpitActionVariable
+ /**
+ * Last selected value
+ */
+ lastSelected: SelectorOption
+ /**
+ * Layout options
+ */
+ layout: {
+ /**
+ * Alignment of the element
+ */
+ selectorOptions: SelectorOption[]
+ /**
+ * Alignment of the element
+ */
+ align: 'start' | 'center' | 'end'
+ /**
+ * The size of the dropdown
+ */
+ width: number
+ }
+ }
+ }
+ /**
+ * Custom widget element - Slider
+ */
+ [CustomWidgetElementType.Slider]: {
+ /**
+ * Element hash
+ */
+ hash: string
+ /**
+ * Element name
+ */
+ name: string
+ /**
+ * Mark as custom mini widget
+ */
+ isCustomElement: boolean
+ /**
+ * Element options
+ */
+ options: {
+ /**
+ * Last known value of the element
+ */
+ lastValue: string | number | boolean | undefined
+ /**
+ * Variable type
+ */
+ variableType: string
+ /**
+ * Action parameter
+ */
+ actionVariable: CockpitActionVariable
+ /**
+ * Layout options
+ */
+ layout: {
+ /**
+ * Alignment of the element
+ */
+ align: 'start' | 'center' | 'end'
+ /**
+ * The size of the slider
+ */
+ size: 'small' | 'medium' | 'large'
+ /**
+ * The color of the slider
+ */
+ color: string
+ /**
+ * The minimum value of the slider
+ */
+ minValue: number
+ /**
+ * The maximum value of the slider
+ */
+ maxValue: number
+ /**
+ * The step value of the slider
+ */
+ showTooltip: boolean
+ /**
+ * The label of the slider
+ */
+ label: string
+ /**
+ * The width of the label
+ */
+ labelWidth: number
+ }
+ }
+ }
+ /**
+ * Custom widget element - Switch
+ */
+ [CustomWidgetElementType.Switch]: {
+ /**
+ * Element hash
+ */
+ hash: string
+ /**
+ * Element name
+ */
+ name: string
+ /**
+ * Value of the switch
+ */
+ toggled: boolean
+ /**
+ * Mark as custom mini widget
+ */
+ isCustomElement: boolean
+ /**
+ * Element options
+ */
+ options: {
+ /**
+ * Last known value of the element
+ */
+ lastValue: string | number | boolean | undefined
+ /**
+ * Variable type
+ */
+ variableType: string
+ /**
+ * Action parameter
+ */
+ actionVariable: CockpitActionVariable
+ /**
+ * Layout options
+ */
+ layout: {
+ /**
+ * Alignment of the element
+ */
+ align: 'start' | 'center' | 'end'
+ /**
+ * The size of the switch
+ */
+ size: 'small' | 'medium' | 'large'
+ /**
+ * The color of the switch
+ */
+ color: string
+ /**
+ * The label of the switch
+ */
+ label: string
+ }
+ }
+ }
+}
+
/**
* External variables used by the widget manager
*/
@@ -143,6 +671,69 @@ export type MiniWidget = {
options: Record // eslint-disable-line @typescript-eslint/no-explicit-any
}
+export type CustomWidget = {
+ /**
+ * Unique identifier for the widget
+ */
+ hash: string
+ /**
+ * Component type of the widget
+ */
+ component: WidgetType
+ /**
+ * 2D position of the widget (top-left corner)
+ */
+ position: Point2D
+ /**
+ * Size of the widget box
+ */
+ size: SizeRect2D
+ /**
+ * Editable name for the widget
+ */
+ name: string
+ /**
+ * Internal options of the widget
+ */
+ elementContainers: Array<{
+ /**
+ * Editable name for the container
+ */
+ name: CustomWidgetElementContainers
+ /**
+ * Array of elements that are stored in the container
+ */
+ elements: CustomWidgetElement[]
+ }>
+ /**
+ * Internal options of the widget
+ */
+ options: Record // eslint-disable-line @typescript-eslint/no-explicit-any
+}
+
+export type CustomWidgetElement = {
+ /**
+ * Unique identifier for the widget
+ */
+ hash: string
+ /**
+ * Editable name for the widget
+ */
+ name: string
+ /**
+ * Component type of the element
+ */
+ component: CustomWidgetElementType
+ /**
+ * If the element is a custom mini widget
+ */
+ isCustomElement?: boolean
+ /**
+ * Internal options of the widget
+ */
+ options: Record // eslint-disable-line @typescript-eslint/no-explicit-any
+}
+
export type MiniWidgetContainer = {
/**
* Array of widgets that are stored in the container
@@ -154,6 +745,17 @@ export type MiniWidgetContainer = {
name: string
}
+export type CustomWidgetElementContainer = {
+ /**
+ * Array of widgets that are stored in the container
+ */
+ elements: CustomWidgetElement[]
+ /**
+ * Editable name for the container
+ */
+ name: string
+}
+
export type MiniWidgetProfile = {
/**
* Array of views that are stored in the profile