Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
3 changes: 3 additions & 0 deletions .github/workflows/create-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ jobs:
`${process.env.GITHUB_WORKSPACE}/ContextMenu/ContextMenu`,
`${process.env.GITHUB_WORKSPACE}/DetailsList/DetailsList`,
`${process.env.GITHUB_WORKSPACE}/Elevation/Elevation`,
`${process.env.GITHUB_WORKSPACE}/Facepile/Facepile`,
`${process.env.GITHUB_WORKSPACE}/Icon/Icon`,
`${process.env.GITHUB_WORKSPACE}/KeyboardShortcuts/KeyboardShortcuts`,
`${process.env.GITHUB_WORKSPACE}/Nav/Nav`,
Expand Down Expand Up @@ -71,6 +72,8 @@ jobs:
working-directory: "./DetailsList"
- run: npm ci
working-directory: "./Elevation"
- run: npm ci
working-directory: "./Facepile"
- run: npm ci
working-directory: "./Icon"
- run: npm ci
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/pr_validate_all.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ jobs:
- "./ContextMenu"
- "./DetailsList"
- "./Elevation"
- "./Facepile"
- "./Icon"
- "./KeyboardShortcuts"
- "./Nav"
Expand Down
55 changes: 55 additions & 0 deletions Facepile/.eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
{
"env": {
"browser": true,
"es2021": true
},
"extends": [
"eslint:recommended",
"plugin:react/recommended",
"plugin:react-hooks/recommended",
"plugin:@typescript-eslint/recommended",
"plugin:prettier/recommended",
"prettier",
"plugin:sonarjs/recommended"
],
"parser": "@typescript-eslint/parser",
"parserOptions": {
"ecmaFeatures": {
"jsx": true
},
"ecmaVersion": 12,
"sourceType": "module"
},
"plugins": [
"react",
"react-hooks",
"@typescript-eslint",
"prettier",
"sonarjs"
],
"settings": {
"react": {
"pragma": "React",
"version": "detect"
}
},
"ignorePatterns": ["**/generated/*.ts"],
"rules": {
"eqeqeq": [2, "smart"],
"prettier/prettier": "error",
"arrow-body-style": "off",
"prefer-arrow-callback": "off",
"linebreak-style": [
"error",
"windows"
],
"quotes": [
"error",
"single"
],
"semi": [
"error",
"always"
]
}
}
17 changes: 17 additions & 0 deletions Facepile/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.

# dependencies
/node_modules

# generated directory
**/generated

# output directory
/out

#coverage directory
/coverage

# msbuild output directories
/bin
/obj
8 changes: 8 additions & 0 deletions Facepile/.prettierrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"semi": true,
"trailingComma": "all",
"singleQuote": true,
"printWidth": 120,
"tabWidth": 4,
"endOfLine":"auto"
}
47 changes: 47 additions & 0 deletions Facepile/Facepile.pcfproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<PowerAppsTargetsPath>$(MSBuildExtensionsPath)\Microsoft\VisualStudio\v$(VisualStudioVersion)\PowerApps</PowerAppsTargetsPath>
</PropertyGroup>

<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" />
<Import Project="$(PowerAppsTargetsPath)\Microsoft.PowerApps.VisualStudio.Pcf.props" Condition="Exists('$(PowerAppsTargetsPath)\Microsoft.PowerApps.VisualStudio.Pcf.props')" />

<PropertyGroup>
<Name>Facepile</Name>
<ProjectGuid>eb11ae1f-1080-4a88-84d3-3367b8155c39</ProjectGuid>
<OutputPath>$(MSBuildThisFileDirectory)out\controls</OutputPath>
<PcfBuildMode>production</PcfBuildMode>
</PropertyGroup>

<PropertyGroup>
<TargetFrameworkVersion>v4.6.2</TargetFrameworkVersion>
<!--Remove TargetFramework when this is available in 16.1-->
<TargetFramework>net462</TargetFramework>
<RestoreProjectStyle>PackageReference</RestoreProjectStyle>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.PowerApps.MSBuild.Pcf" Version="1.*" />
<PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.0" PrivateAssets="All" />
</ItemGroup>

<ItemGroup>
<ExcludeDirectories Include="$(MSBuildThisFileDirectory)\.gitignore" />
<ExcludeDirectories Include="$(MSBuildThisFileDirectory)\bin\**" />
<ExcludeDirectories Include="$(MSBuildThisFileDirectory)\obj\**" />
<ExcludeDirectories Include="$(OutputPath)\**" />
<ExcludeDirectories Include="$(MSBuildThisFileDirectory)\*.pcfproj" />
<ExcludeDirectories Include="$(MSBuildThisFileDirectory)\*.pcfproj.user" />
<ExcludeDirectories Include="$(MSBuildThisFileDirectory)\*.sln" />
<ExcludeDirectories Include="$(MSBuildThisFileDirectory)\node_modules\**" />
</ItemGroup>

