├── .gitignore
├── .pipelines
├── cdpx_run_ps.cmd
├── package.ps1
├── pipeline.user.windows.yml
├── restore.ps1
└── version.ps1
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── Capture report views
├── css
│ ├── content.css
│ ├── header.css
│ ├── modal.css
│ ├── report.css
│ └── share-bookmark.css
├── img
│ ├── allviews.svg
│ ├── captureview.svg
│ ├── contoso.svg
│ ├── cross.svg
│ ├── spinner.svg
│ └── tickicon.svg
├── index.html
├── js
│ ├── error_listener.js
│ ├── globals.js
│ ├── index.js
│ ├── session_utils.js
│ └── share_bookmark.js
└── share_bookmark.html
├── Customize report colors and mode
├── css
│ ├── container.css
│ ├── dropdown.css
│ ├── header.css
│ └── report.css
├── img
│ ├── contoso.svg
│ └── spinner.svg
├── index.html
└── js
│ ├── error_listener.js
│ ├── globals.js
│ ├── index.js
│ ├── session_utils.js
│ └── themes.js
├── Go from insights to quick action
├── css
│ ├── dialog_campaign_list.css
│ ├── dialog_send_coupon.css
│ ├── dialog_success.css
│ ├── embed_area_style.css
│ └── header_style.css
├── img
│ ├── contoso.svg
│ ├── cross.svg
│ ├── spinner.svg
│ └── success.svg
├── index.html
└── js
│ ├── error_listener.js
│ ├── globals.js
│ ├── index.js
│ └── session_utils.js
├── LICENSE
├── Personalize top insights
├── css
│ ├── content.css
│ ├── dropdown.css
│ ├── header.css
│ └── report.css
├── img
│ ├── caret.svg
│ ├── colspan.svg
│ ├── column1.svg
│ ├── column2.svg
│ ├── column3.svg
│ ├── contoso.svg
│ ├── rowspan.svg
│ └── spinner.svg
├── index.html
└── js
│ ├── error_listener.js
│ ├── globals.js
│ ├── index.js
│ └── session_utils.js
├── Quickly create and personalize visuals
├── css
│ ├── content.css
│ ├── header.css
│ ├── popup.css
│ └── report.css
├── img
│ ├── close.svg
│ ├── contoso.svg
│ ├── disabled-images
│ │ ├── center-align.svg
│ │ ├── chevron.svg
│ │ ├── eraser.svg
│ │ ├── left-align.svg
│ │ └── right-align.svg
│ ├── enabled-images
│ │ ├── center-align.svg
│ │ ├── chevron.svg
│ │ ├── eraser.svg
│ │ ├── left-align.svg
│ │ └── right-align.svg
│ ├── pencil.svg
│ └── spinner.svg
├── index.html
└── js
│ ├── error_listener.js
│ ├── globals.js
│ ├── index.js
│ ├── session_utils.js
│ ├── themes.js
│ └── visuals.js
├── README.md
├── SECURITY.md
├── SUPPORT.md
└── package.json
/.gitignore:
--------------------------------------------------------------------------------
1 | .vscode
--------------------------------------------------------------------------------
/.pipelines/cdpx_run_ps.cmd:
--------------------------------------------------------------------------------
1 | setlocal enabledelayedexpansion
2 | pushd "%~dp0\.."
3 | powershell.exe -ExecutionPolicy Unrestricted -NoProfile -WindowStyle Hidden -NonInteractive -File "%~dp0%~1"
4 | endlocal
5 | popd
6 | exit /B %ERRORLEVEL%
7 |
--------------------------------------------------------------------------------
/.pipelines/package.ps1:
--------------------------------------------------------------------------------
1 | $exitCode = 0;
2 |
3 | Write-Host "start: npm pack"
4 | & npm pack
5 | Write-Host "done: npm pack"
6 |
7 | $exitCode += $LASTEXITCODE;
8 |
9 | Write-Host "start: Get content of current folder"
10 | & dir
11 | Write-Host "done: Get content of current folder"
12 |
13 | exit $exitCode
--------------------------------------------------------------------------------
/.pipelines/pipeline.user.windows.yml:
--------------------------------------------------------------------------------
1 | environment:
2 | host:
3 | os: 'windows'
4 | flavor: 'server'
5 | version: '2016'
6 | runtime:
7 | provider: 'appcontainer'
8 | image: 'cdpxwinrs5.azurecr.io/global/vse2019/16.3.7:latest'
9 | source_mode: 'map'
10 |
11 | artifact_publish_options:
12 | publish_to_legacy_artifacts: false
13 | publish_to_pipeline_artifacts: true
14 | publish_to_cloudvault_artifacts: false
15 |
16 | # Enable signing on all declared artifacts.
17 | signing_options:
18 | profile: 'external_distribution'
19 | codesign_validation_glob_pattern: 'regex|.+(?:exe|dll)$;-|*.nd.dll;-|.gdn\\**'
20 |
21 | static_analysis_options:
22 | moderncop_options:
23 | files_to_scan:
24 | - from: '.\'
25 | exclude:
26 | - '**/*.svg'
27 | include:
28 | - '**/*'
29 |
30 | policheck_options:
31 | files_to_scan:
32 | - exclude:
33 | - '**/*.svg'
34 |
35 | binskim_options:
36 | files_to_scan:
37 | - exclude:
38 | - '**/*.svg'
39 |
40 | package_sources:
41 | npm:
42 | feeds:
43 | registry: https://powerbi.pkgs.visualstudio.com/_packaging/SDK.Externals/npm/registry/
44 |
45 | version:
46 | major: 1 # <---- Required field but not being used for 'custom' scheme
47 | minor: 0 # <---- Required field but not being used for 'custom' scheme
48 | system: 'custom' # <---- Set this to 'custom'. we will build the version using package.json in versioning commands.
49 |
50 | versioning:
51 | commands:
52 | - !!defaultcommand
53 | name: 'Set Version'
54 | arguments: 'version.ps1'
55 | command: '.pipelines\cdpx_run_ps.cmd'
56 |
57 | restore:
58 | commands:
59 | - !!defaultcommand
60 | name: 'Install NPM'
61 | arguments: 'restore.ps1'
62 | command: '.pipelines\cdpx_run_ps.cmd'
63 |
64 | # Commands to run during the packaging stage.
65 | package:
66 | commands:
67 | - !!buildcommand
68 | name: 'Package'
69 | arguments: 'package.ps1'
70 | command: '.pipelines\cdpx_run_ps.cmd'
71 | artifacts:
72 | - to: 'tgz-package'
73 | include:
74 | - "**/*.tgz"
75 |
--------------------------------------------------------------------------------
/.pipelines/restore.ps1:
--------------------------------------------------------------------------------
1 | Write-Host "Start build ..."
2 | Write-Host "Global node/npm paths ..."
3 | & where.exe npm
4 | & where.exe node
5 |
6 | Write-Host "Global node version"
7 | & node -v
8 |
9 | Write-Host "Global npm version"
10 | & npm -v
11 |
12 | $exitCode = 0;
13 |
14 | Write-Host "start: try install latest npm version"
15 | & npm install npm@latest -g
16 | Write-Host "done: try install latest npm version"
17 |
18 | # Do not update $exitCode because we do not want to fail if install latest npm version fails.
19 |
20 | exit $exitCode
--------------------------------------------------------------------------------
/.pipelines/version.ps1:
--------------------------------------------------------------------------------
1 | try {
2 | # package.json is in root folder, while version.ps1 runs in .pipelines folder.
3 | $version = (Get-Content "package.json") -join "`n" | ConvertFrom-Json | Select -ExpandProperty "version"
4 | $revision = $env:CDP_DEFINITION_BUILD_COUNT_DAY
5 | $buildNumber = "$version.$revision"
6 |
7 | Write-Host "Build Number is" $buildNumber
8 |
9 | [Environment]::SetEnvironmentVariable("CustomBuildNumber", $buildNumber, "User") # This will allow you to use it from env var in later steps of the same phase
10 | Write-Host "##vso[build.updatebuildnumber]${buildNumber}" # This will update build number on your build
11 | }
12 | catch {
13 | Write-Error $_.Exception
14 | exit 1;
15 | }
16 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Microsoft Open Source Code of Conduct
2 |
3 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/).
4 |
5 | Resources:
6 |
7 | - [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/)
8 | - [Microsoft Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/)
9 | - Contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with questions or concerns
10 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing
2 |
3 | ## Setup
4 |
5 | Clone the repository:
6 | ```
7 | git clone https://github.com/microsoft/PowerBI-Embedded-Showcases
8 | ```
9 |
10 | ## Code structure
11 |
12 | The repository has the code of the following Showcases:
13 | 1) Personalize top insights
14 | 2) Capture report views
15 | 3) Customize report colors and mode
16 | 4) Go from insights to quick action
17 | 5) Quickly create and personalize visuals
18 |
19 | Each Showcase lives in a separate folder as a standalone app.
20 |
21 | ## Build and Run
22 |
23 | The showcases are a set of plain html and javascript files. A build is not required.
24 | To run the showcase, open the index.html in the relevant folder.
25 |
--------------------------------------------------------------------------------
/Capture report views/css/content.css:
--------------------------------------------------------------------------------
1 | /* Copyright (c) Microsoft Corporation.
2 | Licensed under the MIT license. */
3 |
4 | .rotate {
5 | animation: rotation 1s infinite linear;
6 | }
7 |
8 | @keyframes rotation {
9 | from {
10 | transform: translate(-50%, -50%) rotate(0deg);
11 | }
12 | to {
13 | transform: translate(-50%, -50%) rotate(359deg);
14 | }
15 | }
16 |
17 | #overlay {
18 | position: absolute;
19 | width: inherit;
20 | height: inherit;
21 | }
22 |
23 | #overlay #spinner {
24 | position: fixed;
25 | top: 50%;
26 | left: 50%;
27 | }
28 |
29 | #main-div {
30 | background: #FFF;
31 | visibility: hidden;
32 | }
33 |
34 | .line {
35 | border-top: 0.5px solid rgba(0, 0, 0, .2);
36 | clear: both;
37 | margin: 109px 16px 0 16px;
38 | opacity: .7;
39 | width: auto;
40 | }
--------------------------------------------------------------------------------
/Capture report views/css/header.css:
--------------------------------------------------------------------------------
1 | /* Copyright (c) Microsoft Corporation.
2 | Licensed under the MIT license. */
3 |
4 | body {
5 | height: 100%;
6 | scrollbar-color: #A0AEB2 transparent;
7 | scrollbar-width: thin;
8 | }
9 |
10 | /* Change custom scrollbar appearance */
11 | body::-webkit-scrollbar-track {
12 | border-radius: 10px;
13 | background-color: transparent;
14 | }
15 |
16 | body::-webkit-scrollbar {
17 | background-color: transparent;
18 | height: 10px;
19 | width: 8px;
20 | }
21 |
22 | body::-webkit-scrollbar-thumb {
23 | background-color: #A0AEB2;
24 | border-radius: 10px;
25 | height: 30px;
26 | }
27 |
28 | #wrapper {
29 | margin: 0 auto;
30 | width: 100%;
31 | }
32 |
33 | #contoso-header {
34 | align-items: center;
35 | background: #1461F8;
36 | display: flex;
37 | height: 44px;
38 | margin: 0;
39 | }
40 |
41 | #contoso {
42 | height: 41px;
43 | margin-left: 24px;
44 | padding: 5px 0;
45 | width: 74px;
46 | }
--------------------------------------------------------------------------------
/Capture report views/css/modal.css:
--------------------------------------------------------------------------------
1 | /* Copyright (c) Microsoft Corporation.
2 | Licensed under the MIT license. */
3 |
4 | .interactive-buttons {
5 | background: #FFF;
6 | display: flex;
7 | margin-top: 44px;
8 | }
9 |
10 | .btn-action {
11 | align-items: flex-end;
12 | background: transparent;
13 | border: 1px solid #1461F8;
14 | border-radius: 4px;
15 | color: #1461F8;
16 | font: 14px/20px "Segoe UI SemiBold", segoe_ui_semibold, Tahoma, Arial;
17 | outline: none;
18 | text-align: center;
19 | }
20 |
21 | .btn-action:hover {
22 | background: rgba(20, 97, 248, 0.25);
23 | }
24 |
25 | /* For keyboard focus */
26 | .btn-action:focus-visible {
27 | box-shadow: 0 0 0 1.5px rgba(0,123,255,.5);
28 | }
29 |
30 | /* For mouse focus */
31 | .btn-action:focus:not(:focus-visible) {
32 | box-shadow: none;
33 | }
34 |
35 | .btn[aria-expanded="true"] {
36 | background: rgba(20, 97, 248, .15);
37 | color: #1461F8;
38 | }
39 |
40 | .btn-img {
41 | margin: -3.5px 8px 0 0;
42 | }
43 |
44 | #display-btn {
45 | margin: 16px;
46 | }
47 |
48 | #capture-btn {
49 | margin: 16px 16px 16px 0;
50 | }
51 |
52 | .showcase-checkbox-container {
53 | cursor: pointer;
54 | display: flex;
55 | font-weight: normal;
56 | height: auto;
57 | margin: 0;
58 | padding: 8px 0;
59 | position: relative;
60 | }
61 |
62 | .close-dropdown {
63 | cursor: default;
64 | display: flex;
65 | height: 28px;
66 | margin: 1.5px 1.5px 0 0;
67 | }
68 |
69 | .showcase-checkbox-container input {
70 | cursor: pointer;
71 | opacity: 0;
72 | position: absolute;
73 | }
74 |
75 | .showcase-checkmark {
76 | visibility: hidden;
77 | }
78 |
79 | .showcase-checkbox-container input:checked~.showcase-checkmark {
80 | border-color: #3E65FF;
81 | }
82 |
83 | .showcase-checkmark:after {
84 | content: "";
85 | display: none;
86 | position: absolute;
87 | }
88 |
89 | .showcase-checkbox-container input:checked~.showcase-checkmark:after {
90 | display: block;
91 | }
92 |
93 | .showcase-checkbox-container .showcase-checkmark:after {
94 | background: #3E65FF;
95 | border-radius: 50%;
96 | height: 8px;
97 | left: 3px;
98 | top: 3px;
99 | width: 8px;
100 | }
101 |
102 | .checkbox-title {
103 | display: block;
104 | margin: 0 24px;
105 | }
106 |
107 | #close-btn {
108 | background: transparent;
109 | border: 0;
110 | margin-left: auto;
111 | outline: none;
112 | padding: 0;
113 | }
114 |
115 | #close-list-btn {
116 | height: 24px;
117 | padding: 6px;
118 | width: 32px;
119 | }
120 |
121 | #bookmarks-list {
122 | background: #FFF;
123 | box-shadow: 0 1.2px 3.6px rgba(0, 0, 0, .108), 0 6.4px 14.4px rgba(90, 61, 61, .132);
124 | height: calc(100vh - 110px);
125 | margin: 16px 0 0 -16px;
126 | opacity: .98;
127 | /* Show scroll for small screen */
128 | overflow-y: auto;
129 | padding-top: 0;
130 | position: absolute;
131 | top: 0;
132 | transform: translate3d(16px, 50px, 0) !important;
133 | width: 228px;
134 | }
135 |
136 | #bookmarks-list::-webkit-scrollbar-track {
137 | border-radius: 10px;
138 | background-color: transparent;
139 | }
140 |
141 | #bookmarks-list::-webkit-scrollbar {
142 | background-color: transparent;
143 | height: 10px;
144 | width: 5px;
145 | }
146 |
147 | #bookmarks-list::-webkit-scrollbar-thumb {
148 | background-color: #A0AEB2;
149 | border-radius: 10px;
150 | height: 30px;
151 | }
152 |
153 | .capture-link-note {
154 | margin-bottom: 0;
155 | }
156 |
157 | .vertical-alignment-helper {
158 | display: table;
159 | height: 100%;
160 | pointer-events: none;
161 | width: 100%;
162 | }
163 |
164 | #tick-icon {
165 | display: none;
166 | margin-top: -4px;
167 | }
168 |
169 | .vertical-align-center {
170 | display: table-cell;
171 | pointer-events: none;
172 | vertical-align: middle;
173 | }
174 |
175 | .modal-content {
176 | box-shadow: 0 4.8px 14.4px rgba(0, 0, 0, 0.18), 0 25.6px 57.6px rgba(0, 0, 0, 0.22);
177 | border-radius: 2px;
178 | height: 303px;
179 | margin: 0 auto;
180 | max-width: inherit;
181 | pointer-events: all;
182 | width: inherit;
183 | }
184 |
185 | .modal-body {
186 | display: flex;
187 | padding: 0;
188 | position: relative;
189 | }
190 |
191 | .btn-modal {
192 | background: transparent;
193 | border: none;
194 | border-bottom: .5px solid rgba(0, 0, 0, .2);
195 | color: #8A8886;
196 | font: 400 14px "Segoe UI", segoe_ui_regular, Tahoma, Arial;
197 | outline: none;
198 | text-align: center;
199 | width: 50%;
200 | }
201 |
202 | .btn.active.focus,
203 | .btn.active:focus,
204 | .btn.focus,
205 | .btn.focus:active,
206 | .btn:active:focus,
207 | .btn:hover {
208 | box-shadow: none;
209 | color: #1461F8;
210 | outline: 0;
211 | outline-offset: -2px;
212 | text-decoration: none;
213 | }
214 |
215 | .btn-active {
216 | border-bottom: 2px solid #1461F8;
217 | color: #1A1A1A;
218 | font-weight: 600;
219 | }
220 |
221 | .modal-title {
222 | font: 16px "Segoe UI SemiBold", segoe_ui_semibold, Tahoma, Arial;
223 | }
224 |
225 | #close-modal-btn {
226 | border: none;
227 | height: 40px;
228 | margin: 2px 2px 0 0;
229 | opacity: 1;
230 | outline: none;
231 | width: 39px;
232 | }
233 |
234 | #btn-cross {
235 | margin: 10px;
236 | }
237 |
238 | #capture-view-div {
239 | display: none;
240 | }
241 |
242 | .btn-links {
243 | padding: 0 0 8px 0;
244 | }
245 |
246 | .btn-links:focus-visible {
247 | box-shadow: 0 0 0 2.5px rgba(0,123,255,.5);
248 | outline: none;
249 | }
250 |
251 | .btn-links:focus:not(:focus-visible) {
252 | box-shadow: none;
253 | outline: none;
254 | }
255 |
256 | #save-bookmark-btn:focus-visible {
257 | box-shadow: 0 0 0 2.5px rgba(0,123,255,.5);
258 | }
259 |
260 | #save-bookmark-btn:focus:not(:focus-visible) {
261 | box-shadow: none;
262 | }
263 |
264 | .modal-btn:focus-visible {
265 | box-shadow: 0 0 0 2.5px rgba(0,123,255,.5);
266 | }
267 |
268 | .modal-btn:focus:not(:focus-visible) {
269 | box-shadow: none;
270 | }
271 |
272 | .close:focus-visible {
273 | box-shadow: 0 0 0 2.5px rgba(0,123,255,.5);
274 | outline: none;
275 | }
276 |
277 | .close:focus:not(:focus-visible) {
278 | box-shadow: none;
279 | outline: none;
280 | }
281 |
282 | #save-btn-div {
283 | margin-top: 28px;
284 | text-align: center;
285 | }
286 |
287 | .capture-btn-div {
288 | text-align: center;
289 | }
290 |
291 | #save-bookmark-btn {
292 | background: transparent;
293 | border-radius: 4px;
294 | border: 1px solid #1461F8;
295 | color: #1461F8;
296 | font-weight: 600;
297 | margin: 5px 20px 0 5px;
298 | outline: none;
299 | padding: 6px 16px;
300 | }
301 |
302 | #save-bookmark-btn:hover {
303 | background: #1461F8;
304 | color: #FFF;
305 | }
306 |
307 | #save-bookmark-btn:active {
308 | background: rgba(20, 97, 248, 0.15);;
309 | color: #1461F8;
310 | }
311 |
312 | #copy-btn {
313 | outline: none;
314 | margin: 6px 20px 0 5px;
315 | }
316 |
317 | #copy-btn:active {
318 | background: rgba(20, 97, 248, 0.15);;
319 | color: #1461F8;
320 | }
321 |
322 | .modal-text {
323 | background: transparent;
324 | height: 32px;
325 | margin-top: 15px;
326 | outline: 0;
327 | width: 95%;
328 | }
329 |
330 | .modal-text:focus-visible {
331 | box-shadow: 0 0 0 2.5px rgba(0,123,255,.5);
332 | }
333 |
334 | .modal-text:focus:not(:focus-visible) {
335 | box-shadow: none;
336 | }
337 |
338 | #copy-link-text {
339 | border: .5px solid rgba(0, 0, 0, .2);
340 | }
341 |
342 | #copy-link-success-msg {
343 | color: #1461F8;
344 | font: 12px "Segoe UI SemiBold", segoe_ui_semibold, Tahoma, Arial;
345 | margin-top: 11px;
346 | }
347 |
348 | .view-label {
349 | margin-bottom: 0;
350 | }
351 |
352 | #viewname {
353 | border: 0;
354 | border-bottom: .5px solid rgba(0, 0, 0, .2);
355 | padding: 0;
356 | }
357 |
358 | #viewname::placeholder {
359 | font: 400 14px/20px "Segoe UI", segoe_ui_regular, Tahoma, Arial;
360 | }
361 |
362 | .copy-bookmark {
363 | background: transparent;
364 | border-radius: 4px;
365 | border: 1px solid #1461F8;
366 | color: #1461F8;
367 | font-weight: 600;
368 | margin: 0 20px;
369 | padding: 6px 16px;
370 | }
371 |
372 | .copy-bookmark:hover {
373 | background: #1461F8;
374 | color: #FFF;
375 | }
376 |
377 | .modal-header {
378 | border: none;
379 | font: 16px "Segoe UI SemiBold", segoe_ui_semibold, Tahoma, Arial;
380 | margin: -15px 0 30px 25px;
381 | padding: 0;
382 | width: 80%;
383 | }
384 |
385 | .invalid-feedback{
386 | margin-bottom: -21px;
387 | margin-top: 5px;
388 | }
389 |
390 | .inactive-bookmark {
391 | color: #323130;
392 | font: 400 14px/20px "Segoe UI", segoe_ui_regular, Tahoma, Arial;
393 | }
394 |
395 | #content-div {
396 | color: #201F1E;
397 | font: 400 14px "Segoe UI", segoe_ui_regular, Tahoma, Arial;
398 | margin: 30px 0 40px 24px;
399 | }
400 |
401 | .active-bookmark {
402 | color: #1461F8;
403 | font: 14px/20px "Segoe UI SemiBold", segoe_ui_semibold, Tahoma, Arial;
404 | }
405 |
406 | .selected-button {
407 | background: rgba(20, 97, 248, .15);
408 | border-radius: 4px;
409 | border: 1px solid rgba(20, 96, 247, .5);
410 | font-weight: 600;
411 | margin: 0 20px;
412 | outline: none;
413 | padding: 6px 16px;
414 | }
415 |
416 | .focused {
417 | box-shadow: 0 0 0 2px rgba(0, 123, 255, .5);
418 | }
--------------------------------------------------------------------------------
/Capture report views/css/report.css:
--------------------------------------------------------------------------------
1 | /* Copyright (c) Microsoft Corporation.
2 | Licensed under the MIT license. */
3 |
4 | #report-container {
5 | margin: 125px 16px 16px 16px;
6 | width: auto;
7 | height: calc((9/16) * 99vw);
8 | }
9 |
10 | #report-container>iframe {
11 | border: 1px solid #C4C4C4;
12 | }
--------------------------------------------------------------------------------
/Capture report views/css/share-bookmark.css:
--------------------------------------------------------------------------------
1 | /* Copyright (c) Microsoft Corporation.
2 | Licensed under the MIT license. */
3 |
4 | body {
5 | height: 100%;
6 | scrollbar-color: #A0AEB2 transparent;
7 | scrollbar-width: thin;
8 | }
9 |
10 | /* Change custom scrollbar appearance */
11 | body::-webkit-scrollbar-track {
12 | border-radius: 10px;
13 | background-color: transparent;
14 | }
15 |
16 | body::-webkit-scrollbar {
17 | background-color: transparent;
18 | height: 10px;
19 | width: 8px;
20 | }
21 |
22 | body::-webkit-scrollbar-thumb {
23 | background-color: #A0AEB2;
24 | border-radius: 10px;
25 | height: 30px;
26 | }
27 |
28 | #wrapper {
29 | margin: 0 auto;
30 | width: 100%;
31 | }
32 |
33 | #contoso-header {
34 | align-items: center;
35 | background: #1461F8;
36 | display: flex;
37 | height: 44px;
38 | margin: 0;
39 | }
40 |
41 | #contoso {
42 | height: 41px;
43 | margin-left: 24px;
44 | padding: 5px 0;
45 | width: 74px;
46 | }
47 |
48 | #share-bookmark {
49 | height: calc(9/16 * 95vw);
50 | visibility: hidden;
51 | width: auto;
52 | }
53 |
54 | .rotate {
55 | animation: rotation 1s infinite linear;
56 | }
57 |
58 | @keyframes rotation {
59 | from {
60 | transform: translate(-50%, -50%) rotate(0deg);
61 | }
62 | to {
63 | transform: translate(-50%, -50%) rotate(359deg);
64 | }
65 | }
66 |
67 | #overlay {
68 | position: absolute;
69 | width: inherit;
70 | height: inherit;
71 | }
72 |
73 | #overlay #spinner {
74 | position: fixed;
75 | top: 50%;
76 | left: 50%;
77 | }
78 |
79 | #bookmark-container {
80 | height: 100%;
81 | margin-top: 44px;
82 | width: 100%;
83 | }
84 |
85 | #bookmark-container>iframe {
86 | border: none;
87 | }
88 |
--------------------------------------------------------------------------------
/Capture report views/img/allviews.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/Capture report views/img/captureview.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/Capture report views/img/contoso.svg:
--------------------------------------------------------------------------------
1 |
12 |
--------------------------------------------------------------------------------
/Capture report views/img/cross.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/Capture report views/img/spinner.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/Capture report views/img/tickicon.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/Capture report views/index.html:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 | Capture Report Views
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
36 |
37 |
40 |
41 |
42 |
43 |
44 |
45 |
57 |
58 |
59 |
60 |
Copied successfully!
61 |
62 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
79 |
80 |

