client/home: view featured post, add search form

This commit is contained in:
rr- 2016-05-29 12:28:52 +02:00
parent 6d6cce20dd
commit 53fd6fb29b
10 changed files with 310 additions and 20 deletions

View File

@ -3,6 +3,27 @@
h1
margin-top: 0
margin-bottom: 0.5em
.message
margin-bottom: 2em
.post-container
display: flex
align-items: center
justify-content: center
nav
margin-bottom: 0.5em
a
padding: 0.5em
form
width: auto
aside
margin-top: 0.2em
margin-bottom: 1em
footer
font-size: 80%

View File

@ -132,7 +132,6 @@ a .access-key
.access-key
text-decoration: underline
.thumbnail
vertical-align: middle
width: 1.5em
height: 1.5em
margin: calc((2.3em - 1.5em) / 2)
@ -161,15 +160,19 @@ a .access-key
.thumbnail
/*background-image: attr(data-src url)*/ /* not available yet */
vertical-align: middle
background-repeat: no-repeat
background-size: cover
background-position: center
display: inline-block
.thumbnail img
img
opacity: 0
width: 100%
height: 100%
span>.thumbnail
width: 20px
height: 20px
margin: 0 0.4em 0 0.4em
.flexbox-dummy
height: 0 !important

29
client/css/posts.styl Normal file
View File

@ -0,0 +1,29 @@
@import colors
.post-container
text-align: center
.post-content
text-align: left
margin: 0 auto
max-height: 100%
max-width: 100%
object-fit: contain
position: relative
img, object, video, .post-overlay
position: absolute
height: 100%
width: 100%
left: 0
right: 0
top: 0
bottom: 0
.post-overlay>*
position: absolute
left: 0
right: 0
top: 0
bottom: 0
width: 100%
height: 100%

View File

@ -2,7 +2,53 @@
<div class='messages'></div>
<header>
<h1><%= ctx.name %></h1>
<p>Serving <%= ctx.postCount %> posts (<%= ctx.makeFileSize(ctx.diskUsage) %>)</p>
</header>
<footer>Version: <a class='version' href='https://github.com/rr-/szurubooru/commits/master'><%= ctx.version %></a> (built <%= ctx.makeRelativeTime(ctx.buildDate) %>)</footer>
<nav>
<ul>
<% if (ctx.canListPosts) { %>
<li><a href='/posts'><%= ctx.makeAccessKey('Posts', 'P') %></a></li>
<% } %>
<% if (ctx.canListComments) { %>
<li><a href='/comments'><%= ctx.makeAccessKey('Comments', 'C') %></a></li>
<% } %>
<% if (ctx.canListTags) { %>
<li><a href='/tags'><%= ctx.makeAccessKey('Tags', 'T') %></a></li>
<% } %>
<% if (ctx.canListUsers) { %>
<li><a href='/users'><%= ctx.makeAccessKey('Users', 'U') %></a></li>
<% } %>
<li><a href='/help'><%= ctx.makeAccessKey('Help', 'E') %></a></li>
</ul>
</nav>
<% if (ctx.canListPosts) { %>
<form class='horizontal'>
<div class='input'>
<ul>
<li>
<%= ctx.makeTextInput({id: 'search-text', name: 'search-text'}) %>
</li>
</ul>
</div>
<div class='buttons'>
<input type='submit' value='Search'/>
</div>
</form>
<% } %>
<div class='post-container'></div>
<% if (ctx.featuredPost) { %>
<aside>
<%= ctx.makePostLink(ctx.featuredPost.id) %>
uploaded
<%= ctx.makeRelativeTime(ctx.featuredPost.creationTime) %>
by
<%= ctx.makeUserLink(ctx.featuredPost.user) %>
</aside>
<% } %>
<footer>
Serving <%= ctx.postCount %> posts (<%= ctx.makeFileSize(ctx.diskUsage) %>)
&bull;
Running <a class='version' href='https://github.com/rr-/szurubooru/commits/master'><%= ctx.version %></a>
&bull;
Built <%= ctx.makeRelativeTime(ctx.buildDate) %>
</footer>
</div>

