/* ES6 Compliant Syntax */
/* GitHub Copilot v1.0, Claude 3.5 Sonnet, September 1, 2025 */
/* File: index.js */
import WebViewer from '@pdftron/webviewer';
// Annotation Permission section
//
// Code to customize user permissions, completely client-side with 3
// different levels of permissions: administrator, user, and read-only
//
// Default Document
const defaultDoc = 'https://apryse.s3.us-west-1.amazonaws.com/public/files/samples/WebviewerDemoDoc.pdf';
// Set default user
let currentUser = 'Justin';
// List of users with permissions
let userList = {
Justin: { permissions: 'administrator', canView: true, hidden: [] },
Sally: { permissions: 'user', canView: true, hidden: [] },
Brian: { permissions: 'read-only', canView: true, hidden: [] }
};
// Annotation types that can be toggled for visibility
let toggleableTypes = [];
// Customize UI
const customizeUI = async (instance) => {
const { Annotations } = instance.Core;
// Load default document
await instance.Core.documentViewer.loadDocument(defaultDoc, {
extension: 'pdf',
});
instance.UI.setToolbarGroup('toolbarGroup-Annotate', true);
// Add sticky note, free hand, and highlight to toggleable types
toggleableTypes = [
{
displayName: 'Sticky notes',
annotationType: Annotations.StickyAnnotation,
},
{
displayName: 'Free hand',
annotationType: Annotations.FreeHandAnnotation,
},
{
displayName: 'Highlight',
annotationType: Annotations.TextHighlightAnnotation,
},
];
// Set user data for mentions in notes tool
const userData = Object.keys(userList).map((user) => ({
value: user,
email: `${user.toLowerCase()}@pdftron.com`,
}));
instance.UI.mentions.setUserData(userData);
// Set default user in the WebViewer
setUser(instance, currentUser);
};
// Set selected user in the WebViewer
const setUser = (instance, username) => {
const UI = instance.UI;
const { annotationManager }= instance.Core;
annotationManager.setCurrentUser(username);
const permissions = userList[username].permissions;
if (permissions === 'administrator') {
annotationManager.promoteUserToAdmin();
UI.disableViewOnlyMode();
} else if (permissions === 'read-only') {
UI.enableViewOnlyMode();
annotationManager.demoteUserFromAdmin();
} else {
UI.disableViewOnlyMode();
annotationManager.demoteUserFromAdmin();
}
currentUser = username;
setAnnotationsForUser(instance);
updateUIControls();
};
// Set annotations for current user
const setAnnotationsForUser = (instance) => {
const { annotationManager } = instance.Core;
const { hidden } = userList[currentUser];
// First get a list of all the types that should be hidden
const hiddenTypeMap = toggleableTypes.reduce((acc, type) => {
if (hidden.indexOf(type.displayName) > -1) {
acc.push(type.annotationType);
}
return acc;
}, []);
const allAnnots = annotationManager.getAnnotationsList();
const toShow = [];
const toHide = [];
// Generate lists of annotations to show and hide
allAnnots.forEach((annot) => {
const isType = hiddenTypeMap.some((type) => annot instanceof type);
if (isType) {
toHide.push(annot);
} else {
toShow.push(annot);
}
});
// Show and hide annotations
annotationManager.showAnnotations(toShow);
annotationManager.hideAnnotations(toHide);
};
// Add user to user list
const addUser = (instance, name, p) => {
userList = {
...userList,
[name]: { permissions: p, canView: true, hidden: [] },
};
};
// Toggle annotation type visibility for current user
const toggleAnnotations = (instance, type) => {
const { displayName } = type;
const { hidden } = userList[currentUser];
const idx = hidden.indexOf(displayName);
const newArray = hidden.slice(0);
if (idx !== -1) {
newArray.splice(idx, 1);
} else {
newArray.push(displayName);
}
userList = {
...userList,
[currentUser]: {
...userList[currentUser],
hidden: newArray
}
};
setAnnotationsForUser(instance);
updateUIControls();
};
// Check if annotation type is visible for current user
const isChecked = (type) => {
if (!currentUser) return;
const { displayName } = type;
const user = currentUser;
return userList[user].hidden.indexOf(displayName) === -1;
};
// Helper functions for configuration snippet modal
const perm = () => {
return currentUser ? userList[currentUser].permissions : null;
}
const hiddenList = () => {
return currentUser ? userList[currentUser].hidden : [];
};
let text = '';
let annotText = '';
const setText = () => {
const permission = perm();
if (permission === 'administrator') {
text = 'annotationManager.promoteUserToAdmin()';
} else if (permission === 'read-only') {
text = 'UI.enableViewOnlyMode()';
} else {
text = 'annotationManager.demoteUserFromAdmin();\n annotationManager.disableReadOnlyMode();';
}
return text;
};
const setAnnotText = () => {
const list = hiddenList();
if (list.length === 0) {
annotText = `
annotationManager.showAnnotations(allAnnots);
`;
} else {
let ifStatement = list.reduce((acc, hidden) => {
if (hidden === 'Sticky notes') {
acc += ' annot instanceof Annotations.StickyAnnotation || \n';
}
if (hidden === 'Free hand') {
acc += ' annot instanceof Annotations.FreeHandAnnotation || \n';
}
if (hidden === 'Highlight') {
acc += ' annot instanceof Annotations.TextHighlightAnnotation || \n';
}
return acc;
}, '');
ifStatement = ifStatement.substring(8, ifStatement.length - 5);
annotText = `
const hideList = allAnnots.filter(annot => {
return ${ifStatement};
});
annotationManager.hideAnnotations(hideList);
`;
}
return annotText;
};
// WebViewer section
//
// This code initializes the WebViewer with the basic settings
// that are found in the default showcase WebViewer
//
const searchParams = new URLSearchParams(window.location.search);
const history = window.history || window.parent.history || window.top.history;
const licenseKey = 'YOUR_WEBVIEWER_LICENSE_KEY';
const element = document.getElementById('viewer');
// Initialize WebViewer with the specified settings
WebViewer({
path: '/lib',
licenseKey: licenseKey,
enableFilePicker: true,
}, element).then((instance) => {
// Enable the measurement toolbar so it appears with all the other tools, and disable Cloudy rectangular tool
const cloudyTools = [
instance.Core.Tools.ToolNames.CLOUDY_RECTANGULAR_AREA_MEASUREMENT,
instance.Core.Tools.ToolNames.CLOUDY_RECTANGULAR_AREA_MEASUREMENT2,
instance.Core.Tools.ToolNames.CLOUDY_RECTANGULAR_AREA_MEASUREMENT3,
instance.Core.Tools.ToolNames.CLOUDY_RECTANGULAR_AREA_MEASUREMENT4,
];
instance.UI.enableFeatures([instance.UI.Feature.Measurement, instance.UI.Feature.Initials]);
instance.UI.disableTools(cloudyTools);
// Set default toolbar group to Annotate
instance.UI.setToolbarGroup('toolbarGroup-Annotate');
// Set default tool on mobile devices to Pan.
// https://apryse.atlassian.net/browse/WVR-3134
if (isMobileDevice()) {
instance.UI.setToolMode(instance.Core.Tools.ToolNames.PAN);
}
instance.Core.documentViewer.addEventListener('documentUnloaded', () => {
if (searchParams.has('file')) {
searchParams.delete('file');
history.replaceState(null, '', '?' + searchParams.toString());
}
});
instance.Core.annotationManager.enableAnnotationNumbering();
instance.UI.NotesPanel.enableAttachmentPreview();
// Add the demo-specific functionality
customizeUI(instance).then(() => {
// Create UI controls after demo is initialized
createUIControls(instance);
});
});
// Function to check if the user is on a mobile device
const isMobileDevice = () => {
return (
navigator.userAgentData?.mobile ??
/android|iphone|ipad|ipod|blackberry|iemobile|opera mini|mobile/i.test(
window.navigator.userAgent
)
);
}
// Cleanup function for when the demo is closed or page is unloaded
const cleanup = (instance) => {
const { documentViewer } = instance.Core;
if (typeof instance !== 'undefined' && instance.UI) {
// Reset annotation user to default 'Guest' and promote to admin
const annotationManager = documentViewer.getAnnotationManager();
annotationManager.setCurrentUser('Guest');
annotationManager.promoteUserToAdmin();
annotationManager.disableReadOnlyMode();
console.log('Cleaning up compare-files demo');
}
};
// Register cleanup for page unload
window.addEventListener('beforeunload', () => cleanup());
window.addEventListener('unload', () => cleanup());
// UI section
//
// Helper code to add controls to the viewer holding the buttons
// This code creates a container for the buttons, styles them, and adds them to the viewer
//
// User selection
const userPageSection = (instance) => {
// Create a wrapper div for the user section
const wrapper = document.createElement('div');
wrapper.className = 'user-page-section';
// Select User label
const selectUserLabel = document.createElement('h2');
selectUserLabel.className = 'header select-user-header';
selectUserLabel.textContent = 'Select User';
wrapper.appendChild(selectUserLabel);
// Users buttons container
const buttonsContainer = document.createElement('div');
buttonsContainer.className = 'buttons-container';
Object.keys(userList).forEach((username) => {
const button = document.createElement('button');
button.className = 'btn btn-user';
button.textContent = `${username} (${userList[username].permissions})`;
button.onclick = () => {
setUser(instance, username);
};
buttonsContainer.appendChild(button);
});
wrapper.appendChild(buttonsContainer);
// Add User clickable label
const addUserLabel = document.createElement('label');
addUserLabel.className = 'add-user-label';
addUserLabel.textContent = 'Add user';
addUserLabel.onclick = () => {
const addUserContainer = document.querySelector('.add-user-container');
if (addUserContainer.classList.contains('visible')) {
addUserContainer.classList.remove('visible');
addUserLabel.textContent = 'Add user';
} else {
addUserContainer.classList.add('visible');
addUserLabel.textContent = 'Close';
}
};
wrapper.appendChild(addUserLabel);
// Add User container
const addUserContainer = document.createElement('div');
addUserContainer.className = 'add-user-container';
// Add User input field
const input = document.createElement('input');
input.type = 'text';
input.placeholder = 'Username';
input.className = 'input add-user-input';
addUserContainer.appendChild(input);
// Add User permission dropdown
const permission = document.createElement('select');
permission.className = 'input add-user-permission';
permission.options.add(new Option('Administrator', 'administrator'));
permission.options.add(new Option('User', 'user'));
permission.options.add(new Option('Read-Only', 'read-only'));
addUserContainer.appendChild(permission);
// Add User add button
const addButton = document.createElement('button');
addButton.className = 'btn btn-submit-user';
addButton.textContent = 'Add';
addButton.onclick = () => {
// Validate input
const name = input.value;
if (name === '') return;
const p = permission.value;
// Add user to user list
addUser(instance, name, p);
// Add button for new user
const buttonsContainer = document.querySelector('.buttons-container');
const button = document.createElement('button');
button.className = 'btn btn-user';
button.textContent = `${name} (${p})`;
button.onclick = () => {
setUser(instance, name);
};
buttonsContainer.appendChild(button);
// Reset input fields
input.value = '';
permission.options.selectedIndex = 0;
// Close add user section
addUserLabel.click();
};
addUserContainer.appendChild(addButton);
wrapper.appendChild(addUserContainer);
return wrapper;
};
// Role permissions description
const rolePermissionsPageSection = () => {
const wrapper = document.createElement('div');
wrapper.className = 'role-permissions-section';
const rolePermissionsLabel = document.createElement('h2');
rolePermissionsLabel.className = 'header role-permissions-header';
rolePermissionsLabel.textContent = 'Role Permissions';
wrapper.appendChild(rolePermissionsLabel);
const rolePermissionDescription = document.createElement('p');
rolePermissionDescription.className = 'text role-permission-paragraph';
const permission = perm();
if (permission === 'administrator') {
rolePermissionDescription.innerHTML = '<b> Admin: </b> Can add, edit, or remove any annotations created by anyone';
} else if (permission === 'read-only') {
rolePermissionDescription.innerHTML = '<b> Read-Only: </b> Can only view annotations';
} else { // user
rolePermissionDescription.innerHTML = '<b> User: </b> Can create, and edit or remove annotations created by themself';
}
wrapper.appendChild(rolePermissionDescription);
return wrapper;
};
// Viewing permissions checkboxes for selected user
const viewingPermissionsPageSection = (instance) => {
const wrapper = document.createElement('div');
wrapper.className = 'viewing-permissions-section';
// Viewing Permissions label
const viewingPermissionsLabel = document.createElement('label');
viewingPermissionsLabel.className = 'header viewing-permissions-label';
viewingPermissionsLabel.textContent = `Set viewing permissions for ` + (currentUser ? currentUser : '...');
wrapper.appendChild(viewingPermissionsLabel);
// Viewing Permissions for each annotation type
const checkboxContainer = document.createElement('div');
checkboxContainer.className = 'checkbox-container';
toggleableTypes.forEach((type) => {
const checkboxRow = document.createElement('div');
const checkbox = document.createElement('input');
checkbox.type = 'checkbox';
checkbox.id = `view-${type.displayName}-checkbox`;
checkbox.checked = isChecked(type);
checkbox.onchange = () => {
toggleAnnotations(instance, type);
};
checkboxRow.appendChild(checkbox);
const label = document.createElement('label');
label.className = 'text checkbox-label';
label.ariaLabel = `Toggle ${type.displayName} annotations`;
label.textContent = `${type.displayName}`;
checkboxRow.appendChild(label);
checkboxContainer.appendChild(checkboxRow);
});
wrapper.appendChild(checkboxContainer);
return wrapper;
};
// Helper function to create UI controls
const createUIControls = (instance) => {
// Create a container for all controls (label, dropdown, and buttons)
const controlsContainer = document.createElement('div');
controlsContainer.className = 'controls-container';
// Add user section
controlsContainer.appendChild(userPageSection(instance));
// Add role permissions and viewing permissions sections side by side
const roleViewingPermissionsContainer = document.createElement('div');
roleViewingPermissionsContainer.className = 'role-viewing-permissions-container';
roleViewingPermissionsContainer.appendChild(rolePermissionsPageSection());
roleViewingPermissionsContainer.appendChild(viewingPermissionsPageSection(instance));
controlsContainer.appendChild(roleViewingPermissionsContainer);
element.insertBefore(controlsContainer, element.firstChild);
};
// Helper function to update UI controls
const updateUIControls = () => {
// Update role permission description
const rolePermissionDescription = document.querySelector('.role-permission-paragraph');
if (rolePermissionDescription) {
const permission = perm();
if (permission === 'administrator') {
rolePermissionDescription.innerHTML = '<b> Admin: </b> Can add, edit, or remove any annotations created by anyone';
} else if (permission === 'read-only') {
rolePermissionDescription.innerHTML = '<b> Read-Only: </b> Can only view annotations';
} else { // user
rolePermissionDescription.innerHTML = '<b> User: </b> Can create, and edit or remove annotations created by themself';
}
}
// Update viewing permissions label
const viewingPermissionsLabel = document.querySelector('.viewing-permissions-label');
if (viewingPermissionsLabel) {
viewingPermissionsLabel.textContent = `Set viewing permissions for ` + (currentUser ? currentUser : '...');
}
// Update checkboxes
toggleableTypes.forEach((type) => {
const checkbox = document.getElementById(`view-${type.displayName}-checkbox`);
if (checkbox) {
checkbox.checked = isChecked(type);
}
});
// Update configuration snippet text
const codeBlock = document.getElementById('config-snippet-code-block');
if (codeBlock) {
codeBlock.textContent = `const wvElement = document.getElementById('viewer');
WebViewer({ ...options }, wvElement)
.then(instance => {
const { annotationManager } = instance.Core;
annotationManager.setCurrentUser('${currentUser}');
${setText()}
const allAnnots = annotationManager.getAnnotationsList();
${setAnnotText()}
})`;
}
};