81 |
82 |
83 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
--------------------------------------------------------------------------------
/Capture report views/js/error_listener.js:
--------------------------------------------------------------------------------
1 | // ----------------------------------------------------------------------------
2 | // Copyright (c) Microsoft Corporation.
3 | // Licensed under the MIT license.
4 | // ----------------------------------------------------------------------------
5 |
6 | window.addEventListener('error', function(event) {
7 | // Protection against cross-origin failure
8 | try {
9 | if (window.parent.playground && window.parent.playground.logShowcaseError) {
10 | window.parent.playground.logShowcaseError("CaptureReportViews", event);
11 | }
12 | } catch { }
13 | });
--------------------------------------------------------------------------------
/Capture report views/js/globals.js:
--------------------------------------------------------------------------------
1 | // ----------------------------------------------------------------------------
2 | // Copyright (c) Microsoft Corporation.
3 | // Licensed under the MIT license.
4 | // ----------------------------------------------------------------------------
5 |
6 | // To cache report config
7 | let reportConfig = {
8 | accessToken: undefined,
9 | embedUrl: undefined,
10 | reportId: undefined,
11 | type: "report"
12 | }
13 |
14 | // To cache bookmark state
15 | let bookmarkShowcaseState = {
16 | bookmarks: null,
17 | report: null,
18 |
19 | // Next bookmark ID counter
20 | bookmarkCounter: 1
21 | };
22 |
23 | // Cache global DOM objects
24 | const listViewsBtn = $("#display-btn");
25 | const copyLinkSuccessMsg = $("#copy-link-success-msg");
26 | const viewName = $("#viewname");
27 | const tickBtn = $("#tick-btn");
28 | const tickIcon = $("#tick-icon");
29 | const bookmarksList = $("#bookmarks-list");
30 | const copyBtn = $("#copy-btn");
31 | const copyLinkText = $("#copy-link-text");
32 | const copyLinkBtn = $("#copy-link-btn");
33 | const saveViewBtn = $("#save-view-btn");
34 | const captureViewDiv = $("#capture-view-div");
35 | const saveViewDiv = $("#save-view-div");
36 | const overlay = $("#overlay");
37 | const bookmarksDropdown = $(".bookmarks-dropdown");
38 | const captureModal = $("#modal-action");
39 | const closeModal = $("#close-modal-btn");
40 | const viewLinkBtn = $("#copy-btn");
41 | const saveBtn = $("#save-bookmark-btn");
42 | const closeBtn = $("#close-btn");
43 |
44 | // Store keycode for TAB key
45 | const KEYCODE_TAB = 9;
46 |
47 | // Enum for Keys
48 | const Keys = {
49 | TAB : "Tab"
50 | }
51 |
52 | // Freezing the contents of enum object
53 | Object.freeze(Keys);
54 |
55 | // Cache CSS classes
56 | const SELECTED_BUTTON = "selected-button";
57 | const COPY_BOOKMARK = "copy-bookmark";
58 | const ACTIVE_BUTTON = "btn-active";
59 | const VISIBLE = "visible";
60 | const INVISIBLE = "invisible";
61 | const INACTIVE_BOOKMARK = "inactive-bookmark";
62 | const ACTIVE_BOOKMARK = "active-bookmark";
63 | const INVALID_FIELD = "is-invalid";
64 | const FOCUSED = "focused";
65 | const DISPLAY = "show";
66 | const CHECKBOX = "input[type=checkbox]";
67 |
68 | // Store IDs of the elements
69 | const SAVE_VIEW_BUTTON_ID = "save-view-btn";
70 | const COPY_LINK_BUTTON_ID = "copy-link-btn";
71 |
72 | // Cache the report containers
73 | const bookmarkContainer = $("#bookmark-container").get(0);
74 | const reportContainer = $("#report-container").get(0);
75 |
76 | // Store the state for the checkbox focus
77 | let checkBoxState = null;
78 |
79 | // Cache DOM elements to use for trapping the focus inside the modal
80 | const captureModalElements = {
81 | firstElement: closeModal,
82 | lastElement: {
83 | saveView: saveBtn,
84 | copyLink: viewLinkBtn
85 | }
86 | }
87 |
88 | // Store the last active element
89 | let lastActiveElement;
90 |
91 | // Using Regex to get the id parameter from the URL
92 | const regex = new RegExp("[?&]id(=([^]*)|&|#|$)");
93 |
--------------------------------------------------------------------------------
/Capture report views/js/session_utils.js:
--------------------------------------------------------------------------------
1 | // ----------------------------------------------------------------------------
2 | // Copyright (c) Microsoft Corporation.
3 | // Licensed under the MIT license.
4 | // ----------------------------------------------------------------------------
5 |
6 | // API Endpoint to get the JSON response of Embed URL, Embed Token and reportId
7 | const reportEndpoint = "https://aka.ms/CaptureViewsReportEmbedConfig";
8 |
9 | // Set minutes before the access token should get refreshed
10 | const minutesToRefreshBeforeExpiration = 2;
11 |
12 | // Store the amount of time left for refresh token
13 | let tokenExpiration;
14 |
15 | // This function will make the AJAX request to the endpoint and get the JSON response which it will set in the sessions
16 | function populateEmbedConfigIntoCurrentSession(updateCurrentToken) {
17 |
18 | try {
19 | let configFromParentWindow = window.parent.showcases.captureReportViews;
20 | if (configFromParentWindow) {
21 | let diffMs = new Date(configFromParentWindow.expiration) - new Date();
22 | let diffMins = Math.round(((diffMs % 86400000) % 3600000) / 60000);
23 |
24 | embedConfig = {
25 | EmbedUrl: configFromParentWindow.embedUrl,
26 | EmbedToken: {
27 | Token: configFromParentWindow.token
28 | },
29 | Id: configFromParentWindow.id,
30 | MinutesToExpiration: diffMins,
31 | };
32 |
33 | handleNewEmbedConfig(embedConfig, updateCurrentToken);
34 | }
35 |
36 | return;
37 | } catch (error) {
38 | console.error(error);
39 | }
40 |
41 | // This returns the JSON response
42 | return $.getJSON(reportEndpoint, function (embedConfig) {
43 | handleNewEmbedConfig(embedConfig, updateCurrentToken);
44 | });
45 | }
46 |
47 | function handleNewEmbedConfig(embedConfig, updateCurrentToken) {
48 |
49 | // Set Embed Token, Embed URL and Report Id
50 | setConfig(embedConfig.EmbedToken.Token, embedConfig.EmbedUrl, embedConfig.Id);
51 | if (updateCurrentToken) {
52 |
53 | // Get the reference to the embedded element
54 | const reportContainer = $("#report-container").get(0);
55 | if (reportContainer) {
56 | const report = powerbi.get(reportContainer);
57 | report.setAccessToken(embedConfig.EmbedToken.Token);
58 | }
59 |
60 | // Get the reference to the embedded element
61 | const bookmarkContainer = $("#bookmark-container").get(0);
62 | if (bookmarkContainer) {
63 | const bookmarkReport = powerbi.get(bookmarkContainer);
64 | bookmarkReport.setAccessToken(embedConfig.EmbedToken.Token);
65 | }
66 | }
67 |
68 | // Get the milliseconds after token will get refreshed
69 | tokenExpiration = embedConfig.MinutesToExpiration * 60 * 1000;
70 |
71 | // Set the tokenRefresh timer to count the seconds and request the JSON again when token expires
72 | setTokenExpirationListener();
73 | }
74 |
75 | // Check the remaining time and call the API again
76 | function setTokenExpirationListener() {
77 |
78 | const safetyInterval = minutesToRefreshBeforeExpiration * 60 * 1000;
79 |
80 | // Time until token refresh in milliseconds
81 | const timeout = tokenExpiration - safetyInterval;
82 | if (timeout <= 0) {
83 | populateEmbedConfigIntoCurrentSession(true /* updateCurrentToken */);
84 | }
85 | else {
86 | console.log(`Report Embed Token will be updated in ${timeout} milliseconds.`);
87 | setTimeout(function () {
88 | populateEmbedConfigIntoCurrentSession(true /* updateCurrentToken */)
89 | }, timeout);
90 | }
91 | }
92 |
93 | // Add a listener to make sure token is updated after tab was inactive
94 | document.addEventListener("visibilitychange", function () {
95 | // Check the access token when the tab is visible
96 | if (!document.hidden) {
97 | setTokenExpirationListener();
98 | }
99 | });
100 |
101 | // Get the data from the API and pass it to the front-end
102 | function loadSampleReportIntoSession() {
103 |
104 | // Call the function for the first time to call the API, set the sessions values and return the response to the front-end
105 | return populateEmbedConfigIntoCurrentSession(false /* updateCurrentToken */);
106 | }
107 |
108 | // Set the embed config in globals
109 | function setConfig(accessToken, embedUrl, reportId) {
110 |
111 | // Fill the global object
112 | reportConfig.accessToken = accessToken;
113 | reportConfig.embedUrl = embedUrl;
114 | reportConfig.reportId = reportId;
115 | }
--------------------------------------------------------------------------------
/Capture report views/js/share_bookmark.js:
--------------------------------------------------------------------------------
1 | // ----------------------------------------------------------------------------
2 | // Copyright (c) Microsoft Corporation.
3 | // Licensed under the MIT license.
4 | // ----------------------------------------------------------------------------
5 |
6 | // Set props for accessibility insights
7 | function setReportAccessibilityProps(report) {
8 | report.setComponentTitle("Playground showcase sample report");
9 | report.setComponentTabIndex(0);
10 | }
11 |
12 | $(document).ready(function () {
13 |
14 | // Bootstrap the bookmark container
15 | powerbi.bootstrap(bookmarkContainer, reportConfig);
16 |
17 | embedSharedBookmarkReport();
18 | });
19 |
20 | // Embed shared report with bookmark on load
21 | async function embedSharedBookmarkReport() {
22 |
23 | // Load sample report properties into session
24 | await loadSampleReportIntoSession()
25 |
26 | // Get models. models contains enums that can be used
27 | const models = window["powerbi-client"].models;
28 |
29 | // Use View permissions
30 | let permissions = models.Permissions.View;
31 |
32 | // Get the bookmark name from url param
33 | let bookmarkName = getBookmarkNameFromURL();
34 |
35 | // Get the bookmark state from local storage
36 | // any type of database can be used
37 | let bookmarkState = localStorage.getItem(bookmarkName);
38 |
39 | // Embed configuration used to describe the what and how to embed
40 | // This object is used when calling powerbi.embed
41 | // This also includes settings and options such as filters
42 | // You can find more information at https://github.com/Microsoft/PowerBI-JavaScript/wiki/Embed-Configuration-Details
43 | let config = {
44 | type: "report",
45 | tokenType: models.TokenType.Embed,
46 | accessToken: reportConfig.accessToken,
47 | embedUrl: reportConfig.embedUrl,
48 | id: reportConfig.reportId,
49 | permissions: permissions,
50 | settings: {
51 | panes: {
52 | filters: {
53 | visible: false
54 | },
55 | pageNavigation: {
56 | visible: false
57 | }
58 | }
59 | },
60 | layoutType: models.LayoutType.Custom,
61 | customLayout: {
62 | displayOption: models.DisplayOption.FitToWidth
63 | },
64 | // Adding bookmark attribute will apply the bookmark on load
65 | bookmark: {
66 | state: bookmarkState
67 | }
68 | };
69 |
70 | // Embed the report and display it within the div container
71 | bookmarkShowcaseState.report = powerbi.embed(bookmarkContainer, config);
72 |
73 | // For accessibility insights
74 | setReportAccessibilityProps(bookmarkShowcaseState.report);
75 |
76 | bookmarkShowcaseState.report.on("loaded", function () {
77 |
78 | // Hide the loader and display the report
79 | overlay.addClass(INVISIBLE);
80 | $("#share-bookmark").addClass(VISIBLE);
81 | bookmarkShowcaseState.report.off("loaded");
82 | });
83 | }
84 |
85 | // Get the bookmark name from url "id" argument
86 | function getBookmarkNameFromURL() {
87 | let url = (window.location != window.parent.location) ?
88 | document.referrer :
89 | document.location.href;
90 |
91 | const results = regex.exec(url);
92 |
93 | // It can take id parameter from the URL using ? or &
94 | // If ? or & is not in the URL, returns NULL
95 | if (!results) { return null };
96 |
97 | // Returns Empty String if id parameter's value is not specified
98 | if (!results[2]) { return "" };
99 |
100 | // Returns the ID of the Bookmark
101 | return decodeURIComponent(results[2]);
102 | }
--------------------------------------------------------------------------------
/Capture report views/share_bookmark.html:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 | Bookmarked Report
12 |
13 |
14 |
15 |
16 |
19 |
20 |

