deck-presenter 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +83 -0
- package/deck-presenter.min.js +2 -0
- package/package.json +24 -0
package/README.md
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# deck-presenter
|
|
2
|
+
|
|
3
|
+
A presenter window for a [`<deck-shift>`](../deck-shift) slide deck.
|
|
4
|
+
|
|
5
|
+
```html
|
|
6
|
+
<script type="module" src="https://unpkg.com/deck-presenter"></script>
|
|
7
|
+
<deck-presenter></deck-presenter>
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
Or:
|
|
11
|
+
|
|
12
|
+
```sh
|
|
13
|
+
npm install deck-presenter
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
```js
|
|
17
|
+
import "deck-presenter";
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Add `<deck-presenter>` anywhere on the page and press <kbd>s</kbd>, and a new
|
|
21
|
+
tab will open with a timer (click to reset), the position, back and next, the
|
|
22
|
+
clock, the current slide, and the next, with notes below.
|
|
23
|
+
|
|
24
|
+
The popup is built and run from the deck's own window, so its buttons and keys
|
|
25
|
+
drive the main slide deck.
|
|
26
|
+
|
|
27
|
+
### Customizing
|
|
28
|
+
|
|
29
|
+
Add a `<template>` child to the `<deck-presenter>` to inject arbitrary HTML to
|
|
30
|
+
the opened window, this can include CSS, scripts, or even custom elements which
|
|
31
|
+
will all be rendered into the opened window, allowing you to customise the
|
|
32
|
+
presenter view.
|
|
33
|
+
|
|
34
|
+
Scripts run in that window, with URLs relative to the deck. Module scripts run
|
|
35
|
+
once per window.
|
|
36
|
+
|
|
37
|
+
```html
|
|
38
|
+
<deck-presenter>
|
|
39
|
+
<template>
|
|
40
|
+
<script type="module" src="https://esm.sh/webcam-view"></script>
|
|
41
|
+
<style>
|
|
42
|
+
webcam-view {
|
|
43
|
+
position: fixed;
|
|
44
|
+
right: 24px;
|
|
45
|
+
bottom: 24px;
|
|
46
|
+
width: 280px;
|
|
47
|
+
}
|
|
48
|
+
</style>
|
|
49
|
+
<webcam-view mirror></webcam-view>
|
|
50
|
+
</template>
|
|
51
|
+
</deck-presenter>
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
The view lives in a shadow root, so style it from the template with
|
|
55
|
+
`::part()`:
|
|
56
|
+
|
|
57
|
+
- `header`, holding `timer`, `position`, `prev-button`, `next-button` (both
|
|
58
|
+
also `button`) and `clock`.
|
|
59
|
+
- `main`, holding the `current-slide` frame and the `sidebar`, which holds the
|
|
60
|
+
`label`, the `next-slide` frame (both frames are also `frame`), `end` (shown
|
|
61
|
+
over the next frame on the last step) and `notes`.
|
|
62
|
+
- `cue` for each `<mark>` in the notes, plus `cue-hit` once passed or
|
|
63
|
+
`cue-next` for the one the next press reaches.
|
|
64
|
+
- `help`, the shortcuts dialog.
|
|
65
|
+
|
|
66
|
+
```html
|
|
67
|
+
<deck-presenter>
|
|
68
|
+
<template>
|
|
69
|
+
<style>
|
|
70
|
+
::part(main) {
|
|
71
|
+
grid-template-columns: 1fr 1fr;
|
|
72
|
+
}
|
|
73
|
+
::part(cue-next) {
|
|
74
|
+
background: hotpink;
|
|
75
|
+
}
|
|
76
|
+
</style>
|
|
77
|
+
</template>
|
|
78
|
+
</deck-presenter>
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Notes are the slide's `<aside class="notes">`, with a `<mark>` at the word where
|
|
82
|
+
you press for each step. It adds `--present` and S to the deck with
|
|
83
|
+
`addShortcut()`.
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
var S=`:host{position:fixed;inset:0;display:grid;grid-template-rows:auto 1fr;background:#111;color:#eee;font:16px/1.45 ui-sans-serif,system-ui,sans-serif}header{display:flex;gap:28px;align-items:baseline;padding:14px 22px;border-bottom:1px solid #2a2a2a}[part=timer]{font:600 34px/1 ui-monospace,monospace;cursor:pointer;font-variant-numeric:tabular-nums}[part=position]{flex:1;color:#aaa}button{background:#262626;color:#eee;border:1px solid #3a3a3a;border-radius:8px;padding:9px 16px;cursor:pointer;font:600 15px/1 ui-sans-serif,system-ui,sans-serif;&:hover{background:#333}}[part=clock]{font:500 22px/1 ui-monospace,monospace;color:#aaa}main{display:grid;grid-template-columns:1.55fr 1fr;gap:22px;padding:22px;min-height:0}aside{display:grid;grid-template-rows:auto 1fr;gap:18px;min-height:0}label{display:block;color:#888;font-size:13px;text-transform:uppercase;letter-spacing:.12em;margin-bottom:6px}[part=frame]{position:relative;aspect-ratio:16 / 9;background:#000;border-radius:6px;overflow:hidden;iframe{position:absolute;inset:0;width:100%;height:100%;border:0;pointer-events:none}.end{position:absolute;inset:0;display:none;place-items:center;color:#777;font-size:24px;background:#000}&.over .end{display:grid}}[part=notes]{overflow:auto;font-size:24px;line-height:1.45;color:#f2f2f2;padding-right:8px;p{margin:0 0 .7em}em.none{color:#777}mark{background:none;color:inherit;border-bottom:2px solid #555}mark[part~=cue-hit]{border-color:#6c6;color:#9d9}mark[part~=cue-next]{background:var(--deck-ui-accent, #ffd23f);color:#000;border-radius:3px;border-color:transparent}}dialog{border:0;padding:24px 30px 18px;border-radius:12px;background:#fff;color:#111;box-shadow:0 20px 60px #0000004d;font:15px/1.5 ui-sans-serif,system-ui,sans-serif;&::backdrop{background:#0000008c}h1{margin:0 0 12px;font-size:18px}td{padding:3px 14px 3px 0}td:first-child{color:#555;white-space:nowrap}kbd{display:inline-block;min-width:1.4em;padding:0 .35em;border:1px solid rgba(127,127,127,.45);border-bottom-width:2px;border-radius:4px;font:600 .85em/1.5 ui-monospace,monospace;text-align:center}}
|
|
2
|
+
;`;var L='<header part=header><span part=timer title="click to reset">00:00</span><span part=position></span> <button part="button prev-button" aria-label=Back>‹ Back</button><button part="button next-button" aria-label=Next>Next ›</button> <span part=clock></span></header><main part=main><section><div part="frame current-slide"><iframe tabindex=-1></iframe></div></section><aside part=sidebar><div><label part=label>Next</label><div class="frame next" part="frame next-slide"><iframe tabindex=-1></iframe><div class=end part=end>End of deck</div></div></div><div part=notes></div></aside></main><dialog closedby=any part=help aria-label="Keyboard shortcuts"><h1>Presenter view</h1><table><tr><td><kbd>→</kbd> <kbd>Space</kbd> <kbd>PgDn</kbd><td>next step<tr><td><kbd>←</kbd> <kbd>PgUp</kbd><td>back<tr><td><kbd>↓</kbd> <kbd>↑</kbd><td>next / previous slide<tr><td><kbd>Home</kbd> <kbd>End</kbd><td>first / last slide<tr><td>click the timer<td>reset it<tr><td><kbd>?</kbd><td>this help</table></dialog>;';var k=class extends HTMLElement{static define(r="deck-presenter",o=customElements){o.define(r,this)}#e=null;#t=null;async connectedCallback(){if(this.#e)return;let r=this.closest("deck-shift")??this.ownerDocument.querySelector("deck-shift");await customElements.whenDefined(r.localName),this.#e=r,this.addEventListener("command",o=>o.command==="--present"&&this.open()),this.#e.addShortcut("--present","s",()=>this.open(),"Open presenter mode")}open(){let r=open("","deck-presenter","popup,width=1400,height=860");if(!r||!this.#e)return;this.#t?.abort();let{signal:o}=this.#t=new AbortController,n=this.#e,a=r.document;a.title=`Presenter: ${document.title}`,a.body.replaceChildren(),a.body.style.margin="0";let x=a.body.appendChild(a.createElement("div"));x.style.setProperty("--deck-ui-accent",getComputedStyle(this).getPropertyValue("--deck-ui-accent"));let g=this.querySelector(":scope > template");g&&a.body.append(a.importNode(g.content,!0));let l=x.attachShadow({mode:"open"});l.innerHTML=`<style>${S}</style>`+L;let i=e=>l.querySelector(`[part~=${e}]`),[b,s]=l.querySelectorAll("iframe"),y=i("timer"),C=i("position"),K=i("clock"),d=i("notes"),u=i("help");i("prev-button").onclick=()=>n.prev(),i("next-button").onclick=()=>n.next(),r.addEventListener("keydown",e=>{if(e.key==="?")return u.open?u.close():u.showModal();if(/^[sf]$/i.test(e.key)||e.target.isContentEditable||/^(INPUT|TEXTAREA|SELECT)$/.test(e.target.tagName))return;let t=new KeyboardEvent("keydown",{key:e.key,code:e.code,shiftKey:e.shiftKey,altKey:e.altKey,ctrlKey:e.ctrlKey,metaKey:e.metaKey,cancelable:!0});dispatchEvent(t)||e.preventDefault()},{signal:o});let v=Date.now();y.onclick=()=>v=Date.now();let c=e=>String(e).padStart(2,"0"),N=setInterval(()=>{let e=Math.floor((Date.now()-v)/1e3),t=new Date;y.textContent=`${c(Math.floor(e/60))}:${c(e%60)}`,K.textContent=`${c(t.getHours())}:${c(t.getMinutes())}`},250);o.addEventListener("abort",()=>clearInterval(N));let w=(e,t)=>e.contentWindow?.postMessage({deck:"goto",...t},"*"),m=()=>{let{slide:e,step:t,slides:h}=n,q=h[e],M=n.script()[e].title,f=n.steps,p=t<f?[e,t+1]:e+1<h.length?[e+1,0]:null;C.textContent=`${e+1} / ${h.length}${f?` \xB7 step ${t+1} of ${f+1}`:""} \xB7 ${M}`,w(b,{slide:e,step:t}),s.parentElement.classList.toggle("over",!p),p&&w(s,{slide:p[0],step:p[1],animate:!1});let E=q.querySelector(":scope > aside.notes");if(!E)return void(d.innerHTML='<em class="none">No notes for this slide.</em>');d.replaceChildren(...a.importNode(E,!0).childNodes),d.querySelectorAll("mark").forEach((T,$)=>T.setAttribute("part",$<t?"cue cue-hit":$===t?"cue cue-next":"cue")),d.querySelector('mark[part~="cue-next"]')?.scrollIntoView({block:"center",behavior:"smooth"})};n.addEventListener("deckchange",m,{signal:o});for(let e of[b,s])e.addEventListener("load",m,{signal:o});r.addEventListener("pagehide",()=>this.#t.signal===o&&this.#t.abort(),{signal:o}),b.src=s.src=`${location.href.split("#")[0]}#${n.slide+1}.${n.step}`,m()}};new URL(import.meta.url).searchParams.has("nodefine")||k.define();export{k as DeckPresenter};
|
package/package.json
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "deck-presenter",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "A presenter window for a <deck-shift> slide deck: timer, current and next slide, and notes with cues.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"web-components",
|
|
7
|
+
"custom-elements",
|
|
8
|
+
"slides",
|
|
9
|
+
"presentation",
|
|
10
|
+
"speaker-notes"
|
|
11
|
+
],
|
|
12
|
+
"author": "Keith Cirkel",
|
|
13
|
+
"type": "module",
|
|
14
|
+
"exports": "./deck-presenter.min.js",
|
|
15
|
+
"files": [
|
|
16
|
+
"deck-presenter.min.js"
|
|
17
|
+
],
|
|
18
|
+
"scripts": {
|
|
19
|
+
"build": "node esbuild.js"
|
|
20
|
+
},
|
|
21
|
+
"peerDependencies": {
|
|
22
|
+
"deck-shift": "^0.1.0"
|
|
23
|
+
}
|
|
24
|
+
}
|