diff --git a/CHANGELOG.md b/CHANGELOG.md index b3a3f89..67ebd2f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,23 @@ All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. +### [0.3.5](https://ui5-community///compare/v0.3.4...v0.3.5) (2025-01-21) + + +### Features + +* **no-record:** automatic nav to main if no steps were recorded ([b433e54](https://ui5-community///commit/b433e54c47cef4aa758b907bdd13407d8653f02d)) + + +### Bug Fixes + +* **chrome-130:** fixed the issue with dynamically loaded scripts ([0a1c18b](https://ui5-community///commit/0a1c18b73f719d7ee49b1fab9e166520a456a3af)) +* **code-gen:** fixed wrong indentation within generated sources ([446f72c](https://ui5-community///commit/446f72c6b2c56b93cf56b38fc303c74c786f2bf7)) +* **content-inject:** fixed src type within content-inject ([76107ca](https://ui5-community///commit/76107cad4514df1ec3e494b878f6a8977cb26f71)) +* **settings:** changed structure of settings dialog ([d5d0c6f](https://ui5-community///commit/d5d0c6f59c0327e70ae2d305b0c61d049e0643c3)) +* **ui5:** fixed Typos and misspellings after version upgrade ([5f25d78](https://ui5-community///commit/5f25d788833c2dc6c3099f320824c679febf9d06)) +* **unnecessary:** removed unnecessary type from style ([9afb553](https://ui5-community///commit/9afb553d3f5919ddaf5c3d70f777040ec853a9e9)) + ### [0.3.4](https://ui5-community///compare/v0.3.1...v0.3.4) (2024-12-20) diff --git a/package-lock.json b/package-lock.json index f8e0636..71926d7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "com.ui5.journeyrecorder", - "version": "0.3.4", + "version": "0.3.5", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "com.ui5.journeyrecorder", - "version": "0.3.4", + "version": "0.3.5", "license": "Apache-2.0", "dependencies": { "client-zip": "^2.4.6", diff --git a/package.json b/package.json index 8265a72..6ef88f5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "com.ui5.journeyrecorder", - "version": "0.3.4", + "version": "0.3.5", "description": "UI5 Application: com.ui5.journeyrecorder", "author": "Adrian Marten", "license": "Apache-2.0", @@ -15,6 +15,7 @@ "test": "npm run lint && npm run karma-ci-cov", "wdi5": "wdio run ./webapp/test/e2e/\\wdio.conf.ts", "deployBuild": "node ./utils/deployBuild.js", + "deployBuild-keep": "node ./utils/deployBuild.js --keep", "prepare": "husky", "commit": "npx commit", "changelog": "standard-version" diff --git a/webapp/controller/BaseController.ts b/webapp/controller/BaseController.ts index c3ebc69..f451257 100644 --- a/webapp/controller/BaseController.ts +++ b/webapp/controller/BaseController.ts @@ -8,12 +8,11 @@ import Router from "sap/ui/core/routing/Router"; import History from "sap/ui/core/routing/History"; import UI5Element from "sap/ui/core/Element"; import Dialog from "sap/m/Dialog"; +import Fragment from "sap/ui/core/Fragment"; import Event from "sap/ui/base/Event"; import JSONModel from "sap/ui/model/json/JSONModel"; import SettingsStorageService, { AppSettings } from "../service/SettingsStorage.service"; import { TestFrameworks } from "../model/enum/TestFrameworks"; -import { Themes } from "../model/enum/Themes"; -import Theming from "sap/ui/core/Theming"; import { ConnectionStatus } from "../model/enum/ConnectionStatus"; import { IconColor, ValueState } from "sap/ui/core/library"; import { ButtonType, DialogType } from "sap/m/library"; @@ -22,6 +21,7 @@ import Text from "sap/m/Text"; import BusyIndicator from "sap/ui/core/BusyIndicator"; import { ChromeExtensionService } from "../service/ChromeExtension.service"; import MessageToast from "sap/m/MessageToast"; +import XMLView from "sap/ui/core/mvc/XMLView"; /** * @namespace com.ui5.journeyrecorder.controller @@ -30,6 +30,14 @@ export default abstract class BaseController extends Controller { protected settingsDialog: UI5Element; protected _unsafeDialog: Dialog; + private _dialogs: Record; + + private _fragments: Record; + /** * Convenience method for accessing the component of the controller's view. * @returns The component of the controller's view @@ -101,79 +109,7 @@ export default abstract class BaseController extends Controller { } async onOpenSettingsDialog() { - if (!this.settingsDialog) { - this.settingsDialog = await this.loadFragment({ - name: "com.ui5.journeyrecorder.fragment.SettingsDialog" - }) as UI5Element; - this.getView().addDependent(this.settingsDialog); - } - (this.settingsDialog as Dialog).open(); - } - - onCloseDialog(oEvent: Event) { - const closeReason = (oEvent.getSource() as unknown as { data: (s: string) => string }).data("settingsDialogClose"); - if (closeReason === 'save') { - (this.getModel("settings") as JSONModel).getData(); - void SettingsStorageService.save((this.getModel("settings") as JSONModel).getData() as AppSettings); - } else { - void SettingsStorageService.getSettings().then((settings: AppSettings) => { - (this.getModel("settings") as JSONModel).setData(settings); - }) - } - (this.settingsDialog as Dialog).close(); - } - - onDelaySelect(oEvent: Event) { - const index = oEvent.getParameter("selectedIndex" as never); - switch (index) { - case 0: - (this.getModel("settings") as JSONModel).setProperty('/replayDelay', 0.5); - break; - case 1: - (this.getModel("settings") as JSONModel).setProperty('/replayDelay', 1.0); - break; - case 2: - (this.getModel("settings") as JSONModel).setProperty('/replayDelay', 2.0); - break; - default: - (this.getModel("settings") as JSONModel).setProperty('/replayDelay', 0.5); - } - } - - onFrameworkSelect(oEvent: Event) { - const index = oEvent.getParameter("selectedIndex" as never); - switch (index) { - case 0: - (this.getModel("settings") as JSONModel).setProperty('/testFramework', TestFrameworks.OPA5); - break; - case 1: - (this.getModel("settings") as JSONModel).setProperty('/testFramework', TestFrameworks.WDI5); - break; - default: - (this.getModel("settings") as JSONModel).setProperty('/testFramework', TestFrameworks.OPA5); - } - } - - onThemeSelect(oEvent: Event) { - const index = oEvent.getParameter("selectedIndex" as never); - switch (index) { - case 1: - (this.getModel("settings") as JSONModel).setProperty('/theme', Themes.EVENING_HORIZON); - break; - case 2: - (this.getModel("settings") as JSONModel).setProperty('/theme', Themes.QUARTZ_LIGHT); - break; - case 3: - (this.getModel("settings") as JSONModel).setProperty('/theme', Themes.QUARTZ_DARK); - break; - default: - (this.getModel("settings") as JSONModel).setProperty('/theme', Themes.MORNING_HORIZON); - } - Theming.setTheme((this.getModel("settings") as JSONModel).getProperty('/theme') as string); - } - - compareProps(args: unknown[]) { - return args[0] === args[1]; + await this.openDialog("Settings"); } setConnecting() { @@ -266,4 +202,85 @@ export default abstract class BaseController extends Controller { MessageToast.show('Disconnected', { duration: 500 }); } } + + protected openDialog(sDialogName: string, oData?: Record): Promise | void> { + if (!this._dialogs) { + this._dialogs = {}; + } + + return new Promise(async (resolve, reject) => { + if (!this._dialogs[sDialogName]) { + const oDialog = new Dialog({ + showHeader: false + }); + this.getView().addDependent(oDialog); + const oView = await this.getOwnerComponent().runAsOwner(async () => { + return await XMLView.create({ + viewName: `com.ui5.journeyrecorder.view.dialogs.${sDialogName}` + }); + }); + const oController = oView.getController(); + oDialog.addContent(oView); + + this._dialogs[sDialogName] = { + dialog: oDialog, + view: oView, + controller: oController + } + } + const oDialogCompound = this._dialogs[sDialogName]; + if (oData) { + oDialogCompound.view.setModel(new JSONModel(oData), "importData"); + } + + if (oDialogCompound.controller.settings.initialHeight) { + oDialogCompound.dialog.setContentHeight(oDialogCompound.controller.settings.initialHeight); + } + + if (oDialogCompound.controller.settings.initialWidth) { + oDialogCompound.dialog.setContentWidth(oDialogCompound.controller.settings.initialWidth); + } + + const beforeClose = (oEvent: Event) => { + oDialogCompound.dialog.detachBeforeClose(beforeClose); + const pars = oEvent.getParameters() as Record; + oDialogCompound.dialog.close(); + + if (pars.status === "Success") { + if (pars.data) { + resolve(pars.data as Record); + } else { + resolve(); + } + } else { + reject(); + } + }; + + oDialogCompound.dialog.attachBeforeClose(beforeClose); + + oDialogCompound.dialog.open(); + }) + } + + protected async openFragment(sFragmentName: string, sFragmentId?: string): Promise { + if (!sFragmentName) { + throw new Error("At least the Fragment-Name is needed!"); + } + + if (!this._fragments) { + this._fragments = {}; + } + + if (!this._fragments[sFragmentName]) { + const oFragmentDialog = await Fragment.load({ + id: sFragmentId || `${sFragmentName}_id`, + name: `com.ui5.journeyrecorder.view.dialogs.${sFragmentName}`, + controller: this + }) + this.getView().addDependent(oFragmentDialog as UI5Element); + } + + this._fragments[sFragmentName].open(); + } } diff --git a/webapp/controller/JourneyPage.controller.ts b/webapp/controller/JourneyPage.controller.ts index 7ada8b7..3f9149c 100644 --- a/webapp/controller/JourneyPage.controller.ts +++ b/webapp/controller/JourneyPage.controller.ts @@ -404,17 +404,23 @@ export default class JourneyPage extends BaseController { const ui5Version = await this._requestUI5Version(); const data = this.model.getData() as Partial; data.ui5Version = ui5Version; - const journey = JourneyStorageService.createJourneyFromRecording(data); - ChromeExtensionService.getInstance().unregisterRecordingWebsocket( - // eslint-disable-next-line @typescript-eslint/unbound-method - this._onStepRecord, - this - ); - await ChromeExtensionService.getInstance().disableRecording(); - BusyIndicator.hide(); - this.model.setData(journey); - (this.getModel('journeyControl') as JSONModel).setProperty('/unsafed', true); - this._generateCode(journey); + if (data.steps && data.steps.length > 0) { + const journey = JourneyStorageService.createJourneyFromRecording(data); + ChromeExtensionService.getInstance().unregisterRecordingWebsocket( + // eslint-disable-next-line @typescript-eslint/unbound-method + this._onStepRecord, + this + ); + await ChromeExtensionService.getInstance().disableRecording(); + BusyIndicator.hide(); + this.model.setData(journey); + (this.getModel('journeyControl') as JSONModel).setProperty('/unsafed', true); + this._generateCode(journey); + } else { + await ChromeExtensionService.getInstance().disableRecording(); + BusyIndicator.hide(); + this.getRouter().navTo("main"); + } } private _generateCode(journey: Journey) { @@ -483,8 +489,12 @@ export default class JourneyPage extends BaseController { this.setConnected(); void ChromeExtensionService.getInstance().focusTab(tab); BusyIndicator.hide(); - await this._openRecordingDialog(); MessageToast.show('Connected'); + try { + await this._openRecordingDialog(); + } catch (oError) { + + } }).catch(() => { BusyIndicator.hide(); }); diff --git a/webapp/controller/dialogs/BaseDialogController.ts b/webapp/controller/dialogs/BaseDialogController.ts new file mode 100644 index 0000000..74486dc --- /dev/null +++ b/webapp/controller/dialogs/BaseDialogController.ts @@ -0,0 +1,30 @@ +import Event from "sap/ui/base/Event"; +import BaseController from "../BaseController"; +import Dialog from "sap/m/Dialog"; + +/** + * @namespace com.ui5.journeyrecorder.controller.dialogs + */ +export default class BaseDialogController extends BaseController { + closeBySuccess(oPressEvent: Event, data?: Record) { + const oResult: { + origin: unknown, + result: "Confirm" | "Abort", + data?: Record + } = { + origin: oPressEvent.getSource(), + result: "Confirm", + }; + if (data) { + oResult.data = data; + } + (this.getView().getParent() as Dialog).fireBeforeClose(oResult) + } + + closeByAbort(oPressEvent: Event) { + (this.getView().getParent() as Dialog).fireBeforeClose({ + origin: oPressEvent.getSource(), + result: "Abort" + }) + } +} \ No newline at end of file diff --git a/webapp/controller/dialogs/Settings.controller.ts b/webapp/controller/dialogs/Settings.controller.ts new file mode 100644 index 0000000..5603123 --- /dev/null +++ b/webapp/controller/dialogs/Settings.controller.ts @@ -0,0 +1,35 @@ +import BaseDialogController from "./BaseDialogController"; +import { Themes } from "../../model/enum/Themes"; +import Theming from "sap/ui/core/Theming"; +import JSONModel from "sap/ui/model/json/JSONModel"; +import SettingsStorageService, { AppSettings } from "../../service/SettingsStorage.service"; +import ListItem from "sap/ui/core/ListItem"; +import Event from "sap/ui/base/Event"; +import Dialog from "sap/m/Dialog"; + +/** + * @namespace com.ui5.journeyrecorder.controller.dialogs + */ +export default class Settings extends BaseDialogController { + settings = { + initialHeight: '91vh', + initialWidth: '30rem' + } + + onThemeSelect(oEvent: Event) { + const oItem = oEvent.getParameter("selectedItem" as never) as ListItem; + const oModel = this.getModel("settings") as JSONModel; + oModel.setProperty('/theme', oItem.getKey()); + Theming.setTheme(oItem.getKey()); + } + + onCloseDialog(oEvent: Event, bSave: boolean) { + if (bSave) { + void SettingsStorageService.save((this.getModel("settings") as JSONModel).getData() as AppSettings); + } + (this.getView().getParent() as Dialog).fireBeforeClose({ + origin: oEvent.getSource(), + result: bSave ? 'Confirm' : 'Reject' + }); + } +} \ No newline at end of file diff --git a/webapp/service/SettingsStorage.service.ts b/webapp/service/SettingsStorage.service.ts index 1e9484d..47f7d96 100644 --- a/webapp/service/SettingsStorage.service.ts +++ b/webapp/service/SettingsStorage.service.ts @@ -30,8 +30,10 @@ export default class SettingsStorageService { public static async getSettings(): Promise { const values = await chrome.storage.local.get('settings'); - if (values['settings']) { + if (values['settings'] && typeof values['settings'] === 'string') { return JSON.parse(values.settings as string) as AppSettings; + } else if (values['settings'] && typeof values['settings'] === 'object') { + return values.settings as AppSettings; } else { return SettingsStorageService.getDefaults(); } diff --git a/webapp/view/dialogs/Settings.view.xml b/webapp/view/dialogs/Settings.view.xml new file mode 100644 index 0000000..25a1039 --- /dev/null +++ b/webapp/view/dialogs/Settings.view.xml @@ -0,0 +1,127 @@ + + + + + + + </contentLeft> + <contentRight> + <ObjectStatus + title="App Version" + text="{/appVersion}" + /> + </contentRight> + </Bar> + </customHeader> + <content> + <VBox class="sapUiSmallMargin"> + <form:SimpleForm id="Settings-Form-General" + title="General" + editable="true" + layout="ResponsiveGridLayout" + columnsM="2" + columnsXL="2" + backgroundDesign="Transparent" + class="dialog-form"> + <!-- Section 1 --> + <c:Title text="Tab Handling"></c:Title> + <VBox> + <CheckBox selected="{settings>/showUI5only}" + text="Show only UI5 running Tabs" + width="100%" useEntireWidth="true" /> + <CheckBox selected="{settings>/reloadPageDefault}" + text="Page reload at inject" + width="100%" useEntireWidth="true" /> + </VBox> + <c:Title text="Theme" /> + <Select selectedKey="{settings>/theme}" change="onThemeSelect"> + <items> + <c:ListItem key="sap_horizon" text="Morning Horizon"></c:ListItem> + <c:ListItem key="sap_horizon_dark" text="Evening Horizon"></c:ListItem> + <c:ListItem key="sap_fiori_3" text="Quartz Light"></c:ListItem> + <c:ListItem key="sap_fiori_3_dark" text="Quartz Dark"></c:ListItem> + </items> + </Select> + </form:SimpleForm> + <form:SimpleForm id="Settings-Form-Replay" + title="Replay" + editable="true" + layout="ResponsiveGridLayout" + columnsM="2" + columnsXL="2" + backgroundDesign="Transparent" + class="dialog-form"> + <VBox> + <CheckBox selected="{settings>/useRRSelector}" + text="Use Record Replay Selector" + width="100%" useEntireWidth="true" /> + <CheckBox selected="{settings>/manualReplayMode}" + text="Start in manual mode" + width="100%" useEntireWidth="true" /> + </VBox> + <Label text="Automatic replay delay" /> + <SegmentedButton selectedKey="{settings>/replayDelay}"> + <items> + <SegmentedButtonItem + text="0.5 sec" + key="0.5" /> + <SegmentedButtonItem + text="1.0 sec" + key="1.0" /> + <SegmentedButtonItem + text="2.0 sec" + key="2.0" /> + </items> + </SegmentedButton> + </form:SimpleForm> + <form:SimpleForm id="Settings-Form-Code" + title="Code" + editable="true" + layout="ResponsiveGridLayout" + columnsM="2" + columnsXL="2" + backgroundDesign="Transparent" + class="dialog-form"> + <Label text="Framework" /> + <SegmentedButton selectedKey="{settings>/framework}" + selectionChange="onFrameworkChange"> + <items> + <SegmentedButtonItem key="OPA5" text="OPA5"></SegmentedButtonItem> + <SegmentedButtonItem key="wdi5" text="wdi5"></SegmentedButtonItem> + </items> + </SegmentedButton> + <Label text="Code Style" /> + <SegmentedButton selectedKey="{settings>/style}" + selectionChange="onStyleChange"> + <items> + <SegmentedButtonItem key="JS" text="JavaScript"></SegmentedButtonItem> + <SegmentedButtonItem key="TS" text="TypeScript"></SegmentedButtonItem> + </items> + </SegmentedButton> + </form:SimpleForm> + </VBox> + </content> + <footer> + <Bar> + <contentRight> + <Button + text="Save" + press="onCloseDialog($event, true)" + type="Emphasized" + /> + <Button + text="Close" + press="onCloseDialog($event)" + /> + </contentRight> + </Bar> + </footer> + </Page> +</mvc:View> \ No newline at end of file