Enable customization of user permissions to interact with annotations in the PDF file, completely client-side with three different levels of permissions:
This demo lets you:
Implementation steps
To add annotations permissions capability to a PDF with WebViewer:
Step 1: Choose your preferred web stack
Step 2: Add the ES6 JavaScript sample code provided in this guide
Once you generate your license key, it will automatically be included in your sample code below.
Apryse collects some data regarding your usage of the SDK for product improvement.
The data that Apryse collects include:
For clarity, no other data is collected by the SDK and Apryse has no access to the contents of your documents.
If you wish to continue without data collection, contact us and we will email you a no-tracking trial key for you to get started.
1
2/* ES6 Compliant Syntax */
3/* GitHub Copilot v1.0, Claude 3.5 Sonnet, September 1, 2025 */
4/* File: index.js */
5
6import WebViewer from '@pdftron/webviewer';
7
8// Annotation Permission section
9//
10// Code to customize user permissions, completely client-side with 3
11// different levels of permissions: administrator, user, and read-only
12//
13
14// Default Document
15const defaultDoc = 'https://apryse.s3.us-west-1.amazonaws.com/public/files/samples/WebviewerDemoDoc.pdf';
16
17// Set default user
18let currentUser = 'Justin';
19
20// List of users with permissions
21let userList = {
22 Justin: { permissions: 'administrator', canView: true, hidden: [] },
23 Sally: { permissions: 'user', canView: true, hidden: [] },
24 Brian: { permissions: 'read-only', canView: true, hidden: [] }
25};
26
27// Annotation types that can be toggled for visibility
28let toggleableTypes = [];
29
30// Customize UI
31const customizeUI = async (instance) => {
32 const { Annotations } = instance.Core;
33
34 // Load default document
35 await instance.Core.documentViewer.loadDocument(defaultDoc, {
36 extension: 'pdf',
37 });
38 instance.UI.setToolbarGroup('toolbarGroup-Annotate', true);
39
40 // Add sticky note, free hand, and highlight to toggleable types
41 toggleableTypes = [
42 {
43 displayName: 'Sticky notes',
44 annotationType: Annotations.StickyAnnotation,
45 },
46 {
47 displayName: 'Free hand',
48 annotationType: Annotations.FreeHandAnnotation,
49 },
50 {
51 displayName: 'Highlight',
52 annotationType: Annotations.TextHighlightAnnotation,
53 },
54 ];
55
56 // Set user data for mentions in notes tool
57 const userData = Object.keys(userList).map((user) => ({
58 value: user,
59 email: `${user.toLowerCase()}@pdftron.com`,
60 }));
61 instance.UI.mentions.setUserData(userData);
62
63 // Set default user in the WebViewer
64 setUser(instance, currentUser);
65};
66
67// Set selected user in the WebViewer
68const setUser = (instance, username) => {
69 const { annotationManager }= instance.Core;
70 annotationManager.setCurrentUser(username);
71 const permissions = userList[username].permissions;
72
73 if (permissions === 'administrator') {
74 annotationManager.promoteUserToAdmin();
75 annotationManager.disableReadOnlyMode();
76 } else if (permissions === 'read-only') {
77 annotationManager.enableReadOnlyMode();
78 annotationManager.demoteUserFromAdmin();
79 } else {
80 annotationManager.disableReadOnlyMode();
81 annotationManager.demoteUserFromAdmin();
82 }
83
84 currentUser = username;
85 setAnnotationsForUser(instance);
86 updateUIControls();
87};
88
89// Set annotations for current user
90const setAnnotationsForUser = (instance) => {
91 const { annotationManager } = instance.Core;
92
93 const { hidden } = userList[currentUser];
94
95 // First get a list of all the types that should be hidden
96 const hiddenTypeMap = toggleableTypes.reduce((acc, type) => {
97 if (hidden.indexOf(type.displayName) > -1) {
98 acc.push(type.annotationType);
99 }
100 return acc;
101 }, []);
102
103 const allAnnots = annotationManager.getAnnotationsList();
104 const toShow = [];
105 const toHide = [];
106
107 // Generate lists of annotations to show and hide
108 allAnnots.forEach((annot) => {
109 const isType = hiddenTypeMap.some((type) => annot instanceof type);
110 if (isType) {
111 toHide.push(annot);
112 } else {
113 toShow.push(annot);
114 }
115 });
116
117 // Show and hide annotations
118 annotationManager.showAnnotations(toShow);
119 annotationManager.hideAnnotations(toHide);
120};
121
122// Add user to user list
123const addUser = (instance, name, p) => {
124 userList = {
125 ...userList,
126 [name]: { permissions: p, canView: true, hidden: [] },
127 };
128};
129
130// Toggle annotation type visibility for current user
131const toggleAnnotations = (instance, type) => {
132 const { displayName } = type;
133 const { hidden } = userList[currentUser];
134 const idx = hidden.indexOf(displayName);
135 const newArray = hidden.slice(0);
136
137 if (idx !== -1) {
138 newArray.splice(idx, 1);
139 } else {
140 newArray.push(displayName);
141 }
142
143 userList = {
144 ...userList,
145 [currentUser]: {
146 ...userList[currentUser],
147 hidden: newArray
148 }
149 };
150
151 setAnnotationsForUser(instance);
152 updateUIControls();
153};
154
155// Check if annotation type is visible for current user
156const isChecked = (type) => {
157 if (!currentUser) return;
158 const { displayName } = type;
159 const user = currentUser;
160 return userList[user].hidden.indexOf(displayName) === -1;
161};
162
163
164// Helper functions for configuration snippet modal
165const perm = () => {
166 return currentUser ? userList[currentUser].permissions : null;
167}
168const hiddenList = () => {
169 return currentUser ? userList[currentUser].hidden : [];
170};
171
172let text = '';
173let annotText = '';
174
175const setText = () => {
176 const permission = perm();
177 if (permission === 'administrator') {
178 text = 'annotationManager.promoteUserToAdmin()';
179 } else if (permission === 'read-only') {
180 text = 'annotationManager.enableReadOnlyMode()';
181 } else {
182 text = 'annotationManager.demoteUserFromAdmin();\n annotationManager.disableReadOnlyMode();';
183 }
184
185 return text;
186};
187
188const setAnnotText = () => {
189 const list = hiddenList();
190 if (list.length === 0) {
191 annotText = `
192 annotationManager.showAnnotations(allAnnots);
193 `;
194 } else {
195 let ifStatement = list.reduce((acc, hidden) => {
196 if (hidden === 'Sticky notes') {
197 acc += ' annot instanceof Annotations.StickyAnnotation || \n';
198 }
199 if (hidden === 'Free hand') {
200 acc += ' annot instanceof Annotations.FreeHandAnnotation || \n';
201 }
202 if (hidden === 'Highlight') {
203 acc += ' annot instanceof Annotations.TextHighlightAnnotation || \n';
204 }
205
206 return acc;
207 }, '');
208
209 ifStatement = ifStatement.substring(8, ifStatement.length - 5);
210
211 annotText = `
212 const hideList = allAnnots.filter(annot => {
213 return ${ifStatement};
214 });
215 annotationManager.hideAnnotations(hideList);
216 `;
217 }
218
219 return annotText;
220};
221
222// WebViewer section
223//
224// This code initializes the WebViewer with the basic settings
225// that are found in the default showcase WebViewer
226//
227
228const searchParams = new URLSearchParams(window.location.search);
229const history = window.history || window.parent.history || window.top.history;
230const licenseKey = 'YOUR_WEBVIEWER_LICENSE_KEY';
231const element = document.getElementById('viewer');
232
233// Initialize WebViewer with the specified settings
234WebViewer({
235 path: '/lib',
236 licenseKey: licenseKey,
237 enableFilePicker: true,
238}, element).then((instance) => {
239 // Enable the measurement toolbar so it appears with all the other tools, and disable Cloudy rectangular tool
240 const cloudyTools = [
241 instance.Core.Tools.ToolNames.CLOUDY_RECTANGULAR_AREA_MEASUREMENT,
242 instance.Core.Tools.ToolNames.CLOUDY_RECTANGULAR_AREA_MEASUREMENT2,
243 instance.Core.Tools.ToolNames.CLOUDY_RECTANGULAR_AREA_MEASUREMENT3,
244 instance.Core.Tools.ToolNames.CLOUDY_RECTANGULAR_AREA_MEASUREMENT4,
245 ];
246 instance.UI.enableFeatures([instance.UI.Feature.Measurement, instance.UI.Feature.Initials]);
247 instance.UI.disableTools(cloudyTools);
248
249 // Set default toolbar group to Annotate
250 instance.UI.setToolbarGroup('toolbarGroup-Annotate');
251
252 // Set default tool on mobile devices to Pan.
253 // https://apryse.atlassian.net/browse/WVR-3134
254 if (isMobileDevice()) {
255 instance.UI.setToolMode(instance.Core.Tools.ToolNames.PAN);
256 }
257
258 instance.Core.documentViewer.addEventListener('documentUnloaded', () => {
259 if (searchParams.has('file')) {
260 searchParams.delete('file');
261 history.replaceState(null, '', '?' + searchParams.toString());
262 }
263 });
264
265 instance.Core.annotationManager.enableAnnotationNumbering();
266
267 instance.UI.NotesPanel.enableAttachmentPreview();
268
269 // Add the demo-specific functionality
270 customizeUI(instance).then(() => {
271 // Create UI controls after demo is initialized
272 createUIControls(instance);
273 });
274});
275
276// Function to check if the user is on a mobile device
277const isMobileDevice = () => {
278 return (
279 navigator.userAgentData?.mobile ??
280 /android|iphone|ipad|ipod|blackberry|iemobile|opera mini|mobile/i.test(
281 window.navigator.userAgent
282 )
283 );
284}
285
286// Cleanup function for when the demo is closed or page is unloaded
287const cleanup = (instance) => {
288 const { documentViewer } = instance.Core;
289
290 if (typeof instance !== 'undefined' && instance.UI) {
291
292 // Reset annotation user to default 'Guest' and promote to admin
293 const annotationManager = documentViewer.getAnnotationManager();
294 annotationManager.setCurrentUser('Guest');
295 annotationManager.promoteUserToAdmin();
296 annotationManager.disableReadOnlyMode();
297
298 console.log('Cleaning up compare-files demo');
299 }
300};
301
302// Register cleanup for page unload
303window.addEventListener('beforeunload', () => cleanup());
304window.addEventListener('unload', () => cleanup());
305
306
307// UI section
308//
309// Helper code to add controls to the viewer holding the buttons
310// This code creates a container for the buttons, styles them, and adds them to the viewer
311//
312
313// User selection
314const userPageSection = (instance) => {
315 // Create a wrapper div for the user section
316 const wrapper = document.createElement('div');
317 wrapper.className = 'user-page-section';
318
319 // Select User label
320 const selectUserLabel = document.createElement('h2');
321 selectUserLabel.className = 'header select-user-header';
322 selectUserLabel.textContent = 'Select User';
323
324 wrapper.appendChild(selectUserLabel);
325
326 // Users buttons container
327 const buttonsContainer = document.createElement('div');
328 buttonsContainer.className = 'buttons-container';
329
330 Object.keys(userList).forEach((username) => {
331 const button = document.createElement('button');
332 button.className = 'btn btn-user';
333 button.textContent = `${username} (${userList[username].permissions})`;
334 button.onclick = () => {
335 setUser(instance, username);
336 };
337
338 buttonsContainer.appendChild(button);
339 });
340 wrapper.appendChild(buttonsContainer);
341
342 // Add User clickable label
343 const addUserLabel = document.createElement('label');
344 addUserLabel.className = 'add-user-label';
345 addUserLabel.textContent = 'Add user';
346 addUserLabel.onclick = () => {
347 const addUserContainer = document.querySelector('.add-user-container');
348 if (addUserContainer.classList.contains('visible')) {
349 addUserContainer.classList.remove('visible');
350 addUserLabel.textContent = 'Add user';
351 } else {
352 addUserContainer.classList.add('visible');
353 addUserLabel.textContent = 'Close';
354 }
355 };
356
357 wrapper.appendChild(addUserLabel);
358
359 // Add User container
360 const addUserContainer = document.createElement('div');
361 addUserContainer.className = 'add-user-container';
362
363 // Add User input field
364 const input = document.createElement('input');
365 input.type = 'text';
366 input.placeholder = 'Username';
367 input.className = 'input add-user-input';
368
369 addUserContainer.appendChild(input);
370
371 // Add User permission dropdown
372 const permission = document.createElement('select');
373 permission.className = 'input add-user-permission';
374 permission.options.add(new Option('Administrator', 'administrator'));
375 permission.options.add(new Option('User', 'user'));
376 permission.options.add(new Option('Read-Only', 'read-only'));
377
378 addUserContainer.appendChild(permission);
379
380 // Add User add button
381 const addButton = document.createElement('button');
382 addButton.className = 'btn btn-submit-user';
383 addButton.textContent = 'Add';
384 addButton.onclick = () => {
385 // Validate input
386 const name = input.value;
387 if (name === '') return;
388 const p = permission.value;
389
390 // Add user to user list
391 addUser(instance, name, p);
392
393 // Add button for new user
394 const buttonsContainer = document.querySelector('.buttons-container');
395 const button = document.createElement('button');
396 button.className = 'btn btn-user';
397 button.textContent = `${name} (${p})`;
398 button.onclick = () => {
399 setUser(instance, name);
400 };
401 buttonsContainer.appendChild(button);
402
403 // Reset input fields
404 input.value = '';
405 permission.options.selectedIndex = 0;
406
407 // Close add user section
408 addUserLabel.click();
409 };
410
411 addUserContainer.appendChild(addButton);
412
413 wrapper.appendChild(addUserContainer);
414
415 return wrapper;
416};
417
418// Role permissions description
419const rolePermissionsPageSection = () => {
420 const wrapper = document.createElement('div');
421 wrapper.className = 'role-permissions-section';
422
423 const rolePermissionsLabel = document.createElement('h2');
424 rolePermissionsLabel.className = 'header role-permissions-header';
425 rolePermissionsLabel.textContent = 'Role Permissions';
426
427 wrapper.appendChild(rolePermissionsLabel);
428
429 const rolePermissionDescription = document.createElement('p');
430 rolePermissionDescription.className = 'text role-permission-paragraph';
431 const permission = perm();
432 if (permission === 'administrator') {
433 rolePermissionDescription.innerHTML = '<b> Admin: </b> Can add, edit, or remove any annotations created by anyone';
434 } else if (permission === 'read-only') {
435 rolePermissionDescription.innerHTML = '<b> Read-Only: </b> Can only view annotations';
436 } else { // user
437 rolePermissionDescription.innerHTML = '<b> User: </b> Can create, and edit or remove annotations created by themself';
438 }
439
440 wrapper.appendChild(rolePermissionDescription);
441
442 return wrapper;
443};
444
445// Viewing permissions checkboxes for selected user
446const viewingPermissionsPageSection = (instance) => {
447 const wrapper = document.createElement('div');
448 wrapper.className = 'viewing-permissions-section';
449
450 // Viewing Permissions label
451 const viewingPermissionsLabel = document.createElement('label');
452 viewingPermissionsLabel.className = 'header viewing-permissions-label';
453 viewingPermissionsLabel.textContent = `Set viewing permissions for ` + (currentUser ? currentUser : '...');
454
455 wrapper.appendChild(viewingPermissionsLabel);
456
457 // Viewing Permissions for each annotation type
458 const checkboxContainer = document.createElement('div');
459 checkboxContainer.className = 'checkbox-container';
460
461 toggleableTypes.forEach((type) => {
462 const checkboxRow = document.createElement('div');
463
464 const checkbox = document.createElement('input');
465 checkbox.type = 'checkbox';
466 checkbox.id = `view-${type.displayName}-checkbox`;
467 checkbox.checked = isChecked(type);
468 checkbox.onchange = () => {
469 toggleAnnotations(instance, type);
470 };
471
472 checkboxRow.appendChild(checkbox);
473
474 const label = document.createElement('label');
475 label.className = 'text checkbox-label';
476 label.ariaLabel = `Toggle ${type.displayName} annotations`;
477 label.textContent = `${type.displayName}`;
478
479 checkboxRow.appendChild(label);
480 checkboxContainer.appendChild(checkboxRow);
481 });
482 wrapper.appendChild(checkboxContainer);
483
484 return wrapper;
485};
486
487
488// Helper function to create UI controls
489const createUIControls = (instance) => {
490 // Create a container for all controls (label, dropdown, and buttons)
491 const controlsContainer = document.createElement('div');
492 controlsContainer.className = 'controls-container';
493
494 // Add user section
495 controlsContainer.appendChild(userPageSection(instance));
496
497 // Add role permissions and viewing permissions sections side by side
498 const roleViewingPermissionsContainer = document.createElement('div');
499 roleViewingPermissionsContainer.className = 'role-viewing-permissions-container';
500 roleViewingPermissionsContainer.appendChild(rolePermissionsPageSection());
501 roleViewingPermissionsContainer.appendChild(viewingPermissionsPageSection(instance));
502 controlsContainer.appendChild(roleViewingPermissionsContainer);
503
504
505
506 element.insertBefore(controlsContainer, element.firstChild);
507};
508
509// Helper function to update UI controls
510const updateUIControls = () => {
511 // Update role permission description
512 const rolePermissionDescription = document.querySelector('.role-permission-paragraph');
513 if (rolePermissionDescription) {
514 const permission = perm();
515 if (permission === 'administrator') {
516 rolePermissionDescription.innerHTML = '<b> Admin: </b> Can add, edit, or remove any annotations created by anyone';
517 } else if (permission === 'read-only') {
518 rolePermissionDescription.innerHTML = '<b> Read-Only: </b> Can only view annotations';
519 } else { // user
520 rolePermissionDescription.innerHTML = '<b> User: </b> Can create, and edit or remove annotations created by themself';
521 }
522 }
523
524 // Update viewing permissions label
525 const viewingPermissionsLabel = document.querySelector('.viewing-permissions-label');
526 if (viewingPermissionsLabel) {
527 viewingPermissionsLabel.textContent = `Set viewing permissions for ` + (currentUser ? currentUser : '...');
528 }
529
530 // Update checkboxes
531 toggleableTypes.forEach((type) => {
532 const checkbox = document.getElementById(`view-${type.displayName}-checkbox`);
533 if (checkbox) {
534 checkbox.checked = isChecked(type);
535 }
536 });
537
538 // Update configuration snippet text
539 const codeBlock = document.getElementById('config-snippet-code-block');
540 if (codeBlock) {
541 codeBlock.textContent = `const wvElement = document.getElementById('viewer');
542WebViewer({ ...options }, wvElement)
543.then(instance => {
544 const { annotationManager } = instance.Core;
545 annotationManager.setCurrentUser('${currentUser}');
546 ${setText()}
547 const allAnnots = annotationManager.getAnnotationsList();
548 ${setAnnotText()}
549})`;
550 }
551};
1/* CSS Standard Compliant Syntax */
2/* GitHub Copilot v1.0, Claude 3.5 Sonnet, September 1, 2025 */
3/* File: index.css */
4
5/* Base Classes */
6.btn {
7 display: inline-flex;
8 position: relative;
9 outline: 2px solid transparent;
10 outline-offset: 2px;
11 border-radius: 4px;
12 font-size: 14px;
13 font-weight: 600;
14 cursor: pointer;
15 transition-property: background-color, border-color, color, fill, stroke, opacity, box-shadow, transform;
16 transition-duration: 200ms;
17 text-decoration: none;
18 line-height: 100%;
19 user-select: none;
20}
21
22.input {
23 outline: 2px solid transparent;
24 outline-offset: 2px;
25 position: relative;
26 transition-property: background-color, border-color, color, fill, stroke, opacity, box-shadow, transform;
27 transition-duration: 200ms;
28 border-radius: 4px;
29 color: #485056;
30 border-width: 1px;
31 border-style: solid;
32 border-color: #cfd4da;
33 box-sizing: border-box;
34 font-size: 14px;
35 height: 25px;
36 cursor: pointer;
37}
38
39.header {
40 font-weight: bold;
41 font-size: 14px;
42 line-height: 125%;
43 color: #334250;
44 padding-bottom: 10px;
45 display: flex;
46 justify-content: space-between;
47}
48
49.text {
50 font-size: 14px;
51 line-height: 20px;
52 color: #485056;
53 letter-spacing: -0.3px;
54}
55
56/* Controls Container */
57.controls-container {
58 display: flex;
59 flex-direction: column;
60 gap: 16px;
61 margin: 5px 0;
62 padding-bottom: 5px;
63 border-bottom: 1px solid #e0e0e0;
64 background-color: rgba(112, 198, 255, 0.2);
65}
66
67/* User Page Section */
68.user-page-section {
69 margin-bottom: 8px;
70}
71
72/* Headers */
73.select-user-header,
74.role-permissions-header {
75 margin: 0;
76}
77
78/* Buttons Container */
79.buttons-container {
80 display: flex;
81 flex-direction: row;
82 gap: 10px;
83}
84
85/* User Buttons */
86.btn-user {
87 white-space: nowrap;
88 width: 100%;
89 border: 0;
90 padding-inline: 20px;
91 padding-top: 12px;
92 padding-bottom: 12px;
93 height: 40px;
94 min-width: 40px;
95 background-color: #0056b3;
96 color: #ffffff;
97 margin-top: 5px;
98 max-width: fit-content;
99}
100
101.btn-user:hover {
102 background-color: #004494;
103 transform: translateY(-1px);
104 box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
105}
106
107/* Add User Label */
108.add-user-label {
109 font-weight: bold;
110 font-size: 14px;
111 line-height: 125%;
112 color: #0206a8;
113 cursor: pointer;
114}
115
116/* Add User Container */
117.add-user-container {
118 display: none; /* Initially hidden */
119 gap: 3px;
120 align-items: flex-start;
121 margin-top: 10px;
122}
123
124.add-user-container.visible {
125 display: flex;
126}
127
128/* Add User Input */
129.add-user-input {
130 width: 200px;
131 appearance: none;
132 border-radius: 6px;
133 background-color: #ffffff;
134 padding-inline-start: 16px;
135 padding-inline-end: 16px;
136}
137
138/* Add User Permission Dropdown */
139.add-user-permission {
140 padding-inline-end: 24px;
141 padding-bottom: 1px;
142 line-height: normal;
143 background-color: #f0f0f0;
144 margin-left: 3px;
145}
146
147/* Submit Button */
148.btn-submit-user {
149 appearance: none;
150 align-items: center;
151 justify-content: center;
152 padding-top: 12px;
153 padding-bottom: 12px;
154 height: 25px;
155 min-width: 32px;
156 background-color: #ffffff;
157 color: #0056b3;
158 white-space: nowrap;
159 padding-inline: 20px;
160 border-style: solid;
161 border-width: 1px;
162 border-color: #0056b3;
163 margin-left: 3px;
164}
165
166.btn-submit-user:hover {
167 background-color: #0056b3;
168 color: #ffffff;
169}
170/* Role Permissions Section */
171.role-permissions-section {
172 margin-bottom: 8px;
173 width: 50%;
174}
175
176.role-permission-paragraph {
177 margin: 0;
178}
179
180/* Viewing Permissions Section */
181.viewing-permissions-section {
182 display: flex;
183 flex-direction: column;
184}
185
186/* Checkbox Container */
187.checkbox-container {
188 display: flex;
189 flex-direction: row;
190 gap: 8px;
191}
192
193.checkbox-container > div {
194 display: flex;
195 align-items: center;
196 gap: 8px;
197}
198
199/* Checkboxes */
200.checkbox-container input[type="checkbox"] {
201 align-items: center;
202 justify-content: center;
203 width: 20px;
204 transition-property: box-shadow;
205 transition-duration: 200ms;
206 border-width: 2px;
207 border-style: solid;
208 border-radius: 4px;
209 border-color: inherit;
210 height: 20px;
211 display: inline-flex;
212 user-select: none;
213 flex-shrink: 0;
214}
215
216/* Checkbox Labels */
217.checkbox-label {
218 align-content: center;
219 justify-content: center;
220 cursor: pointer;
221}
222
223/* Role and Viewing Permissions Container */
224.role-viewing-permissions-container {
225 display: flex;
226 flex-direction: row;
227 gap: 20px;
228}
229
230/* Responsive Design */
231@media (max-width: 768px) {
232 .buttons-container {
233 flex-direction: column;
234 align-items: flex-start;
235 }
236
237 .btn-user {
238 width: 100%;
239 max-width: 100%;
240 margin: 5px 0;
241 }
242
243 .add-user-container {
244 flex-direction: column;
245 align-items: stretch;
246 }
247
248 .input.add-user-input,
249 .input.add-user-permission {
250 width: 100%;
251 margin-left: 0;
252 margin-bottom: 8px;
253 }
254
255 .btn.btn-submit-user {
256 margin-left: 0;
257 margin-top: 8px;
258 }
259
260 .role-viewing-permissions-container {
261 flex-direction: column;
262 gap: 16px;
263 }
264}
265
266/* Modal Styles */
267.config-modal {
268 display: none;
269 position: fixed;
270 z-index: 1000;
271 left: 0;
272 top: 0;
273 width: 100%;
274 height: 100%;
275 background-color: rgba(0, 0, 0, 0.5);
276}
277
278.config-modal-content {
279 position: fixed;
280 bottom: 0px;
281 left: 0px;
282 right: 0px;
283 max-width: 100vw;
284 transform: translateX(0px) translateY(0px) translateZ(0px);
285 display: flex;
286 flex-direction: column;
287 width: 100%;
288 outline: transparent solid 2px;
289 outline-offset: 2px;
290 z-index: 9999999999999;
291 max-height: 100vh;
292 background-color: #2d2d2d;
293 box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
294 pointer-events: auto;
295 font-size: 14px;
296}
297
298.config-modal-header {
299 display: flex;
300 flex: 0 1 0%;
301 padding-inline-start: 24px;
302 padding-inline-end: 24px;
303 padding-top: 12px;
304 padding-bottom: 0px;
305 font-size: 16px;
306 font-weight: 600;
307 color: #ffffff;
308 vertical-align: middle;
309}
310
311.btn-copy-config {
312 width: 20px;
313 height: 20px;
314 display: inline-block;
315 line-height: 1em;
316 flex-shrink: 0;
317 color: currentColor;
318 vertical-align: middle;
319 margin-left: 15px;
320 margin-bottom: 2px;
321 cursor: pointer;
322 background: none;
323 border: none;
324 font-size: 24px;
325}
326
327.btn-close-config {
328 background: none;
329 border: none;
330 outline: transparent solid 2px;
331 outline-offset: 2px;
332 display: flex;
333 align-items: center;
334 justify-content: center;
335 flex-shrink: 0;
336 width: 32px;
337 height: 32px;
338 border-radius: 6px;
339 transition-property: background-color, border-color, color, fill, stroke, opacity, box-shadow, transform;
340 transition-duration: 200ms;
341 font-size: 24px;
342 position: absolute;
343 top: 8px;
344 right: 12px;
345 color: #ffffff;
346 cursor: pointer;
347}
348
349.btn-copy-config:hover,
350.btn-close-config:hover {
351 color: #c7d2dd;
352}
353
354.config-snippet-code-pre {
355 color: #cccccc;
356 background: #2d2d2d;
357 font-family: monospace;
358 font-size: 1em;
359 text-align: left;
360 white-space: pre;
361 word-spacing: normal;
362 word-break: normal;
363 overflow-wrap: normal;
364 line-height: 1.5;
365 tab-size: 4;
366 hyphens: none;
367 padding: 1em;
368 margin: 0.5em 0px;
369 overflow: auto;
370 padding-inline-start: 32px;
371 padding-inline-end: 32px;
372 padding-top: 0px;
373 padding-bottom: 0px;
374}
375
376.btn-open-config {
377 display: inline-flex;
378 appearance: none;
379 align-items: center;
380 justify-content: center;
381 user-select: none;
382 position: relative;
383 white-space: nowrap;
384 vertical-align: middle;
385 outline: 2px solid transparent;
386 outline-offset: 2px;
387 width: 100%;
388 line-height: 100%;
389 border-radius: 4px;
390 font-size: 16px;
391 font-weight: 600;
392 transition-property: background-color, border-color, color, fill, stroke, opacity, box-shadow, transform;
393 transition-duration: 200ms;
394 cursor: pointer;
395 text-decoration: none;
396 padding-inline: 20px 20px;
397 padding-top: 12px;
398 padding-bottom: 12px;
399 height: 40px;
400 background-color: #ffffff;
401 color: #0056b3;
402 border-style: solid;
403 border-width: 1px;
404 border-color: #0056b3;
405 margin-left: 3px;
406}
407
408.btn-open-config:hover {
409 background-color: #0056b3;
410 color: #ffffff;
411}
Did you find this helpful?
Trial setup questions?
Ask experts on DiscordNeed other help?
Contact SupportPricing or product questions?
Contact Sales