// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. import * as Clipboard from "clipboard"; import * as Adaptive from "adaptivecards"; import * as monaco from "monaco-editor"; import * as Constants from "./constants"; import * as Designer from "./card-designer-surface"; import * as DesignerPeers from "./designer-peers"; import { Pic2Card } from "./pic2card"; import { OpenSampleDialog } from "./open-sample-dialog"; import { OpenJsonSchemaDialog } from "./open-json-schema-dialog"; import { ColorTheme, HostContainer } from "./containers/host-container"; import { adaptiveCardSchema } from "./adaptive-card-schema"; import { OpenImageDialog } from "./open-image-dialog"; import { FullScreenHandler } from "./fullscreen-handler"; import { Toolbar, ToolbarButton, ToolbarChoicePicker, ToolbarElementAlignment } from "./toolbar"; import { IPoint, Utils, defaultHostConfig } from "./miscellaneous"; import { BasePaletteItem, ElementPaletteItem, DataPaletteItem, CustomPaletteItem } from "./tool-palette"; import { DefaultContainer } from "./containers/default/default-container"; import { SidePanel, SidePanelAlignment } from "./side-panel"; import { Toolbox, IToolboxCommand } from "./tool-box"; import { FieldDefinition } from "./data"; import { DataTreeItem } from "./data-treeitem"; import { Strings } from "./strings"; import * as Shared from "./shared"; import { TreeView } from "./tree-view"; import { SampleCatalogue } from "./catalogue"; import { HelpDialog } from "./help-dialog"; import { DeviceEmulation } from "./device-emulation"; import { WidgetContainer, ContainerSize } from "./containers"; export class CardDesigner extends Designer.DesignContext { private static internalProcessMarkdown(text: string, result: Adaptive.IMarkdownProcessingResult) { if (CardDesigner.onProcessMarkdown) { CardDesigner.onProcessMarkdown(text, result); } else { // Check for markdownit if (window["markdownit"]) { result.outputHtml = window["markdownit"]().render(text); result.didProcess = true; } } } static onProcessMarkdown: (text: string, result: Adaptive.IMarkdownProcessingResult) => void = null; private static MAX_UNDO_STACK_SIZE = 50; private _isAttached: boolean = false; private _cardEditor: monaco.editor.IStandaloneCodeEditor; private _sampleDataEditor: monaco.editor.IStandaloneCodeEditor; private _sampleHostDataEditor: monaco.editor.IStandaloneCodeEditor; private _hostContainers: Array; private _deviceEmulations: Array; private _isMonacoEditorLoaded: boolean = false; private _designerSurface: Designer.CardDesignerSurface; private _designerHostElement: HTMLElement; private _draggedPaletteItem: BasePaletteItem; private _draggedElement: HTMLElement; private _currentMousePosition: IPoint; private _hostContainer: HostContainer; private _deviceEmulation: DeviceEmulation; private _undoStack: Array = []; private _undoStackIndex: number = -1; private _startDragPayload: object; private _toolPaletteToolbox: Toolbox; private _propertySheetToolbox: Toolbox; private _propertySheetCard: Adaptive.AdaptiveCard; private _treeViewToolbox: Toolbox; private _jsonEditorsPanel: SidePanel; private _cardEditorToolbox: Toolbox; private _sampleDataEditorToolbox: Toolbox; private _sampleHostDataEditorToolbox: Toolbox; private _dataToolbox: Toolbox; private _assetPath: string; private _dataStructure: FieldDefinition; private _hostDataStructure: FieldDefinition; private _sampleData: any; private _sampleHostData: any; private _bindingPreviewMode: Designer.BindingPreviewMode = Designer.BindingPreviewMode.NoPreview; private _customPeletteItems: CustomPaletteItem[]; private _sampleCatalogue: SampleCatalogue = new SampleCatalogue(); private togglePreview() { this._designerSurface.isPreviewMode = !this._designerSurface.isPreviewMode; if (this._designerSurface.isPreviewMode) { this._togglePreviewButton.toolTip = "Return to Design mode"; this._designerSurface.setCardPayloadAsString(this.getCurrentCardEditorPayload()); } else { this._togglePreviewButton.toolTip = "Switch to Preview mode"; this.updateCardFromJson(false); } this.buildTreeView(); } private buildTreeView() { if (this._treeViewToolbox.content) { this._treeViewToolbox.content.innerHTML = ""; if (this.designerSurface.isPreviewMode) { this.treeViewToolbox.content.innerHTML = '
' + '
The Card Structure isn\'t available in Preview mode.
' + '
'; } else { let treeView = new TreeView(this.designerSurface.rootPeer.treeItem); treeView.onItemInvoked = () => { const propertySheetHosts = document.getElementsByClassName("acd-propertySheet-host"); if (propertySheetHosts.length === 1) { const propertySheetHost = propertySheetHosts[0]; // get focusable children const focusableElements = propertySheetHost.querySelectorAll('a[href], area[href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), button:not([disabled]), [tabindex="0"]'); if (focusableElements.length > 0) { (focusableElements[0]).focus(); } } }; this._treeViewToolbox.content.appendChild(treeView.render()); } } } private buildDataExplorer() { if (this._dataToolbox && this._dataToolbox.content) { this._dataToolbox.content.innerHTML = ""; if (this.dataStructure) { let treeItem = new DataTreeItem(this.dataStructure); let treeView = new TreeView(treeItem); this._dataToolbox.content.appendChild(treeView.render()); } } } private buildPropertySheet(peer: DesignerPeers.DesignerPeer) { if (this._propertySheetToolbox.content) { // if focus is already on _propertySheetCard, remember the focused object's id const restoreFocusId = this._propertySheetCard?.findDOMNodeOwner(document.activeElement)?.id; this._propertySheetToolbox.content.innerHTML = ""; if (peer) { this._propertySheetCard = peer.buildPropertySheetCard(this); } else { this._propertySheetCard = new Adaptive.AdaptiveCard(); this._propertySheetCard.parse( { type: "AdaptiveCard", version: "1.0", body: [ { type: "TextBlock", wrap: true, text: "**Nothing is selected**" }, { type: "TextBlock", wrap: true, text: "Select an element in the card to modify its properties." } ] }, new Adaptive.SerializationContext(this.targetVersion) ); this._propertySheetCard.padding = new Adaptive.PaddingDefinition( Adaptive.Spacing.Small, Adaptive.Spacing.Small, Adaptive.Spacing.Small, Adaptive.Spacing.Small ); } this._propertySheetCard.hostConfig = defaultHostConfig; this._propertySheetToolbox.content.appendChild(this._propertySheetCard.render()); if (restoreFocusId) { // attempt to restore focus if new card has object with same id const focusTarget = this._propertySheetCard.getElementById(restoreFocusId) ?? this._propertySheetCard.getActionById(restoreFocusId); focusTarget?.renderedElement?.focus(); } } } private addPaletteItem(paletteItem: BasePaletteItem, hostElement: HTMLElement) { paletteItem.render(); paletteItem.onStartDrag = (sender: BasePaletteItem) => { this._draggedPaletteItem = sender; this._draggedElement = sender.renderDragVisual(); this._draggedElement.style.position = "absolute"; const adjustedPosition = Utils.adjustPointForScroll(this._currentMousePosition); this._draggedElement.style.left = adjustedPosition.x + "px"; this._draggedElement.style.top = adjustedPosition.y + "px"; document.body.appendChild(this._draggedElement); } hostElement.appendChild(paletteItem.renderedElement); } private buildPalette() { if (!this._isAttached) { return; } this._toolPaletteToolbox.content.innerHTML = ""; let categorizedTypes: object = {}; for (let i = 0; i < this.hostContainer.elementsRegistry.getItemCount(); i++) { let registration = this.hostContainer.elementsRegistry.getItemAt(i); if (registration.schemaVersion.compareTo(this.targetVersion) <= 0) { let peerRegistration = Designer.CardDesignerSurface.cardElementPeerRegistry.findTypeRegistration(registration.objectType); if (peerRegistration) { if (!categorizedTypes.hasOwnProperty(peerRegistration.category)) { categorizedTypes[peerRegistration.category] = []; } let paletteItem = new ElementPaletteItem( registration, peerRegistration ) paletteItem.onDoubleClick = (sender) => { const peer = paletteItem.createPeer(this, this.designerSurface); if (this.designerSurface.rootPeer.tryAdd(peer)) { peer.isSelected = true; }; } categorizedTypes[peerRegistration.category].push(paletteItem); } } } if (this.customPaletteItems) { for (let item of this.customPaletteItems) { if (!categorizedTypes.hasOwnProperty(item.category)) { categorizedTypes[item.category] = []; } categorizedTypes[item.category].push(item); } } for (let category in categorizedTypes) { let categoryList = document.createElement('div'); categoryList.setAttribute("aria-label", category) let node = document.createElement('div'); categoryList.appendChild(node); node.innerText = category; node.className = "acd-palette-category"; this._toolPaletteToolbox.content.appendChild(categoryList); let paletteItemContainer = document.createElement('div'); paletteItemContainer.className = "acd-palette-item-container"; for (let i = 0; i < categorizedTypes[category].length; i++) { this.addPaletteItem(categorizedTypes[category][i], paletteItemContainer); } categoryList.appendChild(paletteItemContainer); } } private endDrag() { if (this._draggedPaletteItem) { this._draggedPaletteItem.endDrag(); this._draggedElement.parentNode.removeChild(this._draggedElement); this._draggedPaletteItem = null; this._draggedElement = null; } } private renderErrorPaneElement(message: string, source?: Adaptive.SerializableObject): HTMLElement { let errorElement = document.createElement("div"); errorElement.className = "acd-error-pane-message"; if (source && source instanceof Adaptive.CardObject) { errorElement.classList.add("selectable"); errorElement.title = "Click to select this element"; errorElement.onclick = (e) => { let peer = this.designerSurface.findPeer(source); if (peer) { peer.isSelected = true; peer.scrollIntoView(); } } } errorElement.innerText = message; return errorElement; } private recreateDesignerSurface() { let styleSheetLinkElement = document.getElementById("adaptiveCardStylesheet"); if (styleSheetLinkElement == null) { styleSheetLinkElement = document.createElement("link"); styleSheetLinkElement.id = "adaptiveCardStylesheet"; document.getElementsByTagName("head")[0].appendChild(styleSheetLinkElement); } styleSheetLinkElement.rel = "stylesheet"; styleSheetLinkElement.type = "text/css"; if (Utils.isAbsoluteUrl(this.hostContainer.getCurrentStyleSheet())) { styleSheetLinkElement.href = this.hostContainer.getCurrentStyleSheet(); } else { styleSheetLinkElement.href = Utils.joinPaths(this._assetPath, this.hostContainer.getCurrentStyleSheet()); } let cardArea = document.getElementById("cardArea"); if (cardArea) { cardArea.style.backgroundColor = this.hostContainer.getBackgroundColor(); } this.hostContainer.initialize(); this._designerHostElement.innerHTML = ""; this.hostContainer.renderTo(this._designerHostElement); let wasInPreviewMode = this._designerSurface ? this._designerSurface.isPreviewMode : false; this._designerSurface = new Designer.CardDesignerSurface(this); this._designerSurface.fixedHeightCard = this.hostContainer.isFixedHeight; this._designerSurface.onSelectedPeerChanged = (peer: DesignerPeers.DesignerPeer) => { this.buildPropertySheet(peer); }; this._designerSurface.onLayoutUpdated = (isFullRefresh: boolean) => { if (isFullRefresh) { this.scheduleUpdateJsonFromCard(); this.buildTreeView(); } }; this._designerSurface.onCardValidated = (logEntries: Adaptive.IValidationEvent[]) => { if (this.onCardValidated) { this.onCardValidated(this, logEntries); } let errorPane = document.getElementById("errorPane"); errorPane.innerHTML = ""; if(this.targetVersion.toString() == "1.4" && (this.hostContainer.name == "Microsoft Teams - Dark" || this.hostContainer.name == "Microsoft Teams - Light")){ errorPane.appendChild(this.renderErrorPaneElement("[Warning] The selected Target Version (" + this.targetVersion.toString() + ") is only supported by bot sent card. For user sent cards please use version 1.3")); } if (this.targetVersion.compareTo(this.hostContainer.targetVersion) > 0 && Shared.GlobalSettings.showTargetVersionMismatchWarning) { errorPane.appendChild(this.renderErrorPaneElement("[Warning] The selected Target Version (" + this.targetVersion.toString() + ") is greater than the version supported by " + this.hostContainer.name + " (" + this.hostContainer.targetVersion.toString() + ")")); } if (logEntries.length > 0) { let dedupedEntries: Adaptive.IValidationEvent[] = []; for (let entry of logEntries) { if (dedupedEntries.indexOf(entry) < 0) { dedupedEntries.push(entry); } } for (let entry of dedupedEntries) { let s: string = ""; switch (entry.phase) { case Adaptive.ValidationPhase.Parse: s = "[Parse]"; break; case Adaptive.ValidationPhase.ToJSON: s = "[Serialize]"; break; default: s = "[Validation]"; break; } errorPane.appendChild(this.renderErrorPaneElement(s + " " + entry.message, entry.source)); } } if (errorPane.childElementCount > 0) { errorPane.classList.remove("acd-hidden"); } else { errorPane.classList.add("acd-hidden"); } }; this._designerSurface.onStartDrag = (sender: Designer.CardDesignerSurface) => { this._startDragPayload = JSON.parse(this.getCurrentCardEditorPayload()); }; this._designerSurface.onEndDrag = (sender: Designer.CardDesignerSurface, wasCancelled: boolean) => { if (wasCancelled) { this.setCardPayload(this._startDragPayload, false); } else { this.addToUndoStack(this._designerSurface.getCardPayloadAsObject()); } }; this.buildPalette(); this.buildPropertySheet(null); this.updateCardFromJson(false); this.updateSampleData(); this._sampleHostDataEditorToolbox.isVisible = (this._hostContainer instanceof WidgetContainer) && Shared.GlobalSettings.enableDataBindingSupport && Shared.GlobalSettings.showSampleHostDataEditorToolbox; this._designerSurface.isPreviewMode = wasInPreviewMode; this.updateFullLayout(); } private activeHostContainerChanged() { this.recreateDesignerSurface(); if (this._deviceEmulationChoicePicker) { this._deviceEmulationChoicePicker.isHidden = !(!!this.hostContainer.enableDeviceEmulation); this.activeDeviceEmulationChanged(); } if (this._containerSizeChoicePicker) { this._containerSizeChoicePicker.isHidden = !(!!this.hostContainer.supportsMultipleSizes); // Update the host parameter data with the value of the choice picker if ((this._hostContainer instanceof WidgetContainer) && this._containerSizeChoicePicker.isEnabled && this._sampleHostData) { this.updateHostDataSizeProperty(); // If the container properties do not align with the choice picker, update the property and recreate the designer surface if (this._containerSizeChoicePicker.value !== (this._hostContainer as WidgetContainer).containerSize) { (this._hostContainer as WidgetContainer).containerSize = this._containerSizeChoicePicker.value as ContainerSize; this.recreateDesignerSurface(); } } } if (this._containerThemeChoicePicker) { this._containerThemeChoicePicker.isEnabled = !!this.hostContainer.supportsMultipleThemes; // Update the host parameter data with the value of the choice picker if (this._containerThemeChoicePicker.isEnabled && this._sampleHostData) { this.updateHostDataThemeProperty(); // If the container properties do not align with the choice picker, update the property and recreate the designer surface if (this._containerThemeChoicePicker.value !== this._hostContainer.colorTheme) { this._hostContainer.colorTheme = this._containerThemeChoicePicker.value as ColorTheme; this.recreateDesignerSurface(); } } } if (this.onActiveHostContainerChanged) { this.onActiveHostContainerChanged(this); } } private activeDeviceEmulationChanged() { if (this.deviceEmulation?.maxWidth && this._hostContainer.enableDeviceEmulation) { this._designerHostElement.style.setProperty('max-width', this.deviceEmulation.maxWidth); } else { this._designerHostElement.style.removeProperty('max-width'); } } private targetVersionChanged() { let cardPayload = this.designerSurface.getCardPayloadAsObject(); if (typeof cardPayload === "object") { cardPayload["version"] = this.targetVersion.toString(); this.setCardPayload(cardPayload, false); } this.recreateDesignerSurface(); if (this.onTargetVersionChanged) { this.onTargetVersionChanged(this); } } private updateToolboxLayout(toolbox: Toolbox, hostPanelRect: ClientRect | DOMRect) { if (toolbox) { let jsonEditorHeaderRect = toolbox.getHeaderBoundingRect(); toolbox.content.style.height = (hostPanelRect.height - jsonEditorHeaderRect.height) + "px"; } } private updateJsonEditorsLayout() { if (this._isMonacoEditorLoaded) { let jsonEditorsPaneRect = this._jsonEditorsPanel.contentHost.getBoundingClientRect(); this.updateToolboxLayout(this._cardEditorToolbox, jsonEditorsPaneRect); this._cardEditor.layout(); if (this._sampleDataEditorToolbox) { this.updateToolboxLayout(this._sampleDataEditorToolbox, jsonEditorsPaneRect); this._sampleDataEditor.layout(); } if (this._sampleHostDataEditorToolbox) { this.updateToolboxLayout(this._sampleHostDataEditorToolbox, jsonEditorsPaneRect); this._sampleHostDataEditor.layout(); } } } private updateFullLayout() { this.scheduleLayoutUpdate(); this.updateJsonEditorsLayout(); } private _jsonUpdateTimer: any; private _cardUpdateTimer: any; private _updateLayoutTimer: any; private _cardPreventUpdate: boolean = false; private _jsonPreventUpdate: boolean = false; private cardPayloadChanged() { if (this.onCardPayloadChanged) { this.onCardPayloadChanged(this); } } private _cardEditorUpdateCounter = 0; private beginCardEditorUpdate() { this._cardEditorUpdateCounter++; } private endCardEditorUpdate() { if (this._cardEditorUpdateCounter > 0) { this._cardEditorUpdateCounter--; } } private setCardPayload(payload: object, addToUndoStack: boolean) { if (this._isMonacoEditorLoaded) { this.beginCardEditorUpdate(); try { if (payload.hasOwnProperty("version")) { payload["version"] = this.targetVersion.toString(); } this._cardEditor.setValue(JSON.stringify(payload, null, 4)); this.updateCardFromJson(addToUndoStack); } finally { this.endCardEditorUpdate(); } } this.cardPayloadChanged(); } private setSampleDataPayload(payload: any) { if (this._isMonacoEditorLoaded && this._sampleDataEditor) { this._sampleDataEditor.setValue(JSON.stringify(payload, null, 4)); } } private setSampleHostDataPayload(payload: any) { if (this._isMonacoEditorLoaded && this._sampleHostDataEditor) { this._sampleHostDataEditor.setValue(JSON.stringify(payload, null, 4)); } } private updateJsonFromCard(addToUndoStack: boolean = true) { try { this._cardPreventUpdate = true; if (!this._jsonPreventUpdate && this._isMonacoEditorLoaded) { let cardPayload = this._designerSurface.getCardPayloadAsObject(); this.setCardPayload(cardPayload, addToUndoStack); } } finally { this._cardPreventUpdate = false; } } private scheduleUpdateJsonFromCard() { clearTimeout(this._jsonUpdateTimer); if (!this._jsonPreventUpdate) { this._jsonUpdateTimer = setTimeout(() => { this.updateJsonFromCard(); }, 100); } } private getCurrentCardEditorPayload(): string { return this._isMonacoEditorLoaded ? this._cardEditor.getValue() : Constants.defaultPayload; } private getCurrentSampleDataEditorPayload(): string { return this._isMonacoEditorLoaded && this._sampleDataEditor ? this._sampleDataEditor.getValue() : ""; } private getCurrentSampleHostDataEditorPayload(): string { return this._isMonacoEditorLoaded && this._sampleHostDataEditor ? this._sampleHostDataEditor.getValue() : ""; } private updateCardFromJson(addToUndoStack: boolean) { try { this._jsonPreventUpdate = true; let currentEditorPayload = this.getCurrentCardEditorPayload(); if (addToUndoStack) { try { this.addToUndoStack(JSON.parse(currentEditorPayload)); } catch { // Swallow the parse error } } if (!this._cardPreventUpdate) { this.designerSurface.setCardPayloadAsString(currentEditorPayload); this.cardPayloadChanged(); } } finally { this._jsonPreventUpdate = false; } } private scheduleUpdateCardFromJson() { clearTimeout(this._cardUpdateTimer); if (!this._cardPreventUpdate) { this._cardUpdateTimer = setTimeout(() => { this.updateCardFromJson(true); }, 300); } } private _isEdgeHTML?: boolean = undefined; private isEdgeHTML(): boolean { if (this._isEdgeHTML === undefined) { this._isEdgeHTML = window.navigator.userAgent.toLowerCase().indexOf("edge") > -1; } return this._isEdgeHTML; } private scheduleLayoutUpdate() { if (this.designerSurface) { if (!this.isEdgeHTML()) { this.designerSurface.updateLayout(false); } else { // In "old" Edge, updateLayout() is *super* slow (because it uses getBoundingClientRect // heavily which is itself super slow) and we have to call it only on idle clearTimeout(this._updateLayoutTimer); this._updateLayoutTimer = setTimeout( () => { if (this.designerSurface) { this.designerSurface.updateLayout(false); } }, 5); } } } private _targetVersion: Adaptive.Version = Adaptive.Versions.latest; private _fullScreenHandler = new FullScreenHandler(); private _fullScreenButton: ToolbarButton; private _hostContainerChoicePicker: ToolbarChoicePicker; private _deviceEmulationChoicePicker: ToolbarChoicePicker; private _versionChoicePicker: ToolbarChoicePicker; private _undoButton: ToolbarButton; private _redoButton: ToolbarButton; private _newCardButton: ToolbarButton; private _copyJSONButton: ToolbarButton; private _togglePreviewButton: ToolbarButton; private _helpButton: ToolbarButton; private _preventRecursiveSetTargetVersion = false; private _containerSizeChoicePicker: ToolbarChoicePicker; private _containerThemeChoicePicker: ToolbarChoicePicker; private prepareToolbar() { if (Shared.GlobalSettings.showVersionPicker) { this._versionChoicePicker = new ToolbarChoicePicker(CardDesigner.ToolbarCommands.VersionPicker); this._versionChoicePicker.label = "Target version:" this._versionChoicePicker.alignment = ToolbarElementAlignment.Right; this._versionChoicePicker.separator = true; for (let i = 0; i < Shared.GlobalSettings.supportedTargetVersions.length; i++) { this._versionChoicePicker.choices.push( { name: Shared.GlobalSettings.supportedTargetVersions[i].label, value: i.toString() }); } this.toolbar.addElement(this._versionChoicePicker); } this._newCardButton = new ToolbarButton( CardDesigner.ToolbarCommands.NewCard, "New card", "acd-icon-newCard", (sender: ToolbarButton) => { let dialog = new OpenSampleDialog({ catalogue: this._sampleCatalogue, handlers: [ { label: "From JSON Schema", onClick: () => { dialog.close() this.launchJsonSchemaPopup() }, onKeyEnterEvent: (e: KeyboardEvent) => { dialog.close(); this.launchJsonSchemaPopup(); }, cardData: { thumbnail: () => { const thumbnail = document.createElement("div"); thumbnail.className = "acd-image-container"; thumbnail.innerHTML = `
json schema image
From JSON Schema
`; return thumbnail; }, }, }, Pic2Card.pic2cardService ? { label: "Pic2Card", onClick: () => { dialog.close() this.launchImagePopup() }, onKeyEnterEvent: (e: KeyboardEvent) => { dialog.close(); this.launchImagePopup(); }, cardData: { thumbnail: () => { const thumbnail = document.createElement("div"); thumbnail.className = "acd-image-container"; thumbnail.innerHTML = `
pic2card image
NEW PREVIEW
Create from Image
Upload your own image and convert it magically to an Adaptive card
`; return thumbnail; }, }, } : null, ] }); dialog.title = "Create"; dialog.closeButton.caption = "Cancel"; dialog.width = "80%"; dialog.height = "80%"; dialog.onClose = (d) => { const newCardButton = this._newCardButton.renderedElement; if (dialog.output) { try { let cardPayload = JSON.parse(dialog.output.cardPayload); this.setCardPayload(cardPayload, true); } catch { alert("The card template could not be loaded."); return } if (dialog.output.sampleData) { try { let sampleDataPayload = JSON.parse(dialog.output.sampleData); this.setSampleDataPayload(sampleDataPayload); this.dataStructure = FieldDefinition.deriveFrom(sampleDataPayload); } catch { alert("The card data could not be loaded.") } } else { this.setSampleDataPayload({}); } if (dialog.output.sampleHostData) { try { let sampleHostDataPayload = JSON.parse(dialog.output.sampleHostData); this.setSampleHostDataPayload(sampleHostDataPayload); this.hostDataStructure = FieldDefinition.deriveFrom(sampleHostDataPayload); // If the new data has size, update the choice picker value; otherwise, update the data based on the choice picker if (this._sampleHostData.widgetSize) { this.updateContainerSizeChoicePicker(); } else { this.updateHostDataSizeProperty(); } // If the new data has theme, update the choice picker value; otherwise, update the data based on the choice picker if (this._sampleHostData.hostTheme) { this.updateContainerThemeChoicePicker(); } else { this.updateHostDataThemeProperty(); } } catch { alert("The card host data could not be loaded.") } } else { this.setSampleHostDataPayload({}); // Update the size and theme properties based on the current state of the choice pickers this.updateHostDataSizeProperty(); this.updateHostDataThemeProperty(); } } if (newCardButton) { newCardButton.focus(); } }; dialog.open(); }); this._newCardButton.separator = true; this.toolbar.addElement(this._newCardButton); if (this._hostContainers && this._hostContainers.length > 0) { this._hostContainerChoicePicker = new ToolbarChoicePicker(CardDesigner.ToolbarCommands.HostAppPicker); this._hostContainerChoicePicker.separator = true; this._hostContainerChoicePicker.label = "Select host app:" for (let i = 0; i < this._hostContainers.length; i++) { this._hostContainerChoicePicker.choices.push( { name: this._hostContainers[i].name, value: i.toString(), } ); } this._hostContainerChoicePicker.onChanged = (sender) => { this.hostContainer = this._hostContainers[parseInt(this._hostContainerChoicePicker.value)]; }; this.toolbar.addElement(this._hostContainerChoicePicker); } const supportsTheme = this._hostContainers.some((x) => { return x.supportsMultipleThemes; }); const supportsSize = this._hostContainers.some((x) => { return x.supportsMultipleSizes; }); if (supportsTheme) { this._containerThemeChoicePicker = new ToolbarChoicePicker(CardDesigner.ToolbarCommands.ContainerThemePicker); this._containerThemeChoicePicker.separator = false; this._containerThemeChoicePicker.label = "Theme:" const themes = HostContainer.supportedContainerThemes; for (let i = 0; i < themes.length; i++) { this._containerThemeChoicePicker.choices.push( { name: themes[i], value: themes[i], } ); } this._containerThemeChoicePicker.onChanged = (sender) => { // Update the container theme and rerender the designer surface this.hostContainer.colorTheme = this._containerThemeChoicePicker.value as ColorTheme; this.recreateDesignerSurface(); if (this._sampleHostData) { this.updateHostDataThemeProperty(); } }; this.toolbar.addElement(this._containerThemeChoicePicker); } if (supportsSize) { this._containerSizeChoicePicker = new ToolbarChoicePicker(CardDesigner.ToolbarCommands.ContainerSizePicker); this._containerSizeChoicePicker.separator = false; this._containerSizeChoicePicker.label = "Container size:" const sizes = WidgetContainer.supportedContainerSizes; for (let i = 0; i < sizes.length; i++) { this._containerSizeChoicePicker.choices.push( { name: sizes[i], value: sizes[i], } ); } this._containerSizeChoicePicker.onChanged = (sender) => { if (this.hostContainer instanceof WidgetContainer) { // Update container size and rerender the designer surface this.hostContainer.containerSize = this._containerSizeChoicePicker.value as ContainerSize; this.recreateDesignerSurface(); if (this._sampleHostData) { this.updateHostDataSizeProperty(); } } }; this.toolbar.addElement(this._containerSizeChoicePicker); } if (this._deviceEmulations && this._deviceEmulations.length > 0) { this._deviceEmulationChoicePicker = new ToolbarChoicePicker(CardDesigner.ToolbarCommands.DeviceEmulationPicker); this._deviceEmulationChoicePicker.separator = true; this._deviceEmulationChoicePicker.label = "Emulate device:" for (let i = 0; i < this._deviceEmulations.length; i++) { this._deviceEmulationChoicePicker.choices.push( { name: this._deviceEmulations[i].name, value: i.toString(), } ); } this._deviceEmulationChoicePicker.onChanged = (sender) => { this.deviceEmulation = this._deviceEmulations[parseInt(this._deviceEmulationChoicePicker.value)]; }; this.toolbar.addElement(this._deviceEmulationChoicePicker); } this._undoButton = new ToolbarButton( CardDesigner.ToolbarCommands.Undo, "Undo", "acd-icon-undo", (sender: ToolbarButton) => { this.undo(); }); this._undoButton.separator = true; this._undoButton.toolTip = "Undo your last change"; this._undoButton.isEnabled = false; this.toolbar.addElement(this._undoButton); this._redoButton = new ToolbarButton( CardDesigner.ToolbarCommands.Redo, "Redo", "acd-icon-redo", (sender: ToolbarButton) => { this.redo(); }); this._redoButton.toolTip = "Redo your last changes"; this._redoButton.isEnabled = false; this.toolbar.addElement(this._redoButton); this._copyJSONButton = new ToolbarButton( CardDesigner.ToolbarCommands.CopyJSON, "Copy card payload", "acd-icon-copy"); this._copyJSONButton.toolTip = "Copy the generated JSON payload of the card (template bound with data) to the clipboard. To copy only the template payload, use the Card Payload Editor."; this.toolbar.addElement(this._copyJSONButton); this._togglePreviewButton = new ToolbarButton( CardDesigner.ToolbarCommands.TogglePreview, "Preview mode", "acd-icon-preview", (sender: ToolbarButton) => { this.togglePreview(); }); this._togglePreviewButton.separator = true; this._togglePreviewButton.allowToggle = true; this._togglePreviewButton.isVisible = Shared.GlobalSettings.enableDataBindingSupport; // On initialization, the preview button is not pressed so set the tooltip to "Switch to Preview mode" this._togglePreviewButton.toolTip = "Switch to Preview mode"; this.toolbar.addElement(this._togglePreviewButton); this._helpButton = new ToolbarButton( CardDesigner.ToolbarCommands.Help, "Help", "acd-icon-help", (sender: ToolbarButton) => { this.showHelp(); }); this._helpButton.toolTip = "Display help."; this._helpButton.separator = true; this._helpButton.alignment = ToolbarElementAlignment.Right; this.toolbar.addElement(this._helpButton); this._fullScreenHandler = new FullScreenHandler(); this._fullScreenHandler.onFullScreenChanged = (isFullScreen: boolean) => { this._fullScreenButton.toolTip = isFullScreen ? "Exit full screen" : "Enter full screen"; this.updateFullLayout(); } } private launchJsonSchemaPopup() { let dialog = new OpenJsonSchemaDialog(); dialog.title = "Create from JSON Schema"; dialog.closeButton.caption = "Cancel"; dialog.preventLightDismissal = true; dialog.width = "80%"; dialog.height = "80%"; dialog.open(); dialog.submitButton.onClick = () => { dialog.close(); if (dialog.output) { try { let cardPayload = JSON.parse(dialog.output.cardPayload); this.setCardPayload(cardPayload, true); } catch { alert("The card template could not be loaded."); return } if (dialog.output.sampleData) { try { let sampleDataPayload = JSON.parse(dialog.output.sampleData); this.setSampleDataPayload(sampleDataPayload); this.dataStructure = FieldDefinition.deriveFrom(sampleDataPayload); } catch { alert("The card data could not be loaded.") } } else { this.setSampleDataPayload({}); } if (dialog.output.sampleHostData) { try { let sampleHostDataPayload = JSON.parse(dialog.output.sampleData); this.setSampleHostDataPayload(sampleHostDataPayload); this.hostDataStructure = FieldDefinition.deriveFrom(sampleHostDataPayload); // If the new data has size, update the choice picker value; otherwise, update the data based on the choice picker if (this._sampleHostData.widgetSize) { this.updateContainerSizeChoicePicker(); } else { this.updateHostDataSizeProperty(); } // If the new data has theme, update the choice picker value; otherwise, update the data based on the choice picker if (this._sampleHostData.hostTheme) { this.updateContainerThemeChoicePicker(); } else { this.updateHostDataThemeProperty(); } } catch { alert("The card host data could not be loaded.") } } else { this.setSampleHostDataPayload({}); // Update the size and theme properties based on the current state of the choice pickers this.updateHostDataSizeProperty(); this.updateHostDataThemeProperty(); } } const newCardButton = this._newCardButton.renderedElement; if (newCardButton) { newCardButton.focus(); } } dialog.onClose = (d) => { const newCardButton = this._newCardButton.renderedElement; if (newCardButton) { newCardButton.focus(); } }; } private launchImagePopup() { let dialog = new OpenImageDialog(); dialog.title = "Pic2card Dialog for Image Upload"; dialog.closeButton.caption = "Cancel"; dialog.preventLightDismissal = true; dialog.width = "80%"; dialog.height = "80%"; dialog.open(); dialog.onClose = (d) => { const newCardButton = this._newCardButton.renderedElement; if (dialog.predictedCardJSON) { const { card, data } = dialog.predictedCardJSON; const addToUndoStack = true; this.setCardPayload(card, addToUndoStack); this.setSampleDataPayload(data); let loadNotification = document.createElement("span"); loadNotification.id = "pic2cardLoadNotification"; loadNotification.setAttribute("role", "status"); loadNotification.setAttribute("aria-label", "Pic2Card generated Adaptive Card loaded"); // It's a bit odd to jump up to the parent element here, but we need a place to park this empty element // so that it's seen by accessibility tools. If we put it on the button itself, the status message gets // read twice (once on DOM entry and again on focus). newCardButton.parentElement.appendChild(loadNotification); // element needs to enter the DOM, but shouldn't stay forever, lest it be read again... setTimeout(() => { newCardButton.parentElement.removeChild(loadNotification); }, 500); } if (newCardButton) { newCardButton.focus(); } }; } private onResize() { this._cardEditor.layout(); if (this._sampleDataEditor) { this._sampleDataEditor.layout(); } if (this._sampleHostDataEditor) { this._sampleHostDataEditor.layout(); } } private updateSampleData() { try { this._sampleData = JSON.parse(this.getCurrentSampleDataEditorPayload()); this.scheduleUpdateCardFromJson(); } catch { // Swallow expression, the payload isn't a valid JSON document } } private updateSampleHostData() { try { this._sampleHostData = JSON.parse(this.getCurrentSampleHostDataEditorPayload()); // Since we have new host data, we should update the container property choice pickers this.updateContainerSizeChoicePicker(); this.updateContainerThemeChoicePicker(); this.scheduleUpdateCardFromJson(); } catch { // Swallow expression, the payload isn't a valid JSON document } } // Update the size property within the sample host data payload private updateHostDataSizeProperty() { if (this._containerSizeChoicePicker?.value) { const value = this._containerSizeChoicePicker.value.toLowerCase(); const currentValue = this._sampleHostData.widgetSize; // only update the payload if the value has changed if (currentValue !== value) { this._sampleHostData.widgetSize = value; this.setSampleHostDataPayload(this._sampleHostData); this.hostDataStructure = FieldDefinition.deriveFrom(this._sampleHostData); } } } // Update the theme property within the sample host data payload private updateHostDataThemeProperty() { if (this._containerThemeChoicePicker?.value) { const value = this._containerThemeChoicePicker.value.toLowerCase(); const currentValue = this._sampleHostData.hostTheme; // only update the payload if the value has changed if (currentValue !== value) { this._sampleHostData.hostTheme = value; this.setSampleHostDataPayload(this._sampleHostData); this.hostDataStructure = FieldDefinition.deriveFrom(this._sampleHostData); } } } // Update the container size choice picker based on the host data value private updateContainerSizeChoicePicker() { // check for `size` in the host data payload if (this._containerSizeChoicePicker && this._sampleHostData.widgetSize) { const size = this._sampleHostData.widgetSize as String; // only update the choice picker if the value is different if (size !== this._containerSizeChoicePicker.value.toLowerCase()) { const choices = this._containerSizeChoicePicker.choices; // search for the matching choice and update the selectedIndex for (let i = 0; i < choices.length; i++) { if (choices.at(i).value.toLowerCase() === size.toLowerCase()) { this._containerSizeChoicePicker.selectedIndex = i; break; } } } } } // Update the container theme choice picker based on the host data value private updateContainerThemeChoicePicker() { // check for `theme` in the host data payload if (this._containerThemeChoicePicker && this._sampleHostData.hostTheme) { const theme = this._sampleHostData.hostTheme as String; // only update the choice picker if the value is different if (theme !== this._containerThemeChoicePicker.value.toLowerCase()) { const choices = this._containerThemeChoicePicker.choices; // search for the matching choice and update the selectedIndex for (let i = 0; i < choices.length; i++) { if (choices.at(i).value.toLowerCase() === theme.toLowerCase()) { this._containerThemeChoicePicker.selectedIndex = i; break; } } } } } private updateToolbar() { this._undoButton.isEnabled = this.canUndo; this._redoButton.isEnabled = this.canRedo; } private addToUndoStack(payload: object) { let doAdd: boolean = !this._designerSurface.draggedPeer; if (doAdd) { if (this._undoStack.length > 0) { doAdd = this._undoStack[this._undoStack.length - 1] != payload; } if (doAdd) { let undoPayloadsToDiscard = this._undoStack.length - (this._undoStackIndex + 1); if (undoPayloadsToDiscard > 0) { this._undoStack.splice(this._undoStackIndex + 1, undoPayloadsToDiscard); } this._undoStack.push(payload); if (this._undoStack.length > CardDesigner.MAX_UNDO_STACK_SIZE) { this._undoStack.splice(0, 1); } this._undoStackIndex = this._undoStack.length - 1; this.updateToolbar(); } } } private handlePointerUp(e: PointerEvent) { this.endDrag(); if (this.designerSurface) { this.designerSurface.endDrag(false); } } private handlePointerMove(e: PointerEvent) { this._currentMousePosition = { x: e.x, y: e.y }; if (this.designerSurface) { let isPointerOverDesigner = this.designerSurface.isPointerOver(this._currentMousePosition.x, this._currentMousePosition.y); let peerDropped = false; if (this._draggedPaletteItem && isPointerOverDesigner) { let peer = this._draggedPaletteItem.createPeer(this, this.designerSurface); let clientCoordinates = this.designerSurface.pageToClientCoordinates(this._currentMousePosition.x, this._currentMousePosition.y); if (this.designerSurface.tryDrop(clientCoordinates, peer)) { this.endDrag(); this.designerSurface.startDrag(peer); peerDropped = true; } } if (!peerDropped && this._draggedElement) { const adjustedPosition = Utils.adjustPointForScroll(this._currentMousePosition); this._draggedElement.style.left = adjustedPosition.x - 10 + "px"; this._draggedElement.style.top = adjustedPosition.y - 10 + "px"; } } } readonly toolbar: Toolbar = new Toolbar(); lockDataStructure: boolean = false; constructor(hostContainers: Array = null, deviceEmulations: Array = null) { super(); Adaptive.GlobalSettings.enableFullJsonRoundTrip = true; Adaptive.GlobalSettings.allowPreProcessingPropertyValues = true; Adaptive.GlobalSettings.setTabIndexAtCardRoot = false; Adaptive.AdaptiveCard.onProcessMarkdown = (text: string, result: Adaptive.IMarkdownProcessingResult) => { CardDesigner.internalProcessMarkdown(text, result); } this._hostContainers = hostContainers ? hostContainers : []; this._deviceEmulations = deviceEmulations ? deviceEmulations : []; this.prepareToolbar(); } monacoModuleLoaded(monaco: any = null) { if (!monaco) { monaco = window["monaco"]; } let monacoConfiguration = { schemas: [ { uri: "http://adaptivecards.io/schemas/adaptive-card.json", schema: adaptiveCardSchema, fileMatch: ["*"], } ], validate: true, allowComments: true } // TODO: set this in our editor instead of defaults monaco.languages.json.jsonDefaults.setDiagnosticsOptions(monacoConfiguration); // Setup card JSON editor this._cardEditorToolbox.content = document.createElement("div"); this._cardEditorToolbox.content.setAttribute("role", "region"); this._cardEditorToolbox.content.setAttribute("aria-label", "card payload editor, press Ctrl+M to toggle behavior of the TAB key"); this._cardEditorToolbox.content.classList.add("acd-code-editor"); this._cardEditor = monaco.editor.create( this._cardEditorToolbox.content, { folding: true, fontSize: 13.5, language: 'json', minimap: { enabled: true } } ); this._cardEditor.onDidChangeModelContent(() => { if (this._cardEditorUpdateCounter == 0) { this.scheduleUpdateCardFromJson(); } }); if (this._sampleDataEditorToolbox) { // Setup sample data JSON editor this._sampleDataEditorToolbox.content = document.createElement("div"); this._sampleDataEditorToolbox.content.setAttribute("role", "region"); this._sampleDataEditorToolbox.content.setAttribute("aria-label", "sample data editor"); this._sampleDataEditorToolbox.content.classList.add("acd-code-editor"); this._sampleDataEditor = monaco.editor.create( this._sampleDataEditorToolbox.content, { folding: true, fontSize: 13.5, language: 'json', minimap: { enabled: false } } ); this._sampleDataEditor.onDidChangeModelContent( () => { this.updateSampleData(); if (!this.lockDataStructure) { try { this.dataStructure = FieldDefinition.deriveFrom(JSON.parse(this.getCurrentSampleDataEditorPayload())); } catch { // Swallow exception } } }); } if (this._sampleHostDataEditorToolbox) { // Setup sample host data JSON editor this._sampleHostDataEditorToolbox.content = document.createElement("div"); this._sampleHostDataEditorToolbox.content.setAttribute("role", "region"); this._sampleHostDataEditorToolbox.content.setAttribute("aria-label", "sample host data editor"); this._sampleHostDataEditorToolbox.content.classList.add("acd-code-editor"); this._sampleHostDataEditor = monaco.editor.create( this._sampleHostDataEditorToolbox.content, { folding: true, fontSize: 13.5, language: 'json', minimap: { enabled: false } } ); this._sampleHostDataEditor.onDidChangeModelContent( () => { // Maybe we want just updateSampleData? this.updateSampleHostData(); if (!this.lockDataStructure) { try { this.hostDataStructure = FieldDefinition.deriveFrom(JSON.parse(this.getCurrentSampleHostDataEditorPayload())); } catch { // Swallow exception } } }); } window.addEventListener('resize', () => { this.onResize(); }); this._isMonacoEditorLoaded = true; this.updateJsonEditorsLayout(); this.updateJsonFromCard(true); } attachTo(root: HTMLElement) { let styleSheetLinkElement = document.createElement("link"); styleSheetLinkElement.id = "__ac-designer"; styleSheetLinkElement.rel = "stylesheet"; styleSheetLinkElement.type = "text/css"; styleSheetLinkElement.href = Utils.joinPaths(this._assetPath, "adaptivecards-designer.css"); document.getElementsByTagName("head")[0].appendChild(styleSheetLinkElement); if (this._hostContainers && this._hostContainers.length > 0) { this._hostContainer = this._hostContainers[0]; } else { this._hostContainer = new DefaultContainer("Default", "adaptivecards-defaulthost.css"); } if (this._deviceEmulationChoicePicker) { this._deviceEmulationChoicePicker.isHidden = !(!!this._hostContainer.enableDeviceEmulation); } if (this._containerSizeChoicePicker) { this._containerSizeChoicePicker.isHidden = !(!!this.hostContainer.supportsMultipleSizes); } if (this._containerThemeChoicePicker) { this._containerThemeChoicePicker.isEnabled = !!this.hostContainer.supportsMultipleThemes; } root.classList.add("acd-designer-root"); root.style.flex = "1 1 auto"; root.style.display = "flex"; root.style.flexDirection = "column"; root.style.overflow = "hidden"; root.innerHTML = '
' + '
' + '
' + '
' + '
' + '
' + '
CARD PREVIEW
' + '
' + '
' + '
' + '
' + '
' + '
' + '
' + '
' + '
' + '
' + '
' + '
' + '
' + '
'; this.toolbar.attachTo(document.getElementById("toolbarHost")); if (this._versionChoicePicker) { this._versionChoicePicker.selectedIndex = Shared.GlobalSettings.supportedTargetVersions.indexOf(this.targetVersion); this._versionChoicePicker.onChanged = (sender: ToolbarChoicePicker) => { this.targetVersion = Shared.GlobalSettings.supportedTargetVersions[parseInt(this._versionChoicePicker.value)]; } } if (this._copyJSONButton.isVisible) { new Clipboard( this._copyJSONButton.renderedElement, { text: (trigger) => JSON.stringify(this.getBoundCard(), null, 4) }) .on("error", () => this._copyJSONButton.renderedElement.focus()) .on("success", () => { this._copyJSONButton.renderedElement.focus() this.toolbar.addAlert("Card payload copied"); }); } // Tool palette panel let toolPaletteHost = document.createElement("div"); toolPaletteHost.className = "acd-dockedPane"; this._toolPaletteToolbox = new Toolbox("toolPalette", Strings.toolboxes.toolPalette.title); this._toolPaletteToolbox.content = toolPaletteHost; let toolPalettePanel = new SidePanel( "toolPalettePanel", SidePanelAlignment.Left, document.getElementById("leftCollapsedPaneTabHost")); toolPalettePanel.addToolbox(this._toolPaletteToolbox); toolPalettePanel.isResizable = false; toolPalettePanel.attachTo(document.getElementById("toolPalettePanel")); // JSON editors panel this._cardEditorToolbox = new Toolbox("cardEditor", Strings.toolboxes.cardEditor.title); this._cardEditorToolbox.content = document.createElement("div"); this._cardEditorToolbox.content.style.padding = "8px"; this._cardEditorToolbox.content.innerText = Strings.loadingEditor; this._jsonEditorsPanel = new SidePanel( "jsonEditorPanel", SidePanelAlignment.Bottom, document.getElementById("bottomCollapsedPaneTabHost")); this._jsonEditorsPanel.onResized = (sender: SidePanel) => { this.updateJsonEditorsLayout(); } this._jsonEditorsPanel.onToolboxResized = (sender: SidePanel) => { this.updateJsonEditorsLayout(); } this._jsonEditorsPanel.onToolboxExpandedOrCollapsed = (sender: SidePanel) => { this.updateJsonEditorsLayout(); } this._jsonEditorsPanel.addToolbox(this._cardEditorToolbox); if (Shared.GlobalSettings.enableDataBindingSupport && Shared.GlobalSettings.showSampleDataEditorToolbox) { this._sampleDataEditorToolbox = new Toolbox("sampleDataEditor", Strings.toolboxes.sampleDataEditor.title); this._sampleDataEditorToolbox.content = document.createElement("div"); this._sampleDataEditorToolbox.content.style.padding = "8px"; this._sampleDataEditorToolbox.content.innerText = Strings.loadingEditor; this._jsonEditorsPanel.addToolbox(this._sampleDataEditorToolbox); } if (Shared.GlobalSettings.enableDataBindingSupport) { this._sampleHostDataEditorToolbox = new Toolbox("sampleHostDataEditor", Strings.toolboxes.sampleHostDataEditor.title); this._sampleHostDataEditorToolbox.content = document.createElement("div"); this._sampleHostDataEditorToolbox.content.style.padding = "8px"; this._sampleHostDataEditorToolbox.content.innerText = Strings.loadingEditor; this._sampleHostDataEditorToolbox.isVisible = Shared.GlobalSettings.showSampleHostDataEditorToolbox && (this._hostContainer instanceof WidgetContainer); this._jsonEditorsPanel.addToolbox(this._sampleHostDataEditorToolbox); } this._jsonEditorsPanel.attachTo(document.getElementById("jsonEditorPanel")); // Property sheet panel let propertySheetHost = document.createElement("div"); propertySheetHost.className = "acd-propertySheet-host"; this._propertySheetToolbox = new Toolbox("propertySheet", Strings.toolboxes.propertySheet.title); this._propertySheetToolbox.content = propertySheetHost; let propertySheetPanel = new SidePanel( "propertySheetPanel", SidePanelAlignment.Right, document.getElementById("rightCollapsedPaneTabHost")); propertySheetPanel.addToolbox(this._propertySheetToolbox); propertySheetPanel.onResized = (sender: SidePanel) => { this.scheduleLayoutUpdate(); } propertySheetPanel.attachTo(document.getElementById("propertySheetPanel")); // Tree view panel let treeViewHost = document.createElement("div"); treeViewHost.className = "acd-treeView-host"; this._treeViewToolbox = new Toolbox("treeView", Strings.toolboxes.cardStructure.title); this._treeViewToolbox.content = treeViewHost; let treeViewPanel = new SidePanel( "treeViewPanel", SidePanelAlignment.Right, document.getElementById("rightCollapsedPaneTabHost")); treeViewPanel.addToolbox(this._treeViewToolbox); treeViewPanel.onResized = (sender: SidePanel) => { this.scheduleLayoutUpdate(); } if (Shared.GlobalSettings.enableDataBindingSupport && Shared.GlobalSettings.showDataStructureToolbox) { let dataExplorerHost = document.createElement("div"); dataExplorerHost.className = "acd-treeView-host"; this._dataToolbox = new Toolbox("data", Strings.toolboxes.dataStructure.title); this._dataToolbox.content = dataExplorerHost; treeViewPanel.addToolbox(this._dataToolbox); } treeViewPanel.attachTo(document.getElementById("treeViewPanel")); this._designerHostElement = document.getElementById("designerHost"); window.addEventListener("pointermove", (e: PointerEvent) => { this.handlePointerMove(e); }); window.addEventListener("resize", () => { this.scheduleLayoutUpdate(); }); window.addEventListener("pointerup", (e: PointerEvent) => { this.handlePointerUp(e); }); this._isAttached = true; this.recreateDesignerSurface(); } createAndAddSampleHostDataEditorToolbox() { this._sampleHostDataEditorToolbox = new Toolbox("sampleHostDataEditor", Strings.toolboxes.sampleHostDataEditor.title); this._sampleHostDataEditorToolbox.content = document.createElement("div"); this._sampleHostDataEditorToolbox.content.style.padding = "8px"; this._sampleHostDataEditorToolbox.content.innerText = Strings.loadingEditor; this._jsonEditorsPanel.addToolbox(this._sampleHostDataEditorToolbox); } clearUndoStack() { this._undoStack = []; this._undoStackIndex = -1; this.updateToolbar(); } setCard(payload: object) { this.clearUndoStack(); this.setCardPayload(payload, true); } getCard(): object { return this._designerSurface ? this._designerSurface.getCardPayloadAsObject() : undefined; } getBoundCard(): object { return this._designerSurface ? this._designerSurface.getBoundCardPayloadAsObject() : undefined; } undo() { if (this.canUndo) { this._undoStackIndex--; let card = this._undoStack[this._undoStackIndex]; this.setCardPayload(card, false); this.updateToolbar(); } } redo() { if (this._undoStackIndex < this._undoStack.length - 1) { this._undoStackIndex++; let payload = this._undoStack[this._undoStackIndex]; this.setCardPayload(payload, false); this.updateToolbar(); } } showHelp() { let helpDialog = new HelpDialog(); helpDialog.title = "Help"; helpDialog.open(); } newCard() { let card = { type: "AdaptiveCard", $schema: "http://adaptivecards.io/schemas/adaptive-card.json", version: this.targetVersion.toString(), body: [ ] } this.setCardPayload(card, true); } onCardPayloadChanged: (designer: CardDesigner) => void; onCardValidated: (designer: CardDesigner, validationLogEntries: Adaptive.IValidationEvent[]) => void; onActiveHostContainerChanged: (designer: CardDesigner) => void; onTargetVersionChanged: (designer: CardDesigner) => void; get targetVersion(): Adaptive.Version { return this._targetVersion; } set targetVersion(value: Adaptive.Version) { if (this._targetVersion.compareTo(value) !== 0 && !this._preventRecursiveSetTargetVersion) { this._preventRecursiveSetTargetVersion = true; try { this._targetVersion = value; this.targetVersionChanged(); if (this._versionChoicePicker) { this._versionChoicePicker.selectedIndex = Shared.GlobalSettings.supportedTargetVersions.indexOf(this._targetVersion); } } finally { this._preventRecursiveSetTargetVersion = false; } } } get dataStructure(): FieldDefinition { return this._dataStructure; } set dataStructure(value: FieldDefinition) { this._dataStructure = value; this.buildDataExplorer(); } get hostDataStructure(): FieldDefinition { return this._hostDataStructure; } set hostDataStructure(value: FieldDefinition) { this._hostDataStructure = value; } get sampleData(): any { return this._sampleData; } set sampleData(value: any) { this._sampleData = value; this.setSampleDataPayload(value); } get sampleHostData(): any { return this._sampleHostDataEditorToolbox.isVisible ? this._sampleHostData : "{}"; } set sampleHostData(value: any) { this._sampleHostData = value; this.setSampleHostDataPayload(value); } get bindingPreviewMode(): Designer.BindingPreviewMode { return this._bindingPreviewMode; } set bindingPreviewMode(value: Designer.BindingPreviewMode) { this._bindingPreviewMode = value; } get hostContainer(): HostContainer { return this._hostContainer; } set hostContainer(value: HostContainer) { if (this._hostContainer !== value) { this._hostContainer = value; this.activeHostContainerChanged(); if (Shared.GlobalSettings.selectedHostContainerControlsTargetVersion) { this.targetVersion = this._hostContainer.targetVersion; } } } get deviceEmulation(): DeviceEmulation { return this._deviceEmulation; } set deviceEmulation(value: DeviceEmulation) { if (this._deviceEmulation !== value) { this._deviceEmulation = value; this.activeDeviceEmulationChanged(); this._designerSurface.updateLayout(true); } } get canUndo(): boolean { return this._undoStackIndex >= 1; } get canRedo(): boolean { return this._undoStackIndex < this._undoStack.length - 1; } get designerSurface(): Designer.CardDesignerSurface { return this._designerSurface; } get treeViewToolbox(): Toolbox { return this._treeViewToolbox; } get propertySheetToolbox(): Toolbox { return this._propertySheetToolbox; } get jsonEditorToolbox(): Toolbox { return this._cardEditorToolbox; } get toolPaletteToolbox(): Toolbox { return this._toolPaletteToolbox; } get dataToolbox(): Toolbox { return this._dataToolbox; } get assetPath(): string { return this._assetPath; } set assetPath(value: string) { this._assetPath = value; } get customPaletteItems(): CustomPaletteItem[] { return this._customPeletteItems; } set customPaletteItems(value: CustomPaletteItem[]) { this._customPeletteItems = value; this.buildPalette(); } get sampleCatalogueUrl(): string { return this._sampleCatalogue.url; } set sampleCatalogueUrl(value: string) { this._sampleCatalogue.url = value; } } export module CardDesigner { export class ToolbarCommands { static readonly HostAppPicker = "__hostAppPicker"; static readonly DeviceEmulationPicker = "__deviceEmulationPicker"; static readonly VersionPicker = "__versionPicker"; static readonly Undo = "__undoButton"; static readonly Redo = "__redoButton"; static readonly NewCard = "__newCardButton"; static readonly CopyJSON = "__copyJsonButton"; static readonly TogglePreview = "__togglePreviewButton"; static readonly Help = "__helpButton"; static readonly ContainerSizePicker = "__containerSizePicker"; static readonly ContainerThemePicker = "__containerThemePicker"; } }