Skip to content

Commit 3c7ab72

Browse files
committed
commit ignore
1 parent b97d635 commit 3c7ab72

35 files changed

+13619
-0
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,3 +137,4 @@ dist
137137
# Vite logs files
138138
vite.config.js.timestamp-*
139139
vite.config.ts.timestamp-*
140+
src/broadcast-pcf/appmodulepicker/push.bat
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2+
3+
# dependencies
4+
/node_modules
5+
6+
# generated directory
7+
**/generated
8+
9+
# output directory
10+
/out
11+
12+
# msbuild output directories
13+
/bin
14+
/obj
15+
16+
# MSBuild Binary and Structured Log
17+
*.binlog
18+
19+
# Visual Studio cache/options directory
20+
/.vs
21+
22+
# macos
23+
.DS_Store
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import * as React from "react"
2+
import { PowerAppsContextProvider, PowerAppsContextService } from "./services/PowerAppsContextService";
3+
import { FluentProvider, webLightTheme } from "@fluentui/react-components";
4+
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
5+
import { AppModulePickerComboBox } from "./components/AppModulePickerComboBox";
6+
import { AppModule } from "./model/appmodule";
7+
import { IInputs } from "./generated/ManifestTypes";
8+
9+
export type AppProps = {
10+
context: ComponentFramework.Context<IInputs>
11+
onChange: (appmodule: AppModule) => void
12+
};
13+
14+
export const App:React.FC<AppProps> = (props) => {
15+
const queryClient = new QueryClient();
16+
const service = new PowerAppsContextService(props.context);
17+
const currentAppModuleId = props.context.parameters.MainField.raw ?? undefined;
18+
return (
19+
<QueryClientProvider client={queryClient}>
20+
<PowerAppsContextProvider Service={service}>
21+
<FluentProvider style={{width:'100%'}} theme={webLightTheme}>
22+
<AppModulePickerComboBox onChange={props.onChange} isReadOnly={service.isReadOnly} currentAppModuleId={currentAppModuleId} />
23+
</FluentProvider>
24+
</PowerAppsContextProvider>
25+
</QueryClientProvider>
26+
)
27+
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
<?xml version="1.0" encoding="utf-8" ?>
2+
<manifest>
3+
<control namespace="Broadcast" constructor="AppModulePicker" version="0.0.1" display-name-key="AppModulePicker" description-key="AppModulePicker description" control-type="virtual" >
4+
<!--external-service-usage node declares whether this 3rd party PCF control is using external service or not, if yes, this control will be considered as premium and please also add the external domain it is using.
5+
If it is not using any external service, please set the enabled="false" and DO NOT add any domain below. The "enabled" will be false by default.
6+
Example1:
7+
<external-service-usage enabled="true">
8+
<domain>www.Microsoft.com</domain>
9+
</external-service-usage>
10+
Example2:
11+
<external-service-usage enabled="false">
12+
</external-service-usage>
13+
-->
14+
<external-service-usage enabled="false">
15+
<!--UNCOMMENT TO ADD EXTERNAL DOMAINS
16+
<domain></domain>
17+
<domain></domain>
18+
-->
19+
</external-service-usage>
20+
<!-- property node identifies a specific, configurable piece of data that the control expects from CDS -->
21+
<property name="MainField" display-name-key="MainField" description-key="Main field to bound the control on" of-type="SingleLine.Text" usage="bound" required="true" />
22+
<!--
23+
Property node's of-type attribute can be of-type-group attribute.
24+
Example:
25+
<type-group name="numbers">
26+
<type>Whole.None</type>
27+
<type>Currency</type>
28+
<type>FP</type>
29+
<type>Decimal</type>
30+
</type-group>
31+
<property name="sampleProperty" display-name-key="Property_Display_Key" description-key="Property_Desc_Key" of-type-group="numbers" usage="bound" required="true" />
32+
-->
33+
<resources>
34+
<code path="index.ts" order="1"/>
35+
<platform-library name="React" version="16.14.0" />
36+
<platform-library name="Fluent" version="9.46.2" />
37+
<!-- UNCOMMENT TO ADD MORE RESOURCES
38+
<css path="css/AppModulePicker.css" order="1" />
39+
<resx path="strings/AppModulePicker.1033.resx" version="1.0.0" />
40+
-->
41+
</resources>
42+
<!-- UNCOMMENT TO ENABLE THE SPECIFIED API
43+
<feature-usage>
44+
<uses-feature name="Device.captureAudio" required="true" />
45+
<uses-feature name="Device.captureImage" required="true" />
46+
<uses-feature name="Device.captureVideo" required="true" />
47+
<uses-feature name="Device.getBarcodeValue" required="true" />
48+
<uses-feature name="Device.getCurrentPosition" required="true" />
49+
<uses-feature name="Device.pickFile" required="true" />
50+
<uses-feature name="Utility" required="true" />
51+
<uses-feature name="WebAPI" required="true" />
52+
</feature-usage>
53+
-->
54+
<feature-usage>
55+
<uses-feature name="WebAPI" required="true" />
56+
</feature-usage>
57+
</control>
58+
</manifest>
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import * as React from "react";
2+
import { AppModule } from "../model/appmodule";
3+
import {
4+
Combobox,
5+
makeStyles,
6+
Option,
7+
OptionOnSelectData,
8+
SelectionEvents,
9+
Spinner,
10+
useId,
11+
} from "@fluentui/react-components";
12+
import { useAppModulesQuery } from "../hooks";
13+
import { ErrorMessageBar } from "./ErrorMessage";
14+
15+
const useStyles = makeStyles({
16+
comboBox: {
17+
// Stack the label above the field with a gap
18+
width: "100%",
19+
},
20+
});
21+
export interface AppModulePickerComboBoxProps {
22+
currentAppModuleId?: string;
23+
onChange: (appmodule: AppModule) => void
24+
isReadOnly?: boolean;
25+
}
26+
27+
export const AppModulePickerComboBox:React.FC<AppModulePickerComboBoxProps> = (props:AppModulePickerComboBoxProps) => {
28+
const { data: appmodules, isLoading, isError, error } = useAppModulesQuery();
29+
const [selectedAppModuleId, setSelectedAppModuleId] = React.useState<string | undefined>(props.currentAppModuleId);
30+
const comboboxId = useId("combobox");
31+
if(isLoading) return <Spinner label="Loading application modules..." />;
32+
if(isError){
33+
return <ErrorMessageBar error={error} title="Error loading application modules" defaultMessage="An error occurred while loading application modules." />;
34+
}
35+
36+
const currentAppModule = appmodules?.find(a => a.appmoduleid === selectedAppModuleId);
37+
const onOptionSelect = (event: SelectionEvents, data: OptionOnSelectData) => {
38+
const appModule = appmodules.find(a => a.appmoduleid === data.optionValue)!;
39+
setSelectedAppModuleId(appModule.appmoduleid);
40+
props.onChange(appModule);
41+
};
42+
const style = useStyles();
43+
return (
44+
<Combobox
45+
clearable
46+
className={style.comboBox}
47+
aria-labelledby={comboboxId}
48+
placeholder="Select an application"
49+
onOptionSelect={onOptionSelect}
50+
selectedOptions= {currentAppModule ?[currentAppModule.appmoduleid] : []}
51+
value={currentAppModule ?`${currentAppModule.name} (${currentAppModule.uniquename})`:undefined}
52+
disabled={props.isReadOnly}
53+
>
54+
{appmodules.map((appmodule) => {
55+
const displayText = `${appmodule.name} (${appmodule.uniquename})`;
56+
return <Option key={appmodule.appmoduleid} value={appmodule.appmoduleid}>{displayText}</Option>;
57+
})}
58+
</Combobox>
59+
60+
);
61+
62+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import { MessageBar, MessageBarBody, MessageBarTitle } from "@fluentui/react-components";
2+
import * as React from "react";
3+
4+
5+
6+
export type ErrorMessageBarProps = {
7+
error:any;
8+
title:string;
9+
defaultMessage:string
10+
}
11+
const getErrorText = (error:any,defaultMessage:string):string => {
12+
const errorType = typeof error;
13+
if(error instanceof TypeError){
14+
return error.message + '\n\t' + error.stack;
15+
}
16+
switch(errorType){
17+
case 'string':
18+
return error;
19+
case 'object':
20+
if(error.message) return error.message;
21+
return JSON.stringify(error);
22+
default:
23+
return defaultMessage;
24+
}
25+
}
26+
export const ErrorMessageBar:React.FC<ErrorMessageBarProps> = ({error,title,defaultMessage})=>{
27+
console.error(error);
28+
const errorText = getErrorText(error,defaultMessage);
29+
return ( <MessageBar intent="error">
30+
<MessageBarBody >
31+
<MessageBarTitle >{title}</MessageBarTitle>
32+
<div style={{whiteSpace:'pre-wrap'}}>{errorText}</div>
33+
</MessageBarBody>
34+
</MessageBar>)
35+
};
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from "./useAppModulesQuery";
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { useQuery } from "@tanstack/react-query";
2+
import { usePowerAppsContext } from "../services/PowerAppsContextService";
3+
4+
export const useAppModulesQuery = ()=> {
5+
const powerAppsContext = usePowerAppsContext();
6+
// Queries
7+
return useQuery({
8+
queryKey: ['appmodules'],
9+
queryFn: async() => {
10+
return await powerAppsContext.AppModuleService.getAppModules();
11+
} });
12+
13+
}
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
/* eslint-disable @typescript-eslint/no-unused-vars */
2+
import { IInputs, IOutputs } from "./generated/ManifestTypes";
3+
import * as React from "react";
4+
import { AppModule } from "./model/appmodule";
5+
import { App, AppProps } from "./App";
6+
7+
export class AppModulePicker implements ComponentFramework.ReactControl<IInputs, IOutputs> {
8+
private notifyOutputChanged: () => void;
9+
private _currentValue?:string;
10+
11+
/**
12+
* Empty constructor.
13+
*/
14+
constructor() {
15+
// Empty
16+
}
17+
18+
/**
19+
* Used to initialize the control instance. Controls can kick off remote server calls and other initialization actions here.
20+
* Data-set values are not initialized here, use updateView.
21+
* @param context The entire property bag available to control via Context Object; It contains values as set up by the customizer mapped to property names defined in the manifest, as well as utility functions.
22+
* @param notifyOutputChanged A callback method to alert the framework that the control has new outputs ready to be retrieved asynchronously.
23+
* @param state A piece of data that persists in one session for a single user. Can be set at any point in a controls life cycle by calling 'setControlState' in the Mode interface.
24+
*/
25+
public init(
26+
context: ComponentFramework.Context<IInputs>,
27+
notifyOutputChanged: () => void,
28+
state: ComponentFramework.Dictionary
29+
): void {
30+
this.notifyOutputChanged = notifyOutputChanged;
31+
}
32+
private setCurrentValueFromContext(context: ComponentFramework.Context<IInputs>){
33+
if(context.parameters.MainField.raw !== null && context.parameters.MainField.raw.length > 0){
34+
this._currentValue = context.parameters.MainField.raw;
35+
}else{
36+
this._currentValue = undefined;
37+
}
38+
}
39+
private onValueChangedFromControl(value?:AppModule) {
40+
console.log("value selected: ",value);
41+
this._currentValue = value?.appmoduleid ?? undefined;
42+
this.notifyOutputChanged();
43+
}
44+
/**
45+
* Called when any value in the property bag has changed. This includes field values, data-sets, global values such as container height and width, offline status, control metadata values such as label, visible, etc.
46+
* @param context The entire property bag available to control via Context Object; It contains values as set up by the customizer mapped to names defined in the manifest, as well as utility functions
47+
* @returns ReactElement root react element for the control
48+
*/
49+
public updateView(context: ComponentFramework.Context<IInputs>): React.ReactElement {
50+
this.setCurrentValueFromContext(context);
51+
const props: AppProps = {
52+
context:context,
53+
onChange:this.onValueChangedFromControl.bind(this)
54+
};
55+
return React.createElement(App,props);
56+
}
57+
58+
/**
59+
* It is called by the framework prior to a control receiving new data.
60+
* @returns an object based on nomenclature defined in manifest, expecting object[s] for property marked as "bound" or "output"
61+
*/
62+
public getOutputs(): IOutputs {
63+
if(this._currentValue !== null && this._currentValue !== undefined){
64+
return {
65+
MainField:this._currentValue
66+
};
67+
}
68+
return { MainField:undefined };
69+
}
70+
71+
/**
72+
* Called when the control is to be removed from the DOM tree. Controls should use this call for cleanup.
73+
* i.e. cancelling any pending remote calls, removing listeners, etc.
74+
*/
75+
public destroy(): void {
76+
// Add code to cleanup control if necessary
77+
}
78+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
export class AppModule {
2+
appmoduleid:string;
3+
appmoduleidunique:string;
4+
name:string;
5+
uniquename:string
6+
}

0 commit comments

Comments
 (0)