create-ec-app 1.5.0 → 1.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +86 -21
- package/dist/cssScope.d.ts +4 -0
- package/dist/cssScope.d.ts.map +1 -0
- package/dist/cssScope.js +124 -0
- package/dist/cssScope.js.map +1 -0
- package/dist/index.js +28 -94
- package/dist/index.js.map +1 -1
- package/dist/pcf.d.ts.map +1 -1
- package/dist/pcf.js +13 -84
- package/dist/pcf.js.map +1 -1
- package/dist/portalContainers.d.ts +7 -0
- package/dist/portalContainers.d.ts.map +1 -0
- package/dist/portalContainers.js +118 -0
- package/dist/portalContainers.js.map +1 -0
- package/package.json +6 -3
- package/scripts/check-generated-css-scope.mjs +98 -37
- package/templates/base/src/App.tsx +1 -1
- package/templates/base/src/index.css +1 -4
- package/templates/base/src/main.tsx +1 -4
- package/templates/pcf/base/README.md +28 -9
- package/templates/pcf/base/index.ts +30 -6
- package/templates/pcf/base/runtime/PcfAppShell.tsx +17 -0
- package/templates/pcf/base/runtime/emptyStyles.js +1 -0
- package/templates/pcf/base/webpack.config.js +13 -0
- package/templates/targets/code-apps/README.md +246 -0
- package/templates/targets/code-apps/package.patch.json +8 -0
- package/templates/targets/code-apps/power.config.example.json +14 -0
- package/templates/targets/code-apps/vite.config.patch.ts +15 -0
- package/templates/targets/power-pages/src/App.patch.tsx +1 -1
- package/templates/targets/webresource/README.md +21 -7
- package/templates/ui/kendo/src/main.patch.tsx +1 -4
- package/templates/ui/shadcn-ui/components.json +1 -1
- package/templates/ui/shadcn-ui/src/index.patch.css +9 -26
- package/templates/base/src/runtime/EcAppShell.tsx +0 -29
|
@@ -4,7 +4,7 @@ import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
|
|
4
4
|
|
|
5
5
|
import App from "{{PROJECT_APP_IMPORT}}";
|
|
6
6
|
import "{{PROJECT_CSS_IMPORT}}";
|
|
7
|
-
import {
|
|
7
|
+
import { PcfAppShell } from "./runtime/PcfAppShell";
|
|
8
8
|
import type { IInputs, IOutputs } from "./control/generated/ManifestTypes";
|
|
9
9
|
import type {
|
|
10
10
|
PcfRuntimeContext,
|
|
@@ -16,13 +16,37 @@ function sanitizeGuid(value: string | null | undefined): string | null {
|
|
|
16
16
|
return value.replace(/[{}]/g, "");
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
+
function getPageClientUrl(
|
|
20
|
+
pageContext:
|
|
21
|
+
| {
|
|
22
|
+
clientUrl?: unknown;
|
|
23
|
+
getClientUrl?: unknown;
|
|
24
|
+
}
|
|
25
|
+
| undefined,
|
|
26
|
+
): string | null {
|
|
27
|
+
const getClientUrl = pageContext?.getClientUrl;
|
|
28
|
+
if (typeof getClientUrl === "function") {
|
|
29
|
+
try {
|
|
30
|
+
const clientUrl = getClientUrl.call(pageContext);
|
|
31
|
+
return typeof clientUrl === "string" ? clientUrl : null;
|
|
32
|
+
} catch {
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return typeof pageContext?.clientUrl === "string"
|
|
38
|
+
? pageContext.clientUrl
|
|
39
|
+
: null;
|
|
40
|
+
}
|
|
41
|
+
|
|
19
42
|
function getContextInfo(context: ComponentFramework.Context<IInputs>) {
|
|
20
43
|
const pageContext = (
|
|
21
44
|
context as ComponentFramework.Context<IInputs> & {
|
|
22
45
|
page?: {
|
|
46
|
+
clientUrl?: unknown;
|
|
23
47
|
entityId?: string;
|
|
24
48
|
entityTypeName?: string;
|
|
25
|
-
getClientUrl?:
|
|
49
|
+
getClientUrl?: unknown;
|
|
26
50
|
};
|
|
27
51
|
}
|
|
28
52
|
).page;
|
|
@@ -39,7 +63,7 @@ function getContextInfo(context: ComponentFramework.Context<IInputs>) {
|
|
|
39
63
|
),
|
|
40
64
|
entityName:
|
|
41
65
|
modeContextInfo?.entityTypeName ?? pageContext?.entityTypeName ?? null,
|
|
42
|
-
clientUrl: pageContext
|
|
66
|
+
clientUrl: getPageClientUrl(pageContext),
|
|
43
67
|
userId: sanitizeGuid(context.userSettings.userId),
|
|
44
68
|
};
|
|
45
69
|
}
|
|
@@ -112,8 +136,8 @@ export class {{PCF_CONSTRUCTOR}}
|
|
|
112
136
|
_state: ComponentFramework.Dictionary,
|
|
113
137
|
container: HTMLDivElement,
|
|
114
138
|
): void {
|
|
115
|
-
container.classList.add("
|
|
116
|
-
container.dataset.
|
|
139
|
+
container.classList.add("pcf-shell-control");
|
|
140
|
+
container.dataset.pcfControl = "{{PCF_CONSTRUCTOR}}";
|
|
117
141
|
this.runtime = createRuntime(context);
|
|
118
142
|
this.root = createRoot(container);
|
|
119
143
|
this.render();
|
|
@@ -144,7 +168,7 @@ export class {{PCF_CONSTRUCTOR}}
|
|
|
144
168
|
StrictMode,
|
|
145
169
|
null,
|
|
146
170
|
React.createElement(
|
|
147
|
-
|
|
171
|
+
PcfAppShell,
|
|
148
172
|
null,
|
|
149
173
|
React.createElement(
|
|
150
174
|
QueryClientProvider,
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
|
|
3
|
+
import { PortalContainerContext } from "{{PROJECT_PORTAL_CONTAINER_IMPORT}}";
|
|
4
|
+
|
|
5
|
+
export function PcfAppShell({ children }: { children: React.ReactNode }) {
|
|
6
|
+
const [portalContainer, setPortalContainer] =
|
|
7
|
+
React.useState<HTMLDivElement | null>(null);
|
|
8
|
+
|
|
9
|
+
return (
|
|
10
|
+
<div data-pcf-app-root="">
|
|
11
|
+
<PortalContainerContext.Provider value={portalContainer}>
|
|
12
|
+
{children}
|
|
13
|
+
<div data-pcf-portal-root="" ref={setPortalContainer} />
|
|
14
|
+
</PortalContainerContext.Provider>
|
|
15
|
+
</div>
|
|
16
|
+
);
|
|
17
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
module.exports = {};
|
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
const path = require("path");
|
|
2
|
+
const webpack = require("webpack");
|
|
3
|
+
|
|
4
|
+
const projectSrcPath = path.resolve(__dirname, "{{PROJECT_SRC_ALIAS}}");
|
|
2
5
|
|
|
3
6
|
module.exports = {
|
|
4
7
|
resolve: {
|
|
@@ -8,4 +11,14 @@ module.exports = {
|
|
|
8
11
|
"react-dom": path.resolve(__dirname, "{{PROJECT_REACT_DOM_ALIAS}}"),
|
|
9
12
|
},
|
|
10
13
|
},
|
|
14
|
+
plugins: [
|
|
15
|
+
new webpack.NormalModuleReplacementPlugin(
|
|
16
|
+
/\.(css|scss|sass)$/,
|
|
17
|
+
(resource) => {
|
|
18
|
+
if (path.resolve(resource.context).startsWith(projectSrcPath)) {
|
|
19
|
+
resource.request = path.resolve(__dirname, "runtime/emptyStyles.js");
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
),
|
|
23
|
+
],
|
|
11
24
|
};
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
# EC Power Apps Code App
|
|
2
|
+
|
|
3
|
+
React + TypeScript template for Power Apps code apps. Generated by `create-ec-app`, it keeps the EC base app structure, Tailwind setup, TanStack Query provider, and selected UI layer, then adds Microsoft Power Apps code app support through `@microsoft/power-apps` and `@microsoft/power-apps-vite`.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- React + TypeScript with Vite
|
|
8
|
+
- Tailwind CSS
|
|
9
|
+
- UI library choice: Kendo UI or shadcn/ui
|
|
10
|
+
- TanStack Query provider pre-wired in `src/main.tsx`
|
|
11
|
+
- Power Apps SDK dependency for code app APIs and generated connector services
|
|
12
|
+
- Power Apps Vite plugin for local play URLs, Power Apps CORS support, and `base: "./"` build output
|
|
13
|
+
|
|
14
|
+
## Prerequisites
|
|
15
|
+
|
|
16
|
+
- Node.js LTS and npm
|
|
17
|
+
- Git
|
|
18
|
+
- A Power Platform environment with Power Apps code apps enabled
|
|
19
|
+
- A Power Apps Premium license for users who run the code app
|
|
20
|
+
|
|
21
|
+
Admins enable code apps in Power Platform admin center by opening the target environment, then Settings > Product > Features, and turning on **Power Apps code apps**.
|
|
22
|
+
|
|
23
|
+
## Getting Started
|
|
24
|
+
|
|
25
|
+
Install dependencies if they were not installed during scaffolding:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
npm install
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Initialize the code app metadata and authenticate against your Power Platform environment:
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
npx power-apps init --display-name "{{APP_NAME}}" --environment-id <environment-id> --app-url http://localhost:5173
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
You can omit the options and run `npx power-apps init` to answer the interactive prompts instead. The command creates `power.config.json`, which the Power Apps SDK, Vite plugin, and deployment commands use.
|
|
38
|
+
|
|
39
|
+
This template includes `power.config.example.json` only as a reference. Do not rename it before running `npx power-apps init`; the Microsoft CLI refuses to initialize when a real `power.config.json` already exists. The generated `power.config.json` includes your environment ID, app metadata, build settings, and connection/data references. It does not contain authentication tokens, but it is environment-specific, so decide whether to commit it based on your ALM flow.
|
|
40
|
+
|
|
41
|
+
## Local Development
|
|
42
|
+
|
|
43
|
+
Start the app locally:
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
npm run dev
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
The Power Apps Vite plugin prints a **Local Play** URL. Open that URL in the same browser profile you use for your Power Platform tenant so authentication and connector calls work as expected.
|
|
50
|
+
|
|
51
|
+
If browser local network access restrictions appear, allow the browser prompt for the local endpoint. For iframe embedding scenarios, Microsoft recommends adding `allow="local-network-access"` to the iframe.
|
|
52
|
+
|
|
53
|
+
## Data Access
|
|
54
|
+
|
|
55
|
+
Code apps should not use the Dynamics webresource `AuthService`/`token.json` pattern. In a code app, the Power Apps host manages end-user authentication, and `@microsoft/power-apps` plus generated services perform data requests through Power Platform connectors and Dataverse references.
|
|
56
|
+
|
|
57
|
+
The normal flow is:
|
|
58
|
+
|
|
59
|
+
1. Create or identify the connection in [Power Apps](https://make.powerapps.com).
|
|
60
|
+
2. Add the data source to this project with the Power Apps CLI.
|
|
61
|
+
3. Import the generated model and service files from `src/generated`.
|
|
62
|
+
4. Build and push the app with `npx power-apps push`.
|
|
63
|
+
|
|
64
|
+
### Connect to Dataverse
|
|
65
|
+
|
|
66
|
+
After `npx power-apps init`, add a Dataverse table by logical name:
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
npx power-apps add-data-source --api-id dataverse --resource-name <table-logical-name>
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Example:
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
npx power-apps add-data-source --api-id dataverse --resource-name account
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
The CLI updates `power.config.json` and generates typed files under `src/generated/models` and `src/generated/services`. For example, after adding Accounts you can use the generated service:
|
|
79
|
+
|
|
80
|
+
```ts
|
|
81
|
+
import { AccountsService } from "./generated/services/AccountsService";
|
|
82
|
+
import type { Accounts } from "./generated/models/AccountsModel";
|
|
83
|
+
|
|
84
|
+
export async function listAccounts() {
|
|
85
|
+
const result = await AccountsService.getAll({
|
|
86
|
+
select: ["name", "accountnumber"],
|
|
87
|
+
top: 50,
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
return result.data ?? [];
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export async function createAccount(name: string) {
|
|
94
|
+
const account = { name } satisfies Partial<Accounts>;
|
|
95
|
+
const result = await AccountsService.create(
|
|
96
|
+
account as Omit<Accounts, "accountid">,
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
return result.data;
|
|
100
|
+
}
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
For Dataverse CRUD, use the generated service methods such as `create`, `get`, `getAll`, `update`, and `delete`. Prefer `select` when reading data so the app retrieves only the columns it needs.
|
|
104
|
+
|
|
105
|
+
### Connect to Other Power Platform Connectors
|
|
106
|
+
|
|
107
|
+
For non-Dataverse connectors, first create or locate the connection in Power Apps, then get the API name and connection ID. You can use the Power Apps maker portal or:
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
pac connection list
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
Add a non-tabular connector data source:
|
|
114
|
+
|
|
115
|
+
```bash
|
|
116
|
+
npx power-apps add-data-source --api-id <api-name> --connection-id <connection-id>
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
Add a tabular connector data source such as SQL or SharePoint:
|
|
120
|
+
|
|
121
|
+
```bash
|
|
122
|
+
npx power-apps add-data-source \
|
|
123
|
+
--api-id <api-name> \
|
|
124
|
+
--connection-id <connection-id> \
|
|
125
|
+
--resource-name <table-or-list-name> \
|
|
126
|
+
--dataset <dataset-name>
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
You can discover datasets and tables through the npm CLI:
|
|
130
|
+
|
|
131
|
+
```bash
|
|
132
|
+
npx power-apps list-datasets --api-id <api-name> --connection-id <connection-id>
|
|
133
|
+
npx power-apps list-tables --api-id <api-name> --connection-id <connection-id> --dataset <dataset-name>
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
For ALM-friendly apps, prefer connection references instead of binding directly to a maker's connection:
|
|
137
|
+
|
|
138
|
+
```bash
|
|
139
|
+
npx power-apps list-connection-references --solution-id <solution-id>
|
|
140
|
+
npx power-apps add-data-source \
|
|
141
|
+
--api-id <api-name> \
|
|
142
|
+
--connection-ref <connection-reference-logical-name> \
|
|
143
|
+
--solution-id <solution-id>
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
For tabular connectors, environment variable references can be used for dataset and table values:
|
|
147
|
+
|
|
148
|
+
```bash
|
|
149
|
+
npx power-apps add-data-source \
|
|
150
|
+
--api-id shared_sharepointonline \
|
|
151
|
+
--connection-id <connection-id> \
|
|
152
|
+
--dataset "@envvar:<site-environment-variable-schema-name>" \
|
|
153
|
+
--resource-name "@envvar:<list-environment-variable-schema-name>"
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
### Dataverse Actions and Functions
|
|
157
|
+
|
|
158
|
+
Dataverse actions and functions use the newer npm-only commands:
|
|
159
|
+
|
|
160
|
+
```bash
|
|
161
|
+
npx power-apps find-dataverse-api --search "WhoAmI"
|
|
162
|
+
npx power-apps add-dataverse-api --api-name WhoAmI
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
The command updates `power.config.json`, writes schema files, and generates a service such as:
|
|
166
|
+
|
|
167
|
+
```ts
|
|
168
|
+
import { WhoAmIService } from "./generated/services/WhoAmIService";
|
|
169
|
+
|
|
170
|
+
const result = await WhoAmIService.WhoAmI();
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
### Runtime Context
|
|
174
|
+
|
|
175
|
+
Use `getContext` from the Power Apps SDK when you need app, environment, user, query parameter, or session details:
|
|
176
|
+
|
|
177
|
+
```ts
|
|
178
|
+
import { getContext } from "@microsoft/power-apps/app";
|
|
179
|
+
|
|
180
|
+
const context = await getContext();
|
|
181
|
+
|
|
182
|
+
console.log(context.app.environmentId);
|
|
183
|
+
console.log(context.user.userPrincipalName);
|
|
184
|
+
console.log(context.host.sessionId);
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
This replaces most webresource-style assumptions about `window.Xrm`, parent frame context, or manually loaded local tokens.
|
|
188
|
+
|
|
189
|
+
## Code Apps vs Webresources
|
|
190
|
+
|
|
191
|
+
This target is intentionally different from the `webresource` target:
|
|
192
|
+
|
|
193
|
+
- Webresources run inside Dynamics 365/model-driven app pages and can use `window.Xrm`; Code Apps run in the Power Apps host.
|
|
194
|
+
- Webresources in this repo use `src/services/AuthService.ts` and `token.json` for local Dataverse Web API calls; Code Apps should use `@microsoft/power-apps`, `power.config.json`, and generated services.
|
|
195
|
+
- The Code Apps scaffold removes webresource/Power Pages auth artifacts if they are present during generation.
|
|
196
|
+
- Webresource builds are tuned for web resource upload with deterministic `main.css`/JS files; Code Apps builds are published with `npx power-apps push`.
|
|
197
|
+
- Code Apps can use Power Platform connectors, connection references, environment variables, generated Dataverse CRUD services, and generated Dataverse action/function services as part of the platform hosting model.
|
|
198
|
+
|
|
199
|
+
## Build and Deploy
|
|
200
|
+
|
|
201
|
+
Build the production assets:
|
|
202
|
+
|
|
203
|
+
```bash
|
|
204
|
+
npm run build
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
Publish a new version to the environment configured in `power.config.json`:
|
|
208
|
+
|
|
209
|
+
```bash
|
|
210
|
+
npx power-apps push
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
When the push succeeds, the command returns a Power Apps URL where you can run the app. You can also open [Power Apps](https://make.powerapps.com) to play, share, or review the app details.
|
|
214
|
+
|
|
215
|
+
## Solution and ALM Notes
|
|
216
|
+
|
|
217
|
+
For healthy ALM, use a non-default solution. If your environment has a preferred solution, new code apps are saved there by default when deployed. To target a specific solution with the PAC CLI path, use:
|
|
218
|
+
|
|
219
|
+
```bash
|
|
220
|
+
pac code push --solutionName <solution-name>
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
If you already deployed the app and need to add it to a solution manually, open Power Apps, go to Solutions, choose the solution, then select Add existing > App > Code app.
|
|
224
|
+
|
|
225
|
+
Microsoft's current npm CLI is the preferred path for new code app work. Older docs still show the PAC CLI flow:
|
|
226
|
+
|
|
227
|
+
```bash
|
|
228
|
+
pac auth create
|
|
229
|
+
pac env select --environment <environment-id>
|
|
230
|
+
pac code init --displayname "{{APP_NAME}}"
|
|
231
|
+
npm run build | pac code push
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
Use the npm CLI unless you specifically need a PAC CLI option that has not moved yet.
|
|
235
|
+
|
|
236
|
+
## Related Microsoft Documentation
|
|
237
|
+
|
|
238
|
+
- [Power Apps code apps overview](https://learn.microsoft.com/en-us/power-apps/developer/code-apps/overview)
|
|
239
|
+
- [Quickstart with npm CLI](https://learn.microsoft.com/en-us/power-apps/developer/code-apps/how-to/npm-quickstart)
|
|
240
|
+
- [Code apps architecture](https://learn.microsoft.com/en-us/power-apps/developer/code-apps/architecture)
|
|
241
|
+
- [Connect your code app to data](https://learn.microsoft.com/en-us/power-apps/developer/code-apps/how-to/connect-to-data)
|
|
242
|
+
- [Connect your code app to Dataverse](https://learn.microsoft.com/en-us/power-apps/developer/code-apps/how-to/connect-to-dataverse)
|
|
243
|
+
- [Add a Dataverse action or function](https://learn.microsoft.com/en-us/power-apps/developer/code-apps/how-to/add-dataverse-action-function)
|
|
244
|
+
- [Get context data](https://learn.microsoft.com/en-us/power-apps/developer/code-apps/how-to/retrieve-context)
|
|
245
|
+
- [Use environment variables in data sources](https://learn.microsoft.com/en-us/power-apps/developer/code-apps/how-to/use-environment-variables)
|
|
246
|
+
- [Application lifecycle management for code apps](https://learn.microsoft.com/en-us/power-apps/developer/code-apps/how-to/alm)
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": "1.0",
|
|
3
|
+
"appId": null,
|
|
4
|
+
"appDisplayName": "{{APP_NAME}}",
|
|
5
|
+
"region": "prod",
|
|
6
|
+
"environmentId": "<environment-id>",
|
|
7
|
+
"description": " ",
|
|
8
|
+
"buildPath": "./dist",
|
|
9
|
+
"buildEntryPoint": "index.html",
|
|
10
|
+
"localAppUrl": "http://localhost:5173",
|
|
11
|
+
"logoPath": "Default",
|
|
12
|
+
"connectionReferences": {},
|
|
13
|
+
"databaseReferences": {}
|
|
14
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import tailwindcss from "@tailwindcss/vite";
|
|
3
|
+
import react from "@vitejs/plugin-react";
|
|
4
|
+
import { powerApps } from "@microsoft/power-apps-vite/plugin";
|
|
5
|
+
import { defineConfig } from "vite";
|
|
6
|
+
|
|
7
|
+
// https://vite.dev/config/
|
|
8
|
+
export default defineConfig({
|
|
9
|
+
plugins: [react(), tailwindcss(), powerApps()],
|
|
10
|
+
resolve: {
|
|
11
|
+
alias: {
|
|
12
|
+
"@": path.resolve(__dirname, "./src"),
|
|
13
|
+
},
|
|
14
|
+
},
|
|
15
|
+
});
|
|
@@ -4,7 +4,7 @@ function App() {
|
|
|
4
4
|
const { isAuthenticated, user } = useAuth(); //INFO: User is where the token information is stored (user?.idToken)
|
|
5
5
|
|
|
6
6
|
return (
|
|
7
|
-
<div className="
|
|
7
|
+
<div className="flex h-screen flex-col items-center justify-center gap-4">
|
|
8
8
|
{isAuthenticated ? (
|
|
9
9
|
<div>You are logged in</div>
|
|
10
10
|
) : (
|
|
@@ -270,10 +270,15 @@ Basic flow:
|
|
|
270
270
|
npm run build
|
|
271
271
|
```
|
|
272
272
|
|
|
273
|
-
2. Run the generator
|
|
273
|
+
2. Run the generator from the webresource root. Point `--pcf-dir` at the webresource root and `--output` at the PCF project folder you want to generate:
|
|
274
274
|
|
|
275
275
|
```bash
|
|
276
|
-
npx create-ec-app@latest
|
|
276
|
+
npx create-ec-app@latest \
|
|
277
|
+
--pcf-dir . \
|
|
278
|
+
--output ./pcf/{{ControlName}} \
|
|
279
|
+
--namespace EC \
|
|
280
|
+
--constructor {{ControlName}} \
|
|
281
|
+
--display-name "Control Name"
|
|
277
282
|
```
|
|
278
283
|
|
|
279
284
|
3. Install dependencies inside that generated PCF directory:
|
|
@@ -281,32 +286,41 @@ npx create-ec-app@latest --pcf-dir ./pcf/{{ControlName}} namespace {{EC}} --cons
|
|
|
281
286
|
```bash
|
|
282
287
|
cd ./pcf/{{ControlName}}
|
|
283
288
|
npm install
|
|
289
|
+
npm run build
|
|
284
290
|
```
|
|
285
291
|
|
|
286
292
|
This writes a standalone PCF project to the `--pcf-dir` folder. The generated control:
|
|
287
293
|
|
|
288
294
|
- imports `src/App.tsx` directly instead of wrapping built HTML in an iframe
|
|
289
|
-
-
|
|
295
|
+
- creates and imports `pcf-scoped.css` from the built `dist/main.css`
|
|
296
|
+
- scopes every non-keyframe CSS selector under the generated PCF host selector
|
|
290
297
|
- creates `src/runtime/types.ts` only if that file does not already exist
|
|
291
298
|
- provides a runtime object with record context and `context.webAPI` access inside the generated PCF shell, following the `PcfBase` pattern
|
|
292
299
|
- mounts your React app directly into the PCF container
|
|
293
300
|
|
|
294
|
-
|
|
301
|
+
Regenerate after app code or CSS changes by running the same sequence again from the webresource root:
|
|
295
302
|
|
|
296
303
|
```bash
|
|
297
|
-
npm install
|
|
298
304
|
npm run build
|
|
299
|
-
npx create-ec-app@latest
|
|
305
|
+
npx create-ec-app@latest \
|
|
306
|
+
--pcf-dir . \
|
|
307
|
+
--output ./pcf/FusionNotebookHost \
|
|
308
|
+
--namespace EC \
|
|
309
|
+
--constructor FusionNotebookHost \
|
|
310
|
+
--display-name "Fusion Notebook Host"
|
|
300
311
|
cd pcf/FusionNotebookHost
|
|
301
312
|
npm install
|
|
302
313
|
npm run build
|
|
303
314
|
```
|
|
304
315
|
|
|
316
|
+
Regeneration removes and recreates the PCF output folder, so keep durable app code in `src` and use generator templates or layers for repeatable PCF-specific changes.
|
|
317
|
+
|
|
305
318
|
What gets generated:
|
|
306
319
|
|
|
307
320
|
- a minimal PCF wrapper project under `pcf/<ConstructorName>`
|
|
308
321
|
- a checked-in PCF shell stamped out from `create-ec-app/templates/pcf/base`
|
|
309
|
-
- direct imports back to your webresource source
|
|
322
|
+
- direct imports back to your webresource source
|
|
323
|
+
- a generated `pcf-scoped.css` file with CSS selectors scoped to the PCF control
|
|
310
324
|
|
|
311
325
|
## Notes
|
|
312
326
|
|
|
@@ -4,7 +4,6 @@ import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
|
|
4
4
|
import "@progress/kendo-theme-fluent/dist/all.css";
|
|
5
5
|
import "./index.css";
|
|
6
6
|
import App from "./App.tsx";
|
|
7
|
-
import { EcAppShell } from "./runtime/EcAppShell.tsx";
|
|
8
7
|
|
|
9
8
|
const queryClient = new QueryClient({
|
|
10
9
|
defaultOptions: {
|
|
@@ -24,9 +23,7 @@ const root = createRoot(document.getElementById("root")!);
|
|
|
24
23
|
root.render(
|
|
25
24
|
<StrictMode>
|
|
26
25
|
<QueryClientProvider client={queryClient}>
|
|
27
|
-
<
|
|
28
|
-
<App />
|
|
29
|
-
</EcAppShell>
|
|
26
|
+
<App />
|
|
30
27
|
</QueryClientProvider>
|
|
31
28
|
</StrictMode>
|
|
32
29
|
);
|
|
@@ -1,12 +1,9 @@
|
|
|
1
|
-
@
|
|
2
|
-
|
|
3
|
-
@import "tailwindcss/theme.css" layer(theme) prefix(ec) important;
|
|
4
|
-
@import "tailwindcss/utilities.css" layer(utilities) prefix(ec) important;
|
|
5
|
-
@import "tw-animate-css/prefix";
|
|
1
|
+
@import "tailwindcss";
|
|
2
|
+
@import "tw-animate-css";
|
|
6
3
|
@import "shadcn/tailwind.css";
|
|
7
4
|
|
|
8
5
|
@custom-variant hover (&:hover);
|
|
9
|
-
@custom-variant dark (&:is(.
|
|
6
|
+
@custom-variant dark (&:is(.dark *));
|
|
10
7
|
|
|
11
8
|
@theme inline {
|
|
12
9
|
--radius-sm: calc(var(--radius) - 4px);
|
|
@@ -46,7 +43,7 @@
|
|
|
46
43
|
--color-sidebar-ring: var(--sidebar-ring);
|
|
47
44
|
}
|
|
48
45
|
|
|
49
|
-
|
|
46
|
+
:root {
|
|
50
47
|
--radius: 0.625rem;
|
|
51
48
|
--background: oklch(1 0 0);
|
|
52
49
|
--foreground: oklch(0.145 0 0);
|
|
@@ -79,13 +76,9 @@
|
|
|
79
76
|
--sidebar-accent-foreground: oklch(0.205 0 0);
|
|
80
77
|
--sidebar-border: oklch(0.922 0 0);
|
|
81
78
|
--sidebar-ring: oklch(0.708 0 0);
|
|
82
|
-
|
|
83
|
-
background: var(--background);
|
|
84
|
-
color: var(--foreground);
|
|
85
79
|
}
|
|
86
80
|
|
|
87
|
-
.
|
|
88
|
-
.ec-app[data-ec-app-id="{{APP_NAME}}"] .dark {
|
|
81
|
+
.dark {
|
|
89
82
|
--background: oklch(0.145 0 0);
|
|
90
83
|
--foreground: oklch(0.985 0 0);
|
|
91
84
|
--card: oklch(0.205 0 0);
|
|
@@ -120,20 +113,10 @@
|
|
|
120
113
|
}
|
|
121
114
|
|
|
122
115
|
@layer base {
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
:where(.ec-app *) {
|
|
128
|
-
border-color: var(--border);
|
|
129
|
-
outline-color: color-mix(in oklab, var(--ring) 50%, transparent);
|
|
116
|
+
* {
|
|
117
|
+
@apply border-border outline-ring/50;
|
|
130
118
|
}
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
font: inherit;
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
:where(.ec-app button:not(:disabled), .ec-app [role="button"]:not(:disabled)) {
|
|
137
|
-
cursor: pointer;
|
|
119
|
+
body {
|
|
120
|
+
@apply bg-background text-foreground;
|
|
138
121
|
}
|
|
139
122
|
}
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
import * as React from "react";
|
|
2
|
-
|
|
3
|
-
export const EC_APP_SCOPE_CLASS = "ec-app";
|
|
4
|
-
export const EC_APP_ID = "{{APP_NAME}}";
|
|
5
|
-
export const EC_PCF_SCOPE_CLASS = "ec-pcf-shell-control";
|
|
6
|
-
|
|
7
|
-
const EcPortalContainerContext = React.createContext<HTMLElement | null>(null);
|
|
8
|
-
|
|
9
|
-
export function useEcPortalContainer() {
|
|
10
|
-
return React.useContext(EcPortalContainerContext);
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export function EcAppShell({ children }: { children: React.ReactNode }) {
|
|
14
|
-
const [portalContainer, setPortalContainer] =
|
|
15
|
-
React.useState<HTMLDivElement | null>(null);
|
|
16
|
-
|
|
17
|
-
return (
|
|
18
|
-
<div
|
|
19
|
-
className={EC_APP_SCOPE_CLASS}
|
|
20
|
-
data-ec-app-id={EC_APP_ID}
|
|
21
|
-
data-ec-app-root=""
|
|
22
|
-
>
|
|
23
|
-
<EcPortalContainerContext.Provider value={portalContainer}>
|
|
24
|
-
{children}
|
|
25
|
-
<div data-ec-portal-root="" ref={setPortalContainer} />
|
|
26
|
-
</EcPortalContainerContext.Provider>
|
|
27
|
-
</div>
|
|
28
|
-
);
|
|
29
|
-
}
|