@fleetbase/ember-ui
Version:
Fleetbase UI provides all the interface components, helpers, services and utilities for building a Fleetbase extension into the Console.
234 lines (209 loc) • 7 kB
JavaScript
import Component from '@glimmer/component';
import { inject as service } from '@ember/service';
import { action } from '@ember/object';
import { task } from 'ember-concurrency';
import { dasherize } from '@ember/string';
import isModel from '@fleetbase/ember-core/utils/is-model';
import getModelName from '@fleetbase/ember-core/utils/get-model-name';
import titleize from 'ember-cli-string-helpers/utils/titleize';
/**
* Component that integrates with the resource-context-panel service
* and renders overlays using the Layout::Resource::Panel component.
*
* @class ResourceContextPanelComponent
* @extends Component
*/
export default class ResourceContextPanelComponent extends Component {
resourceContextPanel;
notifications;
/**
* Map of overlay sizes to actual widths.
* @type {Object}
*/
sizeMap = {
xxs: '400px',
xs: '500px',
sm: '550px',
md: '600px',
lg: '800px',
xl: '1000px',
fullscreen: '100vw',
};
/**
* Map of overlay sizes to resizable settings.
* @type {Object}
*/
resizableMap = {
xs: true,
sm: true,
md: true,
lg: true,
xl: true,
fullscreen: false,
};
/**
* Checks if there are any overlays open.
* @type {Boolean}
*/
get hasOverlays() {
return this.resourceContextPanel.overlays.length > 0;
}
/**
* Returns overlays with computed properties for width, resizable, and tabs.
* @type {Array}
*/
get computedOverlays() {
return this.resourceContextPanel.overlays.map((overlay) => {
const resource = this.findResource(overlay);
return {
...overlay,
resource,
model: resource,
title: overlay.title ?? resource?.name ?? resource?.displayName,
computedWidth: overlay.width ?? this.sizeMap[overlay.size] ?? this.sizeMap.sm,
computedResizable: this.resizableMap[overlay.size] !== undefined ? this.resizableMap[overlay.size] : true,
saveOptions: {
...(overlay.saveOptions ?? {}),
overlay,
},
saveTask: overlay.saveTask ?? (overlay.useDefaultSaveTask ? this.saveTask : null),
computedTabs: overlay.tabs
? overlay.tabs.map((tab) => ({
...tab,
resource,
component: tab.component ?? tab.render,
model: resource,
key: tab.key ?? tab.id ?? dasherize(tab.label ?? tab.title),
id: tab.id ?? tab.key ?? dasherize(tab.label ?? tab.title),
label: tab.label ?? tab.title,
icon: tab.icon,
disabled: tab.disabled || false,
badge: tab.badge,
closable: false,
}))
: [],
};
});
}
/**
* Finds the resource model from a overlay definition
*/
/* eslint-disable no-unused-vars */
findResource(overlay) {
if (!overlay || typeof overlay !== 'object') return null;
if (overlay.resource && isModel(overlay.resource)) return overlay.resource;
if (overlay.model && isModel(overlay.model)) return overlay.model;
for (let [k, v] of Object.entries(overlay)) {
if (isModel(v)) return v;
}
return overlay.resource ?? overlay.model ?? null;
}
getResourceName(resource) {
let resourceName = resource?.name ?? resource?.displayName ?? resource?.display_name;
return resourceName ?? getModelName(resource) ?? 'Resource';
}
getResourceType(resource) {
return titleize(getModelName(resource)) ?? 'Resource';
}
/**
* Handles tab change events from TabNavigation component.
*
* @method handleTabChange
* @param {String} overlayId - The overlay ID
* @param {Object} tab - The selected tab object
* @action
*/
handleTabChange(overlayId, tab) {
this.resourceContextPanel.setActiveTab(overlayId, tab.id);
}
/**
* Closes a specific overlay.
*
* @method closeOverlay
* @param {String} id - The overlay ID
* @action
*/
closeOverlay(id) {
this.resourceContextPanel.close(id);
}
/**
* Closes the active overlay.
*
* @method closeActiveOverlay
* @action
*/
closeActiveOverlay() {
this.resourceContextPanel.close();
}
/**
* Sets the active tab for an overlay.
*
* @method setActiveTab
* @param {String} overlayId - The overlay ID
* @param {String} tabKey - The tab key
* @action
*/
setActiveTab(overlayId, tabKey) {
this.resourceContextPanel.setActiveTab(overlayId, tabKey);
}
/**
* Task for saving a resource.
*
* @task saveTask
* @param {Object} resource - The resource to save
*/
*saveTask(resource, opts = {}) {
const overlayId = opts?.overlay?.id ?? opts?.overlayId;
const isNew = resource?.isNew;
try {
const result = yield resource.save();
this.notifications.success(`${this.getResourceName(resource)} ${isNew ? 'created' : 'updated'} successfully.`);
if (overlayId) {
this.resourceContextPanel.close(overlayId);
}
if (typeof opts.callback === 'function') {
opts.callback(result);
}
return result;
} catch (error) {
this.notifications.serverError(error);
throw error;
}
}
/**
* Handles keyboard events for accessibility.
*
* @method handleKeydown
* @param {KeyboardEvent} event
* @action
*/
handleKeydown(event) {
if (event.key === 'Escape') {
const activeOverlay = this.resourceContextPanel.getActive();
if (activeOverlay && activeOverlay.dismissible) {
this.closeActiveOverlay();
event.preventDefault();
event.stopPropagation();
}
}
}
/**
* Sets up keyboard event listeners when the component is inserted.
*/
constructor() {
super(...arguments);
// Add global keyboard event listener
if (typeof document !== 'undefined') {
document.addEventListener('keydown', this.handleKeydown);
}
}
/**
* Cleans up event listeners when the component is destroyed.
*/
willDestroy() {
super.willDestroy();
if (typeof document !== 'undefined') {
document.removeEventListener('keydown', this.handleKeydown);
}
}
}