2014-06-24 18:34:34 +00:00
|
|
|
/** @jsx React.DOM */
|
|
|
|
|
|
|
|
(function(exports) {
|
|
|
|
var dom = React.DOM;
|
|
|
|
|
|
|
|
var PlaybackControlButton = React.createClass({ displayName: 'PlayButton',
|
|
|
|
// props: playing, onPauseClick, onResumeClick
|
|
|
|
|
|
|
|
render: function() {
|
|
|
|
var icon;
|
|
|
|
|
|
|
|
if (this.props.playing) {
|
|
|
|
icon = asciinema.PauseIcon();
|
|
|
|
} else {
|
|
|
|
icon = asciinema.PlayIcon();
|
|
|
|
}
|
|
|
|
|
|
|
|
return dom.span({ className: "playback-button", onClick: this.handleClick }, icon);
|
|
|
|
},
|
|
|
|
|
|
|
|
handleClick: function(event) {
|
|
|
|
event.preventDefault();
|
|
|
|
|
|
|
|
if (this.props.playing) {
|
|
|
|
this.props.onPauseClick();
|
|
|
|
} else {
|
|
|
|
this.props.onResumeClick();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
var FullscreenToggleButton = React.createClass({ displayName: 'FullscreenToggleButton',
|
|
|
|
// props: fullscreen, onClick
|
|
|
|
|
|
|
|
render: function() {
|
|
|
|
var icon;
|
|
|
|
|
|
|
|
if (this.props.fullscreen) {
|
|
|
|
icon = asciinema.ShrinkIcon();
|
|
|
|
} else {
|
|
|
|
icon = asciinema.ExpandIcon();
|
|
|
|
}
|
|
|
|
|
|
|
|
return dom.span({ className: "fullscreen-button", onClick: this.handleClick }, icon);
|
|
|
|
},
|
|
|
|
|
|
|
|
handleClick: function(event) {
|
|
|
|
event.preventDefault();
|
|
|
|
this.props.onClick();
|
|
|
|
},
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
exports.ControlBar = React.createClass({ displayName: 'ControlBar',
|
|
|
|
// props: playing, fullscreen, currentTime, totalTime, onPauseClick,
|
|
|
|
// onResumeClick, onSeekClick, toggleFullscreen
|
|
|
|
|
|
|
|
render: function() {
|
|
|
|
return (
|
|
|
|
dom.div({ className: "control-bar" },
|
|
|
|
|
|
|
|
PlaybackControlButton({
|
|
|
|
playing: this.props.playing,
|
|
|
|
onPauseClick: this.props.onPauseClick,
|
|
|
|
onResumeClick: this.props.onResumeClick
|
|
|
|
}),
|
|
|
|
|
|
|
|
asciinema.Timer({
|
|
|
|
currentTime: this.props.currentTime,
|
|
|
|
totalTime: this.props.totalTime
|
|
|
|
}),
|
|
|
|
|
|
|
|
FullscreenToggleButton({
|
|
|
|
fullscreen: this.props.fullscreen,
|
|
|
|
onClick: this.props.toggleFullscreen,
|
|
|
|
}),
|
|
|
|
|
|
|
|
asciinema.ProgressBar({
|
|
|
|
value: this.props.currentTime / this.props.totalTime,
|
|
|
|
onClick: this.handleSeek
|
|
|
|
})
|
|
|
|
|
|
|
|
)
|
|
|
|
)
|
|
|
|
},
|
|
|
|
|
|
|
|
handleSeek: function(value) {
|
|
|
|
this.props.onSeekClick(value * this.props.totalTime);
|
|
|
|
},
|
|
|
|
|
|
|
|
shouldComponentUpdate: function(nextProps, nextState) {
|
|
|
|
return nextProps.playing != this.props.playing ||
|
|
|
|
nextProps.currentTime != this.props.currentTime ||
|
|
|
|
nextProps.totalTime != this.props.totalTime ||
|
|
|
|
nextProps.fullscreen != this.props.fullscreen;
|
|
|
|
},
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
})(window.asciinema = window.asciinema || {});
|
|
|
|
|
|
|
|
(function(exports) {
|
|
|
|
var dom = React.DOM;
|
|
|
|
|
|
|
|
exports.Cursor = React.createClass({ displayName: 'Cursor',
|
|
|
|
// props: fg, bg, char, inverse
|
|
|
|
|
|
|
|
render: function() {
|
|
|
|
return dom.span({ className: this.className() }, this.props.char);
|
|
|
|
},
|
|
|
|
|
|
|
|
className: function() {
|
|
|
|
if (this.props.inverse) {
|
|
|
|
return "cursor fg-" + this.props.fg + " bg-" + this.props.bg;
|
|
|
|
} else {
|
|
|
|
return "cursor fg-" + this.props.bg + " bg-" + this.props.fg;
|
|
|
|
}
|
|
|
|
},
|
|
|
|
});
|
|
|
|
|
|
|
|
})(window.asciinema = window.asciinema || {});
|
|
|
|
|
|
|
|
(function(exports) {
|
|
|
|
|
|
|
|
function FunnyMovie() {
|
|
|
|
// this.onFrame = onFrame;
|
|
|
|
this.n = 0;
|
|
|
|
this.direction = 1
|
|
|
|
}
|
|
|
|
|
|
|
|
FunnyMovie.prototype.start = function(onFrame) {
|
|
|
|
setInterval(function() {
|
|
|
|
this.generateFrame(onFrame);
|
|
|
|
}.bind(this), 100);
|
|
|
|
}
|
|
|
|
|
|
|
|
FunnyMovie.prototype.generateFrame = function(onFrame) {
|
|
|
|
var lines = {};
|
|
|
|
lines[this.n] = [[(new Date()).toString(), {}]];
|
|
|
|
onFrame({ lines: lines });
|
|
|
|
|
|
|
|
this.n += this.direction;
|
|
|
|
if (this.n < 0 || this.n >= 10) {
|
|
|
|
this.direction *= -1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
FunnyMovie.prototype.pause = function() {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
FunnyMovie.prototype.resume = function() {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
FunnyMovie.prototype.seek = function(time) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
exports.FunnyMovie = FunnyMovie;
|
|
|
|
|
|
|
|
})(window.asciinema = window.asciinema || {});
|
|
|
|
|
|
|
|
(function(exports) {
|
|
|
|
|
|
|
|
function HttpArraySource(url, speed) {
|
|
|
|
this.url = url;
|
|
|
|
this.speed = speed || 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
HttpArraySource.prototype.start = function(onFrame, onFinish, setLoading) {
|
|
|
|
var controller;
|
|
|
|
|
|
|
|
if (this.data) {
|
|
|
|
controller = this.createController(onFrame, onFinish);
|
|
|
|
} else {
|
|
|
|
this.fetchData(setLoading, function() {
|
|
|
|
controller = this.createController(onFrame, onFinish);
|
|
|
|
}.bind(this));
|
|
|
|
}
|
|
|
|
|
|
|
|
return {
|
|
|
|
time: function() {
|
|
|
|
if (controller && controller.time) {
|
|
|
|
return controller.time();
|
2014-06-25 15:26:14 +00:00
|
|
|
} else {
|
|
|
|
return 0;
|
2014-06-24 18:34:34 +00:00
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
pause: function() {
|
|
|
|
if (controller && controller.pause) {
|
|
|
|
return controller.pause();
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
resume: function() {
|
|
|
|
if (controller && controller.resume) {
|
|
|
|
return controller.resume();
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
seek: function(time) {
|
|
|
|
if (controller && controller.seek) {
|
|
|
|
return controller.seek(time);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
HttpArraySource.prototype.fetchData = function(setLoading, onResult) {
|
|
|
|
setLoading(true);
|
|
|
|
|
|
|
|
var request = $.ajax({ url: this.url, dataType: 'json' });
|
|
|
|
|
|
|
|
request.done(function(data) {
|
|
|
|
setLoading(false);
|
|
|
|
this.data = data;
|
|
|
|
onResult();
|
|
|
|
}.bind(this));
|
|
|
|
|
|
|
|
request.fail(function(jqXHR, textStatus) {
|
|
|
|
setLoading(false);
|
|
|
|
console.error(this.url, textStatus);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
HttpArraySource.prototype.createController = function(onFrame, onFinish) {
|
|
|
|
arraySource = new asciinema.NavigableArraySource(this.data, this.speed);
|
|
|
|
|
|
|
|
return arraySource.start(onFrame, onFinish);
|
|
|
|
}
|
|
|
|
|
|
|
|
exports.HttpArraySource = HttpArraySource;
|
|
|
|
|
|
|
|
})(window.asciinema = window.asciinema || {});
|
|
|
|
|
|
|
|
/** @jsx React.DOM */
|
|
|
|
|
|
|
|
(function(exports) {
|
|
|
|
var dom = React.DOM;
|
|
|
|
|
|
|
|
exports.PlayIcon = React.createClass({ displayName: 'PlayIcon',
|
|
|
|
|
|
|
|
render: function() {
|
|
|
|
return (
|
|
|
|
dom.svg({ version: "1.1", xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 12 12", className: "icon" },
|
|
|
|
dom.path({ d: "M1,0 L11,6 L1,12 Z" })
|
|
|
|
)
|
|
|
|
)
|
|
|
|
},
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
exports.PauseIcon = React.createClass({ displayName: 'PauseIcon',
|
|
|
|
|
|
|
|
render: function() {
|
|
|
|
return (
|
|
|
|
dom.svg({ version: "1.1", xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 12 12", className: "icon" },
|
|
|
|
dom.path({ d: "M1,0 L4,0 L4,12 L1,12 Z" }),
|
|
|
|
dom.path({ d: "M8,0 L11,0 L11,12 L8,12 Z" })
|
|
|
|
)
|
|
|
|
)
|
|
|
|
},
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
exports.ExpandIcon = React.createClass({ displayName: 'ExpandIcon',
|
|
|
|
|
|
|
|
render: function() {
|
|
|
|
return (
|
|
|
|
dom.svg({ version: "1.1", xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 12 12", className: "icon" },
|
|
|
|
dom.path({ d: "M0,0 L5,0 L3,2 L5,4 L4,5 L2,3 L0,5 Z" }),
|
|
|
|
dom.path({ d: "M12,12 L12,7 L10,9 L8,7 L7,8 L9,10 L7,12 Z" })
|
|
|
|
)
|
|
|
|
)
|
|
|
|
},
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
exports.ShrinkIcon = React.createClass({ displayName: 'ShrinkIcon',
|
|
|
|
|
|
|
|
render: function() {
|
|
|
|
return (
|
|
|
|
dom.svg({ version: "1.1", xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 12 12", className: "icon" },
|
|
|
|
dom.path({ d: "M5,5 L5,0 L3,2 L1,0 L0,1 L2,3 L0,5 Z" }),
|
|
|
|
dom.path({ d: "M7,7 L12,7 L10,9 L12,11 L11,12 L9,10 L7,12 Z" })
|
|
|
|
)
|
|
|
|
)
|
|
|
|
},
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
})(window.asciinema = window.asciinema || {});
|
|
|
|
|
|
|
|
(function(exports) {
|
|
|
|
var dom = React.DOM;
|
|
|
|
|
|
|
|
exports.Line = React.createClass({ displayName: 'Line',
|
|
|
|
// props: parts, cursorX, cursorInverted
|
|
|
|
|
|
|
|
render: function() {
|
|
|
|
var lineLength = 0;
|
|
|
|
var cursorX = this.props.cursorX;
|
|
|
|
|
|
|
|
var parts = this.props.parts.map(function(part, index) {
|
|
|
|
var attrs = {};
|
|
|
|
// clone attrs, so we can adjust it below
|
|
|
|
for (key in part[1]) {
|
|
|
|
attrs[key] = part[1][key];
|
|
|
|
}
|
|
|
|
|
|
|
|
var partProps = { text: part[0], attrs: attrs };
|
|
|
|
var partLength = part[0].length;
|
|
|
|
|
|
|
|
if (cursorX !== null) {
|
|
|
|
if (lineLength <= cursorX && cursorX < lineLength + partLength) {
|
|
|
|
partProps.cursorX = cursorX - lineLength;
|
|
|
|
partProps.cursorInverted = this.props.cursorInverted;
|
|
|
|
|
|
|
|
// TODO: remove this hack and update terminal.c to do this instead
|
|
|
|
if (attrs.inverse) {
|
|
|
|
delete attrs.inverse;
|
|
|
|
} else {
|
|
|
|
attrs.inverse = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
lineLength += partLength;
|
|
|
|
|
|
|
|
return asciinema.Part(partProps);
|
|
|
|
}.bind(this));
|
|
|
|
|
|
|
|
return dom.span({ className: "line" }, parts);
|
|
|
|
},
|
|
|
|
|
|
|
|
});
|
|
|
|
})(window.asciinema = window.asciinema || {});
|
|
|
|
|
|
|
|
(function(exports) {
|
|
|
|
|
|
|
|
function Movie(width, height, source, snapshot, totalTime) {
|
|
|
|
this.width = width;
|
|
|
|
this.height = height;
|
|
|
|
this.source = source;
|
|
|
|
this.snapshot = snapshot;
|
|
|
|
this.totalTime = totalTime;
|
|
|
|
}
|
|
|
|
|
|
|
|
Movie.prototype.start = function(onFrame, onFinish, setTime, setLoading) {
|
|
|
|
var timeIntervalId;
|
|
|
|
|
|
|
|
function onSourceFinish() {
|
|
|
|
clearInterval(timeIntervalId);
|
|
|
|
onFinish();
|
|
|
|
}
|
|
|
|
|
|
|
|
var controller = this.source.start(onFrame, onSourceFinish, setLoading);
|
|
|
|
|
|
|
|
timeIntervalId = setInterval(function() {
|
|
|
|
setTime(controller.time());
|
|
|
|
}, 300);
|
|
|
|
|
|
|
|
return controller;
|
|
|
|
}
|
|
|
|
|
|
|
|
exports.Movie = Movie;
|
|
|
|
|
|
|
|
})(window.asciinema = window.asciinema || {});
|
|
|
|
|
|
|
|
// var source = new ArraySource([]);
|
|
|
|
// var source = new CamSource(80, 24);
|
|
|
|
// var source = new WebsocketSource(url);
|
|
|
|
|
|
|
|
// var movie = new Movie(80, 24, source, [], 123.456);
|
|
|
|
|
|
|
|
// var controller = source.start(onFrame, onFinish, setLoading);
|
|
|
|
// controller.pause();
|
|
|
|
|
|
|
|
(function(exports) {
|
|
|
|
|
|
|
|
function now() {
|
|
|
|
return (new Date).getTime() / 1000;
|
|
|
|
}
|
|
|
|
|
|
|
|
function play(frames, speed, onFrame, onFinish) {
|
|
|
|
var frameNo = 0;
|
|
|
|
var startedAt = new Date;
|
|
|
|
var timeoutId;
|
|
|
|
|
|
|
|
function generate() {
|
|
|
|
var frame = frames[frameNo];
|
|
|
|
|
|
|
|
if (!frame) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
onFrame(frame[0], frame[1]);
|
|
|
|
|
|
|
|
frameNo += 1;
|
|
|
|
scheduleNextFrame();
|
|
|
|
}
|
|
|
|
|
|
|
|
function scheduleNextFrame() {
|
|
|
|
var frame = frames[frameNo];
|
|
|
|
|
|
|
|
if (frame) {
|
|
|
|
timeoutId = setTimeout(generate, frames[frameNo][0] * 1000 / speed);
|
|
|
|
} else {
|
|
|
|
onFinish();
|
|
|
|
|
|
|
|
if (window.console) {
|
|
|
|
window.console.log('finished in ' + ((new Date).getTime() - startedAt.getTime()));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function stop() {
|
|
|
|
clearTimeout(timeoutId);
|
|
|
|
}
|
|
|
|
|
|
|
|
scheduleNextFrame();
|
|
|
|
|
|
|
|
return stop;
|
|
|
|
}
|
|
|
|
|
|
|
|
function NavigableArraySource(frames, speed) {
|
|
|
|
this.frames = frames;
|
|
|
|
this.speed = speed || 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
NavigableArraySource.prototype.start = function(onFrame, onFinish, setLoading) {
|
|
|
|
var elapsedTime = 0;
|
|
|
|
var currentFramePauseTime;
|
|
|
|
var lastFrameTime;
|
|
|
|
var paused = false;
|
|
|
|
var finished = false;
|
|
|
|
var stop;
|
|
|
|
|
|
|
|
var playFrom = function(time) {
|
2014-06-25 15:17:30 +00:00
|
|
|
lastFrameTime = now();
|
2014-06-24 18:34:34 +00:00
|
|
|
elapsedTime = time;
|
|
|
|
|
|
|
|
return play(this.framesFrom(time), this.speed, function(delay, changes) {
|
|
|
|
lastFrameTime = now();
|
|
|
|
elapsedTime += delay;
|
|
|
|
onFrame(changes);
|
|
|
|
}, function() {
|
|
|
|
finished = true;
|
|
|
|
onFinish();
|
|
|
|
});
|
|
|
|
}.bind(this);
|
|
|
|
|
|
|
|
var currentFrameTime = function() {
|
|
|
|
return (now() - lastFrameTime) * this.speed;
|
|
|
|
}.bind(this);
|
|
|
|
|
|
|
|
stop = playFrom(0);
|
|
|
|
|
|
|
|
return {
|
|
|
|
pause: function() {
|
|
|
|
if (finished) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
paused = true;
|
|
|
|
stop();
|
|
|
|
currentFramePauseTime = currentFrameTime();
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}.bind(this),
|
|
|
|
|
|
|
|
resume: function() {
|
|
|
|
if (finished) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
paused = false;
|
|
|
|
stop = playFrom(elapsedTime + currentFramePauseTime);
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}.bind(this),
|
|
|
|
|
|
|
|
seek: function(seconds) {
|
|
|
|
if (finished) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
paused = false;
|
|
|
|
stop();
|
|
|
|
stop = playFrom(seconds);
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}.bind(this),
|
|
|
|
|
|
|
|
time: function() {
|
|
|
|
if (finished) {
|
|
|
|
return elapsedTime;
|
|
|
|
} else if (paused) {
|
|
|
|
return elapsedTime + currentFramePauseTime;
|
|
|
|
} else {
|
|
|
|
return elapsedTime + currentFrameTime();
|
|
|
|
}
|
|
|
|
}.bind(this),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
NavigableArraySource.prototype.framesFrom = function(fromTime) {
|
|
|
|
var frameNo = 0;
|
|
|
|
var currentTime = 0;
|
|
|
|
var changes = {};
|
|
|
|
|
|
|
|
while (currentTime + this.frames[frameNo][0] < fromTime) {
|
|
|
|
var frame = this.frames[frameNo];
|
|
|
|
currentTime += frame[0];
|
|
|
|
asciinema.mergeChanges(changes, frame[1]);
|
|
|
|
frameNo += 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
var frames = [[0, changes]];
|
|
|
|
|
|
|
|
var nextFrame = this.frames[frameNo];
|
|
|
|
var delay = nextFrame[0] - (fromTime - currentTime);
|
|
|
|
frames = frames.concat([[delay, nextFrame[1]]]);
|
|
|
|
|
|
|
|
frames = frames.concat(this.frames.slice(frameNo + 1));
|
|
|
|
|
|
|
|
return frames;
|
|
|
|
}
|
|
|
|
|
|
|
|
exports.NavigableArraySource = NavigableArraySource;
|
|
|
|
|
|
|
|
})(window.asciinema = window.asciinema || {});
|
|
|
|
|
|
|
|
(function(exports) {
|
|
|
|
var dom = React.DOM;
|
|
|
|
|
|
|
|
exports.LoadingOverlay = React.createClass({ displayName: 'LoadingOverlay',
|
|
|
|
|
|
|
|
render: function() {
|
|
|
|
return (
|
|
|
|
dom.div({ className: "loading" },
|
|
|
|
dom.div({ className: "loader" })
|
|
|
|
)
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
exports.StartOverlay = React.createClass({ displayName: 'StartOverlay',
|
|
|
|
// props: start
|
|
|
|
|
|
|
|
render: function() {
|
|
|
|
return (
|
|
|
|
dom.div({ className: "start-prompt", onClick: this.onClick },
|
|
|
|
dom.div({ className: "play-button" },
|
|
|
|
dom.div(null,
|
|
|
|
dom.span(null,
|
|
|
|
asciinema.PlayIcon()
|
|
|
|
)
|
|
|
|
)
|
|
|
|
)
|
|
|
|
)
|
|
|
|
);
|
|
|
|
},
|
|
|
|
|
|
|
|
onClick: function(event) {
|
|
|
|
event.preventDefault();
|
|
|
|
this.props.start();
|
|
|
|
},
|
|
|
|
|
|
|
|
});
|
|
|
|
})(window.asciinema = window.asciinema || {});
|
|
|
|
|
|
|
|
(function(exports) {
|
|
|
|
var dom = React.DOM;
|
|
|
|
|
|
|
|
exports.Part = React.createClass({ displayName: 'Part',
|
|
|
|
// props: text, attrs, cursorX, cursorInverted
|
|
|
|
|
|
|
|
render: function() {
|
|
|
|
return dom.span({ className: this.className() }, this.children());
|
|
|
|
},
|
|
|
|
|
|
|
|
children: function() {
|
|
|
|
var text = this.props.text;
|
|
|
|
var cursorX = this.props.cursorX;
|
|
|
|
|
|
|
|
if (cursorX !== undefined) {
|
|
|
|
var elements = [];
|
|
|
|
|
|
|
|
if (cursorX > 0) {
|
|
|
|
elements = elements.concat([text.slice(0, cursorX)])
|
|
|
|
}
|
|
|
|
|
|
|
|
var cursor = asciinema.Cursor({
|
|
|
|
fg: this.fgColor() || 'fg',
|
|
|
|
bg: this.bgColor() || 'bg',
|
|
|
|
char: text[cursorX],
|
|
|
|
inverse: this.props.cursorInverted,
|
|
|
|
});
|
|
|
|
|
|
|
|
elements = elements.concat([cursor]);
|
|
|
|
|
|
|
|
if (cursorX + 1 < text.length) {
|
|
|
|
elements = elements.concat([text.slice(cursorX + 1)]);
|
|
|
|
}
|
|
|
|
|
|
|
|
return elements;
|
|
|
|
} else {
|
|
|
|
return this.props.text;
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
fgColor: function() {
|
|
|
|
var fg = this.props.attrs.fg;
|
|
|
|
|
|
|
|
if (this.props.attrs.bold && fg !== undefined && fg < 8) {
|
|
|
|
fg += 8;
|
|
|
|
}
|
|
|
|
|
|
|
|
return fg;
|
|
|
|
},
|
|
|
|
|
|
|
|
bgColor: function() {
|
|
|
|
var bg = this.props.attrs.bg;
|
|
|
|
|
|
|
|
if (this.props.attrs.blink && bg !== undefined && bg < 8) {
|
|
|
|
bg += 8;
|
|
|
|
}
|
|
|
|
|
|
|
|
return bg;
|
|
|
|
},
|
|
|
|
|
|
|
|
className: function() {
|
|
|
|
var classes = [];
|
|
|
|
var attrs = this.props.attrs;
|
|
|
|
|
|
|
|
var fg = this.fgColor();
|
|
|
|
var bg = this.bgColor();
|
|
|
|
|
|
|
|
if (attrs.inverse) {
|
|
|
|
var fgClass, bgClass;
|
|
|
|
|
|
|
|
if (bg !== undefined) {
|
|
|
|
fgClass = 'fg-' + bg;
|
|
|
|
} else {
|
|
|
|
fgClass = 'fg-bg';
|
|
|
|
}
|
|
|
|
|
|
|
|
if (fg !== undefined) {
|
|
|
|
bgClass = 'bg-' + fg;
|
|
|
|
} else {
|
|
|
|
bgClass = 'bg-fg';
|
|
|
|
}
|
|
|
|
|
|
|
|
classes = classes.concat([fgClass, bgClass]);
|
|
|
|
} else {
|
|
|
|
if (fg !== undefined) {
|
|
|
|
classes = classes.concat(['fg-' + fg]);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (bg !== undefined) {
|
|
|
|
classes = classes.concat(['bg-' + bg]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (attrs.bold) {
|
|
|
|
classes = classes.concat(['bright']);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (attrs.underline) {
|
|
|
|
classes = classes.concat(['underline']);
|
|
|
|
}
|
|
|
|
|
|
|
|
return classes.join(' ');
|
|
|
|
}
|
|
|
|
|
|
|
|
});
|
|
|
|
})(window.asciinema = window.asciinema || {});
|
|
|
|
|
|
|
|
(function(exports) {
|
|
|
|
var dom = React.DOM;
|
|
|
|
|
|
|
|
exports.Player = React.createClass({ displayName: 'Player',
|
|
|
|
// props: movie, autoPlay, fontSize, theme
|
|
|
|
|
|
|
|
getInitialState: function() {
|
|
|
|
var lines = this.props.movie.snapshot || [];
|
|
|
|
var cursor = { x: 0, y: 0, visible: false };
|
|
|
|
var fontSize = this.props.fontSize || 'small';
|
|
|
|
|
|
|
|
return {
|
|
|
|
lines: lines,
|
|
|
|
cursor: cursor,
|
|
|
|
fontSize: fontSize,
|
|
|
|
fullscreen: false,
|
|
|
|
loading: false,
|
|
|
|
state: 'not-started',
|
|
|
|
currentTime: 0,
|
|
|
|
totalTime: this.props.movie.totalTime,
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
componentWillMount: function() {
|
|
|
|
if (this.props.autoPlay) {
|
|
|
|
this.start();
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
componentDidMount: function() {
|
|
|
|
if (screenfull.enabled) {
|
|
|
|
document.addEventListener(screenfull.raw.fullscreenchange, function() {
|
|
|
|
this.setState({ fullscreen: screenfull.isFullscreen });
|
|
|
|
}.bind(this));
|
|
|
|
}
|
|
|
|
|
|
|
|
window.addEventListener('resize', function() {
|
|
|
|
this.setState({
|
|
|
|
windowHeight: window.innerHeight,
|
|
|
|
playerHeight: this.refs.player.getDOMNode().offsetHeight
|
|
|
|
});
|
|
|
|
}.bind(this), true);
|
|
|
|
|
|
|
|
requestAnimationFrame(this.applyChanges);
|
|
|
|
},
|
|
|
|
|
|
|
|
render: function() {
|
|
|
|
var overlay;
|
|
|
|
|
|
|
|
if (this.state.loading) {
|
|
|
|
overlay = asciinema.LoadingOverlay();
|
|
|
|
} else if (!this.props.autoPlay && this.isNotStarted()) {
|
|
|
|
overlay = asciinema.StartOverlay({ start: this.start });
|
|
|
|
}
|
|
|
|
|
|
|
|
return (
|
|
|
|
dom.div({ className: 'asciinema-player-wrapper' },
|
|
|
|
dom.div({ ref: 'player', className: this.playerClassName(), style: this.playerStyle() },
|
|
|
|
|
|
|
|
asciinema.Terminal({
|
|
|
|
width: this.props.movie.width,
|
|
|
|
height: this.props.movie.height,
|
|
|
|
fontSize: this.fontSize(),
|
|
|
|
lines: this.state.lines,
|
|
|
|
cursor: this.state.cursor,
|
|
|
|
cursorBlinking: this.isPlaying(),
|
|
|
|
}),
|
|
|
|
|
|
|
|
asciinema.ControlBar({
|
|
|
|
playing: this.isPlaying(),
|
|
|
|
onPauseClick: this.pause,
|
|
|
|
onResumeClick: this.resume,
|
|
|
|
onSeekClick: this.seek,
|
|
|
|
currentTime: this.state.currentTime,
|
|
|
|
totalTime: this.state.totalTime,
|
|
|
|
fullscreen: this.state.fullscreen,
|
|
|
|
toggleFullscreen: this.toggleFullscreen,
|
|
|
|
}),
|
|
|
|
|
|
|
|
overlay
|
|
|
|
)
|
|
|
|
)
|
|
|
|
);
|
|
|
|
},
|
|
|
|
|
|
|
|
playerClassName: function() {
|
|
|
|
return 'asciinema-player ' + this.themeClassName();
|
|
|
|
},
|
|
|
|
|
|
|
|
themeClassName: function() {
|
|
|
|
return 'asciinema-theme-' + (this.props.theme || 'default');
|
|
|
|
},
|
|
|
|
|
|
|
|
fontSize: function() {
|
|
|
|
if (this.state.fullscreen) {
|
|
|
|
return 'small';
|
|
|
|
} else {
|
|
|
|
return this.state.fontSize;
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
playerStyle: function() {
|
|
|
|
if (this.state.fullscreen && this.state.windowHeight && this.state.playerHeight) {
|
|
|
|
var space = this.state.windowHeight - this.state.playerHeight;
|
|
|
|
|
|
|
|
if (space > 0) {
|
|
|
|
return { marginTop: (space / 2) + 'px' };
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return {};
|
|
|
|
},
|
|
|
|
|
|
|
|
setLoading: function(loading) {
|
|
|
|
this.setState({ loading: loading });
|
|
|
|
},
|
|
|
|
|
|
|
|
start: function() {
|
|
|
|
this.setState({ state: 'playing' });
|
|
|
|
this.movieController = this.props.movie.start(this.onFrame, this.onFinish, this.setTime, this.setLoading);
|
|
|
|
},
|
|
|
|
|
|
|
|
onFinish: function() {
|
|
|
|
this.setState({ state: 'finished' });
|
|
|
|
},
|
|
|
|
|
|
|
|
setTime: function(time) {
|
|
|
|
this.setState({ currentTime: time });
|
|
|
|
},
|
|
|
|
|
|
|
|
pause: function() {
|
|
|
|
if (this.movieController.pause && this.movieController.pause()) {
|
|
|
|
this.setState({ state: 'paused' });
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
resume: function() {
|
|
|
|
if (this.isFinished()) {
|
|
|
|
this.start();
|
|
|
|
} else {
|
|
|
|
if (this.movieController.resume && this.movieController.resume()) {
|
|
|
|
this.setState({ state: 'playing' });
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
seek: function(time) {
|
|
|
|
if (this.movieController.seek && this.movieController.seek(time)) {
|
|
|
|
this.setState({ state: 'playing', currentTime: time });
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
toggleFullscreen: function() {
|
|
|
|
if (screenfull.enabled) {
|
|
|
|
screenfull.toggle(this.getDOMNode());
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
onFrame: function(changes) {
|
|
|
|
this.changes = this.changes || {};
|
|
|
|
asciinema.mergeChanges(this.changes, changes);
|
|
|
|
},
|
|
|
|
|
|
|
|
applyChanges: function() {
|
|
|
|
requestAnimationFrame(this.applyChanges);
|
|
|
|
|
|
|
|
// if (!this.dirty) {
|
|
|
|
// return;
|
|
|
|
// }
|
|
|
|
|
|
|
|
var changes = this.changes || {};
|
|
|
|
var newState = {};
|
|
|
|
|
|
|
|
if (changes.lines) {
|
|
|
|
var lines = [];
|
|
|
|
|
|
|
|
for (var n in this.state.lines) {
|
|
|
|
lines[n] = this.state.lines[n];
|
|
|
|
}
|
|
|
|
|
|
|
|
for (var n in changes.lines) {
|
|
|
|
lines[n] = changes.lines[n];
|
|
|
|
}
|
|
|
|
|
|
|
|
newState.lines = lines;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (changes.cursor) {
|
|
|
|
var cursor = {
|
|
|
|
x: this.state.cursor.x,
|
|
|
|
y: this.state.cursor.y,
|
|
|
|
visible: this.state.cursor.visible
|
|
|
|
};
|
|
|
|
|
|
|
|
for (var key in changes.cursor) {
|
|
|
|
cursor[key] = changes.cursor[key];
|
|
|
|
}
|
|
|
|
|
|
|
|
newState.cursor = cursor;
|
|
|
|
}
|
|
|
|
|
|
|
|
this.setState(newState);
|
|
|
|
this.changes = {};
|
|
|
|
},
|
|
|
|
|
|
|
|
isNotStarted: function() {
|
|
|
|
return this.state.state === 'not-started';
|
|
|
|
},
|
|
|
|
|
|
|
|
isPlaying: function() {
|
|
|
|
return this.state.state === 'playing';
|
|
|
|
},
|
|
|
|
|
|
|
|
isFinished: function() {
|
|
|
|
return this.state.state === 'finished';
|
|
|
|
},
|
|
|
|
});
|
|
|
|
|
|
|
|
exports.mergeChanges = function(dest, src) {
|
|
|
|
if (src.lines) {
|
|
|
|
dest.lines = dest.lines || {};
|
|
|
|
|
|
|
|
for (var n in src.lines) {
|
|
|
|
dest.lines[n] = src.lines[n];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (src.cursor) {
|
|
|
|
dest.cursor = dest.cursor || {};
|
|
|
|
|
|
|
|
for (var key in src.cursor) {
|
|
|
|
dest.cursor[key] = src.cursor[key];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
})(window.asciinema = window.asciinema || {});
|
|
|
|
|
|
|
|
(function(exports) {
|
|
|
|
var dom = React.DOM;
|
|
|
|
|
|
|
|
exports.ProgressBar = React.createClass({ displayName: 'ProgressBar',
|
|
|
|
// props.value
|
|
|
|
// props.onClick
|
|
|
|
|
|
|
|
render: function() {
|
|
|
|
var width = 100 * this.props.value;
|
|
|
|
|
|
|
|
return (
|
|
|
|
dom.span({ className: "progressbar" },
|
|
|
|
dom.span({ className: "bar", ref: "bar", onMouseDown: this.handleClick },
|
|
|
|
dom.span({ className: "gutter" },
|
|
|
|
dom.span({ style: { width: width + "%" } })
|
|
|
|
)
|
|
|
|
)
|
|
|
|
)
|
|
|
|
)
|
|
|
|
},
|
|
|
|
|
|
|
|
handleClick: function(event) {
|
|
|
|
event.preventDefault();
|
|
|
|
|
|
|
|
var target = event.target || event.srcElement,
|
|
|
|
style = target.currentStyle || window.getComputedStyle(target, null),
|
|
|
|
borderLeftWidth = parseInt(style['borderLeftWidth'], 10),
|
|
|
|
borderTopWidth = parseInt(style['borderTopWidth'], 10),
|
|
|
|
rect = target.getBoundingClientRect(),
|
|
|
|
offsetX = event.clientX - borderLeftWidth - rect.left,
|
|
|
|
offsetY = event.clientY - borderTopWidth - rect.top;
|
|
|
|
|
|
|
|
var barWidth = this.refs.bar.getDOMNode().offsetWidth;
|
|
|
|
this.props.onClick(offsetX / barWidth);
|
|
|
|
}
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
})(window.asciinema = window.asciinema || {});
|
|
|
|
|
|
|
|
(function(exports) {
|
|
|
|
var dom = React.DOM;
|
|
|
|
|
|
|
|
exports.Terminal = React.createClass({ displayName: 'Terminal',
|
|
|
|
// props: width, height, fontSize, lines, cursor, cursorBlinking
|
|
|
|
|
|
|
|
getInitialState: function() {
|
|
|
|
return { cursorInverted: false };
|
|
|
|
},
|
|
|
|
|
|
|
|
render: function() {
|
|
|
|
var cursor = this.props.cursor;
|
|
|
|
|
|
|
|
var lines = this.props.lines.map(function(line, index) {
|
|
|
|
if (cursor.visible && cursor.y == index) {
|
|
|
|
return asciinema.Line({
|
|
|
|
parts: line,
|
|
|
|
cursorX: cursor.x,
|
|
|
|
cursorInverted: this.props.cursorBlinking && this.state.cursorInverted,
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
return asciinema.Line({ parts: line });
|
|
|
|
}
|
|
|
|
|
|
|
|
}.bind(this));
|
|
|
|
|
|
|
|
return dom.pre({ className: this.className(), style: this.style() }, lines);
|
|
|
|
},
|
|
|
|
|
|
|
|
className: function() {
|
|
|
|
return "asciinema-terminal " + this.fontClassName();
|
|
|
|
},
|
|
|
|
|
|
|
|
fontClassName: function() {
|
|
|
|
return 'font-' + this.props.fontSize;
|
|
|
|
},
|
|
|
|
|
|
|
|
style: function() {
|
|
|
|
if (this.state.charDimensions) {
|
|
|
|
var dimensions = this.state.charDimensions[this.props.fontSize];
|
|
|
|
var width = this.props.width * dimensions.width + 'px';
|
|
|
|
var height = this.props.height * dimensions.height + 'px';
|
|
|
|
return { width: width, height: height };
|
|
|
|
} else {
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
componentDidMount: function() {
|
|
|
|
this.calculateCharDimensions();
|
|
|
|
this.startBlinking();
|
|
|
|
},
|
|
|
|
|
|
|
|
componentDidUpdate: function(prevProps, prevState) {
|
|
|
|
if (prevProps.lines != this.props.lines || prevProps.cursor != this.props.cursor) {
|
|
|
|
this.restartBlinking();
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
componentWillUnmount: function() {
|
|
|
|
this.stopBlinking();
|
|
|
|
},
|
|
|
|
|
|
|
|
shouldComponentUpdate: function(nextProps, nextState) {
|
|
|
|
return nextProps.lines != this.props.lines ||
|
|
|
|
nextProps.cursor != this.props.cursor ||
|
|
|
|
nextProps.fontSize != this.props.fontSize ||
|
|
|
|
nextState.cursorInverted != this.state.cursorInverted ||
|
|
|
|
nextState.charDimensions != this.state.charDimensions;
|
|
|
|
},
|
|
|
|
|
|
|
|
calculateCharDimensions: function() {
|
2014-06-25 15:17:30 +00:00
|
|
|
var $tmpChild = $('<span class="font-sample"><span class="line"><span class="char">MMMMMMMMMM</span></span></span>');
|
2014-06-24 18:34:34 +00:00
|
|
|
this.getDOMNode().appendChild($tmpChild[0]);
|
|
|
|
var $span = $tmpChild.find('.char');
|
|
|
|
|
|
|
|
var charDimensions = {};
|
|
|
|
|
|
|
|
$tmpChild.addClass('font-small');
|
2014-06-25 15:17:30 +00:00
|
|
|
charDimensions.small = { width: $span.width() / 10, height: $tmpChild.height() };
|
2014-06-24 18:34:34 +00:00
|
|
|
|
|
|
|
$tmpChild.removeClass('font-small');
|
|
|
|
$tmpChild.addClass('font-medium');
|
2014-06-25 15:17:30 +00:00
|
|
|
charDimensions.medium = { width: $span.width() / 10, height: $tmpChild.height() };
|
2014-06-24 18:34:34 +00:00
|
|
|
|
|
|
|
$tmpChild.removeClass('font-medium');
|
|
|
|
$tmpChild.addClass('font-big');
|
2014-06-25 15:17:30 +00:00
|
|
|
charDimensions.big = { width: $span.width() / 10, height: $tmpChild.height() };
|
2014-06-24 18:34:34 +00:00
|
|
|
|
|
|
|
$tmpChild.remove();
|
|
|
|
|
|
|
|
this.setState({ charDimensions: charDimensions });
|
|
|
|
},
|
|
|
|
|
|
|
|
startBlinking: function() {
|
|
|
|
this.cursorBlinkInvervalId = setInterval(this.flip, 500);
|
|
|
|
},
|
|
|
|
|
|
|
|
stopBlinking: function() {
|
|
|
|
clearInterval(this.cursorBlinkInvervalId);
|
|
|
|
},
|
|
|
|
|
|
|
|
restartBlinking: function() {
|
|
|
|
this.stopBlinking();
|
|
|
|
this.reset();
|
|
|
|
this.startBlinking();
|
|
|
|
},
|
|
|
|
|
|
|
|
reset: function() {
|
|
|
|
this.setState({ cursorInverted: false });
|
|
|
|
},
|
|
|
|
|
|
|
|
flip: function() {
|
|
|
|
this.setState({ cursorInverted: !this.state.cursorInverted });
|
|
|
|
},
|
|
|
|
|
|
|
|
});
|
|
|
|
})(typeof exports === 'undefined' ? (this.asciinema = this.asciinema || {}) : exports);
|
|
|
|
|
|
|
|
(function(exports) {
|
|
|
|
var dom = React.DOM;
|
|
|
|
|
|
|
|
exports.Timer = React.createClass({ displayName: 'Timer',
|
|
|
|
// props.currentTime
|
|
|
|
// props.totalTime
|
|
|
|
|
|
|
|
render: function() {
|
|
|
|
return (
|
|
|
|
dom.span({ className: "timer" },
|
|
|
|
dom.span({ className: "time-elapsed" }, this.elapsedTime()),
|
|
|
|
dom.span({ className: "time-remaining" }, this.remainingTime())
|
|
|
|
)
|
|
|
|
)
|
|
|
|
},
|
|
|
|
|
|
|
|
remainingTime: function() {
|
|
|
|
var t = this.props.totalTime - this.props.currentTime;
|
|
|
|
return "-" + this.formatTime(t);
|
|
|
|
},
|
|
|
|
|
|
|
|
elapsedTime: function() {
|
|
|
|
return this.formatTime(this.props.currentTime);
|
|
|
|
},
|
|
|
|
|
|
|
|
formatTime: function(seconds) {
|
|
|
|
if (seconds < 0) {
|
|
|
|
seconds = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
return "" + this.minutes(seconds) + ":" + this.seconds(seconds);
|
|
|
|
},
|
|
|
|
|
|
|
|
minutes: function(s) {
|
|
|
|
var minutes = Math.floor(s / 60)
|
|
|
|
return this.pad2(minutes);
|
|
|
|
},
|
|
|
|
|
|
|
|
seconds: function(s) {
|
|
|
|
var seconds = Math.floor(s % 60)
|
|
|
|
return this.pad2(seconds);
|
|
|
|
},
|
|
|
|
|
|
|
|
pad2: function(number) {
|
|
|
|
if (number < 10) {
|
|
|
|
return '0' + number;
|
|
|
|
} else {
|
|
|
|
return number;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
})(window.asciinema = window.asciinema || {});
|