<ItemGroup>
<None Include="$(MSBuildThisFileDirectory)\**" Exclude="@(ExcludeDirectories)" />
</ItemGroup>

<Import Project="$(MSBuildToolsPath)\Microsoft.Common.targets" />
<Import Project="$(PowerAppsTargetsPath)\Microsoft.PowerApps.VisualStudio.Pcf.targets" Condition="Exists('$(PowerAppsTargetsPath)\Microsoft.PowerApps.VisualStudio.Pcf.targets')" />

</Project>
8 changes: 8 additions & 0 deletions Facepile/Facepile/ContextExtended.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// This is undocumented - but needed since canvas apps sets non-zero tabindexes
// so we must use the tabindex provided by the context for accessibility purposes
export interface ContextEx {
accessibility: {
assignedTabIndex: number;
assignedTooltip?: string;
};
}
50 changes: 50 additions & 0 deletions Facepile/Facepile/ControlManifest.Input.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest>
<control namespace="PowerCAT" constructor="Facepile" version="0.0.1" display-name-key="Facepile" description-key="Facepile_Desc" control-type="virtual">
<property name="AccessibilityLabel" display-name-key="AccessibilityLabel" of-type="SingleLine.Text" usage="input" required="false" />
<property name="Theme" display-name-key="Theme" of-type="Multiple" usage="input" required="false" />
<!-- Input Property -->
<property name="MaxDisplayablePersonas" display-name-key="MaxDisplayablePersonas" of-type="Whole.None" default-value="5" usage="input" />
<property name="ImageShouldFadeIn" display-name-key="ImageShouldFadeIn" of-type="TwoOptions" usage="input" />
<property name="ShowAddButton" display-name-key="ShowAddButton" of-type="TwoOptions" usage="input" />
<property name="OverflowButtonAriaLabel" display-name-key="OverflowButtonAriaLabel" of-type="SingleLine.Text" default-value="More users" usage="input" required="false" />
<property name="AddbuttonAriaLabel" display-name-key="AddbuttonAriaLabel" of-type="SingleLine.Text" default-value="Add a new person to the Facepile" usage="input" required="false" />

<!-- Output Property -->
<property name="EventName" display-name-key="EventName" of-type="SingleLine.Text" usage="output" />
<property name="PersonaSize" display-name-key="PersonaSize" description-key="PersonaSize" usage="input" of-type="Enum" required="false">
<value name="Size8" display-name-key="Size8" description-key="Size8">Size8</value>
<value name="Size24" display-name-key="Size24" description-key="Size24">Size24</value>
<value name="Size32" display-name-key="Size32" description-key="Size32" default="true">Size32</value>
<value name="Size40" display-name-key="Size40" description-key="Size40">Size40</value>
<value name="Size48" display-name-key="Size48" description-key="Size48">Size48</value>
<value name="Size56" display-name-key="Size56" description-key="Size56">size56</value>
</property>
<property name="OverflowButtonType" display-name-key="OverflowButtonType" description-key="OverflowButtonType" usage="input" of-type="Enum" required="true">
<value name="none" display-name-key="none" description-key="none">none</value>
<value name="descriptive" display-name-key="descriptive" description-key="descriptive" default="true">descriptive</value>
<value name="downArrow" display-name-key="downArrow" description-key="downArrow">downArrow</value>
<value name="more" display-name-key="more" description-key="more">more</value>
</property>

<!-- Dataset Property -->
<data-set name="items" display-name-key="Items">
<property-set name="ItemPersonaName" display-name-key="ItemPersonaName" of-type="SingleLine.Text" usage="bound" required="true" />
<property-set name="ItemPersonaKey" display-name-key="ItemPersonaKey" of-type="SingleLine.Text" usage="bound" required="true" />
<property-set name="ItemPersonaImage" display-name-key="ItemPersonaImage" of-type="File" usage="bound" required="false" />
<property-set name="ItemPersonaImageInfo" display-name-key="ItemPersonaImageInfo" of-type="SingleLine.Text" usage="bound" required="false" />
<property-set name="ItemPersonaPresence" display-name-key="ItemPersonaPresence" of-type="SingleLine.Text" usage="bound" required="false" />
<property-set name="IsImage" display-name-key="IsImage" of-type="TwoOptions" usage="bound" required="false" />
<property-set name="ItemPersonaClickable" display-name-key="ItemPersonaClickable" of-type="TwoOptions" usage="bound" required="false" />
</data-set>

