XciD's picture
XciD HF staff
initial commit
8969f81
import { Api } from './Api';
import { Mention } from './Mention';
import { c } from './lib/Log';
import { Utils } from './lib/Utils';
import { VanillaTilt } from './vanilla-tilt';
import { ShareScreenshotModal, SavePublishModal } from './modals';
/// We experimented with a couple of different build systems
/// to integrate Quill (for instance module-then-postprocessing
/// like in `web3d`) but none worked really well so we just
/// hotlink the js and basically copy/paste the @types/quill
/// declaration here.
/// Update: we now use rollup (for html2canvas), but quill is
/// still a pain so it's still not in the same bundle.
const DEBUG = false;
/// ^^ when debugging the quill integration, add the quill.snow.css to layout.hbs
/// <link href="/front/node_modules/quill/dist/quill.snow.css" rel="stylesheet">
/// <link href="/front/node_modules/quill/dist/quill.core.css" rel="stylesheet">
/// We tried doing it programmatically here but it's a bit slow.
if (DEBUG) {
document.head.insertAdjacentHTML(
'beforeend',
`<link href="/front/node_modules/quill/dist/quill.snow.css" rel="stylesheet">`
);
/// ^^ add css to debug. Do it as early as possible.
}
enum Page {
app, landing, model
}
const App = {
page:
(document.body.classList.contains('app')) ? Page.app
: (document.body.classList.contains('landing')) ? Page.landing
: Page.model
,
editable: document.body.dataset.editable === 'true',
header: {
shuffleBtn: document.querySelector('header .js-shuffle') as HTMLAnchorElement,
triggerBtn: document.querySelector('header .js-trigger') as HTMLAnchorElement,
mainInfoBtn: document.querySelector('header .title .info') as HTMLImageElement,
shareBtn: document.querySelector<HTMLAnchorElement>('header .js-share'),
saveBtn: document.querySelector<HTMLAnchorElement>('header .js-save'),
duplicateBtn: document.querySelector<HTMLAnchorElement>('header .js-duplicate'),
},
shareScreenBtn: document.querySelector('.page-container .js-share') as HTMLAnchorElement,
loaderEditor: document.querySelector('.page-container .js-loader') as HTMLImageElement,
sliders: Array.from(
document.querySelectorAll('.decoder-settings input.slider')
) as HTMLInputElement[],
INITIAL_CONTENT: {} as Delta,
/**
* Helper function to more cleanly route different page types.
*/
onLoad: (p: Page, callback: () => void) => {
if (p === App.page) {
document.addEventListener('DOMContentLoaded', () => {
callback();
});
}
},
};
const PROMPTS = [
`Before boarding your rocket to Mars, remember to pack these items`,
`In a shocking finding, scientist discovered a herd of unicorns living in a remote, previously unexplored valley, in the Andes Mountains. Even more surprising to the researchers was the fact that the unicorns spoke perfect English.`,
`Legolas and Gimli advanced on the orcs, raising their weapons with a harrowing war cry.`,
`Today, scientists confirmed the worst possible outcome: the massive asteroid will collide with Earth`,
`
Thor: The Tesseract belongs on Asgard, no human is a match for it.
Tony turns to leave, but Steve stops him.
Steve: You're not going alone!
Tony: You gonna stop me?
`.replace(/\t/g, "").trim().concat("\n"),
];
App.onLoad(Page.app, () => {
const modalScreenshot = new ShareScreenshotModal;
const opts: QuillOptionsStatic = DEBUG
? {
theme: 'snow',
modules: {
mention: {},
},
}
: {
theme: undefined,
// formats: [],
modules: {
toolbar: [],
mention: {},
},
}
;
if (! App.editable) {
opts.readOnly = true;
}
const quill = new Quill('div.editor', opts);
const mention = quill.getModule('mention') as Mention;
(<any>window).quill = quill;
const QUILL_C = (<any>window).QUILL_C;
if (QUILL_C) {
quill.setContents(QUILL_C);
}
quill.container.appendChild(App.loaderEditor);
quill.container.appendChild(App.shareScreenBtn);
//
// div.editor .ql-container <-- quill.container
// +--------------------------------+
// | div.ql-editor contenteditable | <-- quill.root
// | +----------------------------+ |
// | | | |
// | | | |
// | +----------------------------+ |
// +--------------------------------+
//
quill.keyboard.addBinding({ key: Mention.Keys.TAB }, () => {
triggerAutocomplete();
});
quill.keyboard.bindings[Mention.Keys.TAB].unshift(
quill.keyboard.bindings[Mention.Keys.TAB].pop()
);
/// ^^ important.
/// ^^ place it at beginning of bindings.
const triggerAutocomplete = async () => {
/// vv position loader
mention.setCursorPos();
const cursorBbox = quill.getBounds(mention.getCursorPos());
App.loaderEditor.style.top = `${cursorBbox.top - 4}px`;
App.loaderEditor.style.left = `${cursorBbox.left + 4}px`;
App.loaderEditor.classList.remove('hide');
/// vv Launch api request.
const text = quill.getText(0, mention.getCursorPos());
// ^^ That is so much simpler that what we used to do
// when we were embbedding objects like in `quill-mention`.
c.debug(
`%c[About to launch autocomplete for]`,
`color: green;`,
text,
);
const o = await Api.shared.postWithSettings({ context: text });
App.loaderEditor.classList.add('hide');
/// vv Trigger mention module.
for (const x of o.sentences) {
c.log(x.value);
}
mention.trigger(
o.sentences.map(x => x.value)
);
};
App.header.duplicateBtn?.addEventListener('click', async (e) => {
e.preventDefault();
const url = await Api.shared.postDuplicate();
window.location.href = url;
});
if (! App.editable) {
return ;
}
/**
* vv Below is only in editable mode.
*/
const modalSave = new SavePublishModal(quill);
App.header.shuffleBtn.addEventListener('click', (e) => {
e.preventDefault();
quill.setText(
Utils.randomItem(PROMPTS)
);
quill.setSelection(quill.getLength(), 0);
/// ^^ github.com/quilljs/quill/issues/2635
triggerAutocomplete();
});
App.header.triggerBtn.addEventListener('click', (e) => {
e.preventDefault();
triggerAutocomplete();
});
App.header.shareBtn?.addEventListener('click', async (e) => {
e.preventDefault();
const text = `Write With Transformer via @huggingface`;
window.open(`https://twitter.com/share?url=${ encodeURIComponent(window.location.href) }&text=${ encodeURIComponent(text) }`);
});
App.header.saveBtn?.addEventListener('click', (e) => {
e.preventDefault();
mention.hideMentionList();
modalSave.show();
});
App.shareScreenBtn.addEventListener('click', async (e) => {
e.preventDefault();
mention.hideMentionList();
modalScreenshot.show();
});
quill.on('text-change', () => {
App.shareScreenBtn.classList.remove('hide'); /// <- we use a fadeout effect.
const hasTextFromAI = quill.getContents()
.ops
.some(op => op.attributes && op.attributes.bold === true)
;
App.shareScreenBtn.classList.toggle('fadeout', ! hasTextFromAI);
});
document.addEventListener('click', (e) => {
/// Handle clicks on links inside the editor.
if (! (
e.target instanceof HTMLAnchorElement
&& e.target.closest('div.ql-editor') !== null
)) {
return ;
}
/// Ok, let's do this.
e.preventDefault();
e.stopPropagation();
const href = e.target.getAttribute('href'); /// <- caution, get the original string.
c.debug(`[click]`, href);
if (href === '#js-shuffle') {
App.header.shuffleBtn.click();
} else {
window.open(e.target.href);
}
});
document.addEventListener("scroll", e => {
const trigger = document.getElementsByClassName("js-trigger")[0] as HTMLAnchorElement;
if (scrollY > 100) {
trigger.style.position = "fixed";
trigger.style.top = "10px";
trigger.style.border = "1px solid blue";
trigger.style.backgroundColor = "white";
trigger.style.borderRadius = "100px";
trigger.style.padding = "5px";
trigger.style.zIndex = "1";
trigger.style.left = "50%";
trigger.style.transform = "translateX(-50%)";
} else {
trigger.style.position = "relative";
trigger.style.top = "auto";
trigger.style.border = "none";
trigger.style.backgroundColor = "white";
trigger.style.borderRadius = "0";
trigger.style.padding = "0";
trigger.style.zIndex = "1";
trigger.style.left = "auto"
}
});
/**
* Settings
*/
const handleSliderChange = (slider: HTMLInputElement) => {
const div = slider.parentNode as HTMLDivElement;
const spanVal = div.querySelector('.js-val') as HTMLSpanElement;
const value = Number.isInteger(slider.valueAsNumber)
? slider.valueAsNumber
: Number(slider.valueAsNumber.toFixed(2))
;
const valueKey = `value-${value}`;
if (slider.dataset[valueKey]) {
spanVal.innerText = slider.dataset[valueKey]!;
} else {
spanVal.innerText = value.toString();
}
const min = Number(slider.getAttribute('min'));
const max = Number(slider.getAttribute('max'));
if (value < min + (max - min) / 3) {
spanVal.className = "js-val green";
} else if (value < min + 2 * (max - min) / 3) {
spanVal.className = "js-val orange";
} else {
spanVal.className = "js-val red";
}
const isInverted = slider.classList.contains('js-inverted');
if (isInverted) {
if (spanVal.classList.contains('green')) {
spanVal.classList.remove('green');
spanVal.classList.add('red');
} else if (spanVal.classList.contains('red')) {
spanVal.classList.remove('red');
spanVal.classList.add('green');
}
}
};
for (const slider of App.sliders) {
handleSliderChange(slider);
slider.addEventListener('input', () => {
handleSliderChange(slider);
});
}
});
App.onLoad(Page.landing, () => {
/**
* VanillaTilt
*/
VanillaTilt.init(document.querySelectorAll("[data-tilt]"), {
glare: true,
scale: 1.06,
'max-glare': 0.3,
speed: 400,
});
});