import Dialog from 'sap/m/Dialog';
import type { MetadataOptions } from 'sap/ui/core/Element';
import { AvailableOptions } from '../enums';
import SpreadsheetDialogRenderer from './SpreadsheetDialogRenderer';
import ResourceModel from 'sap/ui/model/resource/ResourceModel';
import ResourceBundle from 'sap/base/i18n/ResourceBundle';
import TextToWorkbookService from '../controller/services/TextToWorkbookService';
import MessageToast from 'sap/m/MessageToast';
import Log from 'sap/base/Log';
/**
* Constructor for a new cc.spreadsheetimporter.v2_4_0.SpreadsheetDialog control.
*
* Some class description goes here.
* @extends Dialog
*
* @constructor
* @public
* @name cc.spreadsheetimporter.v2_4_0.SpreadsheetDialog
*/
export default class SpreadsheetDialog extends Dialog {
dropMessageShown: boolean;
private textToWorkbookService: TextToWorkbookService;
private readonly _onPaste = (event: Event) => this.handlePaste(event);
constructor(id?: string | $SpreadsheetDialogSettings);
constructor(id?: string, settings?: $SpreadsheetDialogSettings);
constructor(id?: string, settings?: $SpreadsheetDialogSettings) {
super(id, settings);
this.dropMessageShown = false;
this.textToWorkbookService = new TextToWorkbookService();
}
static readonly metadata: MetadataOptions = {
properties: {
decimalSeparator: { type: 'string' },
availableOptions: { type: 'string[]' },
component: { type: 'object' }
},
events: {
fileDrop: {
parameters: {
files: { type: 'object[]' }
}
},
dataPaste: {
parameters: {
workbook: { type: 'object' },
type: { type: 'string' },
originalData: { type: 'string' }
}
},
decimalSeparatorChanged: {
parameters: {
decimalSeparator: { type: 'string' }
}
},
availableOptionsChanged: {
parameters: {
availableOptions: { type: 'string[]' }
}
}
}
};
onAfterRendering(event: any) {
super.onAfterRendering(event);
const domRef = this.getDomRef();
// Drag and drop events
domRef.addEventListener('dragover', this.handleDragOver.bind(this), false);
domRef.addEventListener('dragenter', this.handleDragEnter.bind(this), false);
domRef.addEventListener('dragleave', this.handleDragLeave.bind(this), false);
domRef.addEventListener('drop', this.handleFileDrop.bind(this), false);
// Paste events - use stable handler reference on DOM element
domRef.addEventListener('paste', this._onPaste);
}
private handleDragOver(event: any) {
event.preventDefault();
event.stopPropagation();
event.dataTransfer.dropEffect = 'copy';
if (!this.dropMessageShown) {
this.showDropMessage(true);
}
}
private handleDragLeave(event: any) {
// Check if the drag is actually leaving the dialog, not just moving between children
if (!event.currentTarget.contains(event.relatedTarget)) {
this.showDropMessage(false);
}
}
private handleFileDrop(event: any) {
event.preventDefault();
event.stopPropagation();
this.showDropMessage(false);
const files = event.dataTransfer.files;
this.fireFileDrop({ files: files } as SpreadsheetDialog$FileDropEventParameters);
}
private handleDragEnter(event: any) {}
/**
* Handle paste events - improved with better guards and busy state
* @param event - Browser paste event
*/
private async handlePaste(event: Event): Promise {
const clipboardEvent = event as ClipboardEvent;
// Early-exit guard order (check cheap conditions first)
if (!this.isOpen() || !clipboardEvent.isTrusted) {
return;
}
const clipboardData = clipboardEvent.clipboardData;
if (!clipboardData) {
return;
}
// Check if this dialog should handle the paste
const dialogDomRef = this.getDomRef();
const activeElement = document.activeElement;
// Only handle if the paste is relevant to our dialog
if (!dialogDomRef || !dialogDomRef.contains(activeElement as Node)) {
return;
}
// Prevent default paste behavior and stop immediate propagation
clipboardEvent.preventDefault();
clipboardEvent.stopImmediatePropagation();
try {
// Show busy indicator for large data processing
this.setBusy(true);
// Show processing message
const resourceBundle = (this.getModel('i18n') as ResourceModel)?.getResourceBundle() as ResourceBundle;
if (resourceBundle) {
MessageToast.show(resourceBundle.getText('spreadsheetimporter.pasteDetected'));
}
// Process clipboard data using the service
const result = await this.textToWorkbookService.processClipboardData(clipboardData);
if (result.workbook) {
// Get original data for debugging/logging purposes
let originalData = '';
if (result.type === 'text') {
originalData = clipboardData.getData('text/plain');
} else if (result.type === 'file') {
const files = clipboardData.files;
originalData = files.length > 0 ? files[0].name : '';
}
// Fire the dataPaste event
this.fireDataPaste({
workbook: result.workbook,
type: result.type,
originalData: originalData
} as SpreadsheetDialog$DataPasteEventParameters);
// Show success message
if (resourceBundle) {
const messageKey = result.type === 'file' ? 'spreadsheetimporter.pasteFileSuccess' : 'spreadsheetimporter.pasteTextSuccess';
MessageToast.show(resourceBundle.getText(messageKey));
}
} else {
// Show error message - no valid data found
if (resourceBundle) {
MessageToast.show(resourceBundle.getText('spreadsheetimporter.pasteNoData'));
}
}
} catch (error) {
Log.error('Error handling paste event', error as Error, 'SpreadsheetDialog');
// Check for specific error types
const resourceBundle = (this.getModel('i18n') as ResourceModel)?.getResourceBundle() as ResourceBundle;
if (resourceBundle) {
const errorMessage = (error as Error).message;
if (errorMessage === 'pasteTooLarge') {
MessageToast.show(resourceBundle.getText('spreadsheetimporter.pasteTooLarge'));
} else {
MessageToast.show(resourceBundle.getText('spreadsheetimporter.pasteError'));
}
}
} finally {
this.setBusy(false);
}
}
private showDropMessage(show: boolean) {
// Ensure the current state matches the desired visibility
if (this.dropMessageShown === show) {
return; // No change is needed if the state is already correct
}
let dropMessage = this.getDomRef().querySelector('.drop-message');
if (!dropMessage) {
// Create the message element if it doesn't exist
dropMessage = document.createElement('div');
dropMessage.className = 'drop-message';
dropMessage.textContent = ((this.getModel('i18n') as ResourceModel).getResourceBundle() as ResourceBundle).getText(
'spreadsheetimporter.dropMessage'
);
this.getDomRef().appendChild(dropMessage);
}
// Toggle visibility class based on the 'show' parameter
dropMessage.classList.toggle('visible', show);
// Update the flag to reflect the new state
this.dropMessageShown = show;
}
public setDecimalSeparator(sDecimalSeparator: string) {
if (sDecimalSeparator === ',' || sDecimalSeparator === '.') {
this.setProperty('decimalSeparator', sDecimalSeparator);
this.fireDecimalSeparatorChanged({ decimalSeparator: sDecimalSeparator } as SpreadsheetDialog$DecimalSeparatorChangedEventParameters);
return this;
} else {
throw new Error("Decimal separator must be either ',' or '.'");
}
}
public setAvailableOptions(aAvailableOptions: AvailableOptions[]) {
for (let option of aAvailableOptions) {
if (!Object.values(AvailableOptions).includes(option as AvailableOptions)) {
throw new Error('Invalid option: ' + option);
}
}
this.setProperty('availableOptions', aAvailableOptions);
this.fireAvailableOptionsChanged({ availableOptions: aAvailableOptions }) as SpreadsheetDialog$AvailableOptionsChangedEventParameters;
return this;
}
/**
* Fire the dataPaste event
* @param parameters - Event parameters
* @returns this
*/
public fireDataPaste(parameters?: SpreadsheetDialog$DataPasteEventParameters): this {
return this.fireEvent('dataPaste', parameters) as this;
}
exit() {
// Remove event listeners to clean up
const domRef = this.getDomRef();
if (domRef) {
domRef.removeEventListener('dragover', this.handleDragOver.bind(this), false);
domRef.removeEventListener('dragleave', this.handleDragLeave.bind(this), false);
domRef.removeEventListener('drop', this.handleFileDrop.bind(this), false);
// Remove paste event listener using stable reference
domRef.removeEventListener('paste', this._onPaste);
}
// Clean up the drop message element if needed
let dropMessage = domRef?.querySelector('.drop-message');
if (dropMessage) {
dropMessage.remove();
}
this.dropMessageShown = false; // Reset visibility flag
super.exit();
}
static renderer: typeof SpreadsheetDialogRenderer = SpreadsheetDialogRenderer;
}