diff --git a/src/assets/defaults.ts b/src/assets/defaults.ts index af46b6ed8..332273542 100644 --- a/src/assets/defaults.ts +++ b/src/assets/defaults.ts @@ -37,6 +37,89 @@ export const defaultMiniWidgetManagerVars: MiniWidgetManagerVars = { highlighted: false, } +export const defaultCustomWidgetContainers = [ + { + name: '0-left', + elements: [], + }, + { + name: '1-left', + elements: [], + }, + { + name: '2-left', + elements: [], + }, + { + name: '3-left', + elements: [], + }, + { + name: '4-left', + elements: [], + }, + { + name: '5-left', + elements: [], + }, + { + name: '6-left', + elements: [], + }, + { + name: '7-left', + elements: [], + }, + { + name: '8-left', + elements: [], + }, + { + name: '9-left', + elements: [], + }, + { + name: '0-right', + elements: [], + }, + { + name: '1-right', + elements: [], + }, + { + name: '2-right', + elements: [], + }, + { + name: '3-right', + elements: [], + }, + { + name: '4-right', + elements: [], + }, + { + name: '5-right', + elements: [], + }, + { + name: '6-right', + elements: [], + }, + { + name: '7-right', + elements: [], + }, + { + name: '8-right', + elements: [], + }, + { + name: '9-right', + elements: [], + }, +] + const hostname = window.location.hostname export const defaultBlueOsAddress = 'http://blueos-avahi.local' export const defaultGlobalAddress = !hostname || hostname == 'localhost' ? defaultBlueOsAddress : hostname diff --git a/src/assets/widgets/CustomWidgetBase.png b/src/assets/widgets/CustomWidgetBase.png new file mode 100644 index 000000000..032ac7f12 Binary files /dev/null and b/src/assets/widgets/CustomWidgetBase.png differ diff --git a/src/components/EditMenu.vue b/src/components/EditMenu.vue index 63aef0f46..d3c4d48eb 100644 --- a/src/components/EditMenu.vue +++ b/src/components/EditMenu.vue @@ -152,7 +152,7 @@

Widget type:

@@ -453,7 +453,7 @@ theme="dark" variant="filled" density="compact" - :items="['Regular', 'Mini']" + :items="['Regular', 'Mini', 'Input']" class="bg-[#27384255] 2xl:scale-100 scale-[80%]" hide-details @change="widgetMode = $event" @@ -463,17 +463,18 @@
To be placed on the main view area
-
- Click or drag to add -
+
(Drag card to add)
To be placed on the top and bottom bars
-
- (Drag card in place to add) +
(Drag card to add)
+
+ Add widget base +
@@ -491,7 +492,7 @@ @dragstart="onRegularWidgetDragStart" @dragend="onRegularWidgetDragEnd(widgetType)" > - + @@ -522,19 +522,49 @@ id="mini-widget-card" :ref="(el) => (miniWidgetContainers[miniWidget.component] = el as HTMLElement)" :key="miniWidget.hash" - class="flex flex-col items-center justify-between w-full rounded-md bg-[#273842] hover:brightness-125 h-[90%] aspect-square cursor-pointer elevation-4 overflow-clip pointer-events-none" + class="flex flex-col items-center w-full justify-between rounded-md bg-[#273842] hover:brightness-125 h-[90%] aspect-square cursor-pointer elevation-4 overflow-clip" :draggable="false" >
-
+
+ +
+
+
+ {{ + miniWidget.name.replace(/([a-z])([A-Z])/g, '$1 $2').replace(/^./, (str) => str.toUpperCase()) || + 'Very generic indicator' + }} +
+
+
+
+
+
+
+
-
+
{{ miniWidget.name.replace(/([a-z])([A-Z])/g, '$1 $2').replace(/^./, (str) => str.toUpperCase()) || - 'Very Generic Indicator' + 'Very generic indicator' }}
@@ -587,6 +617,21 @@ + +
+ +
+
+ + diff --git a/src/components/MiniWidgetContainer.vue b/src/components/MiniWidgetContainer.vue index ff481712c..0477b3412 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" > -
+
@@ -36,12 +36,12 @@
-
+
-
+
@@ -146,6 +146,7 @@ const handleDeleteWidget = (event: DraggableEvent): void => { // Remove miniWidget variableName from Logged variables list CurrentlyLoggedVariables.removeVariable(widgetData.options.displayName) } + widgetStore.elementToShowOnDrawer = undefined trashList.value = [] } diff --git a/src/components/MiniWidgetInstantiator.vue b/src/components/MiniWidgetInstantiator.vue index be0f0c2b7..6acc253e8 100644 --- a/src/components/MiniWidgetInstantiator.vue +++ b/src/components/MiniWidgetInstantiator.vue @@ -3,37 +3,51 @@ 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 @@ + + + + + diff --git a/src/components/custom-widget-elements/Checkbox.vue b/src/components/custom-widget-elements/Checkbox.vue new file mode 100644 index 000000000..91e9a2f7e --- /dev/null +++ b/src/components/custom-widget-elements/Checkbox.vue @@ -0,0 +1,101 @@ + + + + + diff --git a/src/components/custom-widget-elements/Dial.vue b/src/components/custom-widget-elements/Dial.vue new file mode 100644 index 000000000..3fa8e19ff --- /dev/null +++ b/src/components/custom-widget-elements/Dial.vue @@ -0,0 +1,251 @@ + + + + + diff --git a/src/components/custom-widget-elements/Dropdown.vue b/src/components/custom-widget-elements/Dropdown.vue new file mode 100644 index 000000000..7b0f49bc8 --- /dev/null +++ b/src/components/custom-widget-elements/Dropdown.vue @@ -0,0 +1,140 @@ + + + + + diff --git a/src/components/custom-widget-elements/Label.vue b/src/components/custom-widget-elements/Label.vue new file mode 100644 index 000000000..25ffdb2d6 --- /dev/null +++ b/src/components/custom-widget-elements/Label.vue @@ -0,0 +1,100 @@ + + + + + diff --git a/src/components/custom-widget-elements/Slider.vue b/src/components/custom-widget-elements/Slider.vue new file mode 100644 index 000000000..0bd14395f --- /dev/null +++ b/src/components/custom-widget-elements/Slider.vue @@ -0,0 +1,142 @@ + + + + + diff --git a/src/components/custom-widget-elements/Switch.vue b/src/components/custom-widget-elements/Switch.vue new file mode 100644 index 000000000..ff1173731 --- /dev/null +++ b/src/components/custom-widget-elements/Switch.vue @@ -0,0 +1,101 @@ + + + + + diff --git a/src/components/widgets/CustomWidgetBase.vue b/src/components/widgets/CustomWidgetBase.vue new file mode 100644 index 000000000..bae6170a4 --- /dev/null +++ b/src/components/widgets/CustomWidgetBase.vue @@ -0,0 +1,480 @@ + + + + + diff --git a/src/stores/widgetManager.ts b/src/stores/widgetManager.ts index 6f0220b01..4d83a07e9 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,149 @@ 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 miniWidgetLastValues = useBlueOsStorage>('cockpit-mini-widget-last-values', {}) + + 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) + + return customWidgetElement + } + + const showElementPropsDrawer = (customWidgetElementHash: string): void => { + const customWidgetElement = getElementByHash(customWidgetElementHash) + if (!customWidgetElement) { + 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) { + throw new Error('Could not find element with the given hash.') + } + + 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) { + throw new Error('Could not find the custom widget containing the element.') + } + + const customWidgetContainer = customWidget.options.elementContainers.find( + (container: CustomWidgetElementContainer) => container.elements.includes(customWidgetElement) + ) + if (!customWidgetContainer) { + throw new Error('Could not find the container containing the element.') + } + + customWidgetContainer.elements = customWidgetContainer.elements.filter( + (element: CustomWidgetElement) => element.hash !== elementHash + ) + } + + 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 +570,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 +596,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 } /** @@ -664,6 +827,15 @@ export const useWidgetManagerStore = defineStore('widget-manager', () => { }) }) + const setMiniWidgetLastValue = (miniWidgetHash: string, lastValue: any): void => { + miniWidgetLastValues.value[miniWidgetHash] = structuredClone(lastValue) + } + + const getMiniWidgetLastValue = (miniWidgetHash: string): any => { + const lastValue = miniWidgetLastValues.value[miniWidgetHash] + return lastValue === undefined ? undefined : structuredClone(lastValue) + } + // Reassign hashes to profiles using old ones - TODO: Remove for 1.0.0 release Object.values(savedProfiles.value).forEach((profile) => { // If the profile is a correspondent of a cockpit default one, use the correspondent hash @@ -682,6 +854,7 @@ export const useWidgetManagerStore = defineStore('widget-manager', () => { currentMiniWidgetsProfile, savedProfiles, vehicleTypeProfileCorrespondency, + allowMovingAndResizing, loadProfile, saveProfile, resetSavedProfiles, @@ -713,5 +886,15 @@ export const useWidgetManagerStore = defineStore('widget-manager', () => { visibleAreaMinClearancePixels, currentTopBarHeightPixels, currentBottomBarHeightPixels, + showElementPropsDrawer, + isElementsPropsDrawerVisible, + elementToShowOnDrawer, + updateElementOptions, + removeElementFromCustomWidget, + loadWidgetFromFile, + widgetToEdit, + editWidgetByHash, + setMiniWidgetLastValue, + getMiniWidgetLastValue, } }) diff --git a/src/types/widgets.ts b/src/types/widgets.ts index 23e2f704e..62da93c31 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,478 @@ 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 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: { + /** + * Variable type + */ + variableType: 'string' | 'boolean' | 'number' + /** + * 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: { + /** + * Variable type + */ + variableType: 'string' | 'boolean' | 'number' + /** + * 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 options + */ + options: { + /** + * Variable type + */ + variableType: 'string' | 'boolean' | 'number' + /** + * 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: { + /** + * Variable type + */ + variableType: 'string' | 'boolean' | 'number' + /** + * 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: { + /** + * Variable type + */ + variableType: 'string' | 'boolean' | 'number' + /** + * 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: { + /** + * Variable type + */ + variableType: 'string' | 'boolean' | 'number' + /** + * 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 + /** + * Mark as custom mini widget + */ + isCustomElement: boolean + /** + * Element options + */ + options: { + /** + * Variable type + */ + variableType: 'string' | 'boolean' | 'number' + /** + * Action parameter + */ + actionVariable: CockpitActionVariable + /** + * Layout options + */ + layout: { + /** + * Alignment of the element + */ + align: 'start' | 'center' | 'end' + /** + * The color of the switch + */ + color: string + /** + * The label of the switch + */ + label: string + } + } + } +} + /** * External variables used by the widget manager */ @@ -143,6 +618,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 +692,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