21 |
22 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/Customize report colors and mode/css/container.css:
--------------------------------------------------------------------------------
1 | /* Copyright (c) Microsoft Corporation.
2 | Licensed under the MIT license. */
3 |
4 | /* The custom loader animation to be displayed till the report is embedded */
5 | .rotate {
6 | animation: rotation 1s infinite linear;
7 | }
8 |
9 | @keyframes rotation {
10 | from {
11 | transform: translate(-50%, -50%) rotate(0deg);
12 | }
13 | to {
14 | transform: translate(-50%, -50%) rotate(359deg);
15 | }
16 | }
17 |
18 | .container {
19 | display: flex;
20 | height: auto;
21 | padding: 0;
22 | width: 100%;
23 | }
24 |
25 | #overlay {
26 | height: inherit;
27 | position: fixed;
28 | width: inherit;
29 | }
30 |
31 | #overlay #spinner {
32 | left: 50%;
33 | position: fixed;
34 | top: 50%;
35 | }
36 |
37 | .content {
38 | background: #FAF9F8;
39 | display: none;
40 | margin: 0 auto;
41 | text-align: left;
42 | width: 100%;
43 | }
44 |
45 | .content.dark {
46 | background: #3B3A39;
47 | }
48 |
49 | .horizontal-rule {
50 | border-top: .5px solid rgba(0, 0, 0, .2);
51 | clear: both;
52 | margin: 109px 16px 0 16px;
53 | opacity: .7;
54 | width: auto;
55 | z-index: 0;
56 | }
57 |
58 | .horizontal-rule.dark {
59 | border-top: .5px solid #605E5C;
60 | }
61 |
--------------------------------------------------------------------------------
/Customize report colors and mode/css/dropdown.css:
--------------------------------------------------------------------------------
1 | /* Copyright (c) Microsoft Corporation.
2 | Licensed under the MIT license. */
3 |
4 | /* TODO: Change font URL in the final environment */
5 | @font-face {
6 | font-family: "segoe_ui_regular";
7 | src: url("https://content.powerapps.com/resource/powerbiwfe/fonts/SegoeUI-Regular-final.8956d1f5b4190f537497.woff") format("woff");
8 | }
9 |
10 | @font-face {
11 | font-family: "segoe_ui_semibold";
12 | src: url("https://content.powerapps.com/resource/powerbiwfe/fonts/SegoeUI-SemiBold-final.83b7261d0e6f3994ed6d.woff") format("woff");
13 | }
14 |
15 | .dropdown {
16 | background: #FAF9F8;
17 | margin-top: 44px;
18 | }
19 |
20 | .dropdown.dark {
21 | background: #3B3A39;
22 | }
23 |
24 | /* Set the background 40% black when dropdown opens */
25 | .dropdown.show:after {
26 | opacity: 1;
27 | position: fixed;
28 | visibility: visible;
29 | }
30 |
31 | .dropdown:after {
32 | background: rgba(0, 0, 0, .4);
33 | bottom: 0;
34 | content: '';
35 | left: 0;
36 | right: 0;
37 | top: 0;
38 | transition: opacity .15s ease-in-out;
39 | }
40 |
41 | .btn-theme {
42 | background: #FAF9F8;
43 | border: 1px solid #4A565A;
44 | border-radius: 4px;
45 | color: #4A565A;
46 | display: flex;
47 | height: 32px;
48 | margin: 16px;
49 | }
50 |
51 | .btn-theme.dark {
52 | background: #3B3A39;
53 | border: 1px solid #D9E6EA;
54 | color: #D9E6EA;
55 | }
56 |
57 | .btn-theme:hover {
58 | background: #FFF;
59 | color: #4A565A;
60 | }
61 |
62 | .btn-theme.dark:hover {
63 | background: #393939;
64 | color: #D9E6EA;
65 | }
66 |
67 | /* For Keyboard focus */
68 | .btn-theme.dark:focus-visible {
69 | box-shadow: 0 0 4px 1.5px #D9E6EA;
70 | }
71 |
72 | /* Remove focus when mouse navigation is used */
73 | .btn-theme.dark:focus:not(:focus-visible) {
74 | box-shadow: none;
75 | }
76 |
77 | .btn-theme:focus-visible {
78 | box-shadow: 0 0 4px 1.5px rgba(0,123,255,.5);
79 | }
80 |
81 | .btn-theme:focus:not(:focus-visible) {
82 | box-shadow: none;
83 | }
84 |
85 | .btn.active.focus,
86 | .btn.active:focus,
87 | .btn:active:focus {
88 | box-shadow: none;
89 | outline: 0;
90 | outline-offset: -2px;
91 | text-decoration: none;
92 | }
93 |
94 | .btn-theme[aria-expanded="true"] {
95 | background: #D3D6D7;
96 | color: #4A565A;
97 | }
98 |
99 | .btn-theme.dark[aria-expanded="true"] {
100 | background: #393939;
101 | color: #D9E6EA;
102 | }
103 |
104 | input[type="checkbox"]:focus-visible + span.slider.round {
105 | box-shadow: 0 0 4px 2.5px rgba(0,123,255,.5);
106 | outline: none;
107 | }
108 |
109 | input[type="checkbox"]:focus:not(:focus-visible) + span.slider.round {
110 | box-shadow: none;
111 | outline: none;
112 | }
113 |
114 | input[type="checkbox"]:focus-visible + span.slider.round.dark {
115 | box-shadow: 0 0 4px 1.5px #D9E6EA;
116 | outline: none;
117 | }
118 |
119 | input[type="checkbox"]:focus:not(:focus-visible) + span.slider.round.dark {
120 | box-shadow: none;
121 | outline: none;
122 | }
123 |
124 | input[type="radio"]:focus {
125 | outline-offset: initial;
126 | }
127 |
128 | #bucket {
129 | display: flex;
130 | margin: 1px 8px 0 4px;
131 | }
132 |
133 | .bucket-theme.dark {
134 | fill: #D9E6EA;
135 | }
136 |
137 | .theme-selector-label {
138 | cursor: pointer;
139 | display: flex;
140 | font: 14px/20px "Segoe UI SemiBold", "segoe_ui_semibold", Tahoma, Arial, Helvetica;
141 | margin-top: -1.5px;
142 | }
143 |
144 | .theme-container {
145 | background: #FFF;
146 | border: 1px solid #FFF;
147 | border-radius: 4px;
148 | box-shadow: 0 6.4px 14.4px rgba(0, 0, 0, 0.132), 0 1.2px 3.6px rgba(0, 0, 0, 0.108);
149 | box-sizing: border-box;
150 | height: 244px;
151 | margin: 10px 0 0 0;
152 | padding: 16px;
153 | width: 336px;
154 | }
155 |
156 | .theme-container.dark {
157 | background: #3B3A39;
158 | border: 1px solid #69797E;
159 | }
160 |
161 | .theme-element-container {
162 | align-items: center;
163 | box-sizing: border-box;
164 | display: flex;
165 | height: 36px;
166 | padding: 0;
167 | position: relative;
168 | }
169 |
170 | .theme-element-container input {
171 | cursor: pointer;
172 | opacity: 1;
173 | position: relative;
174 | }
175 |
176 | input[type="radio"],
177 | input[type="checkbox"] {
178 | margin: 0;
179 | }
180 |
181 | .data-color-name {
182 | color: #323130;
183 | cursor: pointer;
184 | font: 14px/20px "Segoe UI Regular", "segoe_ui_regular", Tahoma, Arial, Helvetica;
185 | margin-left: 8px;
186 | }
187 |
188 | .data-color-name.dark {
189 | color: #FFF;
190 | }
191 |
192 | .data-color {
193 | display: inline-block;
194 | height: 20px;
195 | width: 20px;
196 | }
197 |
198 | .theme-colors {
199 | border: 1px solid #717171;
200 | cursor: pointer;
201 | display: inline;
202 | height: 22px;
203 | line-height: 0;
204 | margin-left: auto;
205 | }
206 |
207 | .theme-switch-label {
208 | color: #323130;
209 | font: 14px/20px "Segoe UI SemiBold", "segoe_ui_semibold", Tahoma, Arial, Helvetica;
210 | margin-left: 6px;
211 | }
212 |
213 | .theme-switch-label.dark {
214 | color: #FFF;
215 | }
216 |
217 | /* Box around the slider checkbox */
218 | .switch {
219 | border: 1px solid #69797E;
220 | border-radius: 34px;
221 | box-sizing: border-box;
222 | display: flex;
223 | height: 20px;
224 | margin: auto 0;
225 | margin-left: auto;
226 | position: relative;
227 | width: 40px;
228 | }
229 |
230 | /* Hide default HTML checkbox */
231 | .switch input {
232 | height: 0;
233 | opacity: 0;
234 | width: 0;
235 | }
236 |
237 | /* The toggle slider for the theme selection */
238 | .slider {
239 | background: #FFF;
240 | bottom: 0;
241 | cursor: pointer;
242 | left: 0;
243 | position: absolute;
244 | right: 0;
245 | top: 0;
246 | transition: .4s;
247 | -webkit-transition: .4s;
248 | }
249 |
250 | .slider:before {
251 | background: #69797E;
252 | bottom: 3px;
253 | content: "";
254 | height: 12px;
255 | left: 3px;
256 | position: absolute;
257 | transition: .4s;
258 | width: 12px;
259 | -webkit-transition: .4s;
260 | }
261 |
262 | .slider.dark:before {
263 | background: #FFF;
264 | }
265 |
266 | input:checked+.slider.dark {
267 | background: #69797E;
268 | }
269 |
270 | input+.slider.dark {
271 | background: #69797E;
272 | }
273 |
274 | input:checked+.slider:before {
275 | -webkit-transform: translateX(20px);
276 | -ms-transform: translateX(20px);
277 | transform: translateX(20px);
278 | }
279 |
280 | /* Rounded toggle slider for the theme selection */
281 | .slider.round {
282 | border-radius: 34px;
283 | }
284 |
285 | .slider.round:before {
286 | border-radius: 50%;
287 | }
288 |
289 | .dropdown-separator {
290 | border-top: 0.5px solid rgba(0, 0, 0, 0.2);
291 | margin: 16px 0;
292 | opacity: 0.7;
293 | }
294 |
295 | .dropdown-separator.dark {
296 | border-top: 0.5px solid #69797E;
297 | }
--------------------------------------------------------------------------------
/Customize report colors and mode/css/header.css:
--------------------------------------------------------------------------------
1 | /* Copyright (c) Microsoft Corporation.
2 | Licensed under the MIT license. */
3 |
4 | body {
5 | background: #FAF9F8;
6 | height: 100%;
7 | scrollbar-color: #A0AEB2 transparent;
8 | scrollbar-width: thin;
9 | }
10 |
11 | body.dark {
12 | background: #3B3A39;
13 | }
14 |
15 | /* Change custom scrollbar appearance */
16 | body::-webkit-scrollbar-track {
17 | border-radius: 10px;
18 | background-color: transparent;
19 | }
20 |
21 | body::-webkit-scrollbar {
22 | background-color: transparent;
23 | height: 10px;
24 | width: 8px;
25 | }
26 |
27 | body::-webkit-scrollbar-thumb {
28 | background-color: #A0AEB2;
29 | border-radius: 10px;
30 | height: 30px;
31 | }
32 |
33 | .wrapper {
34 | margin: 0 auto;
35 | width: 100%;
36 | }
37 |
38 | .header {
39 | align-items: center;
40 | background: #69797E;
41 | display: flex;
42 | height: 44px;
43 | margin: 0;
44 | }
45 |
46 | .contoso {
47 | height: 41px;
48 | margin-left: 24px;
49 | width: 74px;
50 | }
51 |
--------------------------------------------------------------------------------
/Customize report colors and mode/css/report.css:
--------------------------------------------------------------------------------
1 | /* Copyright (c) Microsoft Corporation.
2 | Licensed under the MIT license. */
3 |
4 | .report-container {
5 | height: calc((9/16) * 85vw + 10px);
6 | margin: 125px 16px 16px 16px;
7 | width: auto;
8 | }
9 |
10 | .report-container > iframe {
11 | border: none;
12 | }
13 |
--------------------------------------------------------------------------------
/Customize report colors and mode/img/contoso.svg:
--------------------------------------------------------------------------------
1 |
12 |
--------------------------------------------------------------------------------
/Customize report colors and mode/img/spinner.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/Customize report colors and mode/index.html:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 | Customize report colors and theme
24 |
25 |
26 |
27 |
28 |
31 |
32 |

