PMA.UI Examples 2.43.3by Pathomation

PMA.live

advancedslide loaderPMA.live
Console
PMA.UI version: 2.43.3
PMA.live example.
live.html
1<!doctype html>
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">&nbsp;</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});