Start #41, put in an interstitial template selector

This puts the selector into the gmail compose window itself as an overlay. For now rendering of the templates stays in the background process, but arguably it should be moved into the compose window.
This commit is contained in:
Ian Bicking 2018-07-26 12:41:28 -05:00
Родитель 5817aec421
Коммит e03638d329
4 изменённых файлов: 74 добавлений и 106 удалений

Просмотреть файл

@ -1,6 +1,4 @@
/* globals TestPilotGA, emailTemplates, templateMetadata */ /* globals TestPilotGA, emailTemplates, templateMetadata */
let selectedTemplate = templateMetadata.defaultTemplateName;
browser.runtime.onMessage.addListener((message, source) => { browser.runtime.onMessage.addListener((message, source) => {
if (message.type === "sendEmail") { if (message.type === "sendEmail") {
sendEmail(message.tabIds).catch((e) => { sendEmail(message.tabIds).catch((e) => {
@ -25,10 +23,8 @@ browser.runtime.onMessage.addListener((message, source) => {
delete message.type; delete message.type;
sendEvent(message); sendEvent(message);
return Promise.resolve(null); return Promise.resolve(null);
} else if (message.type === "setSelectedTemplate") { } else if (message.type === "renderTemplate") {
return setSelectedTemplate(message.name); return renderTabs(message.tabInfo, message.selectedTemplate);
} else if (message.type === "getSelectedTemplate") {
return Promise.resolve(selectedTemplate);
} }
console.error("Unexpected message type:", message.type); console.error("Unexpected message type:", message.type);
return null; return null;
@ -94,16 +90,14 @@ async function getTabInfo(tabIds, {wantsScreenshots, wantsReadability}) {
return tabIds.map(id => tabInfo[id]); return tabIds.map(id => tabInfo[id]);
} }
async function renderTabs(tabIds, templateName) { async function renderTabs(tabInfo, templateName) {
let { wantsScreenshots, wantsReadability } = templateMetadata.getTemplate(templateName);
let tabInfo = await getTabInfo(tabIds, {wantsScreenshots, wantsReadability});
let TemplateComponent = emailTemplates[templateMetadata.getTemplate(templateName).componentName]; let TemplateComponent = emailTemplates[templateMetadata.getTemplate(templateName).componentName];
if (!TemplateComponent) { if (!TemplateComponent) {
throw new Error(`No component found for template: ${templateName}`); throw new Error(`No component found for template: ${templateName}`);
} }
let html = emailTemplates.renderEmail(tabInfo, TemplateComponent); let html = emailTemplates.renderEmail(tabInfo, TemplateComponent);
let subject = emailTemplates.renderSubject(tabInfo); let subject = emailTemplates.renderSubject(tabInfo);
return { html, tabInfo, subject }; return { html, subject };
} }
async function sendEmail(tabIds) { async function sendEmail(tabIds) {
@ -126,22 +120,25 @@ async function sendEmail(tabIds) {
loginInterrupt(); loginInterrupt();
} }
}, 1000); }, 1000);
let { html, tabInfo, subject } = await renderTabs(tabIds, selectedTemplate); let tabInfo = await getTabInfo(tabIds, {wantsScreenshots: true, wantsReadability: true});
await browser.tabs.executeScript(newTab.id, {
file: "templateMetadata.js",
runAt: "document_start",
});
await browser.tabs.executeScript(newTab.id, { await browser.tabs.executeScript(newTab.id, {
file: "set-html-email.js", file: "set-html-email.js",
runAt: "document_start", runAt: "document_start",
}); });
await browser.tabs.sendMessage(newTab.id, { await browser.tabs.sendMessage(newTab.id, {
type: "setHtml", type: "sendTabInfo",
html,
subject,
thisTabId: newTab.id, thisTabId: newTab.id,
tabInfo tabInfo
}); });
} }
async function copyTabHtml(tabIds) { async function copyTabHtml(tabIds) {
let { html } = await renderTabs(tabIds, selectedTemplate); let tabInfo = await getTabInfo(tabIds, {wantsScreenshots: false, wantsReadability: false});
let { html } = await renderTabs(tabInfo, "just_links");
copyHtmlToClipboard(html); copyHtmlToClipboard(html);
} }
@ -195,23 +192,3 @@ async function closeManyTabs(composeTabId, otherTabInfo) {
} }
await browser.tabs.remove(toClose); await browser.tabs.remove(toClose);
} }
async function setSelectedTemplate(newName) {
selectedTemplate = newName;
await browser.storage.local.set({selectedTemplate: newName});
}
async function init() {
let result = await browser.storage.local.get(["selectedTemplate"]);
if (result && result.selectedTemplate) {
try {
// Checks that the template really exists:
templateMetadata.getTemplate(result.selectedTemplate);
selectedTemplate = result.selectedTemplate;
} catch (e) {
console.error("Could not set template", result.selectedTemplate, "to:", String(e));
}
}
}
init();

Просмотреть файл

@ -73,6 +73,15 @@
box-shadow: 0 2px 8px rgba(12, 12, 13, 0.1); box-shadow: 0 2px 8px rgba(12, 12, 13, 0.1);
} }
.big-container {
width: 100%;
height: 100%;
position: absolute;
top: 0;
left: 0;
background-color: rgba(0, 0, 0, 0.8);
}
#loading-container { #loading-container {
width: 100%; width: 100%;
height: 100%; height: 100%;
@ -213,5 +222,16 @@
</div> </div>
</div> </div>
<div id="choose-template" class="container big-container" style="display: none">
<div class="template-template" style="display: none">
<button>
Use <span data-substitute="title"></span>
</button>
</div>
<div id="choose-template-container" class="card-container">
<button id="choose-template-cancel">Cancel</button>
</div>
</div>
</body> </body>
</html> </html>