33 |
34 |
35 |
36 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
--------------------------------------------------------------------------------
/Customize report colors and mode/js/error_listener.js:
--------------------------------------------------------------------------------
1 | // ----------------------------------------------------------------------------
2 | // Copyright (c) Microsoft Corporation.
3 | // Licensed under the MIT license.
4 | // ----------------------------------------------------------------------------
5 |
6 | window.addEventListener('error', function(event) {
7 | // Protection against cross-origin failure
8 | try {
9 | if (window.parent.playground && window.parent.playground.logShowcaseError) {
10 | window.parent.playground.logShowcaseError("CustomizeColors", event);
11 | }
12 | } catch { }
13 | });
--------------------------------------------------------------------------------
/Customize report colors and mode/js/globals.js:
--------------------------------------------------------------------------------
1 | // ----------------------------------------------------------------------------
2 | // Copyright (c) Microsoft Corporation.
3 | // Licensed under the MIT license.
4 | // ----------------------------------------------------------------------------
5 |
6 | // Constants used for report configurations as key-value pair
7 | const reportConfig = {
8 | accessToken: null,
9 | embedUrl: null,
10 | reportId: null,
11 | }
12 |
13 | // Maintain the state for the showcase
14 | const themesShowcaseState = {
15 | themesArray: null,
16 | themesReport: null,
17 | };
18 |
19 | // Declare dynamic DOM objects
20 | let themeSlider;
21 | let dataColorNameElements;
22 | let themeSwitchLabel;
23 | let horizontalSeparator;
24 | let sliderCheckbox;
25 | let allUIElements;
26 |
27 | // Cache global DOM elements
28 | const bodyElement = $("body");
29 | const overlay = $("#overlay");
30 | const dropdownDiv = $(".dropdown");
31 | const themesList = $("#theme-dropdown");
32 | const contentElement = $(".content");
33 | const themeContainer = $(".theme-container");
34 | const horizontalRule = $(".horizontal-rule");
35 | const themeButton = $(".btn-theme");
36 | const themeBucket = $(".bucket-theme");
37 | const embedContainer = $(".report-container").get(0);
38 |
39 | // Store keycode for TAB key
40 | const KEYCODE_TAB = 9;
41 |
42 | // Enum for Keys
43 | const Keys = {
44 | TAB: "Tab"
45 | }
46 |
47 | // Freezing the contents of enum object
48 | Object.freeze(Keys);
--------------------------------------------------------------------------------
/Customize report colors and mode/js/index.js:
--------------------------------------------------------------------------------
1 | // ----------------------------------------------------------------------------
2 | // Copyright (c) Microsoft Corporation.
3 | // Licensed under the MIT license.
4 | // ----------------------------------------------------------------------------
5 |
6 | // Perform events only after DOM is loaded
7 | $(document).ready(function () {
8 |
9 | // Bootstrap the embed-container for the report embedding
10 | powerbi.bootstrap(embedContainer, {
11 | "type": "report"
12 | });
13 |
14 | // Embed the report in the report-container
15 | embedThemesReport();
16 |
17 | // Build Theme palette
18 | buildThemePalette();
19 |
20 | // Cache dynamic elements to toggle the theme
21 | themeSlider = $("#theme-slider");
22 | dataColorNameElements = $(".data-color-name");
23 | themeSwitchLabel = $(".theme-switch-label");
24 | horizontalSeparator = $(".dropdown-separator");
25 | sliderCheckbox = $(".slider");
26 |
27 | // Move the focus back to the button which triggered the dropdown
28 | dropdownDiv.on("hidden.bs.dropdown", function () {
29 | themeButton.focus();
30 | });
31 |
32 | // Focus on the theme slider once the dropdown opens
33 | dropdownDiv.on("shown.bs.dropdown", function () {
34 | themeSlider.focus();
35 | });
36 |
37 | // Get all the UI elements to toggle the dark theme
38 | allUIElements = [bodyElement, contentElement, dropdownDiv, themeContainer, themeSwitchLabel, horizontalSeparator, horizontalRule, sliderCheckbox, themeButton, themeBucket, dataColorNameElements];
39 | });
40 |
41 | // Close the dropdown when focus moves to Choose theme button from Toggle slider
42 | $(document).on("keydown", "#theme-slider", function (e) {
43 |
44 | // Shift + Tab
45 | if (e.shiftKey && (e.key === Keys.TAB || e.keyCode === KEYCODE_TAB)) {
46 | dropdownDiv.removeClass("show");
47 | themesList.removeClass("show");
48 | $(".btn-theme")[0].setAttribute("aria-expanded", false);
49 | }
50 | });
51 |
52 | // Set properties for Accessibility insights
53 | function setReportAccessibilityProps(report) {
54 | report.setComponentTitle("Playground showcase sample Theme report");
55 | report.setComponentTabIndex(0);
56 | }
57 |
58 | // To stop the page load on click event inside dropdown
59 | $(document).on("click", ".allow-focus", function (element) {
60 | element.stopPropagation();
61 | });
62 |
63 | // Embed the report
64 | async function embedThemesReport() {
65 |
66 | // Load sample report properties into session
67 | await loadThemesShowcaseReportIntoSession();
68 |
69 | // Get models. models contains enums that can be used
70 | const models = window["powerbi-client"].models;
71 |
72 | // Get embed application token from globals
73 | const accessToken = reportConfig.accessToken;
74 |
75 | // Get embed URL from globals
76 | const embedUrl = reportConfig.embedUrl;
77 |
78 | // Get report Id from globals
79 | const embedReportId = reportConfig.reportId;
80 |
81 | // Use View permissions
82 | const permissions = models.Permissions.View;
83 |
84 | // Create new theme object
85 | let newTheme = {};
86 |
87 | // Append the data-colors and the theme
88 | $.extend(newTheme, jsonDataColors[0], themes[0]);
89 |
90 | // Embed configuration used to describe the what and how to embed
91 | // This object is used when calling powerbi.embed
92 | // This also includes settings and options such as filters
93 | // You can find more information at https://github.com/Microsoft/PowerBI-JavaScript/wiki/Embed-Configuration-Details
94 | let config = {
95 | type: "report",
96 | tokenType: models.TokenType.Embed,
97 | accessToken: accessToken,
98 | embedUrl: embedUrl,
99 | id: embedReportId,
100 | permissions: permissions,
101 | settings: {
102 | panes: {
103 | filters: {
104 | expanded: false,
105 | visible: false
106 | },
107 | pageNavigation: {
108 | visible: false
109 | },
110 | },
111 | layoutType: models.LayoutType.Custom,
112 | customLayout: {
113 | displayOption: models.DisplayOption.FitToPage
114 | },
115 | background: models.BackgroundType.Transparent
116 | },
117 | // Adding theme attribute to the config, will apply the light theme and default data-colors on load
118 | theme: { themeJson: newTheme },
119 | };
120 |
121 | // Embed the report and display it within the div container
122 | themesShowcaseState.themesReport = powerbi.embed(embedContainer, config);
123 |
124 | // For accessibility insights
125 | setReportAccessibilityProps(themesShowcaseState.themesReport);
126 |
127 | // Clear any other loaded handler events
128 | themesShowcaseState.themesReport.off("loaded");
129 |
130 | // Report.on will add an event handler for report loaded event.
131 | themesShowcaseState.themesReport.on("loaded", function () {
132 |
133 | // Hide the loader
134 | overlay.hide();
135 |
136 | // Show the container
137 | $(".content").show();
138 |
139 | // Set the first data-color on the list as active
140 | themesList.find("#datacolor0").prop("checked", true);
141 | });
142 |
143 | // Clear any other loaded handler events
144 | themesShowcaseState.themesReport.off("rendered");
145 |
146 | // Triggers when a report is successfully embedded in UI
147 | themesShowcaseState.themesReport.on("rendered", function () {
148 | themesShowcaseState.themesReport.off("rendered");
149 | console.log("The customize colors and mode report rendered successfully");
150 |
151 | // Protection against cross-origin failure
152 | try {
153 | if (window.parent.playground && window.parent.playground.logShowcaseDoneRendering) {
154 | window.parent.playground.logShowcaseDoneRendering("CustomizeColors");
155 | }
156 | } catch { }
157 | });
158 | }
159 |
160 | // Build the theme palette
161 | function buildThemePalette() {
162 |
163 | // Create Theme switcher
164 | buildThemeSwitcher();
165 |
166 | // Create separator
167 | buildSeparator();
168 |
169 | // Building the data-colors list
170 | for (let i = 0; i < jsonDataColors.length; i++) {
171 | themesList.append(buildDataColorElement(i));
172 | }
173 | }
174 |
175 | // Build the theme switcher with the theme slider
176 | function buildThemeSwitcher() {
177 |
178 | // Div wrapper element
179 | let divElement = document.createElement("div");
180 | divElement.setAttribute("class", "theme-element-container");
181 | divElement.setAttribute("role", "menuitem");
182 |
183 | let spanElement = document.createElement("span");
184 | spanElement.setAttribute("class", "theme-switch-label");
185 | spanElement.setAttribute("id", "dark-label-text");
186 | let textNodeElement = document.createTextNode("Dark mode");
187 | spanElement.appendChild(textNodeElement);
188 | divElement.appendChild(spanElement);
189 |
190 | // Build the checkbox slider
191 | let checkboxElement = document.createElement("label");
192 | checkboxElement.setAttribute("class", "switch");
193 | checkboxElement.setAttribute("aria-labelledby", "dark-label-text");
194 |
195 | let inputCheckboxElement = document.createElement("input");
196 | inputCheckboxElement.setAttribute("id", "theme-slider");
197 | inputCheckboxElement.setAttribute("type", "checkbox");
198 | inputCheckboxElement.setAttribute("onchange", "toggleTheme()");
199 |
200 | let sliderElement = document.createElement("span");
201 | sliderElement.setAttribute("class", "slider round");
202 |
203 | checkboxElement.appendChild(inputCheckboxElement);
204 | checkboxElement.appendChild(sliderElement);
205 |
206 | // Attach the checox slider to text label
207 | divElement.appendChild(checkboxElement);
208 |
209 | // Append the element to the dropdown
210 | themesList.append(divElement);
211 | }
212 |
213 | // Build horizontal separator between the theme switcher and data color elements
214 | function buildSeparator() {
215 |
216 | // Build the separator between theme-switcher and data-colors
217 | let separatorElement = document.createElement("div");
218 | separatorElement.setAttribute("class", "dropdown-separator");
219 | separatorElement.setAttribute("role", "separator");
220 | themesList.append(separatorElement);
221 | }
222 |
223 | // Build data-colors list based on the JSON object
224 | function buildDataColorElement(id) {
225 |
226 | // Div wrapper element for the data-color
227 | let divElement = document.createElement("div");
228 | divElement.setAttribute("class", "theme-element-container");
229 | divElement.setAttribute("role", "group");
230 |
231 | let inputElement = document.createElement("input");
232 | inputElement.setAttribute("role", "menuitemradio");
233 | inputElement.setAttribute("type", "radio");
234 | inputElement.setAttribute("name", "data-color");
235 | inputElement.setAttribute("id", "datacolor" + id);
236 | inputElement.setAttribute("aria-label", jsonDataColors[id].name + " color theme");
237 | inputElement.setAttribute("onclick", "onDataColorWrapperClicked(this);");
238 | divElement.appendChild(inputElement);
239 |
240 | // Text-element based on the JSON object
241 | let secondSpanElement = document.createElement("span");
242 | secondSpanElement.setAttribute("class", "data-color-name");
243 | secondSpanElement.setAttribute("onclick", "onDataColorWrapperClicked(this)");
244 |
245 | let radioTitleElement = document.createTextNode(jsonDataColors[id].name);
246 | secondSpanElement.appendChild(radioTitleElement);
247 | divElement.appendChild(secondSpanElement);
248 |
249 | // Div for displaying data-colors based on the JSON object
250 | let colorsDivElement = document.createElement("div");
251 | colorsDivElement.setAttribute("class", "theme-colors");
252 | colorsDivElement.setAttribute("onclick", "onDataColorWrapperClicked(this)");
253 |
254 | const dataColors = jsonDataColors[id].dataColors;
255 | for (let i = 0; i < dataColors.length; i++) {
256 | let dataColorElement = document.createElement("div");
257 | dataColorElement.setAttribute("class", "data-color");
258 | dataColorElement.setAttribute("style", "background:#" + dataColors[i].substr(1));
259 | colorsDivElement.appendChild(dataColorElement);
260 | }
261 |
262 | // Add the colors div to the label element
263 | divElement.appendChild(colorsDivElement);
264 |
265 | return divElement;
266 | }
267 |
268 | // Apply the selected data-color to the report from the wrapper element
269 | function onDataColorWrapperClicked(element) {
270 |
271 | // Deselect the previously selected data-color
272 | $("input[name=data-color]:checked", "#theme-dropdown").prop("checked", false);
273 |
274 | // Set the respective data-color as active from the wrapper element
275 | $(element.parentElement.firstElementChild).prop("checked", true);
276 |
277 | // Apply the theme to the report based on the data-color and the background
278 | applyTheme();
279 | }
280 |
281 | // Apply the theme based on the mode and the data-color selected
282 | async function applyTheme() {
283 |
284 | // Get active data-color id
285 | activeDataColorId = Number($("input[name=data-color]:checked", "#theme-dropdown")[0].getAttribute("id").slice(-1));
286 |
287 | // Get the theme state from the slider toggle
288 | let activeThemeSlider = $("#theme-slider").get(0);
289 |
290 | // Get the index of the theme based on the state of the slider
291 | // 1 => Dark theme
292 | // 0 => Light theme
293 | const themeId = (activeThemeSlider.checked) ? 1 : 0;
294 |
295 | // Create new theme object
296 | let newTheme = {};
297 |
298 | // Append the data-colors and the theme
299 | $.extend(newTheme, jsonDataColors[activeDataColorId], themes[themeId]);
300 |
301 | // Apply the theme to the report
302 | await themesShowcaseState.themesReport.applyTheme({ themeJson: newTheme });
303 | }
304 |
305 | // Apply theme to the report and toggle dark theme for the UI elements
306 | async function toggleTheme() {
307 |
308 | // Apply the theme in the report
309 | await applyTheme();
310 |
311 | // Toggle the dark theme for all UI elements
312 | toggleDarkThemeOnElements();
313 | }
314 |
315 | // Toggle dark theme for the UI elements
316 | function toggleDarkThemeOnElements() {
317 |
318 | // Toggle theme for all the UI elements
319 | allUIElements.forEach(element => {
320 | element.toggleClass("dark");
321 | });
322 | }
323 |
--------------------------------------------------------------------------------
/Customize report colors and mode/js/session_utils.js:
--------------------------------------------------------------------------------
1 | // ----------------------------------------------------------------------------
2 | // Copyright (c) Microsoft Corporation.
3 | // Licensed under the MIT license.
4 | // ----------------------------------------------------------------------------
5 |
6 | // API Endpoint to get the JSON response of Embed URL, Embed Token and reportId
7 | const reportEndpoint = "https://aka.ms/ThemeReportEmbedConfig";
8 |
9 | // Set minutes before the access token should get refreshed
10 | const minutesToRefreshBeforeExpiration = 2;
11 |
12 | // Store the amount of time left for refresh token
13 | let tokenExpiration;
14 |
15 | // This function will make the AJAX request to the endpoint and get the JSON response which it will set in the sessions
16 | function populateEmbedConfigIntoCurrentSession(updateCurrentToken) {
17 |
18 | try {
19 | let configFromParentWindow = window.parent.showcases.personalizeReportDesign;
20 | if (configFromParentWindow) {
21 | let diffMs = new Date(configFromParentWindow.expiration) - new Date();
22 | let diffMins = Math.round(((diffMs % 86400000) % 3600000) / 60000);
23 |
24 | embedConfig = {
25 | EmbedUrl: configFromParentWindow.embedUrl,
26 | EmbedToken: {
27 | Token: configFromParentWindow.token
28 | },
29 | Id: configFromParentWindow.id,
30 | MinutesToExpiration: diffMins,
31 | };
32 |
33 | handleNewEmbedConfig(embedConfig, updateCurrentToken);
34 | }
35 |
36 | return;
37 | } catch (error) {
38 | console.error(error);
39 | }
40 |
41 | // This returns the JSON response
42 | return $.getJSON(reportEndpoint, function (embedConfig) {
43 | handleNewEmbedConfig(embedConfig, updateCurrentToken);
44 | });
45 | }
46 |
47 | function handleNewEmbedConfig(embedConfig, updateCurrentToken) {
48 |
49 | // Set Embed Token, Embed URL and Report Id
50 | setConfig(embedConfig.EmbedToken.Token, embedConfig.EmbedUrl, embedConfig.Id);
51 | if (updateCurrentToken) {
52 |
53 | // Get the reference to the embedded element
54 | const reportContainer = $(".report-container").get(0);
55 | if (reportContainer) {
56 | const report = powerbi.get(reportContainer);
57 | report.setAccessToken(embedConfig.EmbedToken.Token);
58 | }
59 | }
60 |
61 | // Get the milliseconds after token will get refreshed
62 | tokenExpiration = embedConfig.MinutesToExpiration * 60 * 1000;
63 |
64 | // Set the tokenRefresh timer to count the seconds and request the JSON again when token expires
65 | setTokenExpirationListener();
66 | }
67 |
68 | // Check the remaining time and call the API again
69 | function setTokenExpirationListener() {
70 |
71 | const safetyInterval = minutesToRefreshBeforeExpiration * 60 * 1000;
72 |
73 | // Time until token refresh in milliseconds
74 | const timeout = tokenExpiration - safetyInterval;
75 | if (timeout <= 0) {
76 | populateEmbedConfigIntoCurrentSession(true /* updateCurrentToken */);
77 | }
78 | else {
79 | console.log(`Report Embed Token will be updated in ${timeout} milliseconds.`);
80 | setTimeout(function () {
81 | populateEmbedConfigIntoCurrentSession(true /* updateCurrentToken */)
82 | }, timeout);
83 | }
84 | }
85 |
86 | // Add a listener to make sure token is updated after tab was inactive
87 | document.addEventListener("visibilitychange", function () {
88 | // Check the access token when the tab is visible
89 | if (!document.hidden) {
90 | setTokenExpirationListener();
91 | }
92 | });
93 |
94 | // Get the data from the API and pass it to the front-end
95 | function loadThemesShowcaseReportIntoSession() {
96 |
97 | // Call the function for the first time to call the API, set the sessions values and return the response to the front-end
98 | return populateEmbedConfigIntoCurrentSession(false /* updateCurrentToken */);
99 | }
100 |
101 | // Set the embed config in globals
102 | function setConfig(accessToken, embedUrl, reportId) {
103 |
104 | // Fill the global object
105 | reportConfig.accessToken = accessToken;
106 | reportConfig.embedUrl = embedUrl;
107 | reportConfig.reportId = reportId;
108 | }
109 |
--------------------------------------------------------------------------------
/Customize report colors and mode/js/themes.js:
--------------------------------------------------------------------------------
1 | // ----------------------------------------------------------------------------
2 | // Copyright (c) Microsoft Corporation.
3 | // Licensed under the MIT license.
4 | // ----------------------------------------------------------------------------
5 |
6 | // For report themes documentation please check https://docs.microsoft.com/en-us/power-bi/desktop-report-themes
7 | const jsonDataColors = [{
8 | "name": "Default",
9 | "dataColors": ["#1A81FB", "#142091", "#E16338", "#5F076E", "#DA3F9D", "#6945B8", "#D3AA22", "#CF404A"],
10 | "foreground": "#252423",
11 | "background": "#FFFFFF",
12 | "tableAccent": "#B73A3A"
13 | },
14 | {
15 | "name": "Divergent",
16 | "dataColors": ["#B73A3A", "#EC5656", "#F28A90", "#F8BCBD", "#99E472", "#23C26F", "#0AAC00", "#026645"],
17 | "foreground": "#252423",
18 | "background": "#F4F4F4",
19 | "tableAccent": "#B73A3A"
20 | },
21 | {
22 | "name": "Executive",
23 | "dataColors": ["#3257A8", "#37A794", "#8B3D88", "#DD6B7F", "#6B91C9", "#F5C869", "#77C4A8", "#DEA6CF"],
24 | "background": "#FFFFFF",
25 | "foreground": "#9C5252",
26 | "tableAccent": "#6076B4"
27 | },
28 | {
29 | "name": "Tidal",
30 | "dataColors": ["#094782", "#0B72D7", "#098BF5", "#54B5FB", "#71C0A7", "#57B956", "#478F48", "#326633"],
31 | "tableAccent": "#094782",
32 | "visualStyles": {
33 | "*": {
34 | "*": {
35 | "background": [{ "show": true, "transparency": 3 }],
36 | "visualHeader": [{
37 | "foreground": { "solid": { "color": "#094782" } },
38 | "transparency": 3
39 | }]
40 | }
41 | },
42 | "group": { "*": { "background": [{ "show": false }] } },
43 | "basicShape": { "*": { "background": [{ "show": false }] } },
44 | "image": { "*": { "background": [{ "show": false }] } },
45 | "page": {
46 | "*": {
47 | "background": [{ "transparency": 100 }],
48 | }
49 | }
50 | }
51 | }
52 | ];
53 |
54 | const themes = [{
55 | "background": "#FFFFFF",
56 | "visualStyles": {
57 | "*": {
58 | "*": {
59 | "border": [{
60 | "show": true,
61 | "color": { "solid": { "color": "#FFFFFF" } },
62 | "radius": 2
63 | }],
64 | "dropShadow": [
65 | {
66 | "color": {
67 | "solid": {
68 | "color": "#FFFFFF"
69 | }
70 | },
71 | "show": true,
72 | "position": "Outer",
73 | "preset": "Custom",
74 | "shadowSpread": 1,
75 | "shadowBlur": 1,
76 | "angle": 45,
77 | "shadowDistance": 1,
78 | "transparency": 95
79 | }
80 | ]
81 | }
82 | }
83 | },
84 | },
85 | {
86 | "background": "#252423",
87 | "foreground": "#FFFFFF",
88 | "tableAccent": "#FFFFFF",
89 | "textClasses": {
90 | "title": {
91 | "color": "#FFF",
92 | "fontFace": "Segoe UI Bold"
93 | },
94 | },
95 | "visualStyles": {
96 | "*": {
97 | "*": {
98 | "*": [{
99 | "fontFamily": "Segoe UI",
100 | "color": { "solid": { "color": "#252423" } },
101 | "labelColor": { "solid": { "color": "#FFFFFF" } },
102 | "secLabelColor": { "solid": { "color": "#FFFFFF" } },
103 | "titleColor": { "solid": { "color": "#FFFFFF" } },
104 | }],
105 | "labels": [{
106 | "color": { "solid": { "color": "#FFFFFF" } }
107 | }],
108 | "categoryLabels": [{
109 | "color": { "solid": { "color": "#FFFFFF" } }
110 | }],
111 | "border": [{
112 | "show": true,
113 | "color": { "solid": { "color": "#484644" } },
114 | "radius": 2
115 | }],
116 | "dropShadow": [
117 | {
118 | "color": {
119 | "solid": {
120 | "color": "#FFFFFF"
121 | }
122 | },
123 | "show": true,
124 | "position": "Outer",
125 | "preset": "Custom",
126 | "shadowSpread": 1,
127 | "shadowBlur": 1,
128 | "angle": 45,
129 | "shadowDistance": 1,
130 | "transparency": 95
131 | }
132 | ]
133 | }
134 | }
135 | }
136 | }
137 | ];
138 |
--------------------------------------------------------------------------------
/Go from insights to quick action/css/dialog_campaign_list.css:
--------------------------------------------------------------------------------
1 | /* Copyright (c) Microsoft Corporation.
2 | Licensed under the MIT license. */
3 |
4 | html {
5 | --table-header-padding: 0 8px 8px 30px;
6 | --table-cell-padding: 6px 0 6px 30px;
7 | --name-column: 170px;
8 | --region-column: 140px;
9 | --mail-column: 270px;
10 | --phone-column: 208px;
11 | --table-cell-font: 14px "Segoe UI Regular", "segoe_ui_regular", Tahoma, Arial, Helvetica;
12 | --table-cell-color: #323130;
13 | }
14 |
15 | .dialog-insight-to-action {
16 | background: #FFF;
17 | border-radius: 2px;
18 | box-shadow: 0 25.6px 57.6px rgba(0, 0, 0, .22), 0 4.8px 14.4px rgba(0, 0, 0, .18);
19 | display: none;
20 | position: relative;
21 | height: 427px;
22 | opacity: 1;
23 | top: 25%;
24 | width: 837px;
25 | }
26 |
27 | .btn-dialog-insight-to-action {
28 | background: #FFF;
29 | border: 1px solid #669B61;
30 | border-radius: 4px;
31 | color: #669B61;
32 | cursor: pointer;
33 | font: 14px/29px "Segoe UI SemiBold", "segoe_ui_semibold", Tahoma, Arial;
34 | height: 32px;
35 | line-height: 28px;
36 | margin-right: 8px;
37 | width: 116px;
38 | }
39 |
40 | .btn-dialog-insight-to-action:hover {
41 | background: rgba(102, 155, 97, 0.15);
42 | }
43 |
44 | .btn-dialog-insight-to-action:focus-visible {
45 | box-shadow: 0 0 0 2px rgba(0,123,255,.5);
46 | outline: none;
47 | }
48 |
49 | .btn-dialog-insight-to-action:focus:not(:focus-visible) {
50 | box-shadow: none;
51 | outline: none;
52 | }
53 |
54 | .btn-dialog-insight-to-action:active {
55 | background: #669B61;
56 | color: #FFF;
57 | }
58 |
59 | #dialog-table {
60 | color: #212121;
61 | height: 288px;
62 | overflow: hidden scroll;
63 | scrollbar-color: #D2D0CE #FFF;
64 | scrollbar-width: thin;
65 | width: 837px;
66 | }
67 |
68 | #dialog-table::-webkit-scrollbar-track {
69 | border-radius: 10px;
70 | background-color: transparent;
71 | }
72 |
73 | #dialog-table::-webkit-scrollbar {
74 | background-color: transparent;
75 | height: 10px;
76 | width: 8px;
77 | }
78 |
79 | #dialog-table::-webkit-scrollbar-thumb {
80 | background-color: #D2D0CE;
81 | border-radius: 10px;
82 | height: 30px;
83 | }
84 |
85 | .table-row {
86 | border-bottom: 1px solid #F3F2F1;
87 | display: flex;
88 | flex-direction: row;
89 | justify-content: flex-start;
90 | }
91 |
92 | .table-headers {
93 | color: #323130;
94 | font: 14px "Segoe UI SemiBold", "segoe_ui_semibold", Tahoma, Arial, Helvetica;
95 | text-align: left;
96 | }
97 |
98 | #name {
99 | margin-left: 42px;
100 | padding: 0 8px 8px 16px;
101 | width: var(--name-column);
102 | }
103 |
104 | #region {
105 | padding: var(--table-header-padding);
106 | width: var(--region-column);
107 | }
108 |
109 | #mail {
110 | padding: var(--table-header-padding);
111 | width: var(--mail-column);
112 | }
113 |
114 | #phone {
115 | padding: var(--table-header-padding);
116 | width: var(--phone-column);
117 | }
118 |
119 | .table-values {
120 | color: #323130;
121 | font: 14px "Segoe UI Regular", "segoe_ui_regular", Tahoma, Arial, Helvetica;
122 | }
123 |
124 | .name-cell {
125 | padding: 6px 0 6px 16px;
126 | width: var(--name-column);
127 | }
128 |
129 | .region-cell {
130 | padding: var(--table-cell-padding);
131 | width: var(--region-column);
132 | }
133 |
134 | .mail-cell {
135 | padding: var(--table-cell-padding);
136 | width: var(--mail-column);
137 | }
138 |
139 | .phone-cell {
140 | padding: 6px 30px;
141 | width: var(--phone-column);
142 | }
143 |
144 | #distribution-dialog>.dialog-footer {
145 | background: #FFF;
146 | bottom: 0;
147 | display: flex;
148 | flex-direction: row;
149 | justify-content: flex-end;
150 | height: 70px;
151 | padding: 19px 15px 16px;
152 | }
153 |
154 | .cell-checkbox {
155 | width: fit-content;
156 | }
157 |
158 | .checkbox-element {
159 | cursor: pointer;
160 | opacity: 0;
161 | }
162 |
163 | .table-checkbox {
164 | cursor: pointer;
165 | display: inline-block;
166 | height: 34px;
167 | width: 42px;
168 | margin: 0;
169 | position: relative;
170 | }
171 |
172 | .checkbox-circle {
173 | background: #FFF;
174 | border: 1px solid #D2D0CE;
175 | border-radius: 16px;
176 | height: 18px;
177 | left: 0;
178 | margin-left: 24px;
179 | position: absolute;
180 | top: 8px;
181 | width: 18px;
182 | }
183 |
184 | input[type="checkbox"]:focus-visible + .checkbox-circle {
185 | box-shadow: 0 0 4px 1.33px rgba(0,123,255,1);
186 | }
187 |
188 | input[type="checkbox"]:focus:not(:focus-visible) + .checkbox-circle {
189 | box-shadow: none;
190 | }
191 |
192 | .table-checkbox>input:checked~.checkbox-circle {
193 | background: #669B61;
194 | border: none;
195 | }
196 |
197 | .table-checkbox>input:checked~.checkbox-checkmark {
198 | border: 0 solid #FFF;
199 | border-width: 0 2.5px 2.5px 0;
200 | height: 11px;
201 | left: 30px;
202 | position: absolute;
203 | top: 10px;
204 | transform: rotate(45deg);
205 | width: 6px;
206 | }
207 |
208 | .overflow-hidden {
209 | overflow: hidden;
210 | }
--------------------------------------------------------------------------------
/Go from insights to quick action/css/dialog_send_coupon.css:
--------------------------------------------------------------------------------
1 | /* Copyright (c) Microsoft Corporation.
2 | Licensed under the MIT license. */
3 |
4 | .send-dialog-field {
5 | font: 14px/20px "Segoe UI SemiBold", "segoe_ui_semibold", Tahoma, Arial, Helvetica;
6 | color: #323130;
7 | height: 20px;
8 | margin: 6px 0 0 24px;
9 | position: relative;
10 | width: 392px;
11 | }
12 |
13 | #send-coupon-title {
14 | margin-top: 0;
15 | }
16 |
17 | .title {
18 | border: 1px solid #A19F9D;
19 | border-radius: 2px;
20 | box-sizing: border-box;
21 | font: 14px/20px "Segoe UI Regular", "segoe_ui_regular", Tahoma, Arial, Helvetica;
22 | color: #323130;
23 | height: 30px;
24 | margin: 6px 24px 9px;
25 | padding: 5px 10px;
26 | width: 392px;
27 | }
28 |
29 | .input-content:focus:not(:focus-visible) {
30 | box-shadow: none;
31 | outline: none;
32 | }
33 |
34 | .input-content:focus-visible {
35 | box-shadow: 0 0 0 2px rgba(0,123,255,.5);
36 | outline: none;
37 | }
38 |
39 | .send-dialog-input {
40 | border: 1px solid #A19F9D;
41 | border-radius: 2px;
42 | box-sizing: border-box;
43 | font: 14px/20px "Segoe UI Regular", "segoe_ui_regular", Tahoma, Arial, Helvetica;
44 | height: 100px;
45 | margin: 6px 24px 0;
46 | padding: 5px 10px;
47 | resize: none;
48 | width: 392px;
49 | }
50 |
51 | .btn-insight-to-action-send {
52 | background: #FFF;
53 | border: 1px solid #669B61;
54 | border-radius: 4px;
55 | color: #669B61;
56 | cursor: pointer;
57 | font: 14px/28px "Segoe UI SemiBold", "segoe_ui_semibold", Tahoma, Arial;
58 | height: 30px;
59 | text-align: center;
60 | width: 64px;
61 | }
62 |
63 | .btn-insight-to-action-send:hover {
64 | background: rgba(102, 155, 97, 0.15);
65 | }
66 |
67 | .btn-insight-to-action-send:focus-visible {
68 | box-shadow: 0 0 0 2px rgba(0,123,255,.5);
69 | outline: none;
70 | }
71 |
72 | .btn-insight-to-action-send:focus:not(:focus-visible) {
73 | box-shadow: none;
74 | outline: none;
75 | }
76 |
77 | .btn-insight-to-action-send:active {
78 | background: #669B61;
79 | color: #FFF;
80 | }
81 |
82 | #send-dialog>.dialog-footer {
83 | border: none;
84 | bottom: 0;
85 | display: flex;
86 | flex-direction: row;
87 | justify-content: flex-end;
88 | height: 64px;
89 | left: 0;
90 | padding: 15px 24px 20px;
91 | right: 0;
92 | }
--------------------------------------------------------------------------------
/Go from insights to quick action/css/dialog_success.css:
--------------------------------------------------------------------------------
1 | /* Copyright (c) Microsoft Corporation.
2 | Licensed under the MIT license. */
3 |
4 | #image-success {
5 | height: 36px;
6 | margin: 73px 0 0 204px;
7 | width: 36px;
8 | }
9 |
10 | .text-success {
11 | color: #323130;
12 | font: 18px "Segoe UI SemiBold", "segoe_ui_semibold", Tahoma, Arial, Helvetica;
13 | margin-top: 5px;
14 | }
15 |
16 | #success-dialog {
17 | text-align: center;
18 | }
19 |
20 | #success-dialog .dialog-header{
21 | display: flex;
22 | justify-content: flex-end;
23 | }
24 |
25 | #success-content {
26 | display: flex;
27 | flex-direction: column;
28 | justify-content: flex-start;
29 | }
--------------------------------------------------------------------------------
/Go from insights to quick action/css/embed_area_style.css:
--------------------------------------------------------------------------------
1 | /* Copyright (c) Microsoft Corporation.
2 | Licensed under the MIT license. */
3 |
4 | /* TODO: Change font URL in the final environment */
5 | @font-face {
6 | font-family: "segoe_ui_regular";
7 | src: url("https://content.powerapps.com/resource/powerbiwfe/fonts/SegoeUI-Regular-final.8956d1f5b4190f537497.woff") format("woff");
8 | }
9 |
10 | @font-face {
11 | font-family: "segoe_ui_semibold";
12 | src: url("https://content.powerapps.com/resource/powerbiwfe/fonts/SegoeUI-SemiBold-final.83b7261d0e6f3994ed6d.woff") format("woff");
13 | }
14 |
15 | #main-div {
16 | display: none;
17 | height: auto;
18 | min-width: 850px;
19 | width: 100%;
20 | }
21 |
22 | #overlay {
23 | height: inherit;
24 | width: inherit;
25 | }
26 |
27 | #overlay #spinner {
28 | left: 50%;
29 | position: fixed;
30 | top: 50%;
31 | }
32 |
33 | .rotate {
34 | animation: rotation 1s infinite linear;
35 | }
36 |
37 | @keyframes rotation {
38 | from {
39 | transform: translate(-50%, -50%) rotate(0deg);
40 | }
41 | to {
42 | transform: translate(-50%, -50%) rotate(359deg);
43 | }
44 | }
45 |
46 | #dialog-mask {
47 | background: rgba(17, 16, 15, .4);
48 | bottom: 0;
49 | display: flex;
50 | flex-direction: row;
51 | justify-content: center;
52 | height: max(calc(calc(9/16) * 100vw + 60px), 100%);
53 | left: 0;
54 | min-width: 850px;
55 | position: absolute;
56 | right: 0;
57 | top: 0;
58 | }
59 |
60 | #report-container {
61 | height: calc(calc(9/16) * 99vw);
62 | margin-top: 44px;
63 | width: 100%;
64 | }
65 |
66 | #report-container>iframe {
67 | border: none;
68 | }
69 |
70 | .text-dialog-header {
71 | font: 18px/24px "Segoe UI SemiBold", "segoe_ui_semibold", Tahoma, Arial, Helvetica;
72 | height: 24px;
73 | margin-left: 24px;
74 | position: relative;
75 | }
76 |
77 | .dialog-body {
78 | position: relative;
79 | }
80 |
81 | .dialog-header {
82 | height: 40px;
83 | display: flex;
84 | flex-direction: row;
85 | justify-content: space-between;
86 | margin-top: 18px;
87 | }
88 |
89 | .wrapper-close {
90 | background: transparent;
91 | border: 0;
92 | height: 40px;
93 | margin: -9px 10px 0 0;
94 | padding: 0;
95 | width: 40px;
96 | }
97 |
98 | .wrapper-close:focus-visible {
99 | box-shadow: 0 0 0 2px rgba(0,123,255,.5);
100 | outline: none;
101 | }
102 |
103 | .wrapper-close:focus:not(:focus-visible) {
104 | box-shadow: none;
105 | outline: none;
106 | }
107 |
108 | .btn-close-distribution-dialog {
109 | cursor: pointer;
110 | height: 16px;
111 | margin: 10px;
112 | position: relative;
113 | width: 16px;
114 | }
115 |
116 | .dialog-insight-to-action-small {
117 | background: #FFF;
118 | border-radius: 2px;
119 | box-shadow: 0 25.6px 57.6px rgba(0, 0, 0, .22), 0 4.8px 14.4px rgba(0, 0, 0, .18);
120 | display: none;
121 | height: 332px;
122 | opacity: 1;
123 | position: relative;
124 | top: 25%;
125 | width: 440px;
126 | }
--------------------------------------------------------------------------------
/Go from insights to quick action/css/header_style.css:
--------------------------------------------------------------------------------
1 | /* Copyright (c) Microsoft Corporation.
2 | Licensed under the MIT license. */
3 |
4 | body {
5 | height: 100%;
6 | scrollbar-color: #A0AEB2 #FFF;
7 | scrollbar-width: thin;
8 | }
9 |
10 | /* Change custom scrollbar appearance */
11 | body::-webkit-scrollbar-track {
12 | border-radius: 10px;
13 | background-color: transparent;
14 | }
15 |
16 | body::-webkit-scrollbar {
17 | background-color: transparent;
18 | height: 10px;
19 | width: 8px;
20 | }
21 |
22 | body::-webkit-scrollbar-thumb {
23 | background-color: #A0AEB2;
24 | border-radius: 10px;
25 | height: 30px;
26 | }
27 |
28 | #wrapper {
29 | margin: 0 auto;
30 | min-width: 838px;
31 | width: 100%;
32 | }
33 |
34 | #contoso-header {
35 | background: #094624;
36 | display: flex;
37 | align-items: center;
38 | height: 44px;
39 | margin: 0;
40 | min-width: 850px;
41 | }
42 |
43 | .fixed-top {
44 | position: fixed;
45 | top: 0;
46 | right: 0;
47 | left: 0;
48 | z-index: 1030;
49 | }
50 |
51 | #contoso {
52 | height: 41px;
53 | margin-left: 24px;
54 | width: 74px;
55 | }
--------------------------------------------------------------------------------
/Go from insights to quick action/img/contoso.svg:
--------------------------------------------------------------------------------
1 |
12 |
--------------------------------------------------------------------------------
/Go from insights to quick action/img/cross.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/Go from insights to quick action/img/spinner.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/Go from insights to quick action/img/success.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/Go from insights to quick action/index.html:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 | Insights to action
25 |
26 |
27 |
28 |
29 |
30 |
33 |
34 |

