Update preview code.

This commit is contained in:
Lars Jung 2016-07-22 19:43:44 +02:00
parent 8a4ccd098c
commit 20e080751d
11 changed files with 166 additions and 194 deletions

View file

@ -10,6 +10,7 @@
* fixes shell command detection on Windows * fixes shell command detection on Windows
* fixes `.htaccess` auth issues * fixes `.htaccess` auth issues
* adds `rust` type and icon * adds `rust` type and icon
* adds `autoplay` option to audio and video preview
* removes *Install* section from `README.md`, caused to much trouble * removes *Install* section from `README.md`, caused to much trouble
* updates build process to use `node 6.0+`, no need for babel now * updates build process to use `node 6.0+`, no need for babel now
* replaces `jquery-qrcode` with `kjua` * replaces `jquery-qrcode` with `kjua`

2
ghu.js
View file

@ -68,7 +68,7 @@ ghu.task('build:scripts', runtime => {
.then(webpack(webpackCfg([SRC]), {showStats: false})) .then(webpack(webpackCfg([SRC]), {showStats: false}))
.then(wrap('\n\n// @include "pre.js"\n\n')) .then(wrap('\n\n// @include "pre.js"\n\n'))
.then(includeit()) .then(includeit())
.then(ife(() => runtime.args.production, uglify())) .then(ife(() => runtime.args.production, uglify({compressor: {warnings: false}})))
.then(wrap(runtime.commentJs)) .then(wrap(runtime.commentJs))
.then(write(mapper, {overwrite: true})); .then(write(mapper, {overwrite: true}));
}); });

View file

@ -220,6 +220,7 @@
*/ */
"preview-aud": { "preview-aud": {
"enabled": true, "enabled": true,
"autoplay": true,
"types": ["aud"] "types": ["aud"]
}, },
@ -283,6 +284,7 @@
*/ */
"preview-vid": { "preview-vid": {
"enabled": true, "enabled": true,
"autoplay": true,
"types": ["vid-avi", "vid-flv", "vid-mkv", "vid-mov", "vid-mp4", "vid-mpg", "vid-webm"] "types": ["vid-avi", "vid-flv", "vid-mkv", "vid-mov", "vid-mp4", "vid-mpg", "vid-webm"]
}, },

View file

@ -1,6 +1,7 @@
#pv-content-txt { #pv-content-txt {
.raised; .raised;
box-sizing: border-box;
max-width: 960px; max-width: 960px;
text-align: left; text-align: left;
background: @col-back-paper; background: @col-back-paper;
@ -17,20 +18,19 @@
color: #68A9FF; color: #68A9FF;
} }
} }
}
&.highlighted { pre#pv-content-txt {
code { code {
line-height: 1.2em; line-height: 1.2em;
} }
} }
&.markdown { div#pv-content-txt {
font-size: 1.1em; font-size: 1.1em;
padding: 8px 24px; padding: 8px 24px;
code { code {
color: #008200; color: #008200;
} }
}
} }

View file

@ -16,7 +16,7 @@
background: @col-grey-900; background: @col-grey-900;
} }
#pv-content { #pv-container {
position: absolute; position: absolute;
} }

View file

