Add: live-reload

This commit is contained in:
eight 2017-09-10 22:05:44 +08:00
parent cb23f89b6a
commit 8c374db353
2 changed files with 142 additions and 26 deletions

View File

@ -44,8 +44,15 @@ h1 small {
margin: 15px 0; margin: 15px 0;
} }
.actions > * { .live-reload {
display: inline-block; width: fit-content;
display: flex;
align-items: center;
margin: 0.5em auto;
}
.live-reload input {
margin: 0 0.5em 0 0;
} }
.external { .external {
@ -68,3 +75,7 @@ button.install {
font-family: monospace; font-family: monospace;
white-space: pre-wrap; white-space: pre-wrap;
} }
.main {
flex-grow: 1;
}

View File

@ -4,17 +4,6 @@
let pendingResource; let pendingResource;
function fetchText(url) {
return new Promise((resolve, reject) => {
// you can't use fetch in Chrome under 'file:' protocol
const xhr = new XMLHttpRequest();
xhr.open('GET', url);
xhr.addEventListener('load', () => resolve(xhr.responseText));
xhr.addEventListener('error', () => reject(xhr));
xhr.send();
});
}
function install(style) { function install(style) {
const request = Object.assign(style, { const request = Object.assign(style, {
method: 'saveUsercss', method: 'saveUsercss',
@ -22,11 +11,12 @@ function install(style) {
updateUrl: location.href updateUrl: location.href
}); });
return communicate(request) return communicate(request)
.then(() => { .then(result => {
$$('.warning') $$('.warning')
.forEach(el => el.remove()); .forEach(el => el.remove());
$('button.install').textContent = 'Installed'; $('button.install').textContent = 'Installed';
$('button.install').disabled = true; $('button.install').disabled = true;
window.dispatchEvent(new CustomEvent('installed', {detail: result}));
}) })
.catch(err => { .catch(err => {
alert(chrome.i18n.getMessage('styleInstallFailed', String(err))); alert(chrome.i18n.getMessage('styleInstallFailed', String(err)));
@ -62,7 +52,7 @@ function getAppliesTo(style) {
return result; return result;
} }
function initInstallPage({style, dup}) { function initInstallPage({style, dup}, sourceLoader) {
return pendingResource.then(() => { return pendingResource.then(() => {
const versionTest = dup && semverCompare(style.version, dup.version); const versionTest = dup && semverCompare(style.version, dup.version);
document.body.innerHTML = ''; document.body.innerHTML = '';
@ -88,8 +78,10 @@ function initInstallPage({style, dup}) {
<a href="${style.supportURL}" target="_blank">Support</a> <a href="${style.supportURL}" target="_blank">Support</a>
</div> </div>
</div> </div>
<div class="main">
<div class="code"></div> <div class="code"></div>
</div> </div>
</div>
`)); `));
if (versionTest < 0) { if (versionTest < 0) {
// FIXME: i18n // FIXME: i18n
@ -109,9 +101,64 @@ function initInstallPage({style, dup}) {
install(style); install(style);
} }
}; };
if (location.protocol === 'file:') {
initLiveReload(sourceLoader);
}
}); });
} }
function initLiveReload(sourceLoader) {
let installed;
const watcher = sourceLoader.watch(source => {
$('.code').textContent = source;
return communicate({
method: 'saveUsercss',
id: installed.id,
source: source
}).then(() => {
$$('.main .warning').forEach(e => e.remove());
}).catch(err => {
const oldWarning = $('.main .warning');
// FIXME: i18n
const warning = tHTML(`
<div class="warning">
Stylus failed to parse usercss:
<pre>${err}</pre>
</div>
`);
if (oldWarning) {
oldWarning.replaceWith(warning);
} else {
$('.main').prepend(warning);
}
});
});
window.addEventListener('installed', ({detail: {style}}) => {
installed = style;
if ($('.live-reload-checkbox').checked) {
watcher.start();
}
});
// FIXME: i18n
$('.actions').append(tHTML(`
<label class="live-reload">
<input type="checkbox" class="live-reload-checkbox">
<span>Live reload</span>
</label>
`));
$('.live-reload-checkbox').onchange = e => {
if (!installed) {
return;
}
if (e.target.checked) {
watcher.start();
} else {
watcher.stop();
}
};
}
function initErrorPage(err, source) { function initErrorPage(err, source) {
return pendingResource.then(() => { return pendingResource.then(() => {
document.body.innerHTML = ''; document.body.innerHTML = '';
@ -127,8 +174,65 @@ function initErrorPage(err, source) {
}); });
} }
function initUsercssInstall() { function createSourceLoader() {
let source; let source;
function fetchText(url) {
return new Promise((resolve, reject) => {
// you can't use fetch in Chrome under 'file:' protocol
const xhr = new XMLHttpRequest();
xhr.open('GET', url);
xhr.addEventListener('load', () => resolve(xhr.responseText));
xhr.addEventListener('error', () => reject(xhr));
xhr.send();
});
}
function load() {
return fetchText(location.href)
.then(_source => {
source = _source;
return source;
});
}
function watch(cb) {
let timer;
const DELAY = 1000;
function start() {
if (timer) {
return;
}
timer = setTimeout(check, DELAY);
}
function stop() {
clearTimeout(timer);
timer = null;
}
function check() {
fetchText(location.href)
.then(_source => {
if (source !== _source) {
source = _source;
return cb(source);
}
})
.catch(console.log)
.then(() => {
timer = setTimeout(check, DELAY);
});
}
return {start, stop};
}
return {load, watch, source: () => source};
}
function initUsercssInstall() {
pendingResource = communicate({ pendingResource = communicate({
method: 'injectResource', method: 'injectResource',
resources: [ resources: [
@ -139,17 +243,18 @@ function initUsercssInstall() {
'/content/install-user-css.css' '/content/install-user-css.css'
] ]
}); });
fetchText(location.href)
.then(_source => { const sourceLoader = createSourceLoader();
source = _source; sourceLoader.load()
return communicate({ .then(() =>
communicate({
method: 'filterUsercss', method: 'filterUsercss',
source, source: sourceLoader.source(),
checkDup: true checkDup: true
});
}) })
.then(initInstallPage) )
.catch(err => initErrorPage(err, source)); .then(result => initInstallPage(result, sourceLoader))
.catch(err => initErrorPage(err, sourceLoader.source()));
} }
function isUsercss() { function isUsercss() {