diff --git a/Annotations/Role based annotation/AuthenticationService/AuthenticationService.csproj b/Annotations/Role based annotation/AuthenticationService/AuthenticationService.csproj new file mode 100644 index 0000000..f5697ef --- /dev/null +++ b/Annotations/Role based annotation/AuthenticationService/AuthenticationService.csproj @@ -0,0 +1,14 @@ + + + + net8.0 + enable + enable + + + + + + + + diff --git a/Annotations/Role based annotation/AuthenticationService/AuthenticationService.csproj.user b/Annotations/Role based annotation/AuthenticationService/AuthenticationService.csproj.user new file mode 100644 index 0000000..4cb34cc --- /dev/null +++ b/Annotations/Role based annotation/AuthenticationService/AuthenticationService.csproj.user @@ -0,0 +1,11 @@ + + + + IIS Express + MvcControllerEmptyScaffolder + root/Common/MVC/Controller + + + ProjectDebugger + + \ No newline at end of file diff --git a/Annotations/Role based annotation/AuthenticationService/AuthenticationService.sln b/Annotations/Role based annotation/AuthenticationService/AuthenticationService.sln new file mode 100644 index 0000000..951468f --- /dev/null +++ b/Annotations/Role based annotation/AuthenticationService/AuthenticationService.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.9.34723.18 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AuthenticationService", "AuthenticationService.csproj", "{7FC0F7C8-6F5B-4FDC-B22E-55CD333E0FA0}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {7FC0F7C8-6F5B-4FDC-B22E-55CD333E0FA0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7FC0F7C8-6F5B-4FDC-B22E-55CD333E0FA0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7FC0F7C8-6F5B-4FDC-B22E-55CD333E0FA0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7FC0F7C8-6F5B-4FDC-B22E-55CD333E0FA0}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {7D63024A-BC8C-4438-AC15-FBD2D739D9FC} + EndGlobalSection +EndGlobal diff --git a/Annotations/Role based annotation/AuthenticationService/Controllers/AuthenticationController.cs b/Annotations/Role based annotation/AuthenticationService/Controllers/AuthenticationController.cs new file mode 100644 index 0000000..a7b522a --- /dev/null +++ b/Annotations/Role based annotation/AuthenticationService/Controllers/AuthenticationController.cs @@ -0,0 +1,56 @@ +using AuthenticationService.Models; +using Microsoft.AspNetCore.Mvc; +using System.Text.Json; + +namespace Authentication.Controllers; + +[ApiController] +[Route("api/[controller]")] +public class AuthenticationController : ControllerBase +{ + private static readonly string _userFilePath = "users.json"; + + // Register the new user with the credentials like Username, Email ID and Password + [HttpPost("register")] + public IActionResult Register([FromBody] User newUser) + { + var Users = LoadUsers(); + if (Users.Any(u => u.Username == newUser.Username)) + { + return BadRequest(new { message = "Username already exists" }); + } + if (Users.Any(u => u.Email == newUser.Email)) + { + return BadRequest(new { message = "Email already registered" }); + } + newUser.Password = BCrypt.Net.BCrypt.HashPassword(newUser.Password); + Users.Add(newUser); + var json = JsonSerializer.Serialize(Users, new JsonSerializerOptions { WriteIndented = true }); + System.IO.File.WriteAllText(_userFilePath, json); + return Ok(new { message = "User registered successfully" }); + } + + // Existing user can login using the credentials such as Email ID and password + [HttpPost("login")] + public IActionResult Login([FromBody] User login) + { + var Users = LoadUsers(); + var user = Users.FirstOrDefault(u => u.Email == login.Email); + if (user == null || !BCrypt.Net.BCrypt.Verify(login.Password, user.Password)) + { + return Unauthorized(new { message = "Invalid credentials" }); + } + + return Ok(new { username = user.Username, email = user.Email }); + } + + // 🔸 Load the user details from the local storage file + private static List LoadUsers() + { + if (!System.IO.File.Exists(_userFilePath)) + return new List(); + + var json = System.IO.File.ReadAllText(_userFilePath); + return JsonSerializer.Deserialize>(json) ?? new List(); + } +} \ No newline at end of file diff --git a/Annotations/Role based annotation/AuthenticationService/Models/User.cs b/Annotations/Role based annotation/AuthenticationService/Models/User.cs new file mode 100644 index 0000000..472f9a4 --- /dev/null +++ b/Annotations/Role based annotation/AuthenticationService/Models/User.cs @@ -0,0 +1,25 @@ +namespace AuthenticationService.Models +{ + + /// + /// Represents a user with basic authentication details. + /// + + public class User + { + /// + /// Gets or sets the username of the user. + /// + public string? Username { get; set; } + + /// + /// Gets or sets the email address of the user. + /// + public string Email { get; set; } + + /// + /// Gets or sets the password of the user. + /// + public string Password { get; set; } + } +} diff --git a/Annotations/Role based annotation/AuthenticationService/Program.cs b/Annotations/Role based annotation/AuthenticationService/Program.cs new file mode 100644 index 0000000..48e3876 --- /dev/null +++ b/Annotations/Role based annotation/AuthenticationService/Program.cs @@ -0,0 +1,36 @@ +var builder = WebApplication.CreateBuilder(args); + +// Add services to the container. +builder.Services.AddControllers(); +builder.Services.AddEndpointsApiExplorer(); +builder.Services.AddSwaggerGen(); + +// ✅ Add CORS configuration BEFORE building the app +builder.Services.AddCors(options => +{ + options.AddDefaultPolicy(policy => + { + policy.AllowAnyOrigin() + .AllowAnyHeader() + .AllowAnyMethod(); + }); +}); + +var app = builder.Build(); + +// Configure the HTTP request pipeline. +if (app.Environment.IsDevelopment()) +{ + app.UseSwagger(); + app.UseSwaggerUI(); +} + +// ✅ Use CORS middleware BEFORE authorization +app.UseCors(); + +app.UseHttpsRedirection(); +app.UseAuthorization(); + +app.MapControllers(); + +app.Run(); diff --git a/Annotations/Role based annotation/AuthenticationService/Properties/launchSettings.json b/Annotations/Role based annotation/AuthenticationService/Properties/launchSettings.json new file mode 100644 index 0000000..177484d --- /dev/null +++ b/Annotations/Role based annotation/AuthenticationService/Properties/launchSettings.json @@ -0,0 +1,41 @@ +{ + "$schema": "http://json.schemastore.org/launchsettings.json", + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:19872", + "sslPort": 44310 + } + }, + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "launchUrl": "swagger", + "applicationUrl": "http://localhost:5063", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "launchUrl": "swagger", + "applicationUrl": "https://localhost:7132;http://localhost:5063", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "launchUrl": "swagger", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/Annotations/Role based annotation/AuthenticationService/appsettings.Development.json b/Annotations/Role based annotation/AuthenticationService/appsettings.Development.json new file mode 100644 index 0000000..0c208ae --- /dev/null +++ b/Annotations/Role based annotation/AuthenticationService/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/Annotations/Role based annotation/AuthenticationService/appsettings.json b/Annotations/Role based annotation/AuthenticationService/appsettings.json new file mode 100644 index 0000000..10f68b8 --- /dev/null +++ b/Annotations/Role based annotation/AuthenticationService/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*" +} diff --git a/Annotations/Role based annotation/AuthenticationService/users.json b/Annotations/Role based annotation/AuthenticationService/users.json new file mode 100644 index 0000000..972f541 --- /dev/null +++ b/Annotations/Role based annotation/AuthenticationService/users.json @@ -0,0 +1,17 @@ +[ + { + "Username": "John", + "Email": "john@gmail.com", + "Password": "$2a$11$1zW3U/d6DC56zxifl8nATuGMQ8zhM8gmyx1t3fFtXAQg8a.xD5cBG" + }, + { + "Username": "Andrew", + "Email": "andrew@gmail.com", + "Password": "$2a$11$lu2VT2RXFNqI5Up8DYSkUuo1nampE5REmWJXQEcsvD69.icuTXUta" + }, + { + "Username": "Janet", + "Email": "janet@gmail.com", + "Password": "$2a$11$42zEoWxWxtReaL3Sgs9haer9Uxb3OnrWa1FM0T9icweSJphEimy/q" + } +] \ No newline at end of file diff --git a/Annotations/Role based annotation/RoleBasedAnnotation/.gitignore b/Annotations/Role based annotation/RoleBasedAnnotation/.gitignore new file mode 100644 index 0000000..4d29575 --- /dev/null +++ b/Annotations/Role based annotation/RoleBasedAnnotation/.gitignore @@ -0,0 +1,23 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.js + +# testing +/coverage + +# production +/build + +# misc +.DS_Store +.env.local +.env.development.local +.env.test.local +.env.production.local + +npm-debug.log* +yarn-debug.log* +yarn-error.log* diff --git a/Annotations/Role based annotation/RoleBasedAnnotation/README.md b/Annotations/Role based annotation/RoleBasedAnnotation/README.md new file mode 100644 index 0000000..58beeac --- /dev/null +++ b/Annotations/Role based annotation/RoleBasedAnnotation/README.md @@ -0,0 +1,70 @@ +# Getting Started with Create React App + +This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). + +## Available Scripts + +In the project directory, you can run: + +### `npm start` + +Runs the app in the development mode.\ +Open [http://localhost:3000](http://localhost:3000) to view it in your browser. + +The page will reload when you make changes.\ +You may also see any lint errors in the console. + +### `npm test` + +Launches the test runner in the interactive watch mode.\ +See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information. + +### `npm run build` + +Builds the app for production to the `build` folder.\ +It correctly bundles React in production mode and optimizes the build for the best performance. + +The build is minified and the filenames include the hashes.\ +Your app is ready to be deployed! + +See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. + +### `npm run eject` + +**Note: this is a one-way operation. Once you `eject`, you can't go back!** + +If you aren't satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project. + +Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you're on your own. + +You don't have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn't feel obligated to use this feature. However we understand that this tool wouldn't be useful if you couldn't customize it when you are ready for it. + +## Learn More + +You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started). + +To learn React, check out the [React documentation](https://reactjs.org/). + +### Code Splitting + +This section has moved here: [https://facebook.github.io/create-react-app/docs/code-splitting](https://facebook.github.io/create-react-app/docs/code-splitting) + +### Analyzing the Bundle Size + +This section has moved here: [https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size](https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size) + +### Making a Progressive Web App + +This section has moved here: [https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app](https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app) + +### Advanced Configuration + +This section has moved here: [https://facebook.github.io/create-react-app/docs/advanced-configuration](https://facebook.github.io/create-react-app/docs/advanced-configuration) + +### Deployment + +This section has moved here: [https://facebook.github.io/create-react-app/docs/deployment](https://facebook.github.io/create-react-app/docs/deployment) + +### `npm run build` fails to minify + +This section has moved here: [https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify](https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify) diff --git a/Annotations/Role based annotation/RoleBasedAnnotation/package.json b/Annotations/Role based annotation/RoleBasedAnnotation/package.json new file mode 100644 index 0000000..16c79ce --- /dev/null +++ b/Annotations/Role based annotation/RoleBasedAnnotation/package.json @@ -0,0 +1,45 @@ +{ + "name": "syncfusion-pdf-viewer", + "version": "0.1.0", + "private": true, + "dependencies": { + "@syncfusion/ej2-base": "^31.2.2", + "@syncfusion/ej2-react-dropdowns": "^31.2.3", + "@syncfusion/ej2-react-pdfviewer": "^31.2.3", + "@testing-library/dom": "^10.4.1", + "@testing-library/jest-dom": "^6.8.0", + "@testing-library/react": "^16.3.0", + "@testing-library/user-event": "^13.5.0", + "bootstrap": "^5.3.8", + "react": "^19.1.1", + "react-dom": "^19.1.1", + "web-vitals": "^2.1.4" + }, + "scripts": { + "start": "react-scripts start", + "build": "react-scripts build", + "test": "react-scripts test", + "eject": "react-scripts eject" + }, + "eslintConfig": { + "extends": [ + "react-app", + "react-app/jest" + ] + }, + "browserslist": { + "production": [ + ">0.2%", + "not dead", + "not op_mini all" + ], + "development": [ + "last 1 chrome version", + "last 1 firefox version", + "last 1 safari version" + ] + }, + "devDependencies": { + "react-scripts": "^5.0.1" + } +} diff --git a/Annotations/Role based annotation/RoleBasedAnnotation/public/Annotations.pdf b/Annotations/Role based annotation/RoleBasedAnnotation/public/Annotations.pdf new file mode 100644 index 0000000..786d19c Binary files /dev/null and b/Annotations/Role based annotation/RoleBasedAnnotation/public/Annotations.pdf differ diff --git a/Annotations/Role based annotation/RoleBasedAnnotation/public/User.png b/Annotations/Role based annotation/RoleBasedAnnotation/public/User.png new file mode 100644 index 0000000..9afed7b Binary files /dev/null and b/Annotations/Role based annotation/RoleBasedAnnotation/public/User.png differ diff --git a/Annotations/Role based annotation/RoleBasedAnnotation/public/index.html b/Annotations/Role based annotation/RoleBasedAnnotation/public/index.html new file mode 100644 index 0000000..aa069f2 --- /dev/null +++ b/Annotations/Role based annotation/RoleBasedAnnotation/public/index.html @@ -0,0 +1,43 @@ + + + + + + + + + + + + + React App + + + +
+ + + diff --git a/Annotations/Role based annotation/RoleBasedAnnotation/public/manifest.json b/Annotations/Role based annotation/RoleBasedAnnotation/public/manifest.json new file mode 100644 index 0000000..080d6c7 --- /dev/null +++ b/Annotations/Role based annotation/RoleBasedAnnotation/public/manifest.json @@ -0,0 +1,25 @@ +{ + "short_name": "React App", + "name": "Create React App Sample", + "icons": [ + { + "src": "favicon.ico", + "sizes": "64x64 32x32 24x24 16x16", + "type": "image/x-icon" + }, + { + "src": "logo192.png", + "type": "image/png", + "sizes": "192x192" + }, + { + "src": "logo512.png", + "type": "image/png", + "sizes": "512x512" + } + ], + "start_url": ".", + "display": "standalone", + "theme_color": "#000000", + "background_color": "#ffffff" +} diff --git a/Annotations/Role based annotation/RoleBasedAnnotation/src/Authentication.jsx b/Annotations/Role based annotation/RoleBasedAnnotation/src/Authentication.jsx new file mode 100644 index 0000000..c04f3c0 --- /dev/null +++ b/Annotations/Role based annotation/RoleBasedAnnotation/src/Authentication.jsx @@ -0,0 +1,135 @@ +import { useState } from "react"; +import "./index.css"; // Make sure this CSS file exists and is correctly styled + +const hostURL = "https://localhost:44310/api/Authentication"; + +function Authentication({ onLogin }) { + const [isRegistering, setIsRegistering] = useState(false); + const [message, setMessage] = useState(""); + const [username, setUsername] = useState(""); + + const validateEmail = (email) => /\S+@\S+\.\S+/.test(email); + + const handleLogin = async (event) => { + event.preventDefault(); + setMessage(""); + + const form = event.currentTarget; + const email = form.elements.email.value.trim(); + const password = form.elements.password.value.trim(); + + if (!validateEmail(email)) { + setMessage("❌ Please enter a valid email."); + return; + } + + try { + const response = await fetch(`${hostURL}/login`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ email, password }), + }); + + if (response.ok) { + const userData = await response.json(); + localStorage.setItem("user", JSON.stringify(userData)); + onLogin(userData); + } else { + setMessage("❌ Invalid email or password."); + } + } catch { + setMessage("⚠️ Server error during login."); + } + }; + + const handleRegister = async (e) => { + e.preventDefault(); + setMessage(""); + + const form = e.currentTarget; + const email = form.elements.email.value; + const password = form.elements.password.value; + + if (!validateEmail(email)) { + setMessage("❌ Please enter a valid email."); + return; + } + + try { + const register = await fetch(`${hostURL}/register`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ username, email, password }), + }); + + const data = await register.json(); + if (register.ok) { + setMessage("✅ Registration successful. You can now log in."); + setIsRegistering(false); + setUsername(""); + } else { + setMessage(`❌ ${data.message || "Registration failed"}`); + } + } catch { + setMessage("⚠️ Server error during registration."); + } + }; + + return ( +
+

{isRegistering ? "Register" : "Login"}

+
+ {isRegistering && ( + <> + + setUsername(e.target.value)} + required + className="auth-input" + placeholder="Your username" + /> + + )} + + + + + +
+ + {message &&

{message}

} +
+ ); +} + +export default Authentication; diff --git a/Annotations/Role based annotation/RoleBasedAnnotation/src/PdfViewer.css b/Annotations/Role based annotation/RoleBasedAnnotation/src/PdfViewer.css new file mode 100644 index 0000000..fec4af4 --- /dev/null +++ b/Annotations/Role based annotation/RoleBasedAnnotation/src/PdfViewer.css @@ -0,0 +1,91 @@ +/* Base */ +body { + margin: 0; + font-family: Arial, sans-serif; +} + +/* Authentication */ +.auth-container { + width: 400px; + padding: 40px; + border: 1px solid #ddd; + border-radius: 10px; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); + background-color: #fff; + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); +} +.auth-title { text-align: center; margin-bottom: 30px; } +.auth-label { display: block; margin-bottom: 8px; font-weight: bold; } +.auth-input { + width: 100%; + padding: 10px; + margin-bottom: 20px; + border-radius: 5px; + border: 1px solid #ccc; + box-sizing: border-box; +} +.auth-button { + width: 100%; + padding: 12px; + border-radius: 5px; + font-weight: bold; + cursor: pointer; + margin-top: 10px; + transition: background-color 0.3s ease, color 0.3s ease; +} +.auth-button.primary { background-color: #3f51b5; color: white; border: none; } +.auth-button.primary:hover { background-color: #213292; } +.auth-button.secondary { background-color: #f0f0f0; border: 1px solid #ccc; color: #333; } +.auth-button.secondary:hover { background-color: #e0e0e0; border-color: #bbb; } +.auth-message { color: red; margin-top: 15px; text-align: center; } + +/* Header + Logout */ +.app-header { + display: flex; + align-items: center; + justify-content: space-between; + gap: 16px; + padding: 12px 16px; + background: #ffffff; + border-bottom: 1px solid #e5e7eb; +} +.logout-btn { + border: 1px solid #e5e7eb; + background: #ffffff; + color: #111827; + font-size: 12px; + padding: 6px 12px; + border-radius: 8px; + cursor: pointer; +} +.logout-btn:hover { background: #f9fafb; } + +/* Rectangle border for the DropDownList (non-overlapping outline) */ +.author-ddl.e-control-wrapper.e-input-group { + border: 1px solid transparent !important; + outline: 1.5px solid #9e9e9e; + outline-offset: 2px; + border-radius: 6px; + padding: 6px 8px; + background: #fff; + box-shadow: none !important; + box-sizing: border-box; +} +.author-ddl.e-control-wrapper.e-input-group.e-input-focus, +.author-ddl.e-control-wrapper.e-input-group:focus-within { + outline-color: #666 !important; +} +.author-ddl-popup.e-popup { + margin-top: 8px !important; /* gap so popup won't overlap the outline */ +} +.author-ddl.e-control-wrapper.e-input-group .e-input, +.author-ddl.e-control-wrapper.e-input-group input.e-input { + box-shadow: none !important; +} + +/* Viewer wrapper */ +.control-section { padding: 0 16px; } + diff --git a/Annotations/Role based annotation/RoleBasedAnnotation/src/PdfViewer.jsx b/Annotations/Role based annotation/RoleBasedAnnotation/src/PdfViewer.jsx new file mode 100644 index 0000000..eb31ed4 --- /dev/null +++ b/Annotations/Role based annotation/RoleBasedAnnotation/src/PdfViewer.jsx @@ -0,0 +1,383 @@ +import React, { useState, useEffect, useRef, useMemo } from 'react'; +import { + PdfViewerComponent, Toolbar, Magnification, Navigation, LinkAnnotation, BookmarkView, + ThumbnailView, Print, TextSelection, Annotation, TextSearch, FormFields, FormDesigner, Inject +} from '@syncfusion/ej2-react-pdfviewer'; +import { DropDownListComponent } from '@syncfusion/ej2-react-dropdowns'; +import Authentication from './Authentication'; +import './PdfViewer.css'; + +const PdfViewer = () => { + const [loggedInUser, setUser] = useState(null); + const pdfViewerRef = useRef(null); + const displayName = useMemo(() => { + if (!loggedInUser) return ''; + return ( + loggedInUser.userName || + loggedInUser.username || + loggedInUser.name || + loggedInUser.email || + '' + ); + }, [loggedInUser]); + + const [authorsList, setAuthorsList] = useState([{ name: 'All Authors' }]); + const [selectedAuthor, setSelectedAuthor] = useState('All Authors'); + const [globalAnnotationsData, setGlobalAnnotationsData] = useState(null); + + useEffect(() => { + try { + const saved = localStorage.getItem("user"); + if (saved) setUser(JSON.parse(saved)); + } catch {/* ignore */} + }, []); + + useEffect(() => { + if (!displayName) { + setAuthorsList([{ name: 'All Authors' }]); + setSelectedAuthor('All Authors'); + return; + } + setAuthorsList([{ name: 'All Authors' }, { name: displayName }]); + setSelectedAuthor(displayName); + }, [displayName]); + + const handleDocumentLoad = async () => { + const viewer = pdfViewerRef.current; + if (!viewer) return; + const currentName = displayName || 'All Authors'; + setAuthorsList([{ name: 'All Authors' }, ...(displayName ? [{ name: displayName }] : [])]); + setSelectedAuthor(currentName); + try { + const exportedData = await viewer.exportAnnotationsAsObject(); + setGlobalAnnotationsData(exportedData); + const authorNames = new Set(displayName ? [displayName] : []); + viewer.annotationCollection?.forEach(annotation => { + const name = (annotation.author || '').trim(); + if (name) authorNames.add(name); + }); + const newAuthorsList = [{ name: 'All Authors' }, ...Array.from(authorNames).map(name => ({ name }))]; + setAuthorsList(newAuthorsList); + if (displayName) { + filterAndLoadAnnotations(displayName, exportedData); + } + } catch (error) { + console.error('Error during document load process:', error); + setGlobalAnnotationsData(null); + } + }; + + const filterAndLoadAnnotations = (userName, allAnnotations) => { + if (!pdfViewerRef.current) return; + setTimeout(() => { + try { pdfViewerRef.current.deleteAnnotations(); } catch {} + if (!allAnnotations) return; + try { + if (userName === 'All Authors') { + let parsedData = + typeof allAnnotations === 'string' && allAnnotations.trim() !== '' + ? JSON.parse(allAnnotations) + : allAnnotations; + if (!parsedData) return; + const dataToImport = parsedData.pdfAnnotation ? parsedData : { pdfAnnotation: parsedData }; + pdfViewerRef.current.importAnnotation(dataToImport); + return; + } + let parsedData = + typeof allAnnotations === 'string' ? JSON.parse(allAnnotations) : allAnnotations; + if (!parsedData?.pdfAnnotation) parsedData = { pdfAnnotation: parsedData }; + const filteredData = { pdfAnnotation: {} }; + Object.keys(parsedData.pdfAnnotation).forEach(pageNumber => { + const pageData = parsedData.pdfAnnotation[pageNumber]; + const list = pageData?.shapeAnnotation || []; + const userAnnotations = list.filter(a => a.title === userName); + if (userAnnotations.length > 0) { + filteredData.pdfAnnotation[pageNumber] = { shapeAnnotation: userAnnotations }; + } + }); + if (Object.keys(filteredData.pdfAnnotation).length > 0) { + pdfViewerRef.current.importAnnotation(filteredData); + } + } catch (error) { + console.error('Error filtering or loading annotations:', error); + } + }, 0); + }; + + const onAuthorChange = async (args) => { + const newAuthor = args?.itemData?.name; + if (!newAuthor) return; + + const viewer = pdfViewerRef.current; + let mergedGlobal = globalAnnotationsData; + + try { + const live = await viewer.exportAnnotationsAsObject(); + mergedGlobal = exportEditedAnnotsIntoGlobal(live, globalAnnotationsData); + setGlobalAnnotationsData(mergedGlobal); + try { localStorage.setItem('annot', mergedGlobal); } catch {} + } catch (e) { + console.warn('Failed to persist edits before switching author:', e); + } + + try { + if (newAuthor === "All Authors" || newAuthor === displayName) { + viewer.toolbar.enableToolbarItem(['AnnotationEditTool'], true); + } else { + viewer.toolbar.enableToolbarItem(['AnnotationEditTool'], false); + viewer.toolbarModule.showAnnotationToolbar(false); + } + } catch {} + + setSelectedAuthor(newAuthor); + filterAndLoadAnnotations(newAuthor, mergedGlobal); + }; + + const exportEditedAnnotsIntoGlobal = (liveJson, globalJson) => { + const parseJsonOrObject = (value) => + typeof value === 'string' ? JSON.parse(value || '{}') : value || {}; + + const liveData = parseJsonOrObject(liveJson); + const globalData = parseJsonOrObject(globalJson); + + globalData.pdfAnnotation = globalData.pdfAnnotation || {}; + const livePagesByKey = liveData.pdfAnnotation || {}; + + const normalizeToArray = (value) => + Array.isArray(value) ? value : value ? Object.values(value) : []; + + for (const pageKey of Object.keys(livePagesByKey)) { + const liveShapeAnnotations = livePagesByKey[pageKey]?.shapeAnnotation; + const liveAnnotationsList = normalizeToArray(liveShapeAnnotations); + if (!liveAnnotationsList.length) continue; + + if (!globalData.pdfAnnotation[pageKey]) { + globalData.pdfAnnotation[pageKey] = { shapeAnnotation: [] }; + } + + const globalPageEntry = globalData.pdfAnnotation[pageKey]; + const globalShapeAnnotations = globalPageEntry.shapeAnnotation; + + if (Array.isArray(globalShapeAnnotations)) { + for (const annotation of liveAnnotationsList) { + if (!annotation?.name) continue; + const existingIndex = globalShapeAnnotations.findIndex( + (a) => a?.name === annotation.name + ); + if (existingIndex >= 0) { + globalShapeAnnotations[existingIndex] = { ...annotation }; + } else { + globalShapeAnnotations.push({ ...annotation }); + } + } + } else { + for (const annotation of liveAnnotationsList) { + if (!annotation?.name) continue; + + let matchedKey = null; + for (const key of Object.keys(globalShapeAnnotations)) { + if (globalShapeAnnotations[key]?.name === annotation.name) { + matchedKey = key; + break; + } + } + if (matchedKey) { + globalShapeAnnotations[matchedKey] = { ...annotation }; + } else { + const nextKey = String(Object.keys(globalShapeAnnotations).length); + globalShapeAnnotations[nextKey] = { ...annotation }; + } + } + } + } + return JSON.stringify(globalData); + }; + + function lockAnnnotations() { + var annotations = pdfViewerRef.current.annotationCollection; + for (let i = 0; i < annotations.length; i++) { + const annot = annotations[i]; + if(annot.author === loggedInUser.username && selectedAuthor === loggedInUser.username) + { + annot.annotationSettings.isLock = false; + } + else if(annot.author === "Guest" && selectedAuthor === "All Authors") + { + annot.annotationSettings.isLock = false; + } + else{ + annot.annotationSettings.isLock = true; + } + pdfViewerRef.current.annotation.editAnnotation(annotations[i]); + } + } + + const onAnnotationAdd = async (args) => { + const viewer = pdfViewerRef.current; + const coll = viewer.annotationCollection || []; + const addedAnnotation = coll[coll.length - 1]; + if (!addedAnnotation) return; + addedAnnotation.author = displayName; + viewer.annotation.editAnnotation(addedAnnotation); + const exportedData = await viewer.exportAnnotationsAsObject(); + const allAnnnots = JSON.parse(exportedData).pdfAnnotation; + const exportData = []; + if (allAnnnots) { + const keys = Object.keys(allAnnnots); + for (let x = 0; x < keys.length; x++) { + const pageAnnots = allAnnnots[keys[x]].shapeAnnotation || []; + for (let y = 0; y < pageAnnots.length; y++) { + const pageAnnot = pageAnnots[y]; + if (pageAnnot.name === args.annotationId || pageAnnot.inreplyto === args.annotationId) { + exportData.push(pageAnnot); + } + } + } + } + combineAnnotations(exportData); + }; + + function combineAnnotations(exportData) { + const existingData = JSON.parse(globalAnnotationsData); + const key = exportData[0].page; + if (existingData.pdfAnnotation[key]) { + if (Array.isArray(existingData.pdfAnnotation[key].shapeAnnotation)) { + for (let x = 0; x < exportData.length; x++) { + existingData.pdfAnnotation[key].shapeAnnotation.push(exportData[x]); + } + } else { + const keysLength = Object.keys(existingData.pdfAnnotation[key].shapeAnnotation).length; + for (let x = 0; x < exportData.length; x++) { + existingData.pdfAnnotation[key].shapeAnnotation[(keysLength + x).toString()] = exportData[x]; + } + } + } else { + existingData.pdfAnnotation[key] = { shapeAnnotation: {} }; + for (let x = 0; x < exportData.length; x++) { + existingData.pdfAnnotation[key].shapeAnnotation[x.toString()] = exportData[x]; + } + } + localStorage.setItem('annot', JSON.stringify(existingData)); + const combinedAnnotations = JSON.stringify(existingData); + setGlobalAnnotationsData(combinedAnnotations); + } + + const onAnnotationRemove = async () => { + try { + const updatedAnnotations = await pdfViewerRef.current.exportAnnotationsAsObject(); + setGlobalAnnotationsData(updatedAnnotations); + } catch (error) { + console.error('Error updating annotations after remove:', error); + } + }; + + const logout = () => { + localStorage.removeItem("user"); + setUser(null); + }; + + // Template for the currently selected item in the dropdown + const dropdownValueTemplate = (data) => { + const name = data?.name ?? selectedAuthor; + const selectedName = data.name ?? selectedAuthor; + const roleText = + selectedName === 'All Authors' + ? 'Aggregated View' + : selectedName === displayName + ? 'LoggedIn User' + : 'Read-Only user'; + return ( +
+ User Avatar +
+
{name}
+
{roleText}
+
+
+ ); + }; + + + // Template for each item in the dropdown list + const dropdownItemTemplate = (data) => { + const name = data?.name || ''; + return (
+ User Avatar +
+
{data.name}
+
+
); + }; + + if (!loggedInUser) { + return ; + } + + return ( +
+
+ + +
+ +
+ + + +
+
+ ); +}; +export default PdfViewer; \ No newline at end of file diff --git a/Annotations/Role based annotation/RoleBasedAnnotation/src/index.css b/Annotations/Role based annotation/RoleBasedAnnotation/src/index.css new file mode 100644 index 0000000..717bb1d --- /dev/null +++ b/Annotations/Role based annotation/RoleBasedAnnotation/src/index.css @@ -0,0 +1,10 @@ + @import '../node_modules/@syncfusion/ej2-base/styles/material.css'; + @import '../node_modules/@syncfusion/ej2-buttons/styles/material.css'; + @import '../node_modules/@syncfusion/ej2-dropdowns/styles/material.css'; + @import '../node_modules/@syncfusion/ej2-inputs/styles/material.css'; + @import '../node_modules/@syncfusion/ej2-navigations/styles/material.css'; + @import '../node_modules/@syncfusion/ej2-popups/styles/material.css'; + @import '../node_modules/@syncfusion/ej2-splitbuttons/styles/material.css'; + @import "../node_modules/@syncfusion/ej2-pdfviewer/styles/material.css"; + + \ No newline at end of file diff --git a/Annotations/Role based annotation/RoleBasedAnnotation/src/index.jsx b/Annotations/Role based annotation/RoleBasedAnnotation/src/index.jsx new file mode 100644 index 0000000..8411c75 --- /dev/null +++ b/Annotations/Role based annotation/RoleBasedAnnotation/src/index.jsx @@ -0,0 +1,13 @@ +import React from 'react'; +import ReactDOM from 'react-dom/client'; +import './index.css'; +import { registerLicense } from '@syncfusion/ej2-base'; +import PdfViewer from './PdfViewer'; + +registerLicense('Ix0oFS8QJAw9HSQvXkVhQlBad1RDX3xKf0x/TGpQb19xflBPallYVBYiSV9jS3tSdkVrWHxccXZVQGlfVk91Xg=='); +const root = ReactDOM.createRoot(document.getElementById('root')); +root.render( + + + +); \ No newline at end of file