diff --git a/src/components/configuration/ActionLinkConfig.vue b/src/components/configuration/ActionLinkConfig.vue new file mode 100644 index 000000000..6d101f20d --- /dev/null +++ b/src/components/configuration/ActionLinkConfig.vue @@ -0,0 +1,144 @@ + + + diff --git a/src/components/configuration/HttpRequestActionConfig.vue b/src/components/configuration/HttpRequestActionConfig.vue index e2830b9ba..42f823bf4 100644 --- a/src/components/configuration/HttpRequestActionConfig.vue +++ b/src/components/configuration/HttpRequestActionConfig.vue @@ -20,7 +20,8 @@ required variant="outlined" density="compact" - > + theme="dark" + /> + theme="dark" + /> diff --git a/src/libs/actions/action-links.ts b/src/libs/actions/action-links.ts new file mode 100644 index 000000000..fbf77b587 --- /dev/null +++ b/src/libs/actions/action-links.ts @@ -0,0 +1,149 @@ +import { listenDataLakeVariable, unlistenDataLakeVariable } from '@/libs/actions/data-lake' +import { executeActionCallback } from '@/libs/joystick/protocols/cockpit-actions' + +/** + * Interface representing a link between an action and data-lake variables + */ +interface ActionLink { + /** The ID of the action */ + actionId: string + /** The type of the action */ + actionType: string + /** Array of data-lake variable IDs to watch */ + variables: string[] + /** Minimum time (in ms) between consecutive action executions */ + minInterval: number + /** Timestamp of the last execution */ + lastExecutionTime: number +} + +const actionLinks: Record = {} +const listenerIds: Record = {} + +/** + * Save a new action link configuration and set up the watchers + * @param {string} actionId The ID of the action to link + * @param {string} actionType The type of the action + * @param {string[]} variables Array of data-lake variable IDs to watch + * @param {number} minInterval Minimum time (in ms) between consecutive action executions + */ +export const saveActionLink = ( + actionId: string, + actionType: string, + variables: string[], + minInterval: number +): void => { + // Remove any existing link for this action + removeActionLink(actionId) + + // Save the new link configuration + actionLinks[actionId] = { + actionId, + actionType, + variables, + minInterval, + lastExecutionTime: 0, + } + + // Set up listeners for each variable + listenerIds[actionId] = variables.map((variableId) => + listenDataLakeVariable(variableId, () => { + executeLinkedAction(actionId) + }) + ) + + saveLinksToPersistentStorage() +} + +/** + * Remove an action link and clean up its watchers + * @param {string} actionId The ID of the action to unlink + */ +export const removeActionLink = (actionId: string): void => { + const link = actionLinks[actionId] + if (!link) return + + // Remove all listeners + if (listenerIds[actionId]) { + link.variables.forEach((variableId, index) => { + unlistenDataLakeVariable(variableId, listenerIds[actionId][index]) + }) + delete listenerIds[actionId] + } + + delete actionLinks[actionId] + + saveLinksToPersistentStorage() +} + +/** + * Get the link configuration for an action + * @param {string} actionId The ID of the action + * @returns {ActionLink | null} The link configuration if it exists, null otherwise + */ +export const getActionLink = (actionId: string): ActionLink | null => { + return actionLinks[actionId] || null +} + +/** + * Execute a linked action, respecting the minimum interval between executions + * @param {string} actionId The ID of the action to execute + */ +const executeLinkedAction = (actionId: string): void => { + const link = actionLinks[actionId] + if (!link) return + + const now = Date.now() + if (now - link.lastExecutionTime >= link.minInterval) { + link.lastExecutionTime = now + executeActionCallback(actionId) + } +} + +/** + * Get all action links + * @returns {Record} Record of all action links + */ +export const getAllActionLinks = (): Record => { + return { ...actionLinks } +} + +// Load saved links from localStorage on startup +const loadSavedLinks = (): void => { + try { + const savedLinks = localStorage.getItem('cockpit-action-links') + if (savedLinks) { + const links = JSON.parse(savedLinks) as Record> + Object.entries(links).forEach(([actionId, link]) => { + saveActionLink(actionId, link.actionType, link.variables, link.minInterval) + }) + } + } catch (error) { + console.error('Failed to load saved action links:', error) + } +} + +// Save links to localStorage when they change +const saveLinksToPersistentStorage = (): void => { + try { + // Don't save the lastExecutionTime + const linksToSave = Object.entries(actionLinks).reduce( + (acc, [id, link]) => ({ + ...acc, + [id]: { + actionId: link.actionId, + actionType: link.actionType, + variables: link.variables, + minInterval: link.minInterval, + }, + }), + {} + ) + localStorage.setItem('cockpit-action-links', JSON.stringify(linksToSave)) + } catch (error) { + console.error('Failed to save action links:', error) + } +} + +// Initialize +loadSavedLinks() diff --git a/src/styles/global.css b/src/styles/global.css index f5604b024..03477ac82 100644 --- a/src/styles/global.css +++ b/src/styles/global.css @@ -52,11 +52,15 @@ ::-webkit-scrollbar-thumb:hover { background-color: hsla(0, 0%, 0%, 0.411); } -.v-text-field input { - background-color: transparent; +.v-text-field input { + background-color: transparent; + border: none !important; + box-shadow: none !important; } .v-field__input { background-color: transparent; + border: none !important; + box-shadow: none !important; } select:focus { outline: none; @@ -68,4 +72,4 @@ select:focus { @keyframes highlightBackground { 0% { background-color: rgba(255, 234, 43, 0.438); } 100% { background-color: transparent; } - } \ No newline at end of file +} diff --git a/src/types/cockpit-actions.ts b/src/types/cockpit-actions.ts new file mode 100644 index 000000000..0d2ecf234 --- /dev/null +++ b/src/types/cockpit-actions.ts @@ -0,0 +1,40 @@ +/** + * Custom action types + */ +export enum customActionTypes { + httpRequest = 'http-request', + mavlinkMessage = 'mavlink-message', + javascript = 'javascript', +} + +/** + * Custom action types names + */ +export const customActionTypesNames: Record = { + [customActionTypes.httpRequest]: 'HTTP Request', + [customActionTypes.mavlinkMessage]: 'MAVLink Message', + [customActionTypes.javascript]: 'JavaScript', +} + +/** + * Represents the configuration of a custom action + */ +export interface ActionConfig { + /** + * Action ID + */ + id: string + /** + * Action name + */ + name: string + /** + * Action type + */ + type: customActionTypes + /** + * Action configuration + * Specific to the action type + */ + config: any +} diff --git a/src/views/ConfigurationActionsView.vue b/src/views/ConfigurationActionsView.vue index 7b9952c4d..d57bf65c1 100644 --- a/src/views/ConfigurationActionsView.vue +++ b/src/views/ConfigurationActionsView.vue @@ -2,10 +2,7 @@