-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathplugin.js
More file actions
146 lines (126 loc) · 4.47 KB
/
Copy pathplugin.js
File metadata and controls
146 lines (126 loc) · 4.47 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
import Inspire from "@inspirejs/core";
import { enterPresenterView, onPresenterSlidechange } from "../presenter/presenter-ui.js";
export const hasCSS = true;
/**
* Experimental presenter plugin built on the W3C Presentation API instead of
* `window.open()`. The presenter (controller) asks the browser to render the
* deck on a secondary display or Cast device, and the two views stay in sync
* by exchanging serialized messages over a PresentationConnection.
*
* Opt in by adding `class="experimental-presentation-api"` to <body>; doing so
* also disables the classic `presenter` plugin (see plugin-autoload.js), so the
* classic window.open presenter is the fallback wherever this isn't supported.
*
* The transport-independent presenter UI (notes, next-slide preview, timing) is
* shared with the classic plugin via ../presenter/presenter-ui.js.
*/
// sessionStorage key holding the live presentation id, used to silently
// reconnect the presenter view after a reload.
const STORAGE_KEY = "inspire-presentation-id";
let transport = null; // { send(message) } while connected, else null
let applyingRemote = false; // guards against echoing a remote update back
const supported = () => "presentation" in navigator && "PresentationRequest" in window;
/** Send a navigation message to the other view, unless we're applying one. */
function sync (message) {
if (!applyingRemote) {
transport?.send(message);
}
}
/** Apply a navigation message received from the other view. */
function applyRemote (message) {
applyingRemote = true;
try {
if (message.type === "goto") {
Inspire.goto(message.which);
}
else if (message.type === "gotoItem") {
Inspire.gotoItem(message.which);
}
}
finally {
applyingRemote = false;
}
}
/**
* Wire up a PresentationConnection as our transport. `isController` is true on
* the presenter side (which persists the id so it can reconnect after reload).
*/
function wireConnection (connection, isController) {
transport = {
send: message => {
if (connection.state === "connected") {
connection.send(JSON.stringify(message));
}
},
};
connection.addEventListener("message", e => applyRemote(JSON.parse(e.data)));
let drop = () => {
transport = null;
if (isController) {
sessionStorage.removeItem(STORAGE_KEY);
document.body.classList.remove("presenter", "show-next");
}
};
connection.addEventListener("close", drop);
connection.addEventListener("terminate", drop);
if (isController) {
sessionStorage.setItem(STORAGE_KEY, connection.id);
}
}
Inspire.hooks.add({
"init-end": () => {
if (navigator.presentation?.receiver) {
// We are the audience view, rendered on the secondary display
document.body.classList.add("projector");
navigator.presentation.receiver.connectionList.then(list => {
list.connections.forEach(c => wireConnection(c, false));
list.addEventListener("connectionavailable", e =>
wireConnection(e.connection, false));
});
}
else if (supported() && sessionStorage.getItem(STORAGE_KEY)) {
// Presenter view was reloaded: silently reconnect to the still-open
// audience display (no picker and no user gesture required).
let id = sessionStorage.getItem(STORAGE_KEY);
new PresentationRequest([location.href]).reconnect(id)
.then(connection => {
enterPresenterView();
wireConnection(connection, true);
})
.catch(() => sessionStorage.removeItem(STORAGE_KEY)); // audience gone
}
},
keyup: env => {
// Ctrl+P : Open Presenter view
if (env.letter !== "P" || transport) {
// Not our shortcut, or we're already presenting
return;
}
if (!supported()) {
console.warn(
"[presenter2] The Presentation API is not supported in this browser. " +
"Remove the `experimental-presentation-api` class to use the classic presenter.",
);
return;
}
// Ask the browser to render the deck on a secondary display. start()
// must run inside this user gesture (the keypress).
new PresentationRequest([location.href]).start()
.then(connection => {
enterPresenterView();
wireConnection(connection, true);
window.focus();
})
.catch(() => {}); // picker cancelled or no display available
},
slidechange: env => {
// Sync slide navigation. Send the slide id (stable & serializable);
// env.which may be an Element, which wouldn't survive JSON.
sync({ type: "goto", which: Inspire.currentSlide.id });
onPresenterSlidechange();
},
"gotoitem-end": env => {
// Sync slide item navigation
sync({ type: "gotoItem", which: env.which });
},
});