Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added adaptivecards-fabric #3106

Merged
merged 7 commits into from
Jun 25, 2019
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,632 changes: 1,632 additions & 0 deletions source/nodejs/adaptivecards-fabric/package-lock.json

Large diffs are not rendered by default.

66 changes: 66 additions & 0 deletions source/nodejs/adaptivecards-fabric/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
{
"name": "adaptivecards-fabric",
"version": "1.2.0",
"description": "Adaptive Cards Office Fabric based extension",
"homepage": "http:https://adaptivecards.io",
"author": "AdaptiveCards",
"license": "MIT",
"keywords": [
"adaptivecards",
"adaptive",
"cards",
"microsoft",
"bot"
],
"repository": {
"type": "git",
"url": "git:https://[email protected]/microsoft/AdaptiveCards.git"
},
"files": [
"dist"
],
"main": "lib/adaptivecards.js",
"types": "lib/adaptivecards.d.ts",
"scripts": {
"test": "jest --forceExit --runInBand",
"clean": "rimraf build lib dist",
"prebuild": "tsc",
"build": "webpack",
"watch": "webpack --watch",
"dts": "dts-generator --name adaptivecards --project . --out dist/adaptivecards.d.ts",
"lint": "tslint -c ../tslint.json 'src/**/*.{ts,tsx}'",
"release": "npm run clean && npm run build && npm test && webpack --mode=production && npm run dts"
},
"peerDependencies": {
"adaptivecards": "^1.2.0",
"office-ui-fabric-react": "^6.189.3",
"react": "^16.8.6",
"react-dom": "^16.8.6"
},
"devDependencies": {
"@types/react": "^16.8.22",
"@types/react-dom": "^16.8.4",
"adaptivecards": "^1.2.0",
"awesome-typescript-loader": "^5.2.1",
"office-ui-fabric-react": "^7.5.2",
"react": "^16.8.6",
"react-dom": "^16.8.6"
},
"jest": {
"rootDir": "src",
"transform": {
"^.+\\.tsx?$": "ts-jest"
},
"testRegex": "(/__tests__/.*|(\\.|/)(test|spec))\\.(jsx?|tsx?)$",
"moduleFileExtensions": [
"ts",
"tsx",
"js",
"jsx",
"json"
],
"modulePathIgnorePatterns": [
"TestUtils"
]
}
}
40 changes: 40 additions & 0 deletions source/nodejs/adaptivecards-fabric/src/Actions/ActionButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

import * as React from "react";
import { PrimaryButton, CompoundButton } from "office-ui-fabric-react";
import * as AC from "adaptivecards";

export interface ActionButtonProps {
text: string;
className?: string;
iconUrl?: string;
iconPlacement?: AC.ActionIconPlacement;
iconSize?: number;
}

export const ActionButton = (props: ActionButtonProps) => (
props.iconUrl ?
<CompoundButton className={props.className} >
<div style={
{
display: "flex",
flexDirection: props.iconPlacement === AC.ActionIconPlacement.LeftOfTitle ? "row" : "column",
justifyContent: "center",
}
}><img src={props.iconUrl}
style={
{
alignSelf: "center",
width: props.iconSize,
height: props.iconSize,
flex: "0 0 auto",
}
} />
<span style={{ alignSelf: "center" }}>{props.text}</span>
</div>
</CompoundButton> :
<PrimaryButton
className={props.className}
text={props.text} />
);
85 changes: 85 additions & 0 deletions source/nodejs/adaptivecards-fabric/src/Actions/Actions.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

import * as React from "react";
import * as ReactDOM from "react-dom";
import * as AC from "adaptivecards";
import * as Shared from "../Utils/shared";
import { ActionButton } from "./ActionButton";

export const createActionDiv = (
title: string,
iconUrl: string,
baseCssClass: string,
iconPlacement: AC.ActionIconPlacement,
iconSize: number): HTMLDivElement => {
const div = Shared.getDiv();
ReactDOM.render(
<ActionButton
text={title}
className={baseCssClass}
iconUrl={iconUrl}
iconPlacement={iconPlacement}
iconSize={iconSize}></ActionButton>, div);
return div;
};