35 |
36 |
37 |
38 |
39 |
54 |
55 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
74 |
75 |
76 |
81 |
82 |

83 |
Sent successfully!
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
--------------------------------------------------------------------------------
/Go from insights to quick action/js/error_listener.js:
--------------------------------------------------------------------------------
1 | // ----------------------------------------------------------------------------
2 | // Copyright (c) Microsoft Corporation.
3 | // Licensed under the MIT license.
4 | // ----------------------------------------------------------------------------
5 |
6 | window.addEventListener('error', function(event) {
7 | // Protection against cross-origin failure
8 | try {
9 | if (window.parent.playground && window.parent.playground.logShowcaseError) {
10 | window.parent.playground.logShowcaseError("InsightToAction", event);
11 | }
12 | } catch { }
13 | });
--------------------------------------------------------------------------------
/Go from insights to quick action/js/globals.js:
--------------------------------------------------------------------------------
1 | // ----------------------------------------------------------------------------
2 | // Copyright (c) Microsoft Corporation.
3 | // Licensed under the MIT license.
4 | // ----------------------------------------------------------------------------
5 |
6 | // Constants used for report configurations as key-value pair
7 | const reportConfig = {
8 | accessToken: null,
9 | embedUrl: null,
10 | reportId: null
11 | }
12 |
13 | // To cache report
14 | const reportShowcaseState = {
15 | report: null,
16 | data: null,
17 | allChecked: false,
18 | tooltipNextPressed: false
19 | }
20 |
21 | // Initialize and cache global DOM objects
22 | const body = $("#insight-to-action");
23 | const embedContainer = $("#report-container").get(0);
24 | const overlay = $("#overlay");
25 | const distributionDialog = $("#distribution-dialog");
26 | const dialogMask = $("#dialog-mask");
27 | const sendDialog = $("#send-dialog");
28 | const successDialog = $("#success-dialog");
29 | const closeBtn1 = $("#close1");
30 | const closeBtn2 = $("#close2");
31 | const sendCouponBtn = $("#send-coupon");
32 | const sendDiscountBtn = $("#send-discount");
33 | const sendMessageBtn = $("#send-message");
34 | const successCross = $("#success-cross");
35 |
36 | // Cache CSS Class
37 | const HIDE_OVERFLOW = "overflow-hidden";
38 |
39 | // Check if dialog box is closed
40 | let isDialogClosed = true;
41 |
42 | // Key codes
43 | const KEYCODE_TAB = 9;
44 | const KEYCODE_ESCAPE = 27;
45 |
46 | // Enum for keys
47 | const Keys = {
48 | TAB : "Tab",
49 | ESCAPE: "Escape"
50 | }
51 |
52 | // Freezing the contents of enum object
53 | Object.freeze(Keys);
54 |
55 | // Table visual GUID
56 | const TABLE_VISUAL_GUID = "1149606f2a101953b4ba";
57 | let tableVisual;
58 |
59 | // Icon for the custom extension
60 | const base64Icon = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFAAAABMCAYAAAD6BTBNAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAYdEVYdFNvZnR3YXJlAHBhaW50Lm5ldCA0LjAuNvyMY98AAAZOSURBVHhe7ZrJSxxBFMbzNyXGLbgfXaLkIgqJmoPJzV0PHqNR/4TkoBfxkIgoKEhIwBjRi6gHcb2riBclF0Er/RX9iprJN0712DNu9eBH93zz+nXNm1dL18wzpZTnFlDR4w4VPe5Q0eMOFT3uUNHjDhU97lDR4w4VPe5Q0eMOFT3uUNHjDhU97lDR4w4VPe5Q0eMOFZ8YYuy9tFDxiaC2t7dVf3+/+vnzJ14yn7RQ8RGj9vf31adPn1RJSYl68eKFPobG/NNCxUeG2t3dVQMDA6q8vFy9fPlS5eXl6eThvL29HS4wdm1aqPgIUHt7e2p4eFiVlZXpZEnSJHF4nZ+fr2ZnZ+HOYjhBxQeK2tnZ0ZWGpCFBz58/10eApCFhQJKI89BYPCeo+EDQhjHt8+fPqrS01CRHEgbktZ1IgPPW1tYwCo3vBBXvOXpM6+vr02MaEpEqSdALCgpUR0eH2tjYUCMjI8YH7/369Qvh2D2coeI9xFSazJ5Igt1FRcMRie3p6VG/f//GpTAd4/Xr18YfPqEl3ysSVLwnqIODAzN7SvcEqCpJmHTHwsJCnbTNzU1cKmbinZ+fax+57uPHj9ohMPuekaHiHWLWaRjTJGHyoXG0x7CKigrV1dWlVlZWcCmMxdRMTk6aWKjcubm5QOa+UaBijjFjGltyJIMqwpi2tbWFS8VYXBvV2Nio4wJ8AaEx30hQMQeow8NDs06TAV+Oco5uiw9cVVWlE/znzx9cCmMxU3J5eanjSfy2trZA1kb9o0DFLKHXaXj2tCvNThoSJq9x3tnZqWfP0FhMJ758+WLuAX78+BHI3DcqVIwRPREMDQ0ljGnSRTEWyWskDpXW3d3tNKZFQDU0NOh7AHx5oTHfyFDxlujHqN7eXlVZWWmSJpUlyZIPJGPa+vo6LhVjcTPi4uJCz9q4F+7/4cOHQNZG/aNCxQwwz55Yp8nYJYmSoyQTsyeWHGtra7gUxmLGwtTUlL63tCOu2VegoiN6P03WadJIJEi6JkCjAaoAY5q1TmMx40Y1NTXpdkj7QmO+GUHFG9DrNKk0O2HJ4xlAF8aYtry8jEthLGbWwOwr7cHx/fv3gayN+mcCFZP4bz9NngRwnpxAdN8M1mlZ4evXr2Y4QdvinH0FKgbcuJ+Gc5vkBEKT5CaD95meDew2xj37CvaL/54IbCRJ6RB/O7F3hbQDxD37CvYLnUDseNTU1JhxI2oy4CvVJ42/K9AOacttt+5TQcUAdXR0ZLaP0O1YspJBQ+0uynxyhbQBicQxF104FfppQirTbpg0DkijZWG8urqKS8VY3KwzMTGRUImLi4uBzH0zhYo3oI6PjxOWMag4u8viXLTi4mLV0tIiO78wFjNrXF1dmS8a7UFbQqP+mUBFR/RMjW3y6upq01AkEo2Vc1QmEioLaes5F8bixolqbm427UE7QmO+GUHFDFCnp6dqdHQ04VEOyZMjkimgMvGDTi4W2NPT0+a+aMvMzEwgc99MoOIt0U8r2IHBmIlGy8N8MqgMqUxrrw/G4maEbCZIAuOejakYI7oy5WdH+RBSmYJUKHzevXunlpaWcCmMxYyKqq+vN/dGDwmN+UaGillCrzPRzevq6swHAji3gYbZPK7KHB8fT7jXwsJCIHPfqFAxB+jKRDdPNZtLMqG9evVKj5lJP1M6c319bcZlxHz79m0ga6P+UaBijkmoTPmgQBKKI7o5wHiGdWbE2dzMxgD3QFKt9zOGineIfgLCOtPeY5TKkbFSjqherO1cKvPbt28mDvj+/Xsgc98oUPGeoGdze50pFSRJEKDJmGktjWAm3t+/f7WPfCFxbS5Q8R6iTk5OdGXi5wBJJirRTqoci4qK9JgZ/vMUpmO8efPG+MU1G1PxnqOfgDAB1dbWmjETScERCZIuDvA+/r2AyhwcHEwYY+fn5xGO3cMZKj4g1NnZmdn4lQRKkiSJAElNHkPj+IGdig8UPZsjmXgCspOIoyRQkgcNPqGxeE5Q8RGgx8xU+5lIoCTxtrMxFR8ZdD9TKtT/yTwa/+1n4vk7NOafFio+EfRsPjY2JrvnzCctVHyCwJieFip63KGixx0qetyhoscdKnrcoaLHHSp63KGixx0qetyhoscdKnrcoaLHHSp63KGixx0qetyhoscV9ewf7lUFSJ1Dr2cAAAAASUVORK5CYII="
61 |
62 | const distributionDialogElements = {
63 | firstElement: closeBtn1,
64 | lastElement: sendDiscountBtn
65 | }
66 |
67 | const sendDialogElements = {
68 | firstElement: closeBtn2,
69 | lastElement: sendMessageBtn
70 | }
71 |
72 | const successDialogElements = {
73 | firstElement: successCross,
74 | lastElement: successCross
75 | }
--------------------------------------------------------------------------------
/Go from insights to quick action/js/session_utils.js:
--------------------------------------------------------------------------------
1 | // ----------------------------------------------------------------------------
2 | // Copyright (c) Microsoft Corporation.
3 | // Licensed under the MIT license.
4 | // ----------------------------------------------------------------------------
5 |
6 | // API Endpoint to get the JSON response of Embed URL, Embed Token and reportId
7 | const insightToActionReportEndpoint = "https://aka.ms/InsightToActionReportEmbedConfig";
8 |
9 | // Set minutes before the access token should get refreshed
10 | const minutesToRefreshBeforeExpiration = 2;
11 |
12 | // Store the amount of time left for refresh token
13 | let tokenExpiration;
14 |
15 | // This function will make the AJAX request to the endpoint and get the JSON response which it will set in the sessions
16 | function populateEmbedConfigIntoCurrentSession(updateCurrentToken) {
17 |
18 | try {
19 | let configFromParentWindow = window.parent.showcases.insightToAction;
20 | if (configFromParentWindow) {
21 | let diffMs = new Date(configFromParentWindow.expiration) - new Date();
22 | let diffMins = Math.round(((diffMs % 86400000) % 3600000) / 60000);
23 |
24 | embedConfig = {
25 | EmbedUrl: configFromParentWindow.embedUrl,
26 | EmbedToken: {
27 | Token: configFromParentWindow.token
28 | },
29 | Id: configFromParentWindow.id,
30 | MinutesToExpiration: diffMins,
31 | };
32 |
33 | handleNewEmbedConfig(embedConfig, updateCurrentToken);
34 | }
35 |
36 | return;
37 | } catch (error) {
38 | console.error(error);
39 | }
40 |
41 | // This returns the JSON response
42 | return $.getJSON(insightToActionReportEndpoint, function (embedConfig) {
43 | handleNewEmbedConfig(embedConfig, updateCurrentToken);
44 | });
45 | }
46 |
47 | function handleNewEmbedConfig(embedConfig, updateCurrentToken) {
48 |
49 | // Set Embed Token, Embed URL and Report Id
50 | setConfig(embedConfig.EmbedToken.Token, embedConfig.EmbedUrl, embedConfig.Id);
51 | if (updateCurrentToken) {
52 |
53 | // Get the reference to the embedded element
54 | const reportContainer = $("#report-container").get(0);
55 | if (reportContainer) {
56 | const report = powerbi.get(reportContainer);
57 | report.setAccessToken(embedConfig.EmbedToken.Token);
58 | }
59 | }
60 |
61 | // Get the milliseconds after token will get refreshed
62 | tokenExpiration = embedConfig.MinutesToExpiration * 60 * 1000;
63 |
64 | // Set the tokenRefresh timer to count the seconds and request the JSON again when token expires
65 | setTokenExpirationListener();
66 | }
67 |
68 | // Check the remaining time and call the API again
69 | function setTokenExpirationListener() {
70 |
71 | const safetyInterval = minutesToRefreshBeforeExpiration * 60 * 1000;
72 |
73 | // Time until token refresh in milliseconds
74 | const timeout = tokenExpiration - safetyInterval;
75 | if (timeout <= 0) {
76 | populateEmbedConfigIntoCurrentSession(true /* updateCurrentToken */);
77 | }
78 | else {
79 | console.log(`Report Embed Token will be updated in ${timeout} milliseconds.`);
80 | setTimeout(function () {
81 | populateEmbedConfigIntoCurrentSession(true /* updateCurrentToken */)
82 | }, timeout);
83 | }
84 | }
85 |
86 | // Add a listener to make sure token is updated after tab was inactive
87 | document.addEventListener("visibilitychange", function () {
88 | // Check the access token when the tab is visible
89 | if (!document.hidden) {
90 | setTokenExpirationListener();
91 | }
92 | });
93 |
94 | // Get the data from the API and pass it to the front-end
95 | function loadReportIntoSession() {
96 |
97 | // Call the function for the first time to call the API, set the sessions values and return the response to the front-end
98 | return populateEmbedConfigIntoCurrentSession(false /* updateCurrentToken */);
99 | }
100 |
101 | // Set the embed config in global object
102 | function setConfig(accessToken, embedUrl, reportId) {
103 |
104 | // Fill the global object
105 | reportConfig.accessToken = accessToken;
106 | reportConfig.embedUrl = embedUrl;
107 | reportConfig.reportId = reportId;
108 | }
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) Microsoft Corporation.
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE
22 |
--------------------------------------------------------------------------------
/Personalize top insights/css/content.css:
--------------------------------------------------------------------------------
1 | /* Copyright (c) Microsoft Corporation.
2 | Licensed under the MIT license. */
3 |
4 | .rotate {
5 | animation: rotation 1s infinite linear;
6 | }
7 |
8 | @keyframes rotation {
9 | from {
10 | transform: translate(-50%, -50%) rotate(0deg);
11 | }
12 | to {
13 | transform: translate(-50%, -50%) rotate(359deg);
14 | }
15 | }
16 |
17 | #overlay {
18 | height: inherit;
19 | position: absolute;
20 | width: inherit;
21 | }
22 |
23 | #overlay #spinner {
24 | position: fixed;
25 | top: 50%;
26 | left: 50%;
27 | }
28 |
29 | #main-div {
30 | background: #F6F6FA;
31 | }
32 |
33 | #main-div>* {
34 | display: none;
35 | }
36 |
37 | .line {
38 | border-top: 0.5px solid rgba(0, 0, 0, 0.2);
39 | clear: both;
40 | margin: 109px 16px 0 16px;
41 | opacity: 0.7;
42 | width: auto;
43 | z-index: 0;
44 | }
--------------------------------------------------------------------------------
/Personalize top insights/css/dropdown.css:
--------------------------------------------------------------------------------
1 | /* Copyright (c) Microsoft Corporation.
2 | Licensed under the MIT license. */
3 |
4 | /* TODO: Change font URL in the final environment */
5 | @font-face {
6 | font-family: "segoe_ui_regular";
7 | src: url("https://content.powerapps.com/resource/powerbiwfe/fonts/SegoeUI-Regular-final.8956d1f5b4190f537497.woff") format("woff");
8 | }
9 |
10 | @font-face {
11 | font-family: "segoe_ui_semibold";
12 | src: url("https://content.powerapps.com/resource/powerbiwfe/fonts/SegoeUI-SemiBold-final.83b7261d0e6f3994ed6d.woff") format("woff");
13 | }
14 |
15 | #action-bar {
16 | margin-top: 44px;
17 | background: #F6F6FA;
18 | }
19 |
20 | .dropdown {
21 | float: left;
22 | }
23 |
24 | .btn-action[aria-expanded="true"] {
25 | background: rgba(73, 70, 198, 0.15);
26 | color: #4946C6;
27 | }
28 |
29 | .btn-action {
30 | background: #F6F6FA;
31 | border: 1px solid #4946C6;
32 | color: #4946C6;
33 | font: 14px "Segoe UI SemiBold", segoe_ui_semibold, Tahoma, Arial;
34 | margin: 16px;
35 | }
36 |
37 | .btn-action:hover {
38 | background: rgba(73, 70, 198, 0.25);
39 | color: #4946C6;
40 | }
41 |
42 | /* For key-board focus */
43 | .btn-action:focus-visible {
44 | box-shadow: 0 0 0 1.5px #4946C6;
45 | }
46 |
47 | /* Remove focus for the mouse navigation */
48 | .btn-action:focus:not(:focus-visible) {
49 | box-shadow: none;
50 | }
51 |
52 | .btn-util:hover > img {
53 | filter: brightness(0) invert(1);
54 | }
55 |
56 | .btn.active.focus,
57 | .btn.active:focus,
58 | .btn.focus:active,
59 | .btn:active:focus {
60 | box-shadow: none;
61 | color: #4946C6;
62 | outline: 0;
63 | outline-offset: -2px;
64 | text-decoration: none;
65 | }
66 |
67 | #choose-layouts-btn {
68 | margin-left: 0
69 | }
70 |
71 | #caret-img {
72 | margin: 0 0 0 4px;
73 | }
74 |
75 | #visuals-list {
76 | border-radius: 2px;
77 | box-shadow: 0 0.6px 1.8px rgba(0, 0, 0, 0.108), 0 3.2px 7.2px rgba(0, 0, 0, 0.132);
78 | margin: 8px 0 0 0;
79 | padding: 0;
80 | width: 246px;
81 | padding: 8px 0;
82 | }
83 |
84 | /* Display the focus on the checkbox on keyboard navigation for the visual selection */
85 | input[type="checkbox"]:focus-visible + .checkbox-checkmark {
86 | box-shadow: 0 0 0 2.5px #4946C6;
87 | }
88 |
89 | /* Remove focus for the mouse navigation */
90 | input[type="checkbox"]:focus:not(:focus-visible) + .checkbox-checkmark {
91 | box-shadow: none;
92 | }
93 |
94 | .checkbox-container {
95 | cursor: pointer;
96 | display: block;
97 | font-size: 16px;
98 | height: 36px;
99 | padding: 8px 8px 8px 35px;
100 | position: relative;
101 | }
102 |
103 | .checkbox-container input {
104 | cursor: pointer;
105 | opacity: 0;
106 | position: absolute;
107 | }
108 |
109 | .checkbox-checkmark {
110 | background: #FFF;
111 | border: 1px solid #C8C8EE;
112 | border-radius: 2px;
113 | height: 20px;
114 | left: 8px;
115 | position: absolute;
116 | top: 8px;
117 | transition: background-color 100ms ease;
118 | width: 20px;
119 | }
120 |
121 | .checkbox-container input:checked~.checkbox-checkmark {
122 | background: rgba(73, 70, 198, 0.3);
123 | }
124 |
125 | .checkbox-container input:checked {
126 | background: #808080;
127 | }
128 |
129 | .checkbox-checkmark:after {
130 | content: "";
131 | display: none;
132 | position: absolute;
133 | }
134 |
135 | .checkbox-container input:checked~.checkbox-checkmark:after {
136 | display: block;
137 | }
138 |
139 | .checkbox-title {
140 | color: #323130;
141 | display: block;
142 | font: 400 14px "Segoe UI", segoe_ui_regular, Tahoma, Arial;
143 | margin-top: -1px;
144 | }
145 |
146 | .checkbox-container .checkbox-checkmark:after {
147 | border: 1px solid #4946C6;
148 | border-width: 0 1px 1px 0;
149 | height: 12px;
150 | left: 6px;
151 | padding: 2px;
152 | top: 1px;
153 | transform: rotate(45deg);
154 | width: 6px;
155 | }
156 |
157 | .checkbox-menu label {
158 | clear: both;
159 | color: #333;
160 | font-weight: 400;
161 | margin: 0;
162 | white-space: nowrap;
163 | }
164 |
165 | #layouts-list {
166 | background: #FFF;
167 | border-radius: 4px;
168 | box-shadow: 0 0.3px 0.9px rgba(0, 0, 0, 0.11), 0 1.6px 3.6px rgba(0, 0, 0, 0.13);
169 | box-sizing: border-box;
170 | height: 32px;
171 | margin: 8px 0 0 -36px;
172 | padding: 0;
173 | position: absolute;
174 | width: 180px;
175 | z-index: 1;
176 | }
177 |
178 | #layouts-list.show {
179 | display: flex;
180 | }
181 |
182 | .btn-util {
183 | align-items: center;
184 | border: 0.25px solid transparent;
185 | box-sizing: border-box;
186 | display: flex;
187 | height: 32px;
188 | justify-content: center;
189 | outline: none;
190 | padding: 0;
191 | width: 36px;
192 | }
193 |
194 | .btn-util:hover {
195 | background: rgba(73, 70, 198, 0.8);
196 | }
197 |
198 | /* For key-board focus */
199 | .btn-util:focus-visible {
200 | border: 2px solid #4946C6;
201 | outline: none;
202 | }
203 |
204 | /* Remove focus for the mouse navigation */
205 | .btn-util:focus:not(:focus-visible) {
206 | border: none;
207 | outline: none;
208 | }
209 |
210 | .btn-layout {
211 | background: #FFF;
212 | }
213 |
214 | .layout-img {
215 | height: 16px;
216 | width: 16px;
217 | }
218 |
219 | .active-columns-btn {
220 | background: rgba(73, 70, 198, 0.3);
221 | }
--------------------------------------------------------------------------------
/Personalize top insights/css/header.css:
--------------------------------------------------------------------------------
1 | /* Copyright (c) Microsoft Corporation.
2 | Licensed under the MIT license. */
3 |
4 | body {
5 | background: #F6F6FA;
6 | height: 100%;
7 | scrollbar-color: #A0AEB2 transparent;
8 | scrollbar-width: thin;
9 | }
10 |
11 | /* Change custom scrollbar appearance */
12 | body::-webkit-scrollbar-track {
13 | background-color: transparent;
14 | border-radius: 10px;
15 | }
16 |
17 | body::-webkit-scrollbar {
18 | background-color: transparent;
19 | height: 10px;
20 | width: 8px;
21 | }
22 |
23 | body::-webkit-scrollbar-thumb {
24 | background-color: #A0AEB2;
25 | border-radius: 10px;
26 | height: 30px;
27 | }
28 |
29 | #wrapper {
30 | margin: 0 auto;
31 | width: 100%;
32 | }
33 |
34 | #contoso-header {
35 | align-items: center;
36 | background: #4946C6;
37 | display: flex;
38 | height: 44px;
39 | margin: 0;
40 | }
41 |
42 | #contoso {
43 | height: 41px;
44 | margin-left: 24px;
45 | padding: 5px 0;
46 | width: 74px;
47 | }
--------------------------------------------------------------------------------
/Personalize top insights/css/report.css:
--------------------------------------------------------------------------------
1 | /* Copyright (c) Microsoft Corporation.
2 | Licensed under the MIT license. */
3 |
4 | #report-container {
5 | display: none;
6 | height: calc((9/16) * 98vw + 20px);
7 | margin-top: 109px;
8 | width: 100%;
9 | }
10 |
11 | #report-container>iframe {
12 | border: none;
13 | }
--------------------------------------------------------------------------------
/Personalize top insights/img/caret.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/Personalize top insights/img/colspan.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/Personalize top insights/img/column1.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/Personalize top insights/img/column2.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/Personalize top insights/img/column3.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/Personalize top insights/img/contoso.svg:
--------------------------------------------------------------------------------
1 |
12 |
--------------------------------------------------------------------------------
/Personalize top insights/img/rowspan.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/Personalize top insights/img/spinner.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/Personalize top insights/index.html:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 | Personalize Top Insights
24 |
25 |
26 |
27 |
28 |
31 |
32 |

