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_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 /(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|ipad|iris|kindle|Android|Silk|lge |maemo|midp|mmp|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows (ce|phone)|xda|xiino/i.test(
280 window.navigator.userAgent
281 ) ||
282 /1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw-(n|u)|c55\/|capi|ccwa|cdm-|cell|chtm|cldc|cmd-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc-s|devi|dica|dmob|do(c|p)o|ds(12|-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(-|_)|g1 u|g560|gene|gf-5|g-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd-(m|p|t)|hei-|hi(pt|ta)|hp( i|ip)|hs-c|ht(c(-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i-(20|go|ma)|i230|iac( |-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|-[a-w])|libw|lynx|m1-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|-([1-8]|c))|phil|pire|pl(ay|uc)|pn-2|po(ck|rt|se)|prox|psio|pt-g|qa-a|qc(07|12|21|32|60|-[2-7]|i-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h-|oo|p-)|sdk\/|se(c(-|0|1)|47|mc|nd|ri)|sgh-|shar|sie(-|m)|sk-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h-|v-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl-|tdg-|tel(i|m)|tim-|t-mo|to(pl|sh)|ts(70|m-|m3|m5)|tx-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas-|your|zeto|zte-/i.test(
283 window.navigator.userAgent.substring(0, 4)
284 )
285 );
286}
287
288// Cleanup function for when the demo is closed or page is unloaded
289const cleanup = (instance) => {
290 const { documentViewer } = instance.Core;
291
292 if (typeof instance !== 'undefined' && instance.UI) {
293
294 // Reset annotation user to default 'Guest' and promote to admin
295 const annotationManager = documentViewer.getAnnotationManager();
296 annotationManager.setCurrentUser('Guest');
297 annotationManager.promoteUserToAdmin();
298 annotationManager.disableReadOnlyMode();
299
300 console.log('Cleaning up compare-files demo');
301 }
302};
303
304// Register cleanup for page unload
305window.addEventListener('beforeunload', () => cleanup());
306window.addEventListener('unload', () => cleanup());
307
308
309// UI section
310//
311// Helper code to add controls to the viewer holding the buttons
312// This code creates a container for the buttons, styles them, and adds them to the viewer
313//
314
315// User selection
316const userPageSection = (instance) => {
317 // Create a wrapper div for the user section
318 const wrapper = document.createElement('div');
319 wrapper.className = 'user-page-section';
320
321 // Select User label
322 const selectUserLabel = document.createElement('h2');
323 selectUserLabel.className = 'header select-user-header';
324 selectUserLabel.textContent = 'Select User';
325
326 wrapper.appendChild(selectUserLabel);
327
328 // Users buttons container
329 const buttonsContainer = document.createElement('div');
330 buttonsContainer.className = 'buttons-container';
331
332 Object.keys(userList).map((username) => {
333 const button = document.createElement('button');
334 button.className = 'btn btn-user';
335 button.textContent = `${username} (${userList[username].permissions})`;
336 button.onclick = () => {
337 setUser(instance, username);
338 };
339
340 buttonsContainer.appendChild(button);
341 });
342 wrapper.appendChild(buttonsContainer);
343
344 // Add User clickable label
345 const addUserLabel = document.createElement('label');
346 addUserLabel.className = 'add-user-label';
347 addUserLabel.textContent = 'Add user';
348 addUserLabel.onclick = () => {
349 const addUserContainer = document.querySelector('.add-user-container');
350 if (addUserContainer.classList.contains('visible')) {
351 addUserContainer.classList.remove('visible');
352 addUserLabel.textContent = 'Add user';
353 } else {
354 addUserContainer.classList.add('visible');
355 addUserLabel.textContent = 'Close';
356 }
357 };
358
359 wrapper.appendChild(addUserLabel);
360
361 // Add User container
362 const addUserContainer = document.createElement('div');
363 addUserContainer.className = 'add-user-container';
364
365 // Add User input field
366 const input = document.createElement('input');
367 input.type = 'text';
368 input.placeholder = 'Username';
369 input.className = 'input add-user-input';
370
371 addUserContainer.appendChild(input);
372
373 // Add User permission dropdown
374 const permission = document.createElement('select');
375 permission.className = 'input add-user-permission';
376 permission.options.add(new Option('Administrator', 'administrator'));
377 permission.options.add(new Option('User', 'user'));
378 permission.options.add(new Option('Read-Only', 'read-only'));
379
380 addUserContainer.appendChild(permission);
381
382 // Add User add button
383 const addButton = document.createElement('button');
384 addButton.className = 'btn btn-submit-user';
385 addButton.textContent = 'Add';
386 addButton.onclick = () => {
387 // Validate input
388 const name = input.value;
389 if (name === '') return;
390 const p = permission.value;
391
392 // Add user to user list
393 addUser(instance, name, p);
394
395 // Add button for new user
396 const buttonsContainer = document.querySelector('.buttons-container');
397 const button = document.createElement('button');
398 button.className = 'btn btn-user';
399 button.textContent = `${name} (${p})`;
400 button.onclick = () => {
401 setUser(instance, name);
402 };
403 buttonsContainer.appendChild(button);
404
405 // Reset input fields
406 input.value = '';
407 permission.options.selectedIndex = 0;
408
409 // Close add user section
410 addUserLabel.click();
411 };
412
413 addUserContainer.appendChild(addButton);
414
415 wrapper.appendChild(addUserContainer);
416
417 return wrapper;
418};
419
420// Role permissions description
421const rolePermissionsPageSection = () => {
422 const wrapper = document.createElement('div');
423 wrapper.className = 'role-permissions-section';
424
425 const rolePermissionsLabel = document.createElement('h2');
426 rolePermissionsLabel.className = 'header role-permissions-header';
427 rolePermissionsLabel.textContent = 'Role Permissions';
428
429 wrapper.appendChild(rolePermissionsLabel);
430
431 const rolePermissionDescription = document.createElement('p');
432 rolePermissionDescription.className = 'text role-permission-paragraph';
433 const permission = perm();
434 if (permission === 'administrator') {
435 rolePermissionDescription.innerHTML = '<b> Admin: </b> Can add, edit, or remove any annotations created by anyone';
436 } else if (permission === 'read-only') {
437 rolePermissionDescription.innerHTML = '<b> Read-Only: </b> Can only view annotations';
438 } else { // user
439 rolePermissionDescription.innerHTML = '<b> User: </b> Can create, and edit or remove annotations created by themself';
440 }
441
442 wrapper.appendChild(rolePermissionDescription);
443
444 return wrapper;
445};
446
447// Viewing permissions checkboxes for selected user
448const viewingPermissionsPageSection = (instance) => {
449 const wrapper = document.createElement('div');
450 wrapper.className = 'viewing-permissions-section';
451
452 // Viewing Permissions label
453 const viewingPermissionsLabel = document.createElement('label');
454 viewingPermissionsLabel.className = 'header viewing-permissions-label';
455 viewingPermissionsLabel.textContent = `Set viewing permissions for ` + (currentUser ? currentUser : '...');
456
457 wrapper.appendChild(viewingPermissionsLabel);
458
459 // Viewing Permissions for each annotation type
460 const checkboxContainer = document.createElement('div');
461 checkboxContainer.className = 'checkbox-container';
462
463 toggleableTypes.map((type) => {
464 const checkboxRow = document.createElement('div');
465
466 const checkbox = document.createElement('input');
467 checkbox.type = 'checkbox';
468 checkbox.id = `view-${type.displayName}-checkbox`;
469 checkbox.checked = isChecked(type);
470 checkbox.onchange = () => {
471 toggleAnnotations(instance, type);
472 };
473
474 checkboxRow.appendChild(checkbox);
475
476 const label = document.createElement('label');
477 label.className = 'text checkbox-label';
478 label.ariaLabel = `Toggle ${type.displayName} annotations`;
479 label.textContent = `${type.displayName}`;
480
481 checkboxRow.appendChild(label);
482 checkboxContainer.appendChild(checkboxRow);
483 });
484 wrapper.appendChild(checkboxContainer);
485
486 return wrapper;
487};
488
489
490// Helper function to create UI controls
491const createUIControls = (instance) => {
492 // Create a container for all controls (label, dropdown, and buttons)
493 const controlsContainer = document.createElement('div');
494 controlsContainer.className = 'controls-container';
495
496 // Add user section
497 controlsContainer.appendChild(userPageSection(instance));
498
499 // Add role permissions and viewing permissions sections side by side
500 const roleViewingPermissionsContainer = document.createElement('div');
501 roleViewingPermissionsContainer.className = 'role-viewing-permissions-container';
502 roleViewingPermissionsContainer.appendChild(rolePermissionsPageSection());
503 roleViewingPermissionsContainer.appendChild(viewingPermissionsPageSection(instance));
504 controlsContainer.appendChild(roleViewingPermissionsContainer);
505
506
507
508 element.insertBefore(controlsContainer, element.firstChild);
509};
510
511// Helper function to update UI controls
512const updateUIControls = () => {
513 // Update role permission description
514 const rolePermissionDescription = document.querySelector('.role-permission-paragraph');
515 if (rolePermissionDescription) {
516 const permission = perm();
517 if (permission === 'administrator') {
518 rolePermissionDescription.innerHTML = '<b> Admin: </b> Can add, edit, or remove any annotations created by anyone';
519 } else if (permission === 'read-only') {
520 rolePermissionDescription.innerHTML = '<b> Read-Only: </b> Can only view annotations';
521 } else { // user
522 rolePermissionDescription.innerHTML = '<b> User: </b> Can create, and edit or remove annotations created by themself';
523 }
524 }
525
526 // Update viewing permissions label
527 const viewingPermissionsLabel = document.querySelector('.viewing-permissions-label');
528 if (viewingPermissionsLabel) {
529 viewingPermissionsLabel.textContent = `Set viewing permissions for ` + (currentUser ? currentUser : '...');
530 }
531
532 // Update checkboxes
533 toggleableTypes.map((type) => {
534 const checkbox = document.getElementById(`view-${type.displayName}-checkbox`);
535 if (checkbox) {
536 checkbox.checked = isChecked(type);
537 }
538 });
539
540 // Update configuration snippet text
541 const codeBlock = document.getElementById('config-snippet-code-block');
542 if (codeBlock) {
543 codeBlock.textContent = `const wvElement = document.getElementById('viewer');
544WebViewer({ ...options }, wvElement)
545.then(instance => {
546 const { annotationManager } = instance.Core;
547 annotationManager.setCurrentUser('${currentUser}');
548 ${setText()}
549 const allAnnots = annotationManager.getAnnotationsList();
550 ${setAnnotText()}
551})`;
552 }
553};
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: #007bff;
96 color: #ffffff;
97 margin-top: 5px;
98 max-width: fit-content;
99}
100
101.btn-user:hover {
102 background-color: #0056b3;
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: #007bff;
158 white-space: nowrap;
159 padding-inline: 20px;
160 border-style: solid;
161 border-width: 1px;
162 border-color: #007bff;
163 margin-left: 3px;
164}
165
166.btn-submit-user:hover {
167 background-color: #007bff;
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: #007bff;
402 white-space: nowrap;
403 border-style: solid;
404 border-width: 1px;
405 border-color: #007bff;
406 margin-left: 3px;
407}
408
409.btn-open-config:hover {
410 background-color: #007bff;
411 color: #ffffff;
412}
Did you find this helpful?
Trial setup questions?
Ask experts on DiscordNeed other help?
Contact SupportPricing or product questions?
Contact Sales