<!-- InputEvent : SetFocus -->
<property name="InputEvent" display-name-key="InputEvent" of-type="SingleLine.Text" usage="input" />
<resources>
<code path="index.ts" order="1" />
<resx path="strings/Facepile.1033.resx" version="1.0.0" />
<platform-library name="React" version="16.8.6" />
<platform-library name="Fluent" version="8.29.0" />
</resources>
</control>
</manifest>
31 changes: 31 additions & 0 deletions Facepile/Facepile/ManifestConstants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
export const enum ManifestPropertyNames {
dataset = 'dataset',
}

export const enum ItemColumns {
DisplayName = 'ItemPersonaName',
Key = 'ItemPersonaKey',
ImageUrl = 'ItemPersonaImageUrl',
Image = 'ItemPersonaImage',
ImageInfo = 'ItemPersonaImageInfo',
Role = 'ItemPersonaRole',
ImageType = 'ItemImageType',
Presence = 'ItemPersonaPresence',
Clickable = 'ItemPersonaClickable',
IsImage = 'IsImage',
}

export const enum InputEvents {
SetFocus = 'SetFocus',
}

export const enum InputProperties {
InputEvent = 'InputEvent',
SelectedKey = 'SelectedKey',
}

export const enum OutputEvents {
PersonaEvent = 'PersonaEvent',
OverFlowButtonEvent = 'OverFlowButtonEvent',
AddButtonEvent = 'AddButtonEvent',
}
92 changes: 92 additions & 0 deletions Facepile/Facepile/__mocks__/mock-context.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
/* istanbul ignore file */

export class MockContext<T> implements ComponentFramework.Context<T> {
constructor(parameters: T) {
this.parameters = parameters;
this.mode = {
allocatedHeight: -1,
allocatedWidth: -1,
isControlDisabled: false,
isVisible: true,
label: '',
setControlState: jest.fn(),
setFullScreen: jest.fn(),
trackContainerResize: jest.fn(),
};
this.client = {
disableScroll: false,
getClient: jest.fn(),
getFormFactor: jest.fn(),
isOffline: jest.fn(),
};

// Canvas apps currently assigns a positive tab-index
// so we must use this property to assign a positive tab-index also
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(this as any).accessibility = { assignedTabIndex: 0 };
}
client: ComponentFramework.Client;
device: ComponentFramework.Device;
factory: ComponentFramework.Factory;
formatting: ComponentFramework.Formatting;
mode: ComponentFramework.Mode;
navigation: ComponentFramework.Navigation;
resources: ComponentFramework.Resources;
userSettings: ComponentFramework.UserSettings;
utils: ComponentFramework.Utility;
webAPI: ComponentFramework.WebApi;
parameters: T;
updatedProperties: string[] = [];
}

export class MockState implements ComponentFramework.Dictionary {}

export class MockStringProperty implements ComponentFramework.PropertyTypes.StringProperty {
constructor(raw?: string | null, formatted?: string | undefined) {
this.raw = raw ?? null;
this.formatted = formatted;
}
raw: string | null;
attributes?: ComponentFramework.PropertyHelper.FieldPropertyMetadata.StringMetadata | undefined;
error: boolean;
errorMessage: string;
formatted?: string | undefined;
security?: ComponentFramework.PropertyHelper.SecurityValues | undefined;
type: string;
}

export class MockWholeNumberProperty implements ComponentFramework.PropertyTypes.WholeNumberProperty {
constructor(raw?: number | null, formatted?: string | undefined) {
this.raw = raw ?? null;
this.formatted = formatted;
}
attributes?: ComponentFramework.PropertyHelper.FieldPropertyMetadata.WholeNumberMetadata | undefined;
raw: number | null;
error: boolean;
errorMessage: string;
formatted?: string | undefined;
security?: ComponentFramework.PropertyHelper.SecurityValues | undefined;
type: string;
}

export class MockEnumProperty<T> implements ComponentFramework.PropertyTypes.EnumProperty<T> {
constructor(raw?: T, type?: string) {
if (raw) this.raw = raw;
if (type) this.type = type;
}
type: string;
raw: T;
}

export class MockTwoOptionsProperty implements ComponentFramework.PropertyTypes.TwoOptionsProperty {
constructor(raw?: boolean) {
if (raw) this.raw = raw;
}
raw: boolean;
attributes?: ComponentFramework.PropertyHelper.FieldPropertyMetadata.TwoOptionMetadata | undefined;
error: boolean;
errorMessage: string;
formatted?: string | undefined;
security?: ComponentFramework.PropertyHelper.SecurityValues | undefined;
type: string;
}
Loading