Просмотреть файл

@ -2,8 +2,6 @@
let activeTabLi; let activeTabLi;
let selected = new Map(); let selected = new Map();
let isChoosingTemplate = false;
let selectedTemplate = templateMetadata.defaultTemplateName;
const LOGIN_ERROR_TIME = 90 * 1000; // 90 seconds const LOGIN_ERROR_TIME = 90 * 1000; // 90 seconds
class Tab extends React.Component { class Tab extends React.Component {
@ -78,7 +76,6 @@ class Popup extends React.Component {
<input checked={allChecked} ref={allCheckbox => this.allCheckbox = allCheckbox} type="checkbox" id="allCheckbox" onChange={this.onClickCheckAll.bind(this)} /> <input checked={allChecked} ref={allCheckbox => this.allCheckbox = allCheckbox} type="checkbox" id="allCheckbox" onChange={this.onClickCheckAll.bind(this)} />
Select All Select All
</label> </label>
<button onClick={this.chooseTemplate.bind(this)}>Change template ({selectedTemplate})</button>
</div> </div>
</div> </div>
<div className="separator"></div> <div className="separator"></div>
@ -143,10 +140,6 @@ class Popup extends React.Component {
}, 300); }, 300);
} }
chooseTemplate() {
isChoosingTemplate = true;
render();
}
} }
class LoginError extends React.Component { class LoginError extends React.Component {
@ -158,47 +151,6 @@ class LoginError extends React.Component {
} }
} }
class TemplateChooser extends React.Component {
render() {
let templates = templateMetadata.metadata.map(template => {
return <TemplateItem key={template.name} selected={template.name === selectedTemplate} {...template} />;
});
return <div>
<ul>
{ templates }
</ul>
<footer className="panel-footer toggle-enabled">
<button onClick={this.onCancel.bind(this)}>
Cancel
</button>
</footer>
</div>;
}
onCancel() {
isChoosingTemplate = false;
render();
}
}
class TemplateItem extends React.Component {
render() {
let className = "";
if (this.props.selected) {
className += " selected";
}
return <li className={className}>
<button onClick={this.selectTemplate.bind(this)}>{this.props.title}</button>
</li>;
}
selectTemplate() {
setSelectedTemplate(this.props.name);
isChoosingTemplate = false;
render();
}
}
async function render(firstRun) { async function render(firstRun) {
let tabs = await browser.tabs.query({currentWindow: true}); let tabs = await browser.tabs.query({currentWindow: true});
if (firstRun) { if (firstRun) {
@ -214,12 +166,7 @@ async function render(firstRun) {
if (Date.now() - showLoginError > LOGIN_ERROR_TIME) { if (Date.now() - showLoginError > LOGIN_ERROR_TIME) {
showLoginError = 0; showLoginError = 0;
} }
let page; let page = <Popup selected={selected} tabs={tabs} showLoginError={showLoginError} />;
if (isChoosingTemplate) {
page = <TemplateChooser />;
} else {
page = <Popup selected={selected} tabs={tabs} showLoginError={showLoginError} />;
}
ReactDOM.render(page, document.getElementById("panel")); ReactDOM.render(page, document.getElementById("panel"));
if (firstRun) { if (firstRun) {
activeTabLi.scrollIntoView({ activeTabLi.scrollIntoView({
@ -296,13 +243,7 @@ for (let eventName of ["onAttached", "onCreated", "onDetached", "onMoved", "onUp
browser.tabs.onRemoved.addListener(renderWithDelay); browser.tabs.onRemoved.addListener(renderWithDelay);
async function setSelectedTemplate(name) {
selectedTemplate = name;
await browser.runtime.sendMessage({type: "setSelectedTemplate", name});
}
async function init() { async function init() {
selectedTemplate = await browser.runtime.sendMessage({type: "getSelectedTemplate"});
render(true); render(true);
} }

Просмотреть файл

@ -1,20 +1,18 @@
/* globals cloneInto */ /* globals cloneInto, templateMetadata */
browser.runtime.onMessage.addListener((message) => { browser.runtime.onMessage.addListener((message) => {
try { try {
thisTabId = message.thisTabId; thisTabId = message.thisTabId;
closeTabInfo = message.tabInfo; tabInfo = message.tabInfo;
setSubject(message.subject);
setHtml(message.html);
} catch (e) { } catch (e) {
console.error("Unable to setHtml:", String(e), e.stack); console.error("Error getting tabInfo:", String(e), e.stack);
throw e; throw e;
} }
}); });
let completed = false; let completed = false;
let thisTabId; let thisTabId;
let closeTabInfo; let tabInfo;
window.addEventListener("beforeunload", () => { window.addEventListener("beforeunload", () => {
if (completed) { if (completed) {
@ -28,7 +26,6 @@ window.addEventListener("beforeunload", () => {
browser.runtime.sendMessage({ browser.runtime.sendMessage({
type: "sendFailed" type: "sendFailed"
}); });
console.error("beforeunload");
}); });
function setSubject(subject) { function setSubject(subject) {
@ -116,7 +113,7 @@ function showCloseButtons() {
let done = iframeDocument.querySelector("#done"); let done = iframeDocument.querySelector("#done");
let doneMsg = iframeDocument.querySelector("#done-message"); let doneMsg = iframeDocument.querySelector("#done-message");
let closeAllTabs = iframeDocument.querySelector("#close-all-tabs"); let closeAllTabs = iframeDocument.querySelector("#close-all-tabs");
let numTabs = closeTabInfo.length; let numTabs = tabInfo.length;
if (numTabs === 1) { if (numTabs === 1) {
closeAllTabs.textContent = closeAllTabs.getAttribute("data-one-tab"); closeAllTabs.textContent = closeAllTabs.getAttribute("data-one-tab");
doneMsg.textContent = doneMsg.getAttribute("data-one-tab"); doneMsg.textContent = doneMsg.getAttribute("data-one-tab");
@ -133,7 +130,7 @@ function showCloseButtons() {
closeAllTabs.addEventListener("click", async () => { closeAllTabs.addEventListener("click", async () => {
await browser.runtime.sendMessage({ await browser.runtime.sendMessage({
type: "closeTabs", type: "closeTabs",
closeTabInfo, closeTabInfo: tabInfo,
composeTabId: thisTabId composeTabId: thisTabId
}); });
}); });
@ -143,6 +140,39 @@ function showLoading() {
showIframe("#loading-container"); showIframe("#loading-container");
} }
function showTemplateSelector() {
showIframe("#choose-template");
let cancel = iframeDocument.querySelector("#choose-template-cancel");
cancel.addEventListener("click", async () => {
completed = true;
await browser.runtime.sendMessage({
type: "closeComposeTab",
tabId: thisTabId,
});
});
let elTemplate = iframeDocument.querySelector(".template-template");
for (let template of templateMetadata.metadata) {
let instance = elTemplate.cloneNode(true);
instance.style.display = "";
instance.classList.remove("template-template");
for (let el of instance.querySelectorAll("*[data-substitute]")) {
el.textContent = template[el.getAttribute("data-substitute")];
el.removeAttribute("data-substitute");
}
instance.addEventListener("click", async () => {
showLoading();
let { html, subject } = await browser.runtime.sendMessage({
type: "renderTemplate",
selectedTemplate: template.name,
tabInfo,
});
setSubject(subject);
setHtml(html);
});
cancel.parentNode.insertBefore(instance, cancel);
}
}
let iframe = null; let iframe = null;
let initPromise; let initPromise;
let iframeDocument = null; let iframeDocument = null;
@ -182,7 +212,7 @@ function createIframe() {
} }
function showIframe(container) { function showIframe(container) {
let containers = ["#loading-container", "#done-container"]; let containers = ["#loading-container", "#done-container", "#choose-template"];
if (!containers.includes(container)) { if (!containers.includes(container)) {
throw new Error(`Unexpected container: ${container}`); throw new Error(`Unexpected container: ${container}`);
} }
@ -203,5 +233,5 @@ function hideIframe() {
createIframe(); createIframe();
initPromise.then(() => { initPromise.then(() => {
showLoading(); showTemplateSelector();
}); });