PMA.live
advancedslide loaderPMA.live
Console
PMA.UI version: 2.43.3
PMA.live example.
live.html
1<!doctype >
2<html lang="en">
3
4<head>
5 <meta charset="utf-8">
6 <meta http-equiv="X-UA-Compatible" content="IE=10">
7 <meta name="viewport" content="initial-scale=1.0, user-scalable=no, width=device-width">
8
9 <!-- Include PMA.UI required libraries downloaded or from CDN -->
10 <script src="./pma.ui/jquery-3.1.0.js" type="text/javascript"></script>
11 <link href="./pma.ui/font-awesome.min.css" type="text/css" rel="stylesheet">
12
13 <!-- Include optional libraries downloaded or from CDN -->
14 <link rel="stylesheet" href="./pma.ui/bootstrap5.min.css">
15 <script src="./pma.ui/bootstrap5.bundle.min.js"></script>
16
17 <!-- Include PMA.UI script & css -->
18 <script src="./pma.ui/pma.ui.js" type="text/javascript"></script>
19 <link href="./pma.ui/pma.ui.css" type="text/css" rel="stylesheet">
20
21 <!-- Include custom script & css -->
22 <script src="./js/live.js" type="text/javascript"></script>
23 <link href="./css/live.css" type="text/css" rel="stylesheet">
24
25 <!-- Include additional scripts -->
26 <script src="https://host.pathomation.com/pma.live/bundles/signalr" type="text/javascript"></script>
27 <script src="https://host.pathomation.com/pma.live/signalr/hubs" type="text/javascript"></script>
28 <script src="https://host.pathomation.com/pma.live/bundles/collaboration" type="text/javascript"></script>
29
30 <title>PMA.live</title>
31</head>
32
33<body>
34 <div class="container-fluid p-0 m-0">
35 <div class="row p-0 m-0">
36 <div class="col p-0 m-0 d-flex">
37 <!-- The element that will host the viewport -->
38 <div id="slideLoader"></div>
39 <div id="slideLoader2" class="d-none"></div>
40 </div>
41 </div>
42 </div>
43 <div class="toast-container position-absolute p-0 bottom-0 start-50 translate-middle-x">
44 <div class="toast show" style="width: auto;">
45 <div class="toast-header">
46 <strong class="me-2">Live session</strong>
47 <small>
48 <span id="stateInfo">Initializing</span>
49 <span id="stateBadge" class="badge rounded-pill bg-primary blinking"> </span>
50 </small>
51 <small class="ms-4 d-flex">
52 <button id="stateBtn" class="btn btn-secondary btn-sm" disabled>Start</button>
53
54 <div class="form-check form-switch mt-2 mx-3" style="min-width:96px">
55 <input class="form-check-input" type="checkbox" role="switch" id="switchSlide">
56 <label>Single slide</label>
57 </div>
58
59 <div class="form-check form-switch mt-2 mx-3" style="min-width:96px">
60 <input class="form-check-input" type="checkbox" role="switch" id="switchControl">
61 <label>Everyone in control</label>
62 </div>
63 </small>
64 </div>
65 </div>
66 </div>
67 <div id="infoBtnWrapper" class="is_hidden">
68 <button class="btn btn-secondary" type="button" data-bs-toggle="offcanvas" data-bs-target="#infoCanvas"
69 aria-controls="infoCanvasBtn" id="infoBtn" disabled>
70 <div id="infoBtnLabel">Show info</div>
71 </button>
72 </div>
73
74 <div class="offcanvas offcanvas-end" tabindex="-1" id="infoCanvas" aria-labelledby="infoCanvasLabel">
75 <div class="offcanvas-header border-bottom">
76 <h5 id="infoCanvasLabel">Live session info</h5>
77 <button type="button" class="btn-close text-reset" data-bs-dismiss="offcanvas" aria-label="Close"></button>
78 </div>
79 <div class="offcanvas-body">
80 <div class="list-group">
81 <h5 class="mb-1">PMA.core server</h5>
82 <p class="mb-4" id="pmaCoreUrl"></p>
83 <h5 class="mb-1">PMA.live server</h5>
84 <p class="mb-4" id="collaborationUrl"></p>
85 <h5 class="mb-1">Number of images</h5>
86 <p class="mb-4" id="noOfImages"></p>
87 <h5 class="mb-1">Live session name</h5>
88 <p class="mb-4" id="liveSessionName"></p>
89 <h5 class="mb-1">Nickname</h5>
90 <p class="mb-4" id="nickname"></p>
91 <h5 class="mb-1">Is master</h5>
92 <p class="mb-4 text-capitalize" id="isMaster"></p>
93 <h5 class="mb-1">Is everyone in control</h5>
94 <p class="mb-4 text-capitalize" id="isEveryoneInControl"></p>
95 <h5 class="mb-1">Join session url</h5>
96 <p class="mb-4" id="sessionUrl"></p>
97 </div>
98 </div>
99 </div>
100</body>
101
102</html>
live.css
1html,
2body {
3 height: 100%;
4 padding: 0px;
5 margin: 0px;
6}
7
8#slideLoader {
9 height: 100vh;
10}
11
12#infoBtn {
13 transform: perspective(6px) rotateY(-3deg) rotateZ(90deg);
14}
15
16#infoBtnLabel {
17 transform: perspective(6px) rotateX(3deg) translateY(-6px);
18 margin: 0 10px;
19}
20
21#infoBtnWrapper {
22 position: fixed;
23 top: 50vh;
24 right: -36px;
25 transition-delay: 0s;
26 transition-duration: 0.3s;
27 transition-property: transform;
28 transition-timing-function: ease-in-out;
29 z-index: 10000;
30}
31
32#infoBtnWrapper.is_shown {
33 transform: translateX(-400px);
34}
35
36#infoBtnWrapper.is_hidden {
37 transform: none;
38}
39
40#slideLoader, #slideLoader2 {
41 flex: 1 1 auto;
42}
43
44.ol-annotations-toolbar {
45 transform: translateX(calc(50vw - 144px));
46}
47
48.blinking {
49 animation: blinker 2s linear infinite;
50}
51
52@keyframes blinker {
53 50% {
54 opacity: 0;
55 }
56}
live.js
1// Initial declarations
2var pmaCoreUrl = "https://host.pathomation.com/pma.core.2/";
3var pmaCoreUsername = "PMA_UI_demo";
4var pmaCorePassword = "PMA_UI_demo";
5var caller = "DemoPortal";
6var slideLoaderElementSelector = "#slideLoader";
7var image = "Reference/3DHistech/CMU-1.mrxs";
8var image2 = "Reference/3DHistech/CMU-2.mrxs";
9var image3 = "Reference/3DHistech/CMU-3.mrxs";
10var collaborationUrl = "https://host.pathomation.com/pma.live/";
11var slideLoaders = [];
12var urlParams = new URLSearchParams(window.location.search);
13var isMaster = !urlParams.has('session');
14var nickname = !urlParams.has('session') ? `Master_${Math.random().toString(36).substr(2, 8)}` : `User_${Math.random().toString(36).substr(2, 8)}`;
15var context = null;
16var currentSession = null;
17var sessionInitialized = false;
18var everyoneInControl = false;
19
20// Callback function that will return the slideloaders on live session start for user
21function getSlideLoader(index, totalNumberOfImages) {
22 if (!isMaster && slideLoaders.length != totalNumberOfImages) {
23 // first connection of client or the master changed the number of available viewers
24 // so the client needs to recreate the slideloaders
25 // A more "smart" client could keep the existing slideloader and extent upon it without recreating everything
26 slideLoaders = [];
27 for (let i = 0; i < totalNumberOfImages; i++) {
28 slideLoaders.push(createSlideLoader(slideLoaderElementSelector + (i > 0 ? `${i + 1}` : "")));
29 }
30
31 // UI updates for client
32 $("#slideLoader2").addClass("d-none");
33 if (totalNumberOfImages > 1) {
34 $("#slideLoader2").removeClass("d-none");
35 }
36 }
37
38 return slideLoaders[index];
39}
40
41// Create slideloader for live session image
42function createSlideLoader(element) {
43 return new PMA.UI.Components.SlideLoader(context, {
44 element: element,
45 annotations: { visible: true, labels: true, showMeasurements: false },
46 filename: false,
47 barcode: false,
48 });
49}
50
51function initializeSlides(single) {
52 // destroy all slide loaders on reinitialize
53 slideLoaders.forEach(s => s.load(null, null));
54
55 if (single) {
56 slideLoaders = [createSlideLoader(slideLoaderElementSelector)];
57 slideLoaders[0].load(pmaCoreUrl, image);
58 isMaster && Collaboration.setNumberOfImages(slideLoaders.length);
59 }
60 else {
61 // create two slideloaders
62 slideLoaders = [createSlideLoader(slideLoaderElementSelector), createSlideLoader(slideLoaderElementSelector + "2")];
63 slideLoaders[0].load(pmaCoreUrl, image2);
64 slideLoaders[1].load(pmaCoreUrl, image3);
65 isMaster && Collaboration.setNumberOfImages(slideLoaders.length);
66 }
67}
68
69function initCollaboration() {
70 var dfd = $.Deferred();
71
72 // Initialize live session
73 Collaboration.initialize(
74 {
75 pmaCoreUrl: pmaCoreUrl,
76 apiUrl: `${collaborationUrl}api/`,
77 hubUrl: `${collaborationUrl}signalr`,
78 master: isMaster,
79 dataChanged: collaborationDataChanged,
80 pointerImageSrc: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyBpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMC1jMDYwIDYxLjEzNDc3NywgMjAxMC8wMi8xMi0xNzozMjowMCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNSBXaW5kb3dzIiB4bXBNTTpJbnN0YW5jZUlEPSJ4bXAuaWlkOkNENUE3RkJFMUMwQjExRTZCRTRBREJEQ0Q2QTY0NzI5IiB4bXBNTTpEb2N1bWVudElEPSJ4bXAuZGlkOkNENUE3RkJGMUMwQjExRTZCRTRBREJEQ0Q2QTY0NzI5Ij4gPHhtcE1NOkRlcml2ZWRGcm9tIHN0UmVmOmluc3RhbmNlSUQ9InhtcC5paWQ6Q0Q1QTdGQkMxQzBCMTFFNkJFNEFEQkRDRDZBNjQ3MjkiIHN0UmVmOmRvY3VtZW50SUQ9InhtcC5kaWQ6Q0Q1QTdGQkQxQzBCMTFFNkJFNEFEQkRDRDZBNjQ3MjkiLz4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+IDw/eHBhY2tldCBlbmQ9InIiPz4xo9bVAAACW0lEQVR42rSVP6hSYRTAr89KDXIoHYygBLFAkESHJieh0MXr0JRDbWoWQaLEW5qFbAgE4dEWQaBDk0u8xTAxdGiJEkoX/xX+yQK1dzrHvu9iF5+9q/cd+DlcvvP9vnP87rkCAAgcWYjID+SmoDD+2XON4BmtRX4jd45DsG8ymcButwMT3VNTcBJp+Xw+6Pf74HA4uOSRWoKL1JpYLAYUnU4HPB4PlzxVQ+CjdblcDngMBgPwer1csodothE8oHWlUgmWYzKZQDAY5JKXrJUbCZ7r9Xpot9sgj/l8DuFwmEteI6c3Ebyz2WywLiKRCJe8Rc4pEdCJeoFAAP4XiUSCS+rI+aMKLtOaVCoFR4lsNgsajYaSPyKX5IITK/6jK/TjdDpX3pDhcChkMhlhNBoJ0+lU0Ol0gtlsFrrdrp216y6SX92vv7FLj2u12uKEuAk0Gg3pxJVKhbdlivxExuRFviMj5OG6Fp1CXhmNRhiPx9Dr9UAUxcWGtDHFbDYDq9VKzz6xllxALMhZ5Ayys05gRT643W4oFotgsVj4sDuIRqNSFclkkldxVel7cJ3KpAoMBgM9+IbcQt6TjF40inq9zgVPlAp2WSKxzyoS2JCDQqEgVcFm01dEp0RAI6KPPEa0Szk2ZB4KhSQB3iR+EFGJYAcxHDLD3lDbWq3WQtBsNgGvKB8XG31w5HGbctPptFSF3++npF/sJm0tMCEDl8slCfL5PG/TfTUEFC8ov1wuQ7VahXg8vjzwVBHcoHz6Vmu1Wko4QD7Lv9fbCGh2lZEvSAa5xt7+Q//kPwIMACgPWAyBhH+UAAAAAElFTkSuQmCC",
81 masterPointerImageSrc: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyBpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMC1jMDYwIDYxLjEzNDc3NywgMjAxMC8wMi8xMi0xNzozMjowMCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNSBXaW5kb3dzIiB4bXBNTTpJbnN0YW5jZUlEPSJ4bXAuaWlkOkNENUE3RkJFMUMwQjExRTZCRTRBREJEQ0Q2QTY0NzI5IiB4bXBNTTpEb2N1bWVudElEPSJ4bXAuZGlkOkNENUE3RkJGMUMwQjExRTZCRTRBREJEQ0Q2QTY0NzI5Ij4gPHhtcE1NOkRlcml2ZWRGcm9tIHN0UmVmOmluc3RhbmNlSUQ9InhtcC5paWQ6Q0Q1QTdGQkMxQzBCMTFFNkJFNEFEQkRDRDZBNjQ3MjkiIHN0UmVmOmRvY3VtZW50SUQ9InhtcC5kaWQ6Q0Q1QTdGQkQxQzBCMTFFNkJFNEFEQkRDRDZBNjQ3MjkiLz4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+IDw/eHBhY2tldCBlbmQ9InIiPz4xo9bVAAACW0lEQVR42rSVP6hSYRTAr89KDXIoHYygBLFAkESHJieh0MXr0JRDbWoWQaLEW5qFbAgE4dEWQaBDk0u8xTAxdGiJEkoX/xX+yQK1dzrHvu9iF5+9q/cd+DlcvvP9vnP87rkCAAgcWYjID+SmoDD+2XON4BmtRX4jd45DsG8ymcButwMT3VNTcBJp+Xw+6Pf74HA4uOSRWoKL1JpYLAYUnU4HPB4PlzxVQ+CjdblcDngMBgPwer1csodothE8oHWlUgmWYzKZQDAY5JKXrJUbCZ7r9Xpot9sgj/l8DuFwmEteI6c3Ebyz2WywLiKRCJe8Rc4pEdCJeoFAAP4XiUSCS+rI+aMKLtOaVCoFR4lsNgsajYaSPyKX5IITK/6jK/TjdDpX3pDhcChkMhlhNBoJ0+lU0Ol0gtlsFrrdrp216y6SX92vv7FLj2u12uKEuAk0Gg3pxJVKhbdlivxExuRFviMj5OG6Fp1CXhmNRhiPx9Dr9UAUxcWGtDHFbDYDq9VKzz6xllxALMhZ5Ayys05gRT643W4oFotgsVj4sDuIRqNSFclkkldxVel7cJ3KpAoMBgM9+IbcQt6TjF40inq9zgVPlAp2WSKxzyoS2JCDQqEgVcFm01dEp0RAI6KPPEa0Szk2ZB4KhSQB3iR+EFGJYAcxHDLD3lDbWq3WQtBsNgGvKB8XG31w5HGbctPptFSF3++npF/sJm0tMCEDl8slCfL5PG/TfTUEFC8ov1wuQ7VahXg8vjzwVBHcoHz6Vmu1Wko4QD7Lv9fbCGh2lZEvSAa5xt7+Q//kPwIMACgPWAyBhH+UAAAAAElFTkSuQmCC",
82 getSlideLoader: getSlideLoader,
83 }, [])
84 // Then create and join live session
85 .then(function () {
86 var sessionName = isMaster ? `Demo_${Math.random().toString(36).substr(2, 8)}` : urlParams.get('session');
87 var sessionActive = false;
88 return Collaboration.joinSession(sessionName, sessionActive, nickname, everyoneInControl);
89 })
90 // Load image after joining live session
91 .then(function (session) {
92 isMaster && initializeSlides(true);
93 console.log(session);
94 currentSession = session;
95 dfd.resolve();
96 });
97
98 return dfd.promise();
99}
100
101function collaborationDataChanged() {
102 console.log("Collaboration data changed");
103 currentSession = Collaboration.getCurrentSession();
104 sessionInitialized && updateUI();
105}
106
107function updateUI() {
108 var isActive = Collaboration.getSessionActive()
109 $("#stateInfo").html(isActive ? "Active" : "Inactive");
110 isActive ? $("#stateBadge").removeClass("bg-danger").addClass("bg-success") : $("#stateBadge").removeClass("bg-success").addClass("bg-danger");
111 $("#stateBtn").html(isActive ? "Stop" : "Start");
112 $("#pmaCoreUrl").html(pmaCoreUrl);
113 $("#collaborationUrl").html(collaborationUrl);
114 $("#noOfImages").html(currentSession.NumberOfImages);
115 $("#liveSessionName").html(currentSession.Name);
116 $("#nickname").html(nickname);
117 $("#isMaster").html(isMaster.toString());
118 $("#isEveryoneInControl").html(currentSession.EveryoneInControl.toString());
119 $("#sessionUrl").html(`<a href="?session=${currentSession.Name}" ref="noopener noreferrer" target="_blank"><i class="fas fa-link"></i> Link</a>`);
120}
121
122jQuery(function () {
123 console.log(`PMA.UI version: ${PMA.UI.getVersion()}`);
124 context = new PMA.UI.Components.Context({ caller: caller });
125 new PMA.UI.Authentication.AutoLogin(context, [{ serverUrl: pmaCoreUrl, username: pmaCoreUsername, password: pmaCorePassword }]);
126
127 !isMaster && $("#stateBtn").parent().remove();
128 !isMaster && $("#switchSlide").parent().remove();
129
130 initCollaboration()
131 .then(function () {
132 console.log("Live session initialized");
133 sessionInitialized = true;
134 console.log("Join url", `${window.location.href}?session=${currentSession.Name}`);
135 $("#infoBtn").prop("disabled", false);
136 $("#stateBtn").prop("disabled", false);
137 $("#stateBadge").removeClass("bg-primary");
138 updateUI();
139 });
140
141 $("#infoCanvas").on('hide.bs.offcanvas', function () {
142 $("#infoBtnWrapper").removeClass("is_shown").addClass("is_hidden");
143 });
144
145 $("#infoCanvas").on('show.bs.offcanvas', function () {
146 $("#infoBtnWrapper").removeClass("is_hidden").addClass("is_shown");
147 });
148
149 $("#stateBtn").on("click", function () {
150 Collaboration.setSessionActive(!Collaboration.getSessionActive());
151 });
152
153 $("#switchControl").on("change", function () {
154 let v = $(this)[0].checked;
155 isMaster && Collaboration.setEveryoneInControl(v);
156 });
157
158 $("#switchSlide").on("change", function () {
159 let v = $(this)[0].checked;
160 $(this).siblings("label").html(v ? "Double slide" : "Single slide");
161 if (v) {
162 $("#slideLoader2").removeClass("d-none");
163 $("#slideLoader").removeClass("d-none");
164 }
165 else {
166 $("#slideLoader2").removeClass("d-none").addClass("d-none");
167 $("#slideLoader").removeClass("d-none");
168 }
169
170 isMaster && initializeSlides(!v);
171 });
172
173});