mirror of
https://github.com/renbaoshuo/S2OJ.git
synced 2024-12-23 23:11:52 +00:00
96d4a3ecf7
Due to historical reasons, the code is in subfolder "1". With SVN removal, we place the code back and remove the annoying "1" folder.
397 lines
8.8 KiB
HTML
397 lines
8.8 KiB
HTML
<!doctype html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="utf-8">
|
|
|
|
<title>reveal.js - Slide Notes</title>
|
|
|
|
<style>
|
|
body {
|
|
font-family: Helvetica;
|
|
}
|
|
|
|
#current-slide,
|
|
#upcoming-slide,
|
|
#speaker-controls {
|
|
padding: 6px;
|
|
box-sizing: border-box;
|
|
-moz-box-sizing: border-box;
|
|
}
|
|
|
|
#current-slide iframe,
|
|
#upcoming-slide iframe {
|
|
width: 100%;
|
|
height: 100%;
|
|
border: 1px solid #ddd;
|
|
}
|
|
|
|
#current-slide .label,
|
|
#upcoming-slide .label {
|
|
position: absolute;
|
|
top: 10px;
|
|
left: 10px;
|
|
font-weight: bold;
|
|
font-size: 14px;
|
|
z-index: 2;
|
|
color: rgba( 255, 255, 255, 0.9 );
|
|
}
|
|
|
|
#current-slide {
|
|
position: absolute;
|
|
width: 65%;
|
|
height: 100%;
|
|
top: 0;
|
|
left: 0;
|
|
padding-right: 0;
|
|
}
|
|
|
|
#upcoming-slide {
|
|
position: absolute;
|
|
width: 35%;
|
|
height: 40%;
|
|
right: 0;
|
|
top: 0;
|
|
}
|
|
|
|
#speaker-controls {
|
|
position: absolute;
|
|
top: 40%;
|
|
right: 0;
|
|
width: 35%;
|
|
height: 60%;
|
|
|
|
font-size: 18px;
|
|
}
|
|
|
|
.speaker-controls-time.hidden,
|
|
.speaker-controls-notes.hidden {
|
|
display: none;
|
|
}
|
|
|
|
.speaker-controls-time .label,
|
|
.speaker-controls-notes .label {
|
|
text-transform: uppercase;
|
|
font-weight: normal;
|
|
font-size: 0.66em;
|
|
color: #666;
|
|
margin: 0;
|
|
}
|
|
|
|
.speaker-controls-time {
|
|
border-bottom: 1px solid rgba( 200, 200, 200, 0.5 );
|
|
margin-bottom: 10px;
|
|
padding: 10px 16px;
|
|
padding-bottom: 20px;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.speaker-controls-time .reset-button {
|
|
opacity: 0;
|
|
float: right;
|
|
color: #666;
|
|
text-decoration: none;
|
|
}
|
|
.speaker-controls-time:hover .reset-button {
|
|
opacity: 1;
|
|
}
|
|
|
|
.speaker-controls-time .timer,
|
|
.speaker-controls-time .clock {
|
|
width: 50%;
|
|
font-size: 1.9em;
|
|
}
|
|
|
|
.speaker-controls-time .timer {
|
|
float: left;
|
|
}
|
|
|
|
.speaker-controls-time .clock {
|
|
float: right;
|
|
text-align: right;
|
|
}
|
|
|
|
.speaker-controls-time span.mute {
|
|
color: #bbb;
|
|
}
|
|
|
|
.speaker-controls-notes {
|
|
padding: 10px 16px;
|
|
}
|
|
|
|
.speaker-controls-notes .value {
|
|
margin-top: 5px;
|
|
line-height: 1.4;
|
|
font-size: 1.2em;
|
|
}
|
|
|
|
.clear {
|
|
clear: both;
|
|
}
|
|
|
|
@media screen and (max-width: 1080px) {
|
|
#speaker-controls {
|
|
font-size: 16px;
|
|
}
|
|
}
|
|
|
|
@media screen and (max-width: 900px) {
|
|
#speaker-controls {
|
|
font-size: 14px;
|
|
}
|
|
}
|
|
|
|
@media screen and (max-width: 800px) {
|
|
#speaker-controls {
|
|
font-size: 12px;
|
|
}
|
|
}
|
|
|
|
</style>
|
|
</head>
|
|
|
|
<body>
|
|
|
|
<div id="current-slide"></div>
|
|
<div id="upcoming-slide"><span class="label">UPCOMING:</span></div>
|
|
<div id="speaker-controls">
|
|
<div class="speaker-controls-time">
|
|
<h4 class="label">Time <span class="reset-button">Click to Reset</span></h4>
|
|
<div class="clock">
|
|
<span class="clock-value">0:00 AM</span>
|
|
</div>
|
|
<div class="timer">
|
|
<span class="hours-value">00</span><span class="minutes-value">:00</span><span class="seconds-value">:00</span>
|
|
</div>
|
|
<div class="clear"></div>
|
|
</div>
|
|
|
|
<div class="speaker-controls-notes hidden">
|
|
<h4 class="label">Notes</h4>
|
|
<div class="value"></div>
|
|
</div>
|
|
</div>
|
|
|
|
<script src="/socket.io/socket.io.js"></script>
|
|
<script src="/plugin/markdown/marked.js"></script>
|
|
|
|
<script>
|
|
(function() {
|
|
|
|
var notes,
|
|
notesValue,
|
|
currentState,
|
|
currentSlide,
|
|
upcomingSlide,
|
|
connected = false;
|
|
|
|
var socket = io.connect( window.location.origin ),
|
|
socketId = '{{socketId}}';
|
|
|
|
socket.on( 'statechanged', function( data ) {
|
|
|
|
// ignore data from sockets that aren't ours
|
|
if( data.socketId !== socketId ) { return; }
|
|
|
|
if( connected === false ) {
|
|
connected = true;
|
|
|
|
setupIframes( data );
|
|
setupKeyboard();
|
|
setupNotes();
|
|
setupTimer();
|
|
|
|
}
|
|
|
|
handleStateMessage( data );
|
|
|
|
} );
|
|
|
|
window.addEventListener( 'message', function( event ) {
|
|
|
|
var data = JSON.parse( event.data );
|
|
|
|
if( data && data.namespace === 'reveal' ) {
|
|
if( /ready/.test( data.eventName ) ) {
|
|
socket.emit( 'connect', { socketId: socketId } );
|
|
}
|
|
}
|
|
|
|
} );
|
|
|
|
/**
|
|
* Called when the main window sends an updated state.
|
|
*/
|
|
function handleStateMessage( data ) {
|
|
|
|
// Store the most recently set state to avoid circular loops
|
|
// applying the same state
|
|
currentState = JSON.stringify( data.state );
|
|
|
|
// No need for updating the notes in case of fragment changes
|
|
if ( data.notes ) {
|
|
notes.classList.remove( 'hidden' );
|
|
if( data.markdown ) {
|
|
notesValue.innerHTML = marked( data.notes );
|
|
}
|
|
else {
|
|
notesValue.innerHTML = data.notes;
|
|
}
|
|
}
|
|
else {
|
|
notes.classList.add( 'hidden' );
|
|
}
|
|
|
|
// Update the note slides
|
|
currentSlide.contentWindow.postMessage( JSON.stringify({ method: 'setState', args: [ data.state ] }), '*' );
|
|
upcomingSlide.contentWindow.postMessage( JSON.stringify({ method: 'setState', args: [ data.state ] }), '*' );
|
|
upcomingSlide.contentWindow.postMessage( JSON.stringify({ method: 'next' }), '*' );
|
|
|
|
}
|
|
|
|
// Limit to max one state update per X ms
|
|
handleStateMessage = debounce( handleStateMessage, 200 );
|
|
|
|
/**
|
|
* Forward keyboard events to the current slide window.
|
|
* This enables keyboard events to work even if focus
|
|
* isn't set on the current slide iframe.
|
|
*/
|
|
function setupKeyboard() {
|
|
|
|
document.addEventListener( 'keydown', function( event ) {
|
|
currentSlide.contentWindow.postMessage( JSON.stringify({ method: 'triggerKey', args: [ event.keyCode ] }), '*' );
|
|
} );
|
|
|
|
}
|
|
|
|
/**
|
|
* Creates the preview iframes.
|
|
*/
|
|
function setupIframes( data ) {
|
|
|
|
var params = [
|
|
'receiver',
|
|
'progress=false',
|
|
'history=false',
|
|
'transition=none',
|
|
'backgroundTransition=none'
|
|
].join( '&' );
|
|
|
|
var hash = '#/' + data.state.indexh + '/' + data.state.indexv;
|
|
var currentURL = '/?' + params + '&postMessageEvents=true' + hash;
|
|
var upcomingURL = '/?' + params + '&controls=false' + hash;
|
|
|
|
currentSlide = document.createElement( 'iframe' );
|
|
currentSlide.setAttribute( 'width', 1280 );
|
|
currentSlide.setAttribute( 'height', 1024 );
|
|
currentSlide.setAttribute( 'src', currentURL );
|
|
document.querySelector( '#current-slide' ).appendChild( currentSlide );
|
|
|
|
upcomingSlide = document.createElement( 'iframe' );
|
|
upcomingSlide.setAttribute( 'width', 640 );
|
|
upcomingSlide.setAttribute( 'height', 512 );
|
|
upcomingSlide.setAttribute( 'src', upcomingURL );
|
|
document.querySelector( '#upcoming-slide' ).appendChild( upcomingSlide );
|
|
|
|
}
|
|
|
|
/**
|
|
* Setup the notes UI.
|
|
*/
|
|
function setupNotes() {
|
|
|
|
notes = document.querySelector( '.speaker-controls-notes' );
|
|
notesValue = document.querySelector( '.speaker-controls-notes .value' );
|
|
|
|
}
|
|
|
|
/**
|
|
* Create the timer and clock and start updating them
|
|
* at an interval.
|
|
*/
|
|
function setupTimer() {
|
|
|
|
var start = new Date(),
|
|
timeEl = document.querySelector( '.speaker-controls-time' ),
|
|
clockEl = timeEl.querySelector( '.clock-value' ),
|
|
hoursEl = timeEl.querySelector( '.hours-value' ),
|
|
minutesEl = timeEl.querySelector( '.minutes-value' ),
|
|
secondsEl = timeEl.querySelector( '.seconds-value' );
|
|
|
|
function _updateTimer() {
|
|
|
|
var diff, hours, minutes, seconds,
|
|
now = new Date();
|
|
|
|
diff = now.getTime() - start.getTime();
|
|
hours = Math.floor( diff / ( 1000 * 60 * 60 ) );
|
|
minutes = Math.floor( ( diff / ( 1000 * 60 ) ) % 60 );
|
|
seconds = Math.floor( ( diff / 1000 ) % 60 );
|
|
|
|
clockEl.innerHTML = now.toLocaleTimeString( 'en-US', { hour12: true, hour: '2-digit', minute:'2-digit' } );
|
|
hoursEl.innerHTML = zeroPadInteger( hours );
|
|
hoursEl.className = hours > 0 ? '' : 'mute';
|
|
minutesEl.innerHTML = ':' + zeroPadInteger( minutes );
|
|
minutesEl.className = minutes > 0 ? '' : 'mute';
|
|
secondsEl.innerHTML = ':' + zeroPadInteger( seconds );
|
|
|
|
}
|
|
|
|
// Update once directly
|
|
_updateTimer();
|
|
|
|
// Then update every second
|
|
setInterval( _updateTimer, 1000 );
|
|
|
|
timeEl.addEventListener( 'click', function() {
|
|
start = new Date();
|
|
_updateTimer();
|
|
return false;
|
|
} );
|
|
|
|
}
|
|
|
|
function zeroPadInteger( num ) {
|
|
|
|
var str = '00' + parseInt( num );
|
|
return str.substring( str.length - 2 );
|
|
|
|
}
|
|
|
|
/**
|
|
* Limits the frequency at which a function can be called.
|
|
*/
|
|
function debounce( fn, ms ) {
|
|
|
|
var lastTime = 0,
|
|
timeout;
|
|
|
|
return function() {
|
|
|
|
var args = arguments;
|
|
var context = this;
|
|
|
|
clearTimeout( timeout );
|
|
|
|
var timeSinceLastCall = Date.now() - lastTime;
|
|
if( timeSinceLastCall > ms ) {
|
|
fn.apply( context, args );
|
|
lastTime = Date.now();
|
|
}
|
|
else {
|
|
timeout = setTimeout( function() {
|
|
fn.apply( context, args );
|
|
lastTime = Date.now();
|
|
}, ms - timeSinceLastCall );
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
})();
|
|
</script>
|
|
|
|
</body>
|
|
</html>
|