@ -1,38 +1,27 @@
const {each, dom} = require('../../util'); const {dom} = require('../../util');
const event = require('../../core/event');
const format = require('../../core/format'); const format = require('../../core/format');
const allsettings = require('../../core/settings'); const allsettings = require('../../core/settings');
const preview = require('./preview'); const preview = require('./preview');
const previewX = require('./preview-x');
const settings = Object.assign({ const settings = Object.assign({
enabled: false, enabled: false,
autoplay: true,
types: [] types: []
}, allsettings['preview-aud']); }, allsettings['preview-aud']);
const tpl = '<audio id="pv-content-aud"/>'; const tpl = '<audio id="pv-content-aud"/>';
let state; let state;
const onAdjustSize = () => { const updateGui = () => {
const el = dom('#pv-content-aud')[0]; const el = dom('#pv-content-aud')[0];
if (!el) { if (!el) {
return; return;
} }
const elContent = dom('#pv-content')[0]; preview.centerContent();
const contentW = elContent.offsetWidth;
const contentH = elContent.offsetHeight;
const elW = el.offsetWidth;
const elH = el.offsetHeight;
dom(el).css({
left: (contentW - elW) * 0.5 + 'px',
top: (contentH - elH) * 0.5 + 'px'
});
preview.setLabels([ preview.setLabels([
state.item.label, state.item.label,
format.formatDate(dom('#pv-content-aud')[0].duration * 1000, 'm:ss') format.formatDate(el.duration * 1000, 'm:ss')
]); ]);
}; };
@ -40,26 +29,25 @@ const loadAudio = item => {
return new Promise(resolve => { return new Promise(resolve => {
const $el = dom(tpl) const $el = dom(tpl)
.on('loadedmetadata', () => resolve($el)) .on('loadedmetadata', () => resolve($el))
// .attr('autoplay', 'autoplay')
.attr('controls', 'controls') .attr('controls', 'controls')
.attr('src', item.absHref); .attr('src', item.absHref);
if (settings.autoplay) {
$el.attr('autoplay', 'autoplay');
}
}); });
// .then(x => new Promise(resolve => setTimeout(() => resolve(x), 1000)));
}; };
const onEnter = (items, idx) => { const onEnter = (items, idx) => {
state = previewX.pvState(items, idx, loadAudio, onAdjustSize); state = preview.state(items, idx, loadAudio, updateGui);
}; };
const initItem = previewX.initItemFn(settings.types, onEnter);
const onViewChanged = added => each(added, initItem);
const init = () => { const init = () => {
if (!settings.enabled) { if (!settings.enabled) {
return; return;
} }
event.sub('view.changed', onViewChanged); preview.register(settings.types, onEnter);
}; };
init(); init();

View file