33 |
34 |
35 |
36 |
37 |
41 |
42 |
43 |
44 |
47 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
--------------------------------------------------------------------------------
/Personalize top insights/js/error_listener.js:
--------------------------------------------------------------------------------
1 | // ----------------------------------------------------------------------------
2 | // Copyright (c) Microsoft Corporation.
3 | // Licensed under the MIT license.
4 | // ----------------------------------------------------------------------------
5 |
6 | window.addEventListener('error', function(event) {
7 | // Protection against cross-origin failure
8 | try {
9 | if (window.parent.playground && window.parent.playground.logShowcaseError) {
10 | window.parent.playground.logShowcaseError("PersonalizeTopInsights", event);
11 | }
12 | } catch { }
13 | });
--------------------------------------------------------------------------------
/Personalize top insights/js/globals.js:
--------------------------------------------------------------------------------
1 | // ----------------------------------------------------------------------------
2 | // Copyright (c) Microsoft Corporation.
3 | // Licensed under the MIT license.
4 | // ----------------------------------------------------------------------------
5 |
6 | // For the decision of the layout
7 | const COLUMNS = {
8 | ONE: 1,
9 | TWO: 2,
10 | THREE: 3
11 | };
12 |
13 | // Freezing the contents of COLUMNS object
14 | Object.freeze(COLUMNS);
15 |
16 | // For the decision of two custom layout with spanning
17 | const SPAN_TYPE = {
18 | NONE: 0,
19 | ROWSPAN: 1,
20 | COLSPAN: 2
21 | };
22 |
23 | // Freezing the contents of SPAN_TYPE object
24 | Object.freeze(SPAN_TYPE);
25 |
26 | // To give consistent margin to each visual in the custom showcase
27 | const LAYOUT_SHOWCASE = {
28 | MARGIN: 16,
29 | VISUAL_ASPECT_RATIO: 9 / 16,
30 | };
31 |
32 | // Constants used for report configurations as key-value pair
33 | let reportConfig = {
34 | accessToken: null,
35 | embedUrl: null,
36 | reportId: null,
37 | }
38 |
39 | // Maintain the state for the showcase
40 | let layoutShowcaseState = {
41 | columns: COLUMNS.TWO,
42 | span: SPAN_TYPE.NONE,
43 | layoutVisuals: null,
44 | layoutReport: null,
45 | layoutPageName: null
46 | };
47 |
48 | // Get models. models contain enums that can be used
49 | const models = window["powerbi-client"].models;
50 |
51 | // Cache DOM elements
52 | const visualsDropdown = $("#visuals-list");
53 | const visualsDiv = $(".dropdown");
54 | const layoutsDiv = $(".layouts");
55 | const layoutsDropdown = $("#layouts-list");
56 | const layoutButtons = $(".btn-util");
57 | const chooseVisualsBtn = $("#choose-visuals-btn");
58 | const chooseLayoutBtn = $("#choose-layouts-btn");
59 |
60 | // Store keycode for TAB key
61 | const KEYCODE_TAB = 9;
62 |
63 | const Keys = {
64 | TAB : "Tab"
65 | }
66 |
67 | // Freezing the contents of enum object
68 | Object.freeze(Keys);
69 |
70 | // Store id for the first visual
71 | let firstVisualId;
72 |
73 | // Store id for first button
74 | const firstButtonId = $(".btn-util")[0].id;
75 |
76 | // Cache the report containers
77 | const reportContainer = $("#report-container").get(0);
--------------------------------------------------------------------------------
/Personalize top insights/js/session_utils.js:
--------------------------------------------------------------------------------
1 | // ----------------------------------------------------------------------------
2 | // Copyright (c) Microsoft Corporation.
3 | // Licensed under the MIT license.
4 | // ----------------------------------------------------------------------------
5 |
6 | // API Endpoint to get the JSON response of Embed URL, Embed Token and reportId
7 | const layoutShowcaseReportEndpoint = "https://aka.ms/layoutReportEmbedConfig";
8 |
9 | // Set minutes before the access token should get refreshed
10 | const minutesToRefreshBeforeExpiration = 2;
11 |
12 | // Store the amount of time left for refresh token
13 | let tokenExpiration;
14 |
15 | // This function will make the AJAX request to the endpoint and get the JSON response which it will set in the sessions
16 | function populateEmbedConfigIntoCurrentSession(updateCurrentToken) {
17 |
18 | try {
19 | let configFromParentWindow = window.parent.showcases.personalizeTopInsights;
20 | if (configFromParentWindow) {
21 | let diffMs = new Date(configFromParentWindow.expiration) - new Date();
22 | let diffMins = Math.round(((diffMs % 86400000) % 3600000) / 60000);
23 |
24 | embedConfig = {
25 | EmbedUrl: configFromParentWindow.embedUrl,
26 | EmbedToken: {
27 | Token: configFromParentWindow.token
28 | },
29 | Id: configFromParentWindow.id,
30 | MinutesToExpiration: diffMins,
31 | };
32 |
33 | handleNewEmbedConfig(embedConfig, updateCurrentToken);
34 | }
35 |
36 | return;
37 | } catch (error) {
38 | console.error(error);
39 | }
40 |
41 | // This returns the JSON response
42 | return $.getJSON(layoutShowcaseReportEndpoint, function (embedConfig) {
43 | handleNewEmbedConfig(embedConfig, updateCurrentToken);
44 | });
45 | }
46 |
47 | function handleNewEmbedConfig(embedConfig, updateCurrentToken) {
48 |
49 | // Set Embed Token, Embed URL and Report Id
50 | setConfig(embedConfig.EmbedToken.Token, embedConfig.EmbedUrl, embedConfig.Id);
51 | if (updateCurrentToken) {
52 |
53 | // Get the reference to the embedded element
54 | const reportContainer = $("#report-container").get(0);
55 | if (reportContainer) {
56 | const report = powerbi.get(reportContainer);
57 | report.setAccessToken(embedConfig.EmbedToken.Token);
58 | }
59 | }
60 |
61 | // Get the milliseconds after token will get refreshed
62 | tokenExpiration = embedConfig.MinutesToExpiration * 60 * 1000;
63 |
64 | // Set the tokenRefresh timer to count the seconds and request the JSON again when token expires
65 | setTokenExpirationListener();
66 | }
67 |
68 | // Check the remaining time and call the API again
69 | function setTokenExpirationListener() {
70 |
71 | const safetyInterval = minutesToRefreshBeforeExpiration * 60 * 1000;
72 |
73 | // Time until token refresh in milliseconds
74 | const timeout = tokenExpiration - safetyInterval;
75 | if (timeout <= 0) {
76 | populateEmbedConfigIntoCurrentSession(true /* updateCurrentToken */);
77 | }
78 | else {
79 | console.log(`Report Embed Token will be updated in ${timeout} milliseconds.`);
80 | setTimeout(function () {
81 | populateEmbedConfigIntoCurrentSession(true /* updateCurrentToken */)
82 | }, timeout);
83 | }
84 | }
85 |
86 | // Add a listener to make sure token is updated after tab was inactive
87 | document.addEventListener("visibilitychange", function () {
88 | // Check the access token when the tab is visible
89 | if (!document.hidden) {
90 | setTokenExpirationListener();
91 | }
92 | });
93 |
94 | // Get the data from the API and pass it to the front-end
95 | function loadLayoutShowcaseReportIntoSession() {
96 |
97 | // Call the function for the first time to call the API, set the sessions values and return the response to the front-end
98 | return populateEmbedConfigIntoCurrentSession(false /* updateCurrentToken */);
99 | }
100 |
101 | // Set the embed config in global object
102 | function setConfig(accessToken, embedUrl, reportId) {
103 |
104 | // Fill the global object
105 | reportConfig.accessToken = accessToken;
106 | reportConfig.embedUrl = embedUrl;
107 | reportConfig.reportId = reportId;
108 | }
--------------------------------------------------------------------------------
/Quickly create and personalize visuals/css/content.css:
--------------------------------------------------------------------------------
1 | /* Copyright (c) Microsoft Corporation.
2 | Licensed under the MIT license. */
3 |
4 | * {
5 | user-select: none;
6 | }
7 |
8 | /* The custom loader animation to be displayed till the report is embedded */
9 | .rotate {
10 | animation: rotation 1s infinite linear;
11 | }
12 |
13 | @keyframes rotation {
14 | from {
15 | transform: translate(-50%, -50%) rotate(0deg);
16 | }
17 | to {
18 | transform: translate(-50%, -50%) rotate(359deg);
19 | }
20 | }
21 |
22 | .container {
23 | display: flex;
24 | height: auto;
25 | padding: 0;
26 | width: 100%;
27 | }
28 |
29 | #overlay {
30 | height: inherit;
31 | position: fixed;
32 | width: inherit;
33 | }
34 |
35 | #overlay #spinner {
36 | left: 50%;
37 | position: fixed;
38 | top: 50%;
39 | }
40 |
41 | .content {
42 | height: auto;
43 | min-width: 850px;
44 | width: 100%;
45 | }
46 |
47 | .content > * {
48 | display: none;
49 | }
50 |
--------------------------------------------------------------------------------
/Quickly create and personalize visuals/css/header.css:
--------------------------------------------------------------------------------
1 | /* Copyright (c) Microsoft Corporation.
2 | Licensed under the MIT license. */
3 |
4 | body {
5 | background: #F7F8F6;
6 | height: 100%;
7 | min-width: 850px;
8 | overflow-x: auto !important;
9 | scrollbar-color: #A0AEB2 transparent;
10 | scrollbar-width: thin;
11 | }
12 |
13 | /* Change custom scrollbar appearance */
14 | body::-webkit-scrollbar-track {
15 | background-color: transparent;
16 | border-radius: 10px;
17 | }
18 |
19 | body::-webkit-scrollbar {
20 | background-color: transparent;
21 | height: 10px;
22 | width: 8px;
23 | }
24 |
25 | body::-webkit-scrollbar-thumb {
26 | background-color: #A0AEB2;
27 | border-radius: 10px;
28 | height: 30px;
29 | }
30 |
31 | .wrapper {
32 | background: #F7F8F6;
33 | margin: 0 auto;
34 | width: 100%;
35 | }
36 |
37 | .header {
38 | align-items: center;
39 | background: #1B81EF;
40 | display: flex;
41 | height: 44px;
42 | margin: 0;
43 | }
44 |
45 | .contoso {
46 | height: 41px;
47 | margin-left: 24px;
48 | width: 74px;
49 | }
50 |
--------------------------------------------------------------------------------
/Quickly create and personalize visuals/css/popup.css:
--------------------------------------------------------------------------------
1 | /* Copyright (c) Microsoft Corporation.
2 | Licensed under the MIT license. */
3 |
4 | /* TODO: Change font URL in the final environment */
5 | @font-face {
6 | font-family: "segoe_ui_regular";
7 | src: url("https://content.powerapps.com/resource/powerbiwfe/fonts/SegoeUI-Regular-final.8956d1f5b4190f537497.woff") format("woff");
8 | }
9 |
10 | @font-face {
11 | font-family: "segoe_ui_semibold";
12 | src: url("https://content.powerapps.com/resource/powerbiwfe/fonts/SegoeUI-SemiBold-final.83b7261d0e6f3994ed6d.woff") format("woff");
13 | }
14 |
15 | *, *:hover{
16 | outline: none;
17 | }
18 |
19 | body.modal-open {
20 | padding-right: 0 !important;
21 | }
22 |
23 | .fade {
24 | transition: opacity .25s linear;
25 | }
26 |
27 | @media screen and (max-width: 850px) {
28 | .modal {
29 | padding: 0 !important;
30 | position: absolute;
31 | width: 850px;
32 | }
33 | }
34 |
35 | .modal {
36 | scrollbar-color: #A0AEB2 transparent;
37 | scrollbar-width: thin;
38 | }
39 |
40 | /* Change custom scrollbar appearance */
41 | .modal::-webkit-scrollbar-track {
42 | background-color: transparent;
43 | border-radius: 10px;
44 | }
45 |
46 | .modal::-webkit-scrollbar {
47 | background-color: transparent;
48 | height: 10px;
49 | width: 8px;
50 | }
51 |
52 | .modal::-webkit-scrollbar-thumb {
53 | background-color: #A0AEB2;
54 | border-radius: 10px;
55 | height: 30px;
56 | }
57 |
58 | .modal-backdrop.show {
59 | height: 100%;
60 | width: 100%;
61 | }
62 |
63 | .modal.show {
64 | display: flex !important;
65 | }
66 |
67 | .modal-container {
68 | align-items: center;
69 | border-radius: 2px 2px 0 0;
70 | display: flex;
71 | height: 647px;
72 | margin: auto;
73 | width: 784px;
74 | }
75 |
76 | .modal.modal-static .modal-dialog {
77 | transform: none;
78 | }
79 |
80 | .modal-content {
81 | height: 647px;
82 | }
83 |
84 | .modal-dialog {
85 | max-width: inherit;
86 | }
87 |
88 | .modal-body {
89 | display: flex;
90 | margin: 0 24px;
91 | padding: 0;
92 | }
93 |
94 | #visual-creator-options {
95 | box-sizing: border-box;
96 | margin-top: -9px;
97 | padding-right: 23px;
98 | width: 279px;
99 | }
100 |
101 | #visual-preview {
102 | background: #F2F5F8;
103 | border-radius: 4px;
104 | box-sizing: border-box;
105 | display: flex;
106 | height: 499px;
107 | width: 460px;
108 | }
109 |
110 | .authoring-area {
111 | background: #FFF;
112 | border: 1px solid #FFF;
113 | border-radius: 2px;
114 | box-sizing: border-box;
115 | box-shadow: 0 1.6px 3.6px rgba(0, 0, 0, 0.13), 0 0.3px 0.9px rgba(0, 0, 0, 0.11);
116 | display: flex;
117 | height: 252px;
118 | margin: auto;
119 | width: 426px;
120 | }
121 |
122 | #edit-image {
123 | display: block;
124 | margin: auto;
125 | }
126 |
127 | .select-items > div:focus {
128 | background-color: #E3E8EE;
129 | }
130 |
131 | .btn-create {
132 | align-items: center;
133 | background: #51ACF5;
134 | border: 1px solid #51ACF5;
135 | border-radius: 4px;
136 | color: #FDFDFD;
137 | display: inline-flex;
138 | font: 14px/20px "Segoe UI SemiBold", "segoe_ui_semibold", Tahoma, Arial, Helvetica;
139 | height: 32px;
140 | justify-content: space-around;
141 | margin: 0 4px 0 0;
142 | width: 94px;
143 | }
144 |
145 | .btn-create:hover {
146 | background: #3992F2;
147 | color: #FDFDFD;
148 | outline: none;
149 | }
150 |
151 | .btn-create:disabled {
152 | background: transparent;
153 | border: 1px solid #C3D4E1;
154 | color: #C3D4E1;
155 | }
156 |
157 | .btn-create:active {
158 | background: #1B81EF;
159 | }
160 |
161 | .btn-create:focus-visible {
162 | box-shadow: 0 0 4px 1.5px rgba(0,123,255,0.5);
163 | }
164 |
165 | .btn-create:focus:not(:focus-visible) {
166 | box-shadow: none;
167 | }
168 |
169 | .btn.active.focus,
170 | .btn.active:focus,
171 | .btn.focus:active,
172 | .btn:active:focus {
173 | box-shadow: none;
174 | outline: 0;
175 | outline-offset: -2px;
176 | text-decoration: none;
177 | }
178 |
179 | .modal-header {
180 | border: 0;
181 | padding: 22px 24px 33px 24px;
182 | }
183 |
184 | #close-modal {
185 | display: flex;
186 | margin-top: -14px;
187 | opacity: 1;
188 | outline: none;
189 | padding: 20px;
190 | }
191 |
192 | #close-modal:focus-visible {
193 | box-shadow: 0 0 0 2.5px rgba(0,123,255,.5);
194 | }
195 |
196 | #close-modal:focus:not(:focus-visible) {
197 | box-shadow: none;
198 | }
199 |
200 | .modal-footer {
201 | border: 0;
202 | padding: 8px 19px 24px 12px;
203 | }
204 |
205 | .modal-title {
206 | color: #1A1A1A;
207 | font: 20px/28px "Segoe UI SemiBold", "segoe_ui_semibold", Tahoma, Arial, Helvetica;
208 | margin-top: -5px;
209 | }
210 |
211 | .select-wrapper, .toggle-wrapper {
212 | display: flex;
213 | height: 32px;
214 | justify-content: space-between;
215 | margin-top: 5px;
216 | }
217 |
218 | .toggle-wrapper.disabled {
219 | pointer-events: none;
220 | }
221 |
222 | .custom-title-wrapper {
223 | align-items: center;
224 | display: flex;
225 | justify-content: space-between;
226 | }
227 |
228 | .custom-title-wrapper.disabled input[type="text"] {
229 | pointer-events: none;
230 | }
231 |
232 | /* Select Menu */
233 | .styled-select {
234 | appearance: none;
235 | background: url('../img/enabled-images/chevron.svg') transparent no-repeat calc(100% - 10px) !important;
236 | border: 1px solid #ACACAC;
237 | border-radius: 2px;
238 | box-sizing: border-box;
239 | cursor: pointer;
240 | font: 14px/20px "Segoe UI Regular", "segoe_ui_regular", Tahoma, Arial, Helvetica;
241 | position: relative;
242 | width: 256px;
243 | -webkit-appearance: none;
244 | -moz-appearance: none;
245 | }
246 |
247 | .styled-select select {
248 | display: none; /*hide original SELECT element: */
249 | }
250 |
251 | .generator-disabled {
252 | pointer-events: none;
253 | }
254 |
255 | .inline-select-text, .inline-toggle-text {
256 | color: #201F1E;
257 | font: 14px/20px "Segoe UI Regular", "segoe_ui_regular", Tahoma, Arial, Helvetica;
258 | line-height: 32px;
259 | }
260 |
261 | .inline-select {
262 | appearance: none;
263 | background: url('../img/enabled-images/chevron.svg') transparent no-repeat calc(100% - 10px) !important;
264 | border: 1px solid #ACACAC;
265 | border-radius: 2px;
266 | box-sizing: border-box;
267 | cursor: pointer;
268 | margin-left: 8px;
269 | width: 199px;
270 | -webkit-appearance: none;
271 | -moz-appearance: none;
272 | }
273 |
274 | #generator-fields, #generator-properties, .title-wrapper {
275 | margin-top: 22px;
276 | }
277 |
278 | /* Toggle Button */
279 | .inline-toggle-text {
280 | width: auto;
281 | }
282 |
283 | /* The switch - the box around the slider */
284 | .switch {
285 | height: 20px;
286 | margin: 7px 0 7px 16px;
287 | position: relative;
288 | width: 40px;
289 | }
290 |
291 | /* Hide default HTML checkbox */
292 | .switch input {
293 | height: 0;
294 | opacity: 0;
295 | width: 0;
296 | }
297 |
298 | /* The slider */
299 | .slider {
300 | border: 1px solid #54ADF4;
301 | background-color: #EDEFF0;
302 | bottom: 0;
303 | cursor: pointer;
304 | height: 20px;
305 | left: 0;
306 | position: absolute;
307 | right: 0;
308 | top: 0;
309 | transition: .4s;
310 | -webkit-transition: .4s;
311 | }
312 |
313 | .slider:before {
314 | background-color: #54ADF4;
315 | bottom: 3px;
316 | content: "";
317 | height: 12px;
318 | left: 3px;
319 | position: absolute;
320 | top: 19%;
321 | transition: .4s;
322 | width: 12px;
323 | -webkit-transition: .4s;
324 | }
325 |
326 | input[type="checkbox"]:focus-visible + .slider.round {
327 | box-shadow: 0 0 4px 2.5px rgba(0,123,255,0.5);
328 | }
329 |
330 | input[type="checkbox"]:focus:not(:focus-visible) + .slider.round {
331 | box-shadow: none;
332 | }
333 |
334 | .alignment-block:focus-visible {
335 | box-shadow: 0 0 0 2.5px #118DFF;
336 | outline: none;
337 | }
338 |
339 | .alignment-block:focus:not(:focus-visible) {
340 | box-shadow: none;
341 | outline: none;
342 | }
343 |
344 | .slider.disabled-sliders:before {
345 | background: #BAC3C8;
346 | border: 0;
347 | top: 20%;
348 | }
349 |
350 | input:checked+.slider.disabled-sliders {
351 | background: #EDEFF0;
352 | border: 0;
353 | }
354 |
355 | input+.slider.disabled-sliders {
356 | background: #EDEFF0;
357 | border: 0;
358 | }
359 |
360 | input:checked + .slider {
361 | background-color: #2196F3;
362 | border: 0;
363 | }
364 |
365 | input:checked + .slider:before {
366 | background-color: #FFF;
367 | top: 20%;
368 | transform: translateX(22px);
369 | -webkit-transform: translateX(22px);
370 | -ms-transform: translateX(22px);
371 | }
372 |
373 | /* Rounded sliders */
374 | .slider.round {
375 | border-radius: 40px;
376 | }
377 |
378 | .slider.round:before {
379 | border-radius: 50%;
380 | }
381 |
382 | #alignment-blocks-wrapper {
383 | height: 32px;
384 | margin-top: 4px;
385 | }
386 |
387 | #alignment-blocks-wrapper img {
388 | margin: auto;
389 | }
390 |
391 | .alignment-block {
392 | background: transparent;
393 | border: none;
394 | display: inline-flex;
395 | height: 25px;
396 | margin-right: 6px;
397 | padding: 1px 4px;
398 | user-select: none;
399 | width: 25px;
400 | }
401 |
402 | .alignment-block.selected {
403 | background: #BAC3C8;
404 | }
405 |
406 | .cursor {
407 | cursor: pointer;
408 | }
409 |
410 | .title-alignment-wrapper {
411 | display: flex;
412 | justify-content: space-between;
413 | margin-top: 10px;
414 | }
415 |
416 | #visual-title {
417 | border: 1px solid #A19F9D;
418 | border-radius: 2px;
419 | font: 14px/20px "Segoe UI Regular", "segoe_ui_regular", Tahoma, Arial, Helvetica;
420 | height: 32px;
421 | margin-top: 8px;
422 | padding-left: 8px;
423 | width: 206px;
424 | }
425 |
426 | #visual-title:focus {
427 | box-shadow: 0 0 0 2.5px rgba(0,123,255,.5);
428 | outline: none;
429 | }
430 |
431 | #visual-title::placeholder {
432 | color: #A19F9D;
433 | font-size: 14px;
434 | vertical-align: middle;
435 | }
436 |
437 | .eraser-button {
438 | background: transparent;
439 | border: none;
440 | margin-top: 5px;
441 | }
442 |
443 | #erase-tool-enabled {
444 | cursor: pointer;
445 | display: flex;
446 | margin-left: 8px;
447 | }
448 |
449 | #erase-tool-disabled {
450 | display: flex;
451 | margin-left: 8px;
452 | }
453 |
454 | .generator-options {
455 | color: #201F1E;
456 | font: 14px/20px "Segoe UI SemiBold", "segoe_ui_semibold", Tahoma, Arial, Helvetica;
457 | }
458 |
459 | #erase-tool-enabled {
460 | display: none;
461 | }
462 |
463 | #erase-tool-enabled:focus-visible {
464 | box-shadow: 0 0 0 2.5px #118DFF;
465 | outline: none;
466 | }
467 |
468 | #erase-tool-enabled:focus:not(:focus-visible) {
469 | box-shadow: none;
470 | outline: none;
471 | }
472 |
473 | #aligns-enabled {
474 | display: none;
475 | }
476 |
477 | /* Style the items (options), including the selected item: */
478 | .select-selected, .select-items div {
479 | color: #000;
480 | cursor: pointer;
481 | padding: 5px 10px;
482 | }
483 |
484 | .select-selected {
485 | border-radius: 2px;
486 | }
487 |
488 | .select-selected:focus-visible{
489 | box-shadow: 0 0 0 2.5px rgba(0,123,255,.5);
490 | }
491 |
492 | .select-selected:focus:not(:focus-visible){
493 | box-shadow: none;
494 | }
495 |
496 | .select-items {
497 | background: #FFFFFF;
498 | box-shadow: 0 3.2px 7.2px rgba(0, 0, 0, 0.132), 0 0.6px 1.8px rgba(0, 0, 0, 0.108);
499 | border-radius: 2px;
500 | left: 0;
501 | position: absolute;
502 | right: 0;
503 | top: 100%;
504 | z-index: 99;
505 | }
506 |
507 | /* Hide the items when the select box is closed */
508 | .select-hide {
509 | display: none;
510 | }
511 |
512 | .select-items div:hover, .same-as-selected {
513 | background-color: #E3E8EE;
514 | }
515 |
516 | .same-as-selected {
517 | background: #F2F5F8;
518 | font-weight: 600;
519 | }
520 |
521 | div.generator-type-disabled span {
522 | color: #BEBBB8;
523 | }
524 |
525 | div.generator-type-disabled div.select-selected {
526 | color: #BEBBB8;
527 | }
528 |
529 | div.generator-type-disabled div.styled-select {
530 | background: url('../img/disabled-images/chevron.svg') transparent no-repeat calc(100% - 10px) !important;
531 | }
532 |
533 | div.generator-fields-disabled span {
534 | color: #BEBBB8;
535 | }
536 |
537 | div.generator-fields-disabled div.select-selected {
538 | color: #BEBBB8;
539 | }
540 |
541 | div.generator-fields-disabled div.inline-select {
542 | background: url('../img/disabled-images/chevron.svg') transparent no-repeat calc(100% - 10px) !important;
543 | }
544 |
545 | div.generator-properties-disabled span {
546 | color: #BEBBB8;
547 | }
--------------------------------------------------------------------------------
/Quickly create and personalize visuals/css/report.css:
--------------------------------------------------------------------------------
1 | /* Copyright (c) Microsoft Corporation.
2 | Licensed under the MIT license. */
3 |
4 | .report-container {
5 | background: #F7F8F6;
6 | height: calc((9/16) * 98vw + 20px); /* Remove the top empty space of the report and maintain 16:9 aspect ratio */
7 | margin: 44px 16px 16px 16px;
8 | width: calc(100% - 33px); /* Position the report-container in center in all resolutions */
9 | }
10 |
11 | .report-container > iframe {
12 | border: none;
13 | }
14 |
15 | #visual-authoring-container > iframe {
16 | border: none;
17 | }
18 |
--------------------------------------------------------------------------------
/Quickly create and personalize visuals/img/close.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/Quickly create and personalize visuals/img/contoso.svg:
--------------------------------------------------------------------------------
1 |
12 |
--------------------------------------------------------------------------------
/Quickly create and personalize visuals/img/disabled-images/center-align.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/Quickly create and personalize visuals/img/disabled-images/chevron.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/Quickly create and personalize visuals/img/disabled-images/eraser.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/Quickly create and personalize visuals/img/disabled-images/left-align.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/Quickly create and personalize visuals/img/disabled-images/right-align.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/Quickly create and personalize visuals/img/enabled-images/center-align.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/Quickly create and personalize visuals/img/enabled-images/chevron.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/Quickly create and personalize visuals/img/enabled-images/eraser.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/Quickly create and personalize visuals/img/enabled-images/left-align.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/Quickly create and personalize visuals/img/enabled-images/right-align.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/Quickly create and personalize visuals/img/pencil.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/Quickly create and personalize visuals/img/spinner.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/Quickly create and personalize visuals/js/error_listener.js:
--------------------------------------------------------------------------------
1 | // ----------------------------------------------------------------------------
2 | // Copyright (c) Microsoft Corporation.
3 | // Licensed under the MIT license.
4 | // ----------------------------------------------------------------------------
5 |
6 | window.addEventListener('error', function(event) {
7 | // Protection against cross-origin failure
8 | try {
9 | if (window.parent.playground && window.parent.playground.logShowcaseError) {
10 | window.parent.playground.logShowcaseError("QuickCreate", event);
11 | }
12 | } catch { }
13 | });
--------------------------------------------------------------------------------
/Quickly create and personalize visuals/js/globals.js:
--------------------------------------------------------------------------------
1 | // ----------------------------------------------------------------------------
2 | // Copyright (c) Microsoft Corporation.
3 | // Licensed under the MIT license.
4 | // ----------------------------------------------------------------------------
5 |
6 | const visualCreatorShowcaseState = {
7 | report: null,
8 | page: null, // The page from where the 3x3 visuals will be displayed
9 | newVisual: null, // New visual to be created on the page for the base-report
10 | visualType: null,
11 | dataRoles: {
12 | Legend: null,
13 | Values: null,
14 | Axis: null,
15 | Tooltips: null,
16 | },
17 | dataFieldsCount: 0,
18 | properties: {
19 | legend: true,
20 | xAxis: true,
21 | yAxis: true,
22 | title: true,
23 | titleText: null,
24 | titleAlign: null
25 | },
26 | }
27 |
28 | const selectedVisual = {
29 | visual: null,
30 | }
31 |
32 | const baseReportState = {
33 | report: null,
34 | visuals: null,
35 | page: null
36 | }
37 |
38 | const VISUAL_CREATOR_SHOWCASE = {
39 | COLUMNS: 3,
40 | MARGIN: 16,
41 | VISUAL_ASPECT_RATIO: 9 / 16
42 | }
43 |
44 | // Distance between the action button and the image visual inside the custom visual
45 | const DISTANCE = 18;
46 |
47 | // Constants used for report configurations as key-value pair
48 | const reportConfig = {
49 | accessToken: null,
50 | embedUrl: null,
51 | reportId: null,
52 | }
53 |
54 | // Visual overlapping
55 | const MAIN_VISUAL_GUID = "a6d74a71de4135e00a59";
56 |
57 | const imageVisual = {
58 | name: "2270e4eea9242400a0cd",
59 | yPos: undefined,
60 | height: undefined,
61 | ratio: {
62 | widthRatioWithMainVisual: 36 / 426,
63 | heightRatioWithMainVisual: 36 / 252,
64 | xPositionRatioWithMainVisual: 195 / 426,
65 | yPositionRatioWithMainVisual: 90 / 252
66 | }
67 | }
68 |
69 | const actionButtonVisual = {
70 | name: "946862f32d49b6573406",
71 | height: 32,
72 | width: 151,
73 | }
74 |
75 | // Cache DOM Elements
76 | const overlay = $("#overlay");
77 | const visualDisplayArea = $("#visual-authoring-container").get(0);
78 | const editArea = $("#edit-area");
79 | const visualAuthoringArea = $("#visual-authoring-container");
80 | const visualTypeDropdown = $("#selected-value-0");
81 | const createVisualButton = $("#create-visual-btn");
82 | const generatorType = $("#generator-type");
83 | const generatorFields = $("#generator-fields");
84 | const generatorProperties = $("#generator-properties");
85 | const disabledEraseTool = $("#erase-tool-disabled");
86 | const enabledEraseTool = $("#erase-tool-enabled");
87 | const disabledAligns = $("#aligns-disabled");
88 | const enabledAligns = $("#aligns-enabled");
89 | const visualCreatorModal = $("#visual-creator");
90 | const visualTitleText = $("#visual-title");
91 | const legendToggle = $("#legend-toggle");
92 | const xAxisToggle = $("#xAxis-toggle");
93 | const yAxisToggle = $("#yAxis-toggle");
94 | const titleToggle = $("#title-toggle");
95 | const alignRight = $("#align-right");
96 | const closeModalButton = $("#close-modal");
97 | const alignLeft = $("#align-left");
98 | const reportContainer = $(".report-container").get(0);
99 | const customTitleWrapper = $(".custom-title-wrapper");
100 | const alignmentBlocks = $(".alignment-block");
101 | const visualPropertiesCheckboxes = $(".property-checkbox");
102 | const toggleWrappers = $(".toggle-wrapper");
103 | const togglePropertiesSliders = $(".slider");
104 |
105 | // Cache showcasePropertiesLength
106 | const showcasePropertiesLength = showcaseProperties.length;
107 |
108 | // Get models. models contain enums that can be used
109 | const models = window["powerbi-client"].models;
110 |
111 | // CSS Classes
112 | const DISABLED = "generator-disabled";
113 | const HIDE = "select-hide";
114 | const TYPES_DISABLED = "generator-type-disabled";
115 | const FIELDS_DISABLED = "generator-fields-disabled";
116 | const PROPERTIES_DISABLED = "generator-properties-disabled";
117 | const SELECTED = "selected";
118 | const SAME_AS_SELECTED = "same-as-selected";
119 | const TOGGLE_WRAPPERS_DISABLED = "disabled";
120 | const DISABLED_SLIDERS = "disabled-sliders";
121 | const TYPE_DROPDOWN_ID = "selected-value-0";
122 |
123 | // Key codes
124 | const KEYCODE_TAB = 9;
125 | const KEYCODE_ENTER = 13;
126 | const KEYCODE_ESCAPE = 27;
127 | const KEYCODE_SPACE = 32;
128 |
129 | // enum for keys
130 | const Keys = {
131 | TAB: "Tab",
132 | SPACE: "Space",
133 | ENTER: "Enter",
134 | ESCAPE: "Escape"
135 | }
136 |
137 | // Freezing the contents for enum object
138 | Object.freeze(Keys);
139 |
140 | // Store the position of the main visual [basicShape]
141 | let mainVisualState;
142 |
143 | // Get the reference for the iframe inside the modal to remove it from the tab-order
144 | let authoringiFrame;
145 |
146 | // Custom title for the visual
147 | let customVisualTitle = "";
148 |
149 | // To store the state of the visual creation
150 | let visualCreationInProgress = false;
151 |
152 | // To apply setting to the new visual created in the Modal
153 | const visualHeaderReportSetting = {
154 | visualSettings: {
155 | visualHeaders: [
156 | {
157 | settings: {
158 | visible: false
159 | }
160 | }
161 | ]
162 | }
163 | }
164 |
165 | // Headers
166 | const VISUAL_TYPE_HEADER = "Select visual type";
--------------------------------------------------------------------------------
/Quickly create and personalize visuals/js/session_utils.js:
--------------------------------------------------------------------------------
1 | // ----------------------------------------------------------------------------
2 | // Copyright (c) Microsoft Corporation.
3 | // Licensed under the MIT license.
4 | // ----------------------------------------------------------------------------
5 |
6 | // API Endpoint to get the JSON response of Embed URL, Embed Token and reportId
7 | const quickVisualCreatorReportEndpoint = "https://aka.ms/QuickVisualCreatorReportEmbedConfig";
8 |
9 | // Set minutes before the access token should get refreshed
10 | const minutesToRefreshBeforeExpiration = 2;
11 |
12 | // Store the amount of time left for refresh token
13 | let tokenExpiration;
14 |
15 | // This function will make the AJAX request to the endpoint and get the JSON response which it will set in the sessions
16 | function populateEmbedConfigIntoCurrentSession(updateCurrentToken) {
17 |
18 | try {
19 | let configFromParentWindow = window.parent.showcases.quickVisualCreator;
20 | if (configFromParentWindow) {
21 | let diffMs = new Date(configFromParentWindow.expiration) - new Date();
22 | let diffMins = Math.round(((diffMs % 86400000) % 3600000) / 60000);
23 |
24 | embedConfig = {
25 | EmbedUrl: configFromParentWindow.embedUrl,
26 | EmbedToken: {
27 | Token: configFromParentWindow.token
28 | },
29 | Id: configFromParentWindow.id,
30 | MinutesToExpiration: diffMins,
31 | };
32 |
33 | handleNewEmbedConfig(embedConfig, updateCurrentToken);
34 | }
35 |
36 | return;
37 | } catch (error) {
38 | console.error(error);
39 | }
40 |
41 | // This returns the JSON response
42 | return $.getJSON(quickVisualCreatorReportEndpoint, function (embedConfig) {
43 | handleNewEmbedConfig(embedConfig, updateCurrentToken);
44 | });
45 | }
46 |
47 | function handleNewEmbedConfig(embedConfig, updateCurrentToken) {
48 |
49 | // Set Embed Token, Embed URL and Report Id
50 | setConfig(embedConfig.EmbedToken.Token, embedConfig.EmbedUrl, embedConfig.Id);
51 | if (updateCurrentToken) {
52 |
53 | // Get the reference to the embedded element
54 | const reportContainer = $(".report-container").get(0);
55 | if (reportContainer) {
56 | const report = powerbi.get(reportContainer);
57 | report.setAccessToken(embedConfig.EmbedToken.Token);
58 | }
59 |
60 | // Get the reference to the embedded element
61 | const visualDisplayArea = $("#visual-authoring-container").get(0);
62 | if (visualDisplayArea) {
63 | const visualAuthoringReport = powerbi.get(visualDisplayArea);
64 | visualAuthoringReport.setAccessToken(embedConfig.EmbedToken.Token);
65 | }
66 | }
67 |
68 | // Get the milliseconds after token will get refreshed
69 | tokenExpiration = embedConfig.MinutesToExpiration * 60 * 1000;
70 |
71 | // Set the tokenRefresh timer to count the seconds and request the JSON again when token expires
72 | setTokenExpirationListener();
73 | }
74 |
75 | // Check the remaining time and call the API again
76 | function setTokenExpirationListener() {
77 |
78 | const safetyInterval = minutesToRefreshBeforeExpiration * 60 * 1000;
79 |
80 | // Time until token refresh in milliseconds
81 | const timeout = tokenExpiration - safetyInterval;
82 | if (timeout <= 0) {
83 | populateEmbedConfigIntoCurrentSession(true /* updateCurrentToken */);
84 | }
85 | else {
86 | console.log(`Report Embed Token will be updated in ${timeout} milliseconds.`);
87 | setTimeout(function () {
88 | populateEmbedConfigIntoCurrentSession(true /* updateCurrentToken */)
89 | }, timeout);
90 | }
91 | }
92 |
93 | // Add a listener to make sure token is updated after tab was inactive
94 | document.addEventListener("visibilitychange", function () {
95 | // Check the access token when the tab is visible
96 | if (!document.hidden) {
97 | setTokenExpirationListener();
98 | }
99 | });
100 |
101 | // Get the data from the API and pass it to the front-end
102 | function loadQuickVisualCreatorReportConfigIntoSession() {
103 |
104 | // Call the function for the first time to call the API, set the sessions values and return the response to the front-end
105 | return populateEmbedConfigIntoCurrentSession(false /* updateCurrentToken */);
106 | }
107 |
108 | // Set the embed config in global object
109 | function setConfig(accessToken, embedUrl, reportId) {
110 |
111 | // Fill the global object
112 | reportConfig.accessToken = accessToken;
113 | reportConfig.embedUrl = embedUrl;
114 | reportConfig.reportId = reportId;
115 | }
--------------------------------------------------------------------------------
/Quickly create and personalize visuals/js/themes.js:
--------------------------------------------------------------------------------
1 | // ----------------------------------------------------------------------------
2 | // Copyright (c) Microsoft Corporation.
3 | // Licensed under the MIT license.
4 | // ----------------------------------------------------------------------------
5 |
6 | const theme = {
7 | "name": "visualsTheme",
8 | "dataColors": [
9 | "#118dff",
10 | "#12239e",
11 | "#e66c37",
12 | "#6B007B",
13 | "#E044A7",
14 | "#744EC2",
15 | "#D9B300",
16 | "#D64550",
17 | "#000000"
18 | ],
19 | "visualStyles": {
20 | "*": {
21 | "*": {
22 | "dropShadow": [
23 | {
24 | "color": {
25 | "solid": {
26 | "color": "#000000"
27 | }
28 | },
29 | "show": true,
30 | "position": "Outer",
31 | "preset": "Custom",
32 | "shadowSpread": 1,
33 | "shadowBlur": 3,
34 | "angle": 45,
35 | "shadowDistance": 1,
36 | "transparency": 87
37 | }
38 | ]
39 | },
40 | },
41 | "image": {
42 | "*": {
43 | "dropShadow": [
44 | {
45 | "show": false,
46 | }
47 | ]
48 | },
49 | },
50 | "actionButton": {
51 | "*": {
52 | "dropShadow": [
53 | {
54 | "show": false,
55 | }
56 | ]
57 | },
58 | }
59 | }
60 | }
--------------------------------------------------------------------------------
/Quickly create and personalize visuals/js/visuals.js:
--------------------------------------------------------------------------------
1 | // ----------------------------------------------------------------------------
2 | // Copyright (c) Microsoft Corporation.
3 | // Licensed under the MIT license.
4 | // ----------------------------------------------------------------------------
5 |
6 | // Define the available data roles for the visual types
7 | const visualTypeToDataRoles = [
8 | { name: "columnChart", displayName: "Column chart", dataRoles: ["Axis", "Values", "Tooltips"], dataRoleNames: ["Category", "Y", "Tooltips"] },
9 | { name: "areaChart", displayName: "Area chart", dataRoles: ["Axis", "Legend", "Values"], dataRoleNames: ["Category", "Series", "Y"] },
10 | { name: "barChart", displayName: "Bar chart", dataRoles: ["Axis", "Values", "Tooltips"], dataRoleNames: ["Category", "Y", "Tooltips"] },
11 | { name: "pieChart", displayName: "Pie chart", dataRoles: ["Legend", "Values", "Tooltips"], dataRoleNames: ["Category", "Y", "Tooltips"] },
12 | { name: "lineChart", displayName: "Line chart", dataRoles: ["Axis", "Legend", "Values"], dataRoleNames: ["Category", "Series", "Y"] },
13 | ];
14 |
15 | // Define the available fields for each data role
16 | const dataRolesToFields = [
17 | { dataRole: "Axis", Fields: ["Industry", "Opportunity Status", "Lead Rating", "Salesperson"] },
18 | { dataRole: "Values", Fields: ["Actual Revenue", "Estimated Revenue", "Number of Opportunities", "Salesperson"] },
19 | { dataRole: "Legend", Fields: ["Industry", "Lead Rating", "Opportunity Status", "Salesperson"] },
20 | { dataRole: "Tooltips", Fields: ["Industry", "Actual Close Date", "Actual Revenue", "Estimated Revenue"] },
21 | ];
22 |
23 | // Define schemas for visuals API
24 | const schemas = {
25 | column: "http://powerbi.com/product/schema#column",
26 | measure: "http://powerbi.com/product/schema#measure",
27 | property: "http://powerbi.com/product/schema#property",
28 | default: "http://powerbi.com/product/schema#default",
29 | };
30 |
31 | // Define mapping from fields to target table and column/measure
32 | const dataFieldsTargets = {
33 | ActualRevenue: { column: "Actual Revenue", table: "QVC Report", schema: schemas.column },
34 | NumberofOpportunities: { measure: "Number of Opportunities", table: "QVC Report", schema: schemas.measure },
35 | Salesperson: { column: "Salesperson", table: "QVC Report", schema: schemas.column },
36 | EstimatedRevenue: { column: "Estimated Revenue", table: "QVC Report", schema: schemas.column },
37 | OpportunityStatus: { column: "Opportunity Status", table: "QVC Report", schema: schemas.column },
38 | Industry: { column: "Industry", table: "QVC Report", schema: schemas.column },
39 | LeadRating: { column: "Lead Rating", table: "QVC Report", schema: schemas.column },
40 | Salesperson: { column: "Salesperson", table: "QVC Report", schema: schemas.column },
41 | ActualCloseDate: { column: "Actual Close Date", table: "QVC Report", schema: schemas.column },
42 | };
43 |
44 | const dataFieldsMappings = {
45 | ActualRevenue: "Actual Revenue",
46 | NumberofOpportunities: "Number of Opportunities",
47 | Salesperson: "Salesperson",
48 | EstimatedRevenue: "Estimated Revenue",
49 | OpportunityStatus: "Opportunity Status",
50 | Industry: "Industry",
51 | LeadRating: "Lead Rating",
52 | Salesperson: "Salesperson",
53 | ActualCloseDate: "Actual Close Date"
54 | }
55 |
56 | // Define the available properties
57 | const showcaseProperties = ["legend", "xAxis", "yAxis"];
58 |
59 | // Define title related properties
60 | const titleProperties = ["title", "titleText", "titleAlign"];
61 |
62 | const visualTypeProperties = {
63 | columnChart: ["xAxis", "yAxis"],
64 | areaChart: ["legend", "xAxis", "yAxis"],
65 | barChart: ["xAxis", "yAxis"],
66 | pieChart: ["legend"],
67 | lineChart: ["legend", "xAxis", "yAxis"]
68 | };
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Introduction
2 | Showcases for Power BI Embedded analytics playground
3 |
4 | # Contents
5 | Playground Showcases available in this package currently:
6 | 1. Personalize top insights
7 | 2. Capture report views
8 | 3. Customize report colors and mode
9 | 4. Go from insights to quick action
10 | 5. Quickly create and personalize visuals
11 |
12 | ## Contributing
13 |
14 | This project welcomes contributions and suggestions. Most contributions require you to agree to a
15 | Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us
16 | the rights to use your contribution. For details, visit https://cla.opensource.microsoft.com.
17 |
18 | When you submit a pull request, a CLA bot will automatically determine whether you need to provide
19 | a CLA and decorate the PR appropriately (e.g., status check, comment). Simply follow the instructions
20 | provided by the bot. You will only need to do this once across all repos using our CLA.
21 |
22 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/).
23 | For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or
24 | contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments.
25 |
26 | ## Trademarks
27 |
28 | This project may contain trademarks or logos for projects, products, or services. Authorized use of Microsoft
29 | trademarks or logos is subject to and must follow
30 | [Microsoft's Trademark & Brand Guidelines](https://www.microsoft.com/en-us/legal/intellectualproperty/trademarks/usage/general).
31 | Use of Microsoft trademarks or logos in modified versions of this project must not cause confusion or imply Microsoft sponsorship.
32 | Any use of third-party trademarks or logos are subject to those third-party's policies.
33 |
34 | ## Support
35 |
36 | - **Feature Requests:** Submit your ideas and suggestions to the [Fabric Ideas Portal](https://ideas.fabric.microsoft.com/), where you can also vote on ideas from other developers.
37 | - **Bug Reports and Technical Assistance:** Visit the [Fabric Developer Community Forum](https://community.fabric.microsoft.com/t5/Developer/bd-p/Developer). Our team and community experts are ready to assist you.
38 | - **Additional Support:** Contact your account manager or reach out to the [Fabric Support Team](https://support.fabric.microsoft.com/en-us/support/).
39 |
--------------------------------------------------------------------------------
/SECURITY.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | ## Security
4 |
5 | Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/Microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet), [Xamarin](https://github.com/xamarin), and [our GitHub organizations](https://opensource.microsoft.com/).
6 |
7 | If you believe you have found a security vulnerability in any Microsoft-owned repository that meets [Microsoft's definition of a security vulnerability](https://docs.microsoft.com/en-us/previous-versions/tn-archive/cc751383(v=technet.10)), please report it to us as described below.
8 |
9 | ## Reporting Security Issues
10 |
11 | **Please do not report security vulnerabilities through public GitHub issues.**
12 |
13 | Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://msrc.microsoft.com/create-report).
14 |
15 | If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the [Microsoft Security Response Center PGP Key page](https://www.microsoft.com/en-us/msrc/pgp-key-msrc).
16 |
17 | You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://www.microsoft.com/msrc).
18 |
19 | Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue:
20 |
21 | * Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.)
22 | * Full paths of source file(s) related to the manifestation of the issue
23 | * The location of the affected source code (tag/branch/commit or direct URL)
24 | * Any special configuration required to reproduce the issue
25 | * Step-by-step instructions to reproduce the issue
26 | * Proof-of-concept or exploit code (if possible)
27 | * Impact of the issue, including how an attacker might exploit the issue
28 |
29 | This information will help us triage your report more quickly.
30 |
31 | If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://microsoft.com/msrc/bounty) page for more details about our active programs.
32 |
33 | ## Preferred Languages
34 |
35 | We prefer all communications to be in English.
36 |
37 | ## Policy
38 |
39 | Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://www.microsoft.com/en-us/msrc/cvd).
40 |
41 |
--------------------------------------------------------------------------------
/SUPPORT.md:
--------------------------------------------------------------------------------
1 | # TODO: The maintainer of this repo has not yet edited this file
2 |
3 | **REPO OWNER**: Do you want Customer Service & Support (CSS) support for this product/project?
4 |
5 | - **No CSS support:** Fill out this template with information about how to file issues and get help.
6 | - **Yes CSS support:** Fill out an intake form at [aka.ms/spot](https://aka.ms/spot). CSS will work with/help you to determine next steps. More details also available at [aka.ms/onboardsupport](https://aka.ms/onboardsupport).
7 | - **Not sure?** Fill out a SPOT intake as though the answer were "Yes". CSS will help you decide.
8 |
9 | *Then remove this first heading from this SUPPORT.MD file before publishing your repo.*
10 |
11 | # Support
12 |
13 | ## How to file issues and get help
14 |
15 | This project uses GitHub Issues to track bugs and feature requests. Please search the existing
16 | issues before filing new issues to avoid duplicates. For new issues, file your bug or
17 | feature request as a new Issue.
18 |
19 | For help and questions about using this project, please **REPO MAINTAINER: INSERT INSTRUCTIONS HERE
20 | FOR HOW TO ENGAGE REPO OWNERS OR COMMUNITY FOR HELP. COULD BE A STACK OVERFLOW TAG OR OTHER
21 | CHANNEL. WHERE WILL YOU HELP PEOPLE?**.
22 |
23 | ## Microsoft Support Policy
24 |
25 | Support for this **PROJECT or PRODUCT** is limited to the resources listed above.
26 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@powerbi/playground-showcases",
3 | "version": "2.5.2",
4 | "description": "Showcases for the Power BI Embedded playground",
5 | "repository": {
6 | "type": "git",
7 | "url": "https://powerbi.visualstudio.com/DefaultCollection/Embedded/_git/playground-showcases"
8 | },
9 | "author": "Microsoft",
10 | "files": [
11 | "Personalize top insights",
12 | "Capture report views",
13 | "Customize report colors and mode",
14 | "Go from insights to quick action",
15 | "Quickly create and personalize visuals"
16 | ]
17 | }
18 |
--------------------------------------------------------------------------------