export class OpenUrlActionFabric extends AC.OpenUrlAction {

private internalRenderedElement: any;

get renderedElement(): HTMLElement {
return this.internalRenderedElement;
}

public render(baseCssClass?: string) {
let actionsConfig = this.parent.hostConfig.actions;
const div = createActionDiv(this.title, this.iconUrl, baseCssClass, actionsConfig.iconPlacement, actionsConfig.iconSize);
this.internalRenderedElement = div;
}
}

export class SubmitActionFabric extends AC.SubmitAction {

private internalRenderedElement: HTMLElement;

get renderedElement(): HTMLElement {
return this.internalRenderedElement;
}

public render(baseCssClass?: string): void {
let actionsConfig = this.parent.hostConfig.actions;
const div = createActionDiv(this.title, this.iconUrl, baseCssClass, actionsConfig.iconPlacement, actionsConfig.iconSize);
this.internalRenderedElement = div;
}
}

export class ShowCardActionFabric extends AC.ShowCardAction {

private internalRenderedElement: HTMLElement;

get renderedElement(): HTMLElement {
return this.internalRenderedElement;
}

public render(baseCssClass?: string): void {
let actionsConfig = this.parent.hostConfig.actions;
const div = createActionDiv(this.title, this.iconUrl, baseCssClass, actionsConfig.iconPlacement, actionsConfig.iconSize);
this.internalRenderedElement = div;
}
}

export class ToggleVisibilityActionFabric extends AC.ToggleVisibilityAction {

private internalRenderedElement: HTMLElement;

get renderedElement(): HTMLElement {
return this.internalRenderedElement;
}

public render(baseCssClass?: string): void {
const div = Shared.getDiv();
ReactDOM.render(<ActionButton text={this.title} className={baseCssClass} />, div);
this.internalRenderedElement = div;
}
}
1 change: 1 addition & 0 deletions source/nodejs/adaptivecards-fabric/src/Actions/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./Actions";
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

import * as React from "react";
import * as ReactDOM from "react-dom";
import * as AC from "adaptivecards";
import { initializeIcons, FocusZone, FocusZoneDirection, FocusZoneTabbableElements } from "office-ui-fabric-react";
import * as Components from ".";
import * as Actions from "../Actions";
import { getDiv } from "../Utils/shared";

