@nakedobjects/gemini
Version:
Single Page Application client for a Naked Objects application.
1 lines • 402 kB
Source Map (JSON)
{"version":3,"file":"nakedobjects-gemini.mjs","sources":["../../../gemini/src/helpers-components.ts","../../../gemini/src/click.directive.ts","../../../gemini/src/action/action.component.ts","../../../gemini/src/action/action.component.html","../../../gemini/src/action-list/action-list.component.ts","../../../gemini/src/action-list/action-list.component.html","../../../gemini/src/action-bar/action-bar.component.ts","../../../gemini/src/action-bar/action-bar.component.html","../../../gemini/src/version.ts","../../../gemini/src/application-properties/application-properties.component.ts","../../../gemini/src/application-properties/application-properties.component.html","../../../gemini/src/pane/pane.ts","../../../gemini/src/attachment/attachment.component.ts","../../../gemini/src/attachment/attachment.component.html","../../../gemini/src/attachment-property/attachment-property.component.ts","../../../gemini/src/attachment-property/attachment-property.component.html","../../../gemini/src/base-dialog/base-dialog.component.ts","../../../gemini/src/field/field.component.ts","../../../gemini/src/clear.directive.ts","../../../gemini/src/date-picker/date-picker.component.ts","../../../gemini/src/date-picker/date-picker.component.html","../../../gemini/src/date-picker-facade/date-picker-facade.component.ts","../../../gemini/src/date-picker-facade/date-picker-facade.component.html","../../../gemini/src/auto-complete/auto-complete.component.ts","../../../gemini/src/auto-complete/auto-complete.component.html","../../../gemini/src/time-picker/time-picker.component.ts","../../../gemini/src/time-picker/time-picker.component.html","../../../gemini/src/time-picker-facade/time-picker-facade.component.ts","../../../gemini/src/time-picker-facade/time-picker-facade.component.html","../../../gemini/src/edit-parameter/edit-parameter.component.ts","../../../gemini/src/edit-parameter/edit-parameter.component.html","../../../gemini/src/view-parameter/view-parameter.component.ts","../../../gemini/src/view-parameter/view-parameter.component.html","../../../gemini/src/parameters/parameters.component.ts","../../../gemini/src/parameters/parameters.component.html","../../../gemini/src/dialog/dialog.component.ts","../../../gemini/src/dialog/dialog.component.html","../../../gemini/src/row/row.component.ts","../../../gemini/src/row/row.component.html","../../../gemini/src/header/header.component.ts","../../../gemini/src/header/header.component.html","../../../gemini/src/collection/collection.component.ts","../../../gemini/src/collection/collection.component.html","../../../gemini/src/collections/collections.component.ts","../../../gemini/src/collections/collections.component.html","../../../gemini/src/error/error.component.ts","../../../gemini/src/error/error.component.html","../../../gemini/src/list/list.component.ts","../../../gemini/src/list/list.component.html","../../../gemini/src/edit-property/edit-property.component.ts","../../../gemini/src/edit-property/edit-property.component.html","../../../gemini/src/view-property/view-property.component.ts","../../../gemini/src/view-property/view-property.component.html","../../../gemini/src/properties/properties.component.ts","../../../gemini/src/properties/properties.component.html","../../../gemini/src/edit-dialog/edit-dialog.component.ts","../../../gemini/src/edit-dialog/edit-dialog.component.html","../../../gemini/src/create-new-dialog/create-new-dialog.component.ts","../../../gemini/src/create-new-dialog/create-new-dialog.component.html","../../../gemini/src/object/object.component.ts","../../../gemini/src/object/object.component.html","../../../gemini/src/object-not-found-error/object-not-found-error.component.ts","../../../gemini/src/object-not-found-error/object-not-found-error.component.html","../../../gemini/src/custom-component-config.service.ts","../../../gemini/src/custom-component.service.ts","../../../gemini/src/dynamic-error/dynamic-error.component.ts","../../../gemini/src/dynamic-error/dynamic-error.component.html","../../../gemini/src/dynamic-list/dynamic-list.component.ts","../../../gemini/src/dynamic-list/dynamic-list.component.html","../../../gemini/src/dynamic-object/dynamic-object.component.ts","../../../gemini/src/dynamic-object/dynamic-object.component.html","../../../gemini/src/footer/footer.component.ts","../../../gemini/src/footer/footer.component.html","../../../gemini/src/menu-bar/menu-bar.component.ts","../../../gemini/src/menu-bar/menu-bar.component.html","../../../gemini/src/home/home.component.ts","../../../gemini/src/home/home.component.html","../../../gemini/src/multi-line-dialog/multi-line-dialog.component.ts","../../../gemini/src/multi-line-dialog/multi-line-dialog.component.html","../../../gemini/src/recent/recent.component.ts","../../../gemini/src/recent/recent.component.html","../../../gemini/src/login/login.component.ts","../../../gemini/src/login/login.component.html","../../../gemini/src/logoff/logoff.component.ts","../../../gemini/src/logoff/logoff.component.html","../../../gemini/src/callback/callback.component.ts","../../../gemini/src/callback/callback.component.html","../../../gemini/src/lib.module.ts","../../../gemini/src/nakedobjects-gemini.ts"],"sourcesContent":["import { ElementRef } from '@angular/core';\nimport { AbstractControl, FormBuilder, FormGroup } from '@angular/forms';\nimport { DialogViewModel, FieldViewModel, IDraggableViewModel, ParameterViewModel } from '@nakedobjects/view-models';\nimport { Dictionary } from 'lodash';\nimport forEach from 'lodash-es/forEach';\nimport map from 'lodash-es/map';\nimport mapValues from 'lodash-es/mapValues';\nimport zipObject from 'lodash-es/zipObject';\nimport { SubscriptionLike as ISubscription } from 'rxjs';\n\nexport function safeUnsubscribe(sub?: ISubscription) {\n if (sub) {\n sub.unsubscribe();\n }\n}\n\nfunction isFocusable(nativeElement: unknown): nativeElement is { focus: () => void } {\n return !!(nativeElement && nativeElement instanceof Object && 'focus' in nativeElement);\n}\n\nfunction safeFocus(nativeElement: unknown) {\n if (isFocusable(nativeElement)) {\n nativeElement.focus();\n }\n}\n\nexport function focus(element?: ElementRef) {\n setTimeout(() => safeFocus(element?.nativeElement));\n return true;\n}\n\nexport function createForm(dialog: DialogViewModel, formBuilder: FormBuilder): { form: FormGroup, dialog: DialogViewModel, parms: Dictionary<ParameterViewModel>, sub: ISubscription } {\n const pps = dialog.parameters;\n const parms = zipObject(map(pps, p => p.id), map(pps, p => p)) as Dictionary<ParameterViewModel>;\n const controls = mapValues(parms, p => [p.getValueForControl(), (a: AbstractControl) => p.validator(a)]);\n const form = formBuilder.group(controls);\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const sub = form.valueChanges.subscribe((data : any) => {\n // cache parm values\n forEach(data, (v, k) => parms[k!].setValueFromControl(v));\n dialog.setParms();\n });\n\n return { form: form, dialog: dialog, parms: parms, sub: sub };\n}\n\nexport function accept(droppableVm: FieldViewModel, component: { canDrop: boolean }, draggableVm: IDraggableViewModel) {\n if (draggableVm) {\n draggableVm.canDropOn(droppableVm.returnType).\n then(canDrop => component.canDrop = canDrop).\n catch(() => component.canDrop = false);\n return true;\n }\n return false;\n}\n\nexport function dropOn(draggableVm: IDraggableViewModel, droppable: FieldViewModel, component: { canDrop: boolean, control: AbstractControl }) {\n if (component.canDrop) {\n droppable.drop(draggableVm)\n .then(() => {\n component.control.setValue(droppable.selectedChoice);\n });\n }\n}\n\nexport function paste(event: KeyboardEvent, droppable: FieldViewModel, component: { control: AbstractControl }, get: () => IDraggableViewModel | null, clear: () => void) {\n const vKeyCode = 86;\n const deleteKeyCode = 46;\n if (event && (event.keyCode === vKeyCode && event.ctrlKey)) {\n const cvm = get();\n\n if (cvm) {\n droppable.drop(cvm)\n .then(() => {\n component.control.setValue(droppable.selectedChoice);\n });\n event.preventDefault();\n }\n }\n if (event && event.keyCode === deleteKeyCode) {\n clear();\n }\n}\n","import { Directive, ElementRef, EventEmitter, HostListener, Output } from '@angular/core';\n\n@Directive({ selector: '[nofClick]' })\nexport class ClickDirective {\n private readonly el: HTMLElement;\n constructor(el: ElementRef) {\n this.el = el.nativeElement;\n }\n\n @Output() leftClick = new EventEmitter();\n @Output() rightClick = new EventEmitter();\n\n @HostListener('click') onClick() {\n\n this.leftClick.emit('event');\n return false;\n }\n\n handleKey(event: KeyboardEvent) {\n const enterKeyCode = 13;\n if (event.which === enterKeyCode) {\n const trigger = event.shiftKey ? this.rightClick : this.leftClick;\n trigger.emit('event');\n return false;\n }\n\n return true;\n }\n\n @HostListener('keydown', ['$event']) onEnter(event: KeyboardEvent) {\n return this.handleKey(event);\n }\n\n @HostListener('keypress', ['$event']) onEnter1(event: KeyboardEvent) {\n return this.handleKey(event);\n }\n\n @HostListener('contextmenu') onContextMenu() {\n this.rightClick.emit('event');\n return false;\n }\n}\n","import { Component, ElementRef, Input, QueryList, ViewChildren } from '@angular/core';\nimport { ActionViewModel } from '@nakedobjects/view-models';\nimport { focus } from '../helpers-components';\n\nexport interface IActionHolder {\n doClick: () => void;\n doRightClick?: () => void;\n show: () => boolean;\n disabled: () => boolean | null;\n tempDisabled: () => boolean | null;\n value: string;\n title: () => string;\n accesskey: string | null;\n presentationHint: string;\n showDialog: () => boolean;\n}\n\nexport function wrapAction(a: ActionViewModel): IActionHolder {\n return {\n value: a.title,\n doClick: () => a.doInvoke(),\n doRightClick: () => a.doInvoke(true),\n show: () => true,\n disabled: () => a.disabled() ? true : null,\n tempDisabled: () => a.tempDisabled(),\n title: () => a.description,\n accesskey: null,\n presentationHint: a.presentationHint,\n showDialog: () => a.showDialog()\n };\n}\n\n@Component({\n selector: 'nof-action',\n templateUrl: 'action.component.html',\n styleUrls: ['action.component.css']\n})\nexport class ActionComponent {\n\n @Input({ required: true })\n action!: IActionHolder;\n\n @ViewChildren('focus')\n focusList?: QueryList<ElementRef>;\n\n private canClick() {\n return !(this.disabled() || this.tempDisabled());\n }\n\n doClick() {\n if (this.canClick()) {\n this.action.doClick();\n }\n }\n\n doRightClick() {\n if (this.canClick() && this.action.doRightClick) {\n this.action.doRightClick();\n }\n }\n\n class() {\n return ({\n tempdisabled: this.tempDisabled(),\n [this.dialogClass()]: true,\n });\n }\n\n show() {\n return this.action.show();\n }\n\n disabled() {\n return this.action.disabled();\n }\n\n tempDisabled() {\n return this.action.tempDisabled();\n }\n\n dialogClass() {\n return this.showDialog() ? 'has-params' : 'no-params';\n }\n\n showDialog() {\n return this.action.showDialog();\n }\n\n get value() {\n return this.action.value;\n }\n\n get title() {\n return this.action.title();\n }\n\n focus() {\n if (this.disabled()) {\n return false;\n }\n return !!(this.focusList && this.focusList.first) && focus(this.focusList.first);\n }\n}\n","<input #focus tabindex=\"0\" type=\"button\" nofClick (leftClick)=\"doClick()\" (rightClick)=\"doRightClick()\" [value]=\"value\" [disabled]=\"disabled()\" *ngIf=\"show()\" [title]=\"title\" [ngClass]=\"class()\">\n","import { AfterViewInit, Component, Input, OnDestroy, QueryList, ViewChildren } from '@angular/core';\nimport { ActionViewModel, IMenuHolderViewModel, MenuItemViewModel } from '@nakedobjects/view-models';\nimport difference from 'lodash-es/difference';\nimport findIndex from 'lodash-es/findIndex';\nimport first from 'lodash-es/first';\nimport map from 'lodash-es/map';\nimport some from 'lodash-es/some';\nimport { SubscriptionLike as ISubscription } from 'rxjs';\nimport { ActionComponent } from '../action/action.component';\nimport { IActionHolder, wrapAction } from '../action/action.component';\nimport { safeUnsubscribe } from '../helpers-components';\n\n@Component({\n selector: 'nof-action-list',\n templateUrl: 'action-list.component.html',\n styleUrls: ['action-list.component.css']\n})\nexport class ActionListComponent implements AfterViewInit, OnDestroy {\n\n private previousActionChildrenNames: string[] = [];\n private holder!: IMenuHolderViewModel;\n private sub?: ISubscription;\n\n @ViewChildren(ActionComponent)\n actionChildren?: QueryList<ActionComponent>;\n\n @Input()\n set menuHolder(mh: IMenuHolderViewModel) {\n this.holder = mh;\n this.actionHolders = []; // clear cache;\n }\n\n get menuHolder() {\n return this.holder;\n }\n\n get items() {\n return this.menuHolder.menuItems;\n }\n\n private actionHolders: IActionHolder[][] = [];\n\n private getActionHolders(menuItem: MenuItemViewModel) {\n return map(menuItem.actions, a => wrapAction(a));\n }\n\n hasActions = (menuItem: MenuItemViewModel) => {\n const actions = menuItem.actions;\n return actions && actions.length > 0;\n };\n\n hasItems = (menuItem: MenuItemViewModel) => {\n const items = menuItem.menuItems;\n return items && items.length > 0;\n };\n\n menuName = (menuItem: MenuItemViewModel) => menuItem.name;\n\n menuItems = (menuItem: MenuItemViewModel) => menuItem.menuItems;\n\n menuActions = (menuItem: MenuItemViewModel, index: number) => {\n if (!this.actionHolders[index]) {\n this.actionHolders[index] = this.getActionHolders(menuItem);\n }\n return this.actionHolders[index];\n };\n\n toggleCollapsed = (menuItem: MenuItemViewModel) => menuItem.toggleCollapsed();\n\n navCollapsed = (menuItem: MenuItemViewModel) => menuItem.navCollapsed;\n\n displayClass = (menuItem: MenuItemViewModel) => ({ collapsed: menuItem.navCollapsed, open: !menuItem.navCollapsed, rootMenu: !menuItem.name });\n\n classes(action: ActionViewModel | IActionHolder ) {\n const hint = action.presentationHint ?? '';\n return hint.trim();\n }\n\n focusFromIndex(actions: QueryList<ActionComponent>, index = 0) {\n\n const toFocus = actions.toArray().slice(index);\n\n if (toFocus && toFocus.length > 0) {\n // until first element returns true\n some(toFocus, i => i.focus());\n }\n }\n\n focus(actions?: QueryList<ActionComponent>) {\n if (actions && actions.length > 0) {\n const actionChildrenNames = map(actions.toArray(), a => a.action.value);\n const newActions = difference(actionChildrenNames, this.previousActionChildrenNames);\n let index = 0;\n\n if (newActions && newActions.length > 0) {\n const firstAction = first(newActions);\n index = findIndex(actions.toArray(), a => a.action.value === firstAction);\n index = index < 0 ? 0 : index;\n }\n this.previousActionChildrenNames = actionChildrenNames;\n this.focusFromIndex(actions, index);\n }\n }\n\n ngAfterViewInit(): void {\n this.focus(this.actionChildren);\n this.sub = this.actionChildren?.changes.subscribe((ql: QueryList<ActionComponent>) => this.focus(ql));\n }\n\n ngOnDestroy(): void {\n safeUnsubscribe(this.sub);\n }\n}\n","<ng-container *ngFor=\"let menu of items; let i = index\">\n\n <div *ngIf=\"menuName(menu)\" (click)=\"toggleCollapsed(menu)\" (keydown.enter)=\"toggleCollapsed(menu)\" class=\"submenu\" [ngSwitch]=\"navCollapsed(menu)\" tabindex=\"0\">\n {{menuName(menu)}}\n <div *ngSwitchCase=\"true\" (keydown.enter)=\"toggleCollapsed(menu)\" class=\"icon-expand\" tabindex=\"0\"></div>\n <div *ngSwitchCase=\"false\" (keydown.enter)=\"toggleCollapsed(menu)\" class=\"icon-collapse\" tabindex=\"0\"></div>\n </div>\n <div *ngIf=\"!navCollapsed(menu)\" class=\"menuitem\" [ngClass]=\"displayClass(menu)\">\n <ng-container *ngIf=\"hasActions(menu)\">\n <ng-container *ngFor=\"let action of menuActions(menu, i)\">\n <nof-action [ngClass]=\"classes(action)\" [action]=\"action\"></nof-action>\n </ng-container>\n </ng-container>\n <ng-container *ngIf=\"hasItems(menu)\">\n <nof-action-list [menuHolder]=\"menu\"></nof-action-list>\n </ng-container>\n </div>\n</ng-container>\n","import { AfterViewInit, Component, Input, OnDestroy, QueryList, ViewChildren } from '@angular/core';\nimport { IMenuHolderViewModel, MenuItemViewModel } from '@nakedobjects/view-models';\nimport flatten from 'lodash-es/flatten';\nimport map from 'lodash-es/map';\nimport some from 'lodash-es/some';\nimport { SubscriptionLike as ISubscription } from 'rxjs';\nimport { IActionHolder, wrapAction } from '../action/action.component';\nimport { ActionComponent } from '../action/action.component';\nimport { safeUnsubscribe } from '../helpers-components';\n\n@Component({\n selector: 'nof-action-bar',\n templateUrl: 'action-bar.component.html',\n styleUrls: ['action-bar.component.css']\n})\nexport class ActionBarComponent implements OnDestroy, AfterViewInit {\n\n @Input()\n actions?: IActionHolder[];\n\n @Input()\n set menuHolder(mhvm: IMenuHolderViewModel) {\n const menuItems = mhvm.menuItems;\n const avms = flatten(map(menuItems || [], (mi: MenuItemViewModel) => mi.actions!));\n this.actions = map(avms, a => wrapAction(a));\n }\n\n @ViewChildren(ActionComponent)\n actionChildren?: QueryList<ActionComponent>;\n\n private sub?: ISubscription;\n\n classes(action: IActionHolder) {\n const hint = action.presentationHint ?? '';\n return hint.trim();\n }\n\n focusOnFirstAction(actions?: QueryList<ActionComponent>) {\n if (actions) {\n // until first element returns true\n some(actions.toArray(), i => i.focus());\n }\n }\n\n ngAfterViewInit(): void {\n this.focusOnFirstAction(this.actionChildren);\n this.sub = this.actionChildren?.changes.subscribe((ql: QueryList<ActionComponent>) => this.focusOnFirstAction(ql));\n }\n\n ngOnDestroy(): void {\n safeUnsubscribe(this.sub);\n }\n}\n","<nof-action [ngClass]=\"classes(action)\" *ngFor=\"let action of actions\" [action]=\"action\"></nof-action>\n\n","// updated by build do not update manually or change name or regex may not match\nexport const clientVersion = '16.1.0';\n","import { Component, OnInit } from '@angular/core';\nimport { ApplicationPropertiesViewModel, ViewModelFactoryService } from '@nakedobjects/view-models';\nimport { clientVersion } from '../version';\n\n@Component({\n selector: 'nof-application-properties',\n templateUrl: 'application-properties.component.html',\n styleUrls: ['application-properties.component.css']\n})\nexport class ApplicationPropertiesComponent implements OnInit {\n\n constructor(private readonly viewModelFactory: ViewModelFactoryService) { }\n\n get applicationName() {\n return this.applicationProperties?.applicationName ?? '';\n }\n\n get userName() {\n return this.applicationProperties?.userName ?? '';\n }\n\n get serverUrl() {\n return this.applicationProperties?.serverUrl ?? '';\n }\n\n get implVersion() {\n return this.applicationProperties?.serverVersion?.implVersion ?? '';\n }\n\n get apiVersion() {\n return this.applicationProperties?.serverVersion?.specVersion ?? '';\n }\n\n get appVersion() {\n return this.applicationProperties?.serverVersion?.appVersion ?? '';\n }\n\n get clientVersion() {\n return clientVersion;\n }\n\n private applicationProperties?: ApplicationPropertiesViewModel;\n\n ngOnInit(): void {\n this.applicationProperties = this.viewModelFactory.applicationPropertiesViewModel();\n }\n}\n","<div id=\"pane1\" class=\"single\">\n <div class=\"applicationproperties\">\n <div class=\"header\">\n <div class=\"title\">Application Properties</div>\n </div>\n <div class=\"main-column\">\n <div class=\"properties\">\n <div class=\"property\">\n Application Name: {{applicationName}}\n </div>\n <div class=\"property\">\n User Name: {{userName}}\n </div>\n <div class=\"property\">\n Server Url: {{serverUrl}}\n </div>\n <div class=\"property\">\n Server API version: {{apiVersion}}\n </div>\n <div class=\"property\">\n Server Framework version: {{implVersion}}\n </div>\n <div class=\"property\">\n Server Application version: {{appVersion}}\n </div>\n <div class=\"property\">\n Client version: {{clientVersion}}\n </div>\n </div>\n </div>\n </div>\n</div>\n","import { Component, OnDestroy, OnInit } from '@angular/core';\nimport { ActivatedRoute } from '@angular/router';\nimport { ContextService, ICustomActivatedRouteData, Pane, PaneName, PaneRouteData, PaneType, UrlManagerService } from '@nakedobjects/services';\nimport { SubscriptionLike as ISubscription } from 'rxjs';\nimport { safeUnsubscribe } from '../helpers-components';\n\n@Component({template : '<div></div>'})\nexport abstract class PaneComponent implements OnInit, OnDestroy {\n\n constructor(\n protected readonly activatedRoute: ActivatedRoute,\n protected readonly urlManager: UrlManagerService,\n protected readonly context: ContextService\n ) {\n }\n\n private activatedRouteDataSub?: ISubscription;\n private paneRouteDataSub?: ISubscription;\n private lastPaneRouteData?: PaneRouteData;\n\n // pane API\n paneId?: Pane;\n paneType?: PaneType;\n paneIdName?: PaneName;\n arData?: ICustomActivatedRouteData;\n\n onChild() {\n setTimeout(() => this.paneType = 'split');\n }\n\n onChildless() {\n setTimeout(() => this.paneType = 'single');\n }\n\n protected abstract setup(routeData: PaneRouteData): void;\n protected doSetup(routeData: PaneRouteData) {\n return !routeData.isEqual(this.lastPaneRouteData);\n }\n\n ngOnInit(): void {\n this.activatedRouteDataSub = this.activatedRoute.data.subscribe(d => {\n const data = d as ICustomActivatedRouteData;\n this.arData = data;\n this.paneId = data.pane;\n this.paneType = data.paneType;\n this.paneIdName = this.paneId === 1 ? 'pane1' : 'pane2';\n\n if (!this.paneRouteDataSub) {\n this.paneRouteDataSub =\n this.urlManager.getPaneRouteDataObservable(this.paneId)\n .subscribe((paneRouteData: PaneRouteData) => {\n if (!paneRouteData.isEqualIgnoringReload(this.lastPaneRouteData)) {\n // only remove messages if something more than reload flag has changed\n this.context.clearMessages();\n this.context.clearWarnings();\n }\n\n if (this.doSetup(paneRouteData)) {\n this.lastPaneRouteData = paneRouteData;\n this.setup(paneRouteData);\n }\n });\n }\n });\n }\n\n ngOnDestroy(): void {\n safeUnsubscribe(this.activatedRouteDataSub);\n safeUnsubscribe(this.paneRouteDataSub);\n }\n}\n","import { Component } from '@angular/core';\nimport { ActivatedRoute } from '@angular/router';\nimport * as Ro from '@nakedobjects/restful-objects';\nimport { ConfigService, ContextService, ErrorService, ErrorWrapper, PaneRouteData, UrlManagerService } from '@nakedobjects/services';\nimport { ViewModelFactoryService } from '@nakedobjects/view-models';\nimport { PaneComponent } from '../pane/pane';\n\n@Component({\n selector: 'nof-attachment',\n templateUrl: 'attachment.component.html',\n styleUrls: ['attachment.component.css']\n})\nexport class AttachmentComponent extends PaneComponent {\n\n constructor(\n activatedRoute: ActivatedRoute,\n urlManager: UrlManagerService,\n private readonly viewModelFactory: ViewModelFactoryService,\n context: ContextService,\n private readonly error: ErrorService,\n private readonly configService: ConfigService\n ) {\n super(activatedRoute, urlManager, context);\n }\n\n // template API\n image?: string;\n title = '';\n\n protected setup(routeData: PaneRouteData) {\n\n const oid = Ro.ObjectIdWrapper.fromObjectId(routeData.objectId!, this.configService.config.keySeparator);\n\n this.context.getObject(routeData.paneId, oid, routeData.interactionMode!)\n .then((object: Ro.DomainObjectRepresentation) => {\n\n const attachmentId = routeData.attachmentId;\n const attachment = attachmentId ? object.propertyMember(attachmentId) : undefined;\n\n if (attachment) {\n const avm = this.viewModelFactory.attachmentViewModel(attachment, routeData.paneId);\n\n if (avm) {\n avm.setImage(this);\n }\n }\n })\n .catch((reject: ErrorWrapper) => this.error.handleError(reject));\n }\n}\n","<div [attr.id]=\"paneIdName\" [ngClass]=\"paneType\">\n <div class=\"attachment view\">\n <div class=\"reference\">\n <img *ngIf=\"image\" src=\"{{image}}\" alt=\"{{title}}\" />\n </div>\n </div>\n</div>\n<router-outlet (activate)=\"onChild()\" (deactivate)=\"onChildless()\"></router-outlet>\n","import { Component, Input } from '@angular/core';\nimport { ClickHandlerService, ErrorService, ErrorWrapper, UrlManagerService } from '@nakedobjects/services';\nimport { AttachmentViewModel } from '@nakedobjects/view-models';\n\n@Component({\n selector: 'nof-attachment-property',\n templateUrl: 'attachment-property.component.html',\n styleUrls: ['attachment-property.component.css']\n})\nexport class AttachmentPropertyComponent {\n\n constructor(\n private readonly error: ErrorService,\n private readonly urlManager: UrlManagerService,\n private readonly clickHandlerService: ClickHandlerService\n ) { }\n\n private attach: AttachmentViewModel | null = null;\n\n @Input()\n set attachment(avm: AttachmentViewModel | null) {\n this.attach = avm;\n this.setup();\n }\n\n get attachment() {\n return this.attach;\n }\n\n title = 'Empty';\n image?: string;\n\n doAttachmentClick = (right?: boolean) => {\n if (this.attachment!.empty && !this.image) {\n return;\n }\n\n if (this.attachment!.displayInline()) {\n this.urlManager.setAttachment(this.attachment!.link, this.clickHandlerService.pane(this.attachment!.onPaneId, right));\n } else {\n this.attachment!.downloadFile()\n .then(blob => {\n const burl = URL.createObjectURL(blob);\n window.open(burl);\n })\n .catch((reject: ErrorWrapper) => this.error.handleError(reject));\n }\n };\n\n private setup() {\n if (this.attachment) {\n if (this.attachment.displayInline()) {\n this.attachment.setImage(this);\n } else {\n this.attachment.setTitle(this);\n }\n }\n }\n}\n","<div *ngIf=\"attachment\" class=\"reference file-attachment\" nofClick (leftClick)=\"doAttachmentClick()\" (rightClick)=\"doAttachmentClick(true)\" tabindex=\"0\">\n <div *ngIf=\"!attachment.empty && !image\">{{title}}</div>\n <img *ngIf=\"!attachment.empty && image\" src=\"{{image}}\" alt=\"{{title}}\" />\n <div *ngIf=\"attachment.empty\">{{title}}</div>\n</div>","import { Component, Input, OnDestroy, OnChanges } from '@angular/core';\nimport { FormBuilder, FormGroup } from '@angular/forms';\nimport * as Ro from '@nakedobjects/restful-objects';\nimport { ContextService, ErrorService, ErrorWrapper } from '@nakedobjects/services';\nimport {\n ActionViewModel,\n CollectionViewModel,\n DialogViewModel,\n DomainObjectViewModel,\n ListViewModel,\n MenuViewModel,\n ParameterViewModel,\n ViewModelFactoryService\n} from '@nakedobjects/view-models';\nimport { Dictionary } from 'lodash';\nimport find from 'lodash-es/find';\nimport forEach from 'lodash-es/forEach';\nimport { SubscriptionLike as ISubscription } from 'rxjs';\nimport { createForm, safeUnsubscribe } from '../helpers-components';\n\n@Component({template : '<div></div>'})\nexport class BaseDialogComponent implements OnDestroy, OnChanges {\n\n constructor(\n private readonly viewModelFactory: ViewModelFactoryService,\n private readonly error: ErrorService,\n private readonly context: ContextService,\n private readonly formBuilder: FormBuilder) {\n }\n\n private parentViewModel?: MenuViewModel | DomainObjectViewModel | ListViewModel | CollectionViewModel;\n private parms?: Dictionary<ParameterViewModel>;\n \n private formSub?: ISubscription;\n protected sub?: ISubscription;\n private createFormSub?: ISubscription;\n\n protected set parent(parent: MenuViewModel | DomainObjectViewModel | ListViewModel | CollectionViewModel) {\n this.parentChanged = this.parentViewModel !== parent;\n this.parentViewModel = parent;\n }\n\n protected get parent(): MenuViewModel | DomainObjectViewModel | ListViewModel | CollectionViewModel {\n return this.parentViewModel!;\n }\n\n private currentDialogId?: string;\n private parentChanged = false;\n\n @Input()\n set selectedDialogId(id: string | undefined) {\n this.currentDialogId = id;\n }\n\n get selectedDialogId(): string | undefined {\n return this.currentDialogId;\n }\n\n dialog: DialogViewModel | null = null;\n\n form?: FormGroup;\n\n get title() {\n const dialog = this.dialog;\n return dialog ? dialog.title : '';\n }\n\n get message() {\n const dialog = this.dialog;\n return dialog ? dialog.getMessage() : '';\n }\n\n get parameters() {\n const dialog = this.dialog;\n return dialog ? dialog.parameters : [];\n }\n\n get tooltip(): string {\n const dialog = this.dialog;\n return dialog ? dialog.tooltip() : '';\n }\n\n onSubmit(right?: boolean) {\n if (this.dialog) {\n forEach(this.parms,\n (p, _) => {\n if (p.isEditable) {\n const newValue = this.form!.value[p.id];\n p.setValueFromControl(newValue);\n }\n });\n this.dialog.doInvoke(right);\n }\n }\n\n close = () => {\n if (this.dialog) {\n this.dialog.doCloseReplaceHistory();\n this.dialog = null;\n }\n };\n\n private createForm(dialog: DialogViewModel) {\n safeUnsubscribe(this.formSub);\n safeUnsubscribe(this.createFormSub);\n ({ form: this.form, dialog: this.dialog, parms: this.parms, sub: this.createFormSub } = createForm(dialog, this.formBuilder));\n this.formSub = this.form.valueChanges.subscribe((_) => this.onValueChanged());\n }\n\n onValueChanged() {\n if (this.dialog) {\n // clear messages if dialog changes\n this.dialog.resetMessage();\n this.context.clearMessages();\n this.context.clearWarnings();\n }\n }\n\n closeExistingDialog() {\n if (this.dialog) {\n if (this.dialog.id !== this.currentDialogId) {\n this.dialog.doCloseKeepHistory();\n } else {\n this.dialog.doCloseKeepUrl();\n }\n this.dialog = null;\n }\n }\n\n getDialog() {\n\n // if it's the same dialog just return\n\n if (this.parent && this.currentDialogId) {\n\n if (!this.parentChanged && this.dialog && this.dialog.id === this.currentDialogId) {\n return;\n }\n this.parentChanged = false;\n\n const p = this.parent;\n let action: Ro.ActionMember | Ro.ActionRepresentation | null = null;\n let actionViewModel: ActionViewModel | null = null;\n\n if (p instanceof MenuViewModel) {\n action = p.menuRep.actionMember(this.currentDialogId);\n }\n\n if (p instanceof DomainObjectViewModel && p.domainObject.hasActionMember(this.currentDialogId)) {\n action = p.domainObject.actionMember(this.currentDialogId);\n }\n\n if (p instanceof ListViewModel) {\n action = p.actionMember(this.currentDialogId)!;\n actionViewModel = find(p.actions, a => a.actionRep.actionId() === this.currentDialogId) || null;\n }\n\n if (p instanceof CollectionViewModel && p.hasMatchingLocallyContributedAction(this.currentDialogId)) {\n action = p.actionMember(this.currentDialogId)!;\n actionViewModel = find(p.actions, a => a.actionRep.actionId() === this.currentDialogId) || null;\n }\n\n if (action) {\n this.context.getInvokableAction(action)\n .then(details => {\n // only if we still have a dialog (may have beenn removed while getting invokable action)\n\n if (this.currentDialogId) {\n // must be a change\n this.closeExistingDialog();\n const dialogViewModel = this.viewModelFactory.dialogViewModel(this.parent!.routeData, details, actionViewModel, false);\n this.createForm(dialogViewModel);\n }\n })\n .catch((reject: ErrorWrapper) => {\n this.error.handleError(reject);\n });\n } else {\n this.closeExistingDialog();\n }\n\n } else {\n this.closeExistingDialog();\n }\n }\n\n ngOnDestroy(): void {\n safeUnsubscribe(this.createFormSub);\n safeUnsubscribe(this.formSub);\n safeUnsubscribe(this.sub);\n this.closeExistingDialog();\n }\n\n ngOnChanges(): void {\n this.getDialog();\n }\n}\n","import { Component, ElementRef, OnDestroy, QueryList, Renderer2 } from '@angular/core';\nimport { AbstractControl, FormGroup } from '@angular/forms';\nimport * as Ro from '@nakedobjects/restful-objects';\nimport { LoggerService, Pane } from '@nakedobjects/services';\nimport {\n ChoiceViewModel,\n DialogViewModel,\n DomainObjectViewModel,\n DragAndDropService,\n IDraggableViewModel,\n MenuViewModel,\n ParameterViewModel,\n PropertyViewModel\n} from '@nakedobjects/view-models';\nimport { Dictionary } from 'lodash';\nimport every from 'lodash-es/every';\nimport find from 'lodash-es/find';\nimport keys from 'lodash-es/keys';\nimport mapValues from 'lodash-es/mapValues';\nimport omit from 'lodash-es/omit';\nimport { BehaviorSubject, SubscriptionLike as ISubscription } from 'rxjs';\nimport { debounceTime } from 'rxjs/operators';\nimport { AutoCompleteComponent } from '../auto-complete/auto-complete.component';\nimport { DatePickerFacadeComponent } from '../date-picker-facade/date-picker-facade.component';\nimport { accept, dropOn, focus, paste, safeUnsubscribe } from '../helpers-components';\nimport { TimePickerFacadeComponent } from '../time-picker-facade/time-picker-facade.component';\nimport { CdkDrag, CdkDropList, CdkDragDrop } from '@angular/cdk/drag-drop';\n\n@Component({ template: '<div></div>' })\nexport abstract class FieldComponent implements OnDestroy {\n\n protected constructor(\n private readonly loggerService: LoggerService,\n private readonly renderer: Renderer2,\n protected readonly dragAndDrop: DragAndDropService\n ) { }\n\n set formGroup(fm: FormGroup) {\n this.formGrp = fm;\n this.formGrp.valueChanges.pipe(debounceTime(200)).subscribe(_ => this.onValueChanged());\n this.onValueChanged(); // (re)set validation messages now\n }\n\n get formGroup() {\n return this.formGrp;\n }\n\n get message() {\n return this.model.getMessage();\n }\n\n get isBoolean() {\n return this.model.returnType === 'boolean';\n }\n\n get subject() {\n if (!this.bSubject) {\n const initialValue = this.control.value;\n this.bSubject = new BehaviorSubject(initialValue);\n\n this.sub = this.control.valueChanges.subscribe((data) => {\n this.bSubject!.next(data);\n });\n }\n\n return this.bSubject;\n }\n\n private formGrp!: FormGroup;\n private vmParent?: DialogViewModel | DomainObjectViewModel | MenuViewModel;\n private model!: ParameterViewModel | PropertyViewModel;\n private isConditionalChoices?: boolean;\n private isAutoComplete?: boolean;\n private bSubject?: BehaviorSubject<unknown>;\n private sub?: ISubscription;\n private lastArgs?: Dictionary<Ro.Value>;\n\n control!: AbstractControl;\n currentOptions: ChoiceViewModel[] = [];\n pArgs?: Dictionary<Ro.Value>;\n paneId?: Pane;\n canDrop = false;\n dragOver = false;\n\n abstract checkboxList?: QueryList<ElementRef>;\n abstract focusList?: QueryList<ElementRef | DatePickerFacadeComponent | TimePickerFacadeComponent | AutoCompleteComponent>;\n\n protected init(\n vmParent: DialogViewModel | DomainObjectViewModel | MenuViewModel | undefined,\n vm: ParameterViewModel | PropertyViewModel,\n control: AbstractControl) {\n\n this.vmParent = vmParent;\n this.model = vm;\n this.control = control;\n\n this.paneId = this.model.onPaneId;\n\n this.isConditionalChoices = (this.model.entryType === Ro.EntryType.ConditionalChoices ||\n this.model.entryType === Ro.EntryType.MultipleConditionalChoices);\n\n this.isAutoComplete = this.model.entryType === Ro.EntryType.AutoComplete;\n\n if (this.isConditionalChoices) {\n this.pArgs = omit(this.model.promptArguments, 'x-ro-nof-members') as Dictionary<Ro.Value>;\n this.populateDropdown();\n }\n }\n\n get accept() {\n // eslint-disable-next-line @typescript-eslint/no-this-alias\n const _this = this;\n return (cdkDrag: CdkDrag<IDraggableViewModel>, _cdkDropList: CdkDropList) => {\n return accept(_this.model, _this, cdkDrag.data);\n };\n }\n\n drop(event: CdkDragDrop<CdkDrag<IDraggableViewModel>>) {\n const cdkDrag: CdkDrag<IDraggableViewModel> = event.item;\n if (event.isPointerOverContainer) {\n dropOn(cdkDrag.data, this.model, this);\n }\n this.canDrop = false;\n this.dragOver = false;\n }\n\n exit() {\n this.canDrop = false;\n this.dragOver = false;\n }\n\n enter() {\n this.dragOver = true;\n }\n\n private isDomainObjectViewModel(object: unknown): object is DomainObjectViewModel {\n return !!(object && object instanceof Object && 'properties' in object);\n }\n\n private mapValues(args: Dictionary<Ro.Value> | undefined, parmsOrProps: { argId: string, getValue: () => Ro.Value }[]) {\n return mapValues(args,\n (v, n) => {\n const pop = find(parmsOrProps, p => p.argId === n);\n return pop!.getValue();\n });\n }\n\n private populateArguments() {\n\n const dialog = this.vmParent as DialogViewModel;\n const object = this.vmParent as DomainObjectViewModel;\n\n if (!dialog && !object) {\n this.loggerService.throw('FieldComponent:populateArguments Expect dialog or object');\n }\n\n let parmsOrProps: { argId: string, getValue: () => Ro.Value }[];\n\n if (this.isDomainObjectViewModel(object)) {\n parmsOrProps = object.properties;\n } else {\n parmsOrProps = dialog.parameters;\n }\n\n return this.mapValues(this.pArgs, parmsOrProps);\n }\n\n private argsChanged(newArgs: Dictionary<Ro.Value>) {\n const same = this.lastArgs &&\n keys(this.lastArgs).length === keys(newArgs).length &&\n every(this.lastArgs, (v, k) => newArgs[k].toValueString() === v.toValueString());\n\n this.lastArgs = newArgs;\n return !same;\n }\n\n private populateDropdown() {\n const nArgs = this.populateArguments();\n if (this.argsChanged(nArgs)) {\n const prompts = this.model.conditionalChoices;\n if (prompts) {\n prompts(nArgs).\n then((cvms: ChoiceViewModel[]) => {\n // if unchanged return\n if (cvms.length === this.currentOptions.length && every(cvms, (c, i) => c.equals(this.currentOptions[i]))) {\n return;\n }\n this.model.choices = cvms;\n this.currentOptions = cvms;\n\n if (this.isConditionalChoices) {\n // need to reset control to find the selected options\n if (this.model.entryType === Ro.EntryType.MultipleConditionalChoices) {\n this.control.reset(this.model.selectedMultiChoices);\n } else {\n this.control.reset(this.model.selectedChoice);\n }\n }\n }).\n catch(() => {\n // error clear everything\n this.model.selectedChoice = null;\n this.currentOptions = [];\n });\n }\n }\n }\n\n private onChange() {\n if (this.isConditionalChoices) {\n this.populateDropdown();\n } else if (this.isAutoComplete) {\n this.populateAutoComplete();\n } else if (this.isBoolean) {\n this.populateBoolean();\n }\n }\n\n private onValueChanged() {\n if (this.model) {\n this.onChange();\n }\n }\n\n private populateAutoComplete() {\n const input = this.control.value;\n\n if (input instanceof ChoiceViewModel) {\n return;\n }\n\n const prompt = this.model.prompt;\n\n if (prompt && input && input.length > 0 && input.length >= (this.model.minLength ?? 0)) {\n prompt(input)\n .then((cvms: ChoiceViewModel[]) => {\n if (cvms.length === this.currentOptions.length && every(cvms, (c, i) => c.equals(this.currentOptions[i]))) {\n return;\n }\n this.model.choices = cvms;\n this.currentOptions = cvms;\n this.model.selectedChoice = null;\n })\n .catch(() => {\n this.model.choices = [];\n this.currentOptions = [];\n this.model.selectedChoice = null;\n });\n } else {\n this.model.choices = [];\n this.currentOptions = [];\n this.model.selectedChoice = null;\n }\n }\n\n protected populateBoolean() {\n\n // editable booleans only\n if (this.isBoolean && this.control) {\n const input = this.control.value;\n const element = this.checkboxList?.first.nativeElement;\n if (input == null) {\n this.renderer.setProperty(element, 'indeterminate', true);\n this.renderer.setProperty(element, 'checked', null);\n } else {\n this.renderer.setProperty(element, 'indeterminate', false);\n this.renderer.setProperty(element, 'checked', !!input);\n }\n }\n }\n\n private select(item: ChoiceViewModel) {\n this.model.choices = [];\n this.model.selectedChoice = item;\n this.control.reset(item);\n }\n\n fileUpload(evt: Event) {\n\n const file: File = (evt.target as HTMLInputElement)!.files![0];\n const fileReader = new FileReader();\n fileReader.onloadend = () => {\n const link = new Ro.Link({\n href: fileReader.result as string,\n type: file.type,\n title: file.name\n });\n\n this.control.reset(link);\n this.model.file = link;\n };\n\n fileReader.readAsDataURL(file);\n }\n\n paste(event: KeyboardEvent) {\n paste(event, this.model, this, () => this.dragAndDrop.getCopyViewModel(), () => this.dragAndDrop.setCopyViewModel(null));\n }\n\n clear() {\n if (this.model.isEditable) {\n this.control.reset('');\n this.model.clear();\n }\n }\n\n private filterEnter(event: KeyboardEvent) {\n const enterKeyCode = 13;\n if (event && event.keyCode === enterKeyCode) {\n event.preventDefault();\n }\n }\n\n protected handleKeyEvents(event: KeyboardEvent, isMultiline: boolean) {\n this.paste(event);\n // catch and filter enters or they will submit form - ok for multiline\n if (!isMultiline) {\n this.filterEnter(event);\n }\n }\n\n private triStateClick = (currentValue: unknown) => {\n\n switch (currentValue) {\n case false:\n return true;\n case true:\n return null;\n default: // null\n return false;\n }\n };\n\n protected handleClick(event: Event) {\n if (this.isBoolean && this.model.optional) {\n const currentValue = this.control.value;\n setTimeout(() => this.control.setValue(this.triStateClick(currentValue)));\n event.preventDefault();\n }\n }\n\n focus() {\n const first = this.focusList && this.focusList.first;\n\n if (first instanceof ElementRef) {\n return focus(first);\n }\n return first && first.focus();\n }\n\n ngOnDestroy() {\n safeUnsubscribe(this.sub);\n }\n}\n","import {\n Directive,\n ElementRef,\n EventEmitter,\n HostListener,\n Input,\n OnDestroy,\n OnInit,\n Output\n} from '@angular/core';\nimport { safeUnsubscribe } from './helpers-components';\nimport { BehaviorSubject, SubscriptionLike as ISubscription } from 'rxjs';\n\n@Directive({ selector: '[nofClear]' })\nexport class ClearDirective implements OnInit, OnDestroy {\n\n private readonly nativeEl: HTMLInputElement;\n\n constructor(\n private readonly el: ElementRef\n ) {\n this.nativeEl = this.el.nativeElement;\n }\n\n @Input('nofClear')\n subject!: BehaviorSubject<unknown>;\n\n @Output()\n clear = new EventEmitter();\n\n private sub?: ISubscription;\n\n ngOnInit(): void {\n this.onChange();\n this.sub = this.subject.subscribe(_ => this.onChange());\n }\n\n // not need the ngClass directive on element even though it doesn't do anything\n // otherwise we lose all the classes added here\n onChange() {\n\n this.nativeEl.classList.add('ng-clearable');\n\n if (this.subject.getValue()) {\n this.nativeEl.classList.add('ng-x');\n } else {\n this.nativeEl.classList.remove('ng-x');\n }\n }\n\n onMouseMove(event: MouseEvent) {\n if (this.nativeEl.classList.contains('ng-x')) {\n const onX = this.nativeEl.offsetWidth - 18 < event.clientX - this.nativeEl.getBoundingClientRect().left;\n if (onX) {\n this.nativeEl.classList.add('ng-onX');\n } else {\n this.nativeEl.classList.remove('ng-onX');\n }\n }\n }\n\n onClick(event: KeyboardEvent) {\n if (this.nativeEl.classList.contains('ng-onX')) {\n\n event.preventDefault();\n this.nativeEl.classList.remove('ng-x');\n this.nativeEl.classList.remove('ng-onX');\n this.clear.emit('event');\n }\n }\n\n @HostListener('c