@ -1,9 +1,7 @@
const {each, dom} = require('../../util'); const {dom} = require('../../util');
const server = require('../../server'); const server = require('../../server');
const event = require('../../core/event');
const allsettings = require('../../core/settings'); const allsettings = require('../../core/settings');
const preview = require('./preview'); const preview = require('./preview');
const previewX = require('./preview-x');
const settings = Object.assign({ const settings = Object.assign({
enabled: false, enabled: false,
@ -14,22 +12,15 @@ const tpl = '<img id="pv-content-img"/>';
let state; let state;
const onAdjustSize = () => { const updateGui = () => {
const el = dom('#pv-content-img')[0]; const el = dom('#pv-content-img')[0];
if (!el) { if (!el) {
return; return;
} }
const elContent = dom('#pv-content')[0]; preview.centerContent();
const contentW = elContent.offsetWidth;
const contentH = elContent.offsetHeight;
const elW = el.offsetWidth;
const elH = el.offsetHeight;
dom(el).css({ const elW = el.offsetWidth;
left: (contentW - elW) * 0.5 + 'px',
top: (contentH - elH) * 0.5 + 'px'
});
const labels = [state.item.label]; const labels = [state.item.label];
if (!settings.size) { if (!settings.size) {
@ -61,26 +52,22 @@ const loadImage = item => {
return settings.size ? requestSample(href) : href; return settings.size ? requestSample(href) : href;
}) })
.then(href => new Promise(resolve => { .then(href => new Promise(resolve => {
const $img = dom(tpl) const $el = dom(tpl)
.on('load', () => resolve($img)) .on('load', () => resolve($el))
.attr('src', href); .attr('src', href);
})); }));
// .then(x => new Promise(resolve => setTimeout(() => resolve(x), 1000)));
}; };
const onEnter = (items, idx) => { const onEnter = (items, idx) => {
state = previewX.pvState(items, idx, loadImage, onAdjustSize); state = preview.state(items, idx, loadImage, updateGui);
}; };
const initItem = previewX.initItemFn(settings.types, onEnter);
const onViewChanged = added => each(added, initItem);
const init = () => { const init = () => {
if (!settings.enabled) { if (!settings.enabled) {
return; return;
} }
event.sub('view.changed', onViewChanged); preview.register(settings.types, onEnter);
}; };
init(); init();

View file

@ -1,33 +1,28 @@
const lolight = require('lolight'); const lolight = require('lolight');
const marked = require('marked'); const marked = require('marked');
const {each, keys, dom} = require('../../util'); const {keys, dom} = require('../../util');
const {win} = require('../../globals'); const {win} = require('../../globals');
const event = require('../../core/event');
const allsettings = require('../../core/settings'); const allsettings = require('../../core/settings');
const preview = require('./preview'); const preview = require('./preview');
const previewX = require('./preview-x');
const XHR = win.XMLHttpRequest; const XHR = win.XMLHttpRequest;
const settings = Object.assign({ const settings = Object.assign({
enabled: false, enabled: false,
styles: {} styles: {}
}, allsettings['preview-txt']); }, allsettings['preview-txt']);
const tplText = '<pre id="pv-content-txt" class="highlighted"></pre>'; const tplPre = '<pre id="pv-content-txt"></pre>';
const tplMarkdown = '<div id="pv-content-txt" class="markdown"></div>'; const tplDiv = '<div id="pv-content-txt"></div>';
let state; let state;
const onAdjustSize = () => { const updateGui = () => {
const el = dom('#pv-content-txt')[0]; const el = dom('#pv-content-txt')[0];
if (!el) { if (!el) {
return; return;
} }
const elContent = dom('#pv-content')[0]; const container = dom('#pv-container')[0];
el.style.height = container.offsetHeight - 16 + 'px';
dom(el).css({
height: elContent.offsetHeight - 16 + 'px'
});
preview.setLabels([ preview.setLabels([
state.item.label, state.item.label,
@ -56,43 +51,36 @@ const requestTextContent = href => {
const loadText = item => { const loadText = item => {
return requestTextContent(item.absHref) return requestTextContent(item.absHref)
.catch(err => '[ajax error] ' + err) .catch(err => '[request failed] ' + err)
.then(content => { .then(content => {
const style = settings.styles[state.item.type]; const style = settings.styles[item.type];
let $text;
if (style === 1) { if (style === 1) {
$text = dom(tplText).text(content); return dom(tplPre).text(content);
} else if (style === 2) { } else if (style === 2) {
$text = dom(tplMarkdown).html(marked(content)); return dom(tplDiv).html(marked(content));
} else if (style === 3) { } else if (style === 3) {
$text = dom(tplText); const $code = dom('<code></code>').text(content);
const $code = dom('<code/>').text(content).appTo($text);
win.setTimeout(() => { win.setTimeout(() => {
lolight.el($code[0]); lolight.el($code[0]);
}, content.length < 20000 ? 0 : 500); }, content.length < 20000 ? 0 : 500);
} else { return dom(tplPre).app($code);
$text = dom(tplMarkdown).text(content);
} }
return $text; return dom(tplDiv).text(content);
}); });
// .then(x => new Promise(resolve => setTimeout(() => resolve(x), 1000)));
}; };
const onEnter = (items, idx) => { const onEnter = (items, idx) => {
state = previewX.pvState(items, idx, loadText, onAdjustSize); state = preview.state(items, idx, loadText, updateGui);
}; };
const initItem = previewX.initItemFn(keys(settings.styles), onEnter);
const onViewChanged = added => each(added, initItem);
const init = () => { const init = () => {
if (!settings.enabled) { if (!settings.enabled) {
return; return;
} }
event.sub('view.changed', onViewChanged); preview.register(keys(settings.styles), onEnter);
}; };
init(); init();

View file

@ -1,34 +1,25 @@
const {each, dom} = require('../../util'); const {dom} = require('../../util');
const event = require('../../core/event');
const allsettings = require('../../core/settings'); const allsettings = require('../../core/settings');
const preview = require('./preview'); const preview = require('./preview');
const previewX = require('./preview-x');
const settings = Object.assign({ const settings = Object.assign({
enabled: false, enabled: false,
autoplay: true,
types: [] types: []
}, allsettings['preview-vid']); }, allsettings['preview-vid']);
const tpl = '<video id="pv-content-vid"/>'; const tpl = '<video id="pv-content-vid"/>';
let state; let state;
const onAdjustSize = () => { const updateGui = () => {
const el = dom('#pv-content-vid')[0]; const el = dom('#pv-content-vid')[0];
if (!el) { if (!el) {
return; return;
} }
const elContent = dom('#pv-content')[0]; preview.centerContent();
const contentW = elContent.offsetWidth;
const contentH = elContent.offsetHeight;
const elW = el.offsetWidth; const elW = el.offsetWidth;
const elH = el.offsetHeight;
dom(el).css({
left: (contentW - elW) * 0.5 + 'px',
top: (contentH - elH) * 0.5 + 'px'
});
const elVW = el.videoWidth; const elVW = el.videoWidth;
const elVH = el.videoHeight; const elVH = el.videoHeight;
@ -43,26 +34,25 @@ const loadVideo = item => {
return new Promise(resolve => { return new Promise(resolve => {
const $el = dom(tpl) const $el = dom(tpl)
.on('loadedmetadata', () => resolve($el)) .on('loadedmetadata', () => resolve($el))
// .attr('autoplay', 'autoplay')
.attr('controls', 'controls') .attr('controls', 'controls')
.attr('src', item.absHref); .attr('src', item.absHref);
if (settings.autoplay) {
$el.attr('autoplay', 'autoplay');
}
}); });
// .then(x => new Promise(resolve => setTimeout(() => resolve(x), 1000)));
}; };
const onEnter = (items, idx) => { const onEnter = (items, idx) => {
state = previewX.pvState(items, idx, loadVideo, onAdjustSize); state = preview.state(items, idx, loadVideo, updateGui);
}; };
const initItem = previewX.initItemFn(settings.types, onEnter);
const onViewChanged = added => each(added, initItem);
const init = () => { const init = () => {
if (!settings.enabled) { if (!settings.enabled) {
return; return;
} }
event.sub('view.changed', onViewChanged); preview.register(settings.types, onEnter);
}; };
init(); init();

View file

@ -1,68 +0,0 @@
const {includes, compact, dom} = require('../../util');
const preview = require('./preview');
const initItemFn = (types, onEnter) => {
return item => {
if (item.$view && includes(types, item.type)) {
item.$view.find('a').on('click', ev => {
ev.preventDefault();
const matchedItems = compact(dom('#items .item').map(el => {
const matchedItem = el._item;
return includes(types, matchedItem.type) ? matchedItem : null;
}));
onEnter(matchedItems, matchedItems.indexOf(item));
});
}
};
};
const pvState = (items, idx = 0, load, adjust) => {
const inst = Object.assign(Object.create(pvState.prototype), {items, load, adjust});
preview.setOnAdjustSize(adjust);
preview.setOnIndexChange(delta => inst.moveIdx(delta));
preview.enter();
inst.setIdx(idx);
return inst;
};
pvState.prototype = {
constructor: pvState,
setIdx(idx) {
this.idx = (idx + this.items.length) % this.items.length;
this.item = this.items[this.idx];
preview.setLabels([this.item.label]);
preview.setIndex(this.idx + 1, this.items.length);
preview.setRawLink(this.item.absHref);
this.loadContent(this.item);
},
moveIdx(delta) {
this.setIdx(this.idx + delta);
},
loadContent(item) {
Promise.resolve()
.then(() => {
dom('#pv-content').hide().clr();
preview.showSpinner(true, item.thumbSquare || item.icon, 200);
})
.then(() => this.load(item))
.then($content => {
if (item !== this.item) {
return;
}
preview.showSpinner(false);
dom('#pv-content').clr().app($content).show();
this.adjust();
});
}
};
module.exports = {
initItemFn,
pvState
};

View file

@ -1,16 +1,16 @@
const {each, isFn, isNum, dom} = require('../../util'); const {each, isFn, isNum, dom, includes, compact} = require('../../util');
const {win} = require('../../globals'); const {win} = require('../../globals');
const event = require('../../core/event');
const resource = require('../../core/resource'); const resource = require('../../core/resource');
const allsettings = require('../../core/settings'); const allsettings = require('../../core/settings');
const store = require('../../core/store'); const store = require('../../core/store');
const settings = Object.assign({ const settings = Object.assign({
enabled: true enabled: true
}, allsettings.preview); }, allsettings.preview);
const tplOverlay = const tplOverlay =
`<div id="pv-overlay"> `<div id="pv-overlay">
<div id="pv-content"></div> <div id="pv-container"></div>
<div id="pv-spinner"><img class="back"/><img class="spinner" src="${resource.image('spinner')}"/></div> <div id="pv-spinner"><img class="back"/><img class="spinner" src="${resource.image('spinner')}"/></div>
<div id="pv-prev-area" class="hof"><img src="${resource.image('preview-prev')}"/></div> <div id="pv-prev-area" class="hof"><img src="${resource.image('preview-prev')}"/></div>
<div id="pv-next-area" class="hof"><img src="${resource.image('preview-next')}"/></div> <div id="pv-next-area" class="hof"><img src="${resource.image('preview-next')}"/></div>
@ -40,7 +40,7 @@ const adjustSize = () => {
const margin = isFullscreen ? 0 : 20; const margin = isFullscreen ? 0 : 20;
const barHeight = isFullscreen ? 0 : 48; const barHeight = isFullscreen ? 0 : 48;
dom('#pv-content').css({ dom('#pv-container').css({
width: winWidth - 2 * margin + 'px', width: winWidth - 2 * margin + 'px',
height: winHeight - 2 * margin - barHeight + 'px', height: winHeight - 2 * margin - barHeight + 'px',
left: margin + 'px', left: margin + 'px',
@ -130,7 +130,7 @@ const onKeydown = ev => {
const onEnter = () => { const onEnter = () => {
setLabels([]); setLabels([]);
dom('#pv-content').clr(); dom('#pv-container').clr();
dom('#pv-overlay').show(); dom('#pv-overlay').show();
dom(win).on('keydown', onKeydown); dom(win).on('keydown', onKeydown);
adjustSize(); adjustSize();
@ -138,7 +138,7 @@ const onEnter = () => {
const onExit = () => { const onExit = () => {
setLabels([]); setLabels([]);
dom('#pv-content').clr(); dom('#pv-container').clr();
dom('#pv-overlay').hide(); dom('#pv-overlay').hide();
dom(win).off('keydown', onKeydown); dom(win).off('keydown', onKeydown);
}; };
@ -192,8 +192,98 @@ const showSpinner = (show, src, delay) => {
$spinner.show(); $spinner.show();
}; };
const isSpinnerVisible = () => { const centerContent = () => {
return spinnerVisible; const $container = dom('#pv-container');
const container = $container[0];
const content = $container.children()[0];
if (!container || !content) {
return;
}
const containerW = container.offsetWidth;
const containerH = container.offsetHeight;
const contentW = content.offsetWidth;
const contentH = content.offsetHeight;
dom(content).css({
left: (containerW - contentW) * 0.5 + 'px',
top: (containerH - contentH) * 0.5 + 'px'
});
};
const state = (items, idx, load, adjust) => {
const inst = Object.assign(Object.create(state.prototype), {items, load, adjust});
inst.setIdx(idx);
setOnAdjustSize(adjust);
setOnIndexChange(delta => inst.moveIdx(delta));
onEnter();
return {
get item() {
return inst.item;
}
};
};
state.prototype = {
setIdx(idx) {
this.idx = (idx + this.items.length) % this.items.length;
this.item = this.items[this.idx];
this.updateGui();
this.updateContent();
},
moveIdx(delta) {
this.setIdx(this.idx + delta);
},
updateGui() {
setLabels([this.item.label]);
setIndex(this.idx + 1, this.items.length);
setRawLink(this.item.absHref);
},
updateContent() {
const item = this.item;
Promise.resolve()
.then(() => {
dom('#pv-container').hide().clr();
showSpinner(true, item.thumbSquare || item.icon, 200);
})
.then(() => this.load(item))
// delay for testing
// .then(x => new Promise(resolve => setTimeout(() => resolve(x), 1000)))
.then($content => {
if (item !== this.item) {
return;
}
showSpinner(false);
dom('#pv-container').clr().app($content).show();
this.adjust();
});
}
};
const register = (types, enter) => {
const initItem = item => {
if (item.$view && includes(types, item.type)) {
item.$view.find('a').on('click', ev => {
ev.preventDefault();
const matchedItems = compact(dom('#items .item').map(el => {
const matchedItem = el._item;
return includes(types, matchedItem.type) ? matchedItem : null;
}));
enter(matchedItems, matchedItems.indexOf(item));
});
}
};
event.sub('view.changed', added => each(added, initItem));
}; };
const init = () => { const init = () => {
@ -208,7 +298,7 @@ const init = () => {
.on('mousemove', userAlive) .on('mousemove', userAlive)
.on('mousedown', userAlive) .on('mousedown', userAlive)
.on('click', ev => { .on('click', ev => {
if (ev.target.id === 'pv-overlay' || ev.target.id === 'pv-content') { if (ev.target.id === 'pv-overlay' || ev.target.id === 'pv-container') {
onExit(); onExit();
} }
}) })
@ -228,17 +318,11 @@ const init = () => {
.on('load', adjustSize); .on('load', adjustSize);
}; };
init(); init();
module.exports = { module.exports = {
enter: onEnter,
exit: onExit,
setIndex,
setRawLink,
setLabels, setLabels,
setOnIndexChange, centerContent,
setOnAdjustSize, state,
showSpinner, register
isSpinnerVisible
}; };