View File

@ -0,0 +1,30 @@
<div class='post-content post-type-<%= ctx.post.type %>'>
<% if (['image', 'animation'].includes(ctx.post.type)) { %>
<img alt='<%= ctx.post.id %>' src='<%= ctx.post.contentUrl %>'/>
<% } else if (ctx.post.type === 'flash') { %>
<object width='<%= ctx.post.canvasWidth %>' height='<%= ctx.post.canvasHeight %>' data='<%= ctx.post.contentUrl %>'>
<param name='wmode' value='opaque'/>
<param name='movie' value='<%= ctx.post.contentUrl %>'/>
</object>
<% } else if (ctx.post.type === 'video') { %>
<% if (ctx.post.flags.includes('loop')) { %>
<video id='video' controls loop='loop'>
<% } else { %>
<video id='video' controls>
<% } %>
<source type='<%= ctx.post.mimeType %>' src='<%= ctx.post.contentUrl %>'/>
Your browser doesn't support HTML5 videos.
</video>
<% } else { console.log(new Error('Unknown post type')); } %>
<div class='post-overlay'>
</div>
</div>

View File

@ -24,13 +24,22 @@ class HomeController {
api.get('/info')
.then(response => {
this._homeView.render({
canListPosts: api.hasPrivilege('posts:list'),
canListComments: api.hasPrivilege('comments:list'),
canListTags: api.hasPrivilege('tags:list'),
canListUsers: api.hasPrivilege('users:list'),
diskUsage: response.diskUsage,
postCount: response.postCount,
featuredPost: response.featuredPost,
});
},
response => {
this._homeView.render({});
this._homeView.render({
canListPosts: api.hasPrivilege('posts:list'),
canListComments: api.hasPrivilege('comments:list'),
canListTags: api.hasPrivilege('tags:list'),
canListUsers: api.hasPrivilege('users:list'),
});
events.notify(events.Error, response.description);
});
}

View File

@ -0,0 +1,79 @@
'use strict';
const views = require('../util/views.js');
const optimizedResize = require('../util/optimized_resize.js');
class PostContentControl {
constructor(containerNode, post, viewportSizeCalculator) {
post.canvasWidth = post.canvasWidth || 800;
post.canvasHeight = post.canvasHeight || 450;
this._post = post;
this._viewportSizeCalculator = viewportSizeCalculator;
this._containerNode = containerNode;
this._template = views.getTemplate('post-content');
this._install();
this._currentFitFunction = this.fitBoth;
this._currentFitFunction();
}
fitWidth() {
this._currentFitFunction = this.fitWidth;
const mul = this._post.canvasHeight / this._post.canvasWidth;
this._resize(this._viewportWidth, this._viewportWidth * mul);
}
fitHeight() {
this._currentFitFunction = this.fitHeight;
const mul = this._post.canvasWidth / this._post.canvasHeight;
this._resize(this._viewportHeight * mul, this._viewportHeight);
}
fitBoth() {
this._currentFitFunction = this.fitBoth;
let mul = this._post.canvasHeight / this._post.canvasWidth;
if (this._viewportWidth * mul < this._viewportHeight) {
this._resize(this._viewportWidth, this._viewportWidth * mul);
} else {
mul = this._post.canvasWidth / this._post.canvasHeight;
this._resize(this._viewportHeight * mul, this._viewportHeight);
}
}
get _viewportWidth() {
return this._viewportSizeCalculator()[0];
}
get _viewportHeight() {
return this._viewportSizeCalculator()[1];
}
_resize(width, height) {
const postContentNode =
this._containerNode.querySelector('.post-content');
postContentNode.style.width = width + 'px';
postContentNode.style.height = height + 'px';
}
_refreshSize() {
this._currentFitFunction();
}
_install() {
const postContentNode = this._template({
post: this._post,
});
this._containerNode.appendChild(postContentNode);
optimizedResize.add(() => this._refreshSize());
views.monitorNodeRemoval(
this._containerNode, () => { this._uninstall(); });
}
_uninstall() {
optimizedResize.remove(() => this._refreshSize());
}
}
module.exports = PostContentControl;

View File

@ -0,0 +1,33 @@
'use strict';
let callbacks = [];
let running = false;
function resize() {
if (!running) {
running = true;
if (window.requestAnimationFrame) {
window.requestAnimationFrame(runCallbacks);
} else {
setTimeout(runCallbacks, 66);
}
}
}
function runCallbacks() {
callbacks.forEach(function(callback) {
callback();
});
running = false;
}
function add(callback) {
callbacks.push(callback);
}
function remove(callback) {
callbacks = callbacks.filter(c => c !== callback);
}
window.addEventListener('resize', resize);
module.exports = {add: add, remove: remove};

View File

@ -141,19 +141,27 @@ function makeColorInput(options) {
'label', {class: 'color'}, colorInput + textInput);
}
function makeTagLink(name) {
let category = null;
try {
category = tags.getTagByName(name).category;
} catch (e) {
category = 'unknown';
function makePostLink(id) {
return makeNonVoidElement('a', {
'href': '/post/' + id,
}, '@' + id);
}
function makeTagLink(name) {
const tag = tags.getTagByName(name);
let category = tag ? tag.category : 'unknown';
return makeNonVoidElement('a', {
'href': '/tag/' + name,
'class': 'tag-' + category,
}, name);
}
function makeUserLink(user) {
return makeNonVoidElement('span', {class: 'user'},
makeThumbnail(user.avatarUrl) +
makeNonVoidElement('a', {'href': '/user/' + user.name}, user.name));
}
function makeFlexboxAlign(options) {
return Array.from(misc.range(20))
.map(() => '<li class="flexbox-dummy"></li>').join('');
@ -265,7 +273,9 @@ function getTemplate(templatePath) {
makePasswordInput: makePasswordInput,
makeEmailInput: makeEmailInput,
makeColorInput: makeColorInput,
makePostLink: makePostLink,
makeTagLink: makeTagLink,
makeUserLink: makeUserLink,
makeFlexboxAlign: makeFlexboxAlign,
makeAccessKey: makeAccessKey,
});

View File

@ -1,25 +1,55 @@
'use strict';
const page = require('page');
const config = require('../config.js');
const misc = require('../util/misc.js');
const views = require('../util/views.js');
const PostContentControl = require('../controls/post_content_control.js');
const TagAutoCompleteControl =
require('../controls/tag_auto_complete_control.js');
class HomeView {
constructor() {
this._template = views.getTemplate('home');
this._homeTemplate = views.getTemplate('home');
}
render(ctx) {
const target = document.getElementById('content-holder');
const source = this._template({
Object.assign(ctx, {
name: config.name,
version: config.meta.version,
buildDate: config.meta.buildDate,
diskUsage: ctx.diskUsage,
postCount: ctx.postCount,
});
const target = document.getElementById('content-holder');
const source = this._homeTemplate(ctx);
views.listenToMessages(source);
views.showView(target, source);
const form = source.querySelector('form');
if (form) {
const searchTextInput = form.querySelector('input');
new TagAutoCompleteControl(searchTextInput);
form.addEventListener('submit', e => {
e.preventDefault();
const text = searchTextInput.value;
searchTextInput.blur();
page('/posts/' + misc.formatSearchQuery({text: text}));
});
}
const postContainerNode = source.querySelector('.post-container');
if (postContainerNode && ctx.featuredPost) {
new PostContentControl(
postContainerNode,
ctx.featuredPost,
() => {
return [
window.innerWidth * 0.8,
window.innerHeight * 0.7,
];
});
}
}
}