Files

82 lines
3.0 KiB
JavaScript

// Pace already received text while preserving network completion and cancellation.
var ChatReply = (function(){
function create(options){
const now = options.now || Date.now;
const readyAt = now() + 300;
const segmenter = typeof Intl !== 'undefined' && Intl.Segmenter
? new Intl.Segmenter(undefined, {granularity:'grapheme'}) : null;
let text = '', characters = [], visible = 0;
let startedAt = null, timer = null, card = null;
let complete = false, closed = false;
function clearTimer(){
if(timer !== null) clearTimeout(timer);
timer = null;
}
function schedule(delay){
if(timer === null && !closed) timer = setTimeout(function(){
try{ draw(false); }
catch(error){ closed = true; clearTimer(); options.onError(error); }
}, delay);
}
function draw(force){
clearTimer();
if(closed) return;
const animate = !force && options.shouldAnimate();
if(animate && now() < readyAt){ schedule(readyAt - now()); return; }
if(card){
const show = card;
card = null;
show();
}
if(visible < characters.length){
if(startedAt === null) startedAt = now();
const elapsed = Math.max(0, now() - startedAt);
const rate = Math.max(45, characters.length / 4);
visible = animate ? Math.min(characters.length, Math.max(visible + 1, Math.floor(elapsed * rate / 1000))) : characters.length;
options.onText(characters.slice(0, visible).join(''));
}
if(visible < characters.length){ schedule(16); return; }
if(complete){
closed = true;
options.onComplete();
}
}
function update(){
if(closed) return;
if(!options.shouldAnimate()) draw(true);
else schedule(Math.max(0, readyAt - now()));
}
function setText(value){
text = value;
characters = segmenter ? Array.from(segmenter.segment(text), item => item.segment) : Array.from(text);
}
return {
append:function(value){
if(closed) return;
card = null;
setText(text + value);
update();
},
replace:function(value){
if(closed) return;
card = null;
setText(value);
visible = 0;
update();
},
card:function(show){
if(closed) return;
setText('');
visible = 0;
card = show;
update();
},
complete:function(){ complete = true; update(); },
flush:function(){ draw(true); },
cancel:function(){ closed = true; clearTimer(); },
};
}
return {create:create};
})();