export default class AdaptiveCardFabric extends AC.AdaptiveCard {
private static iconInitialized = false;

constructor() {
super();
if (!AdaptiveCardFabric.iconInitialized) {
initializeIcons();
AdaptiveCardFabric.iconInitialized = true;
}

// add component extensions
AdaptiveCardFabric.elementTypeRegistry.registerType("Input.Date", () => new Components.InputDateFabric());
AdaptiveCardFabric.elementTypeRegistry.registerType("Input.Text", () => new Components.InputTextFabric());
AdaptiveCardFabric.elementTypeRegistry.registerType("Input.Time", () => new Components.InputTimeFabric());
AdaptiveCardFabric.elementTypeRegistry.registerType("Input.Number", () => new Components.InputNumberFabric());
AdaptiveCardFabric.elementTypeRegistry.registerType("Input.Toggle", () => new Components.InputToggleFabric());
AdaptiveCardFabric.elementTypeRegistry.registerType("Input.ChoiceSet", () => new Components.InputChoiceSetFabric());

// add actions extensions
AdaptiveCardFabric.actionTypeRegistry.registerType("Action.OpenUrl", () => new Actions.OpenUrlActionFabric());
AdaptiveCardFabric.actionTypeRegistry.registerType("Action.Submit", () => new Actions.SubmitActionFabric());
AdaptiveCardFabric.actionTypeRegistry.registerType("Action.ShowCard", () => new Actions.ShowCardActionFabric());
AdaptiveCardFabric.actionTypeRegistry.registerType("Action.ToggleVisibility", () => new Actions.ToggleVisibilityActionFabric());
}

public render(target?: HTMLElement): HTMLElement {
const card = super.render(target);
const focusZone = (
<FocusZone
direction={FocusZoneDirection.horizontal}
handleTabKey={FocusZoneTabbableElements.all}
isCircularNavigation={true}
>
<div ref={(n) => n.appendChild(card)}></div>
</FocusZone>);
let cardWithFocus = getDiv();
ReactDOM.render(focusZone, cardWithFocus);
return cardWithFocus;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

import * as React from "react";
import * as AC from "adaptivecards";
import * as FabricUI from "office-ui-fabric-react";
import * as Shared from "../../Utils/shared";

interface InputChoiceProps {
type?: string;
title: string;
value: string;
}

export class InputChoiceSetFabric extends Shared.ReactInputElement {

private isMultiSelect: boolean;
private choices: InputChoiceProps[];
private style: string;
private selectedValues: string[] = [];

public parse = (json: any, errors?: AC.IValidationError[]) => {
this.id = json.id ? AC.getStringValue(json.id) : null;
this.value = json.value;
this.selectedValues = this.defaultValueToArray(this.value);
this.choices = json.choices;
this.isMultiSelect = AC.getBoolValue(json.isMultiSelect, false);
this.title = AC.getStringValue(json.title);
this.style = AC.getStringValue(json.style);
}

protected renderReact = (): JSX.Element => (
this.style === "compact" ?
<this.ComboBox /> :
this.createExpandedView()
)

public getJsonTypeName = (): string => "Input.ChoiceSet";

private ComboBox = (): JSX.Element => (
<FabricUI.ComboBox
onChange={this.handleComboBoxChange}
id={this.id}
options={this.inputChoicesToComboBoxOptions(this.choices)}
multiSelect={this.isMultiSelect}
selectedKey={this.defaultValueToArray(this.value)}
/>
)

private createExpandedView = (): JSX.Element => {
return this.isMultiSelect ?
this.inputChoicesToCheckbox() :
this.createChoiceGroup();
}

private createChoiceGroup = (): JSX.Element => (
<FabricUI.ChoiceGroup
id={this.id}
onChange={this.handleChoiceGroupChange}
options={this.inputChoicesToChoiceGroupOptions(
this.choices,
this.defaultValueToArray(this.value))}
multiple={this.isMultiSelect}
/>
)

private inputChoicesToCheckbox = (): JSX.Element => (
<React.Fragment>
{this.choices.map((c, i) =>
<FabricUI.Checkbox
key={c.value}
label={c.title}
defaultChecked={this.defaultValueToArray(this.value).includes(c.value)}
onChange={(ev, checked) => {
this.updateMultiselectData(checked, c.value);
ev.stopPropagation();
ev.preventDefault();
}}
styles={{
root: {
paddingTop: i > 0 ? this.hostConfig.spacing.default : 0,
},
}}
/>,
)}
</React.Fragment>
)

private defaultValueToArray = (value: string): string[] => {
if (value) {
return value.split(",");
}
return [];
}

private inputChoicesToComboBoxOptions = (choices: InputChoiceProps[]): FabricUI.IComboBoxOption[] => {
return choices.map<FabricUI.IComboBoxOption>(c => ({ key: c.value, text: c.title }));
}

private inputChoicesToChoiceGroupOptions = (choices: InputChoiceProps[], value: string[]): FabricUI.IChoiceGroupOption[] => {
return choices.map<FabricUI.IChoiceGroupOption>(c => ({ key: c.value, text: c.title, checked: value.includes(c.value) }));
}

private handleChoiceGroupChange =
(ev?: React.FormEvent<HTMLElement | HTMLInputElement>, option?: FabricUI.IChoiceGroupOption): void => {
this.value = option.key;
ev.stopPropagation();
ev.preventDefault();
}

private handleComboBoxChange =
(event: React.FormEvent<FabricUI.IComboBox>, option?: FabricUI.IComboBoxOption, index?: number, value?: string): void => {
if (this.isMultiSelect) {
this.updateMultiselectData(option.selected, option.key);
} else {
this.value = option.key;
}
event.stopPropagation();
event.preventDefault();
}

private updateMultiselectData = (selected: boolean, key: any): void => {
this.updateSelectedValues(selected, key);
this.value = this.selectedValues.join(",");
}

private updateSelectedValues = (selected: boolean, key: any): void => {
selected ? this.selectedValues.push(key) : this.removeItemFromArray(this.selectedValues, key);
}

private removeItemFromArray = (arr: any[], item: any): void => {
const index = arr.indexOf(item);
arr.splice(index, 1);
}
}
Loading