aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorrr-2016-05-29 12:28:52 +0200
committerrr-2016-05-29 12:41:22 +0200
commit53fd6fb29bb489c112a87c5effaf6c650a1e5fba (patch)
treea1503e48b6d6e3baf039076a4258a63cffe683f8
parent6d6cce20dd176e19401f4c4ae8f8c32eb7834cf1 (diff)
client/home: view featured post, add search form
-rw-r--r--client/css/home.styl21
-rw-r--r--client/css/main.styl15
-rw-r--r--client/css/posts.styl29
-rw-r--r--client/html/home.tpl50
-rw-r--r--client/html/post_content.tpl30
-rw-r--r--client/js/controllers/home_controller.js11
-rw-r--r--client/js/controls/post_content_control.js79
-rw-r--r--client/js/util/optimized_resize.js33
-rw-r--r--client/js/util/views.js22
-rw-r--r--client/js/views/home_view.js40
10 files changed, 310 insertions, 20 deletions
diff --git a/client/css/home.styl b/client/css/home.styl
index 18fc877..0c6d549 100644
--- a/client/css/home.styl
+++ b/client/css/home.styl
@@ -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%
diff --git a/client/css/main.styl b/client/css/main.styl
index 0f114f9..8fd92f9 100644
--- a/client/css/main.styl
+++ b/client/css/main.styl
@@ -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
- opacity: 0
- width: 100%
- height: 100%
+ 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
diff --git a/client/css/posts.styl b/client/css/posts.styl
new file mode 100644
index 0000000..3ea0f8b
--- /dev/null
+++ b/client/css/posts.styl
@@ -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%
diff --git a/client/html/home.tpl b/client/html/home.tpl
index b14b0c9..78d995f 100644
--- a/client/html/home.tpl
+++ b/client/html/home.tpl
@@ -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>
diff --git a/client/html/post_content.tpl b/client/html/post_content.tpl
new file mode 100644
index 0000000..41be1bd
--- /dev/null
+++ b/client/html/post_content.tpl
@@ -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>
diff --git a/client/js/controllers/home_controller.js b/client/js/controllers/home_controller.js
index 25800d0..cd44a61 100644
--- a/client/js/controllers/home_controller.js
+++ b/client/js/controllers/home_controller.js
@@ -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);
});
}
diff --git a/client/js/controls/post_content_control.js b/client/js/controls/post_content_control.js
new file mode 100644
index 0000000..9775ec4
--- /dev/null
+++ b/client/js/controls/post_content_control.js
@@ -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;
diff --git a/client/js/util/optimized_resize.js b/client/js/util/optimized_resize.js
new file mode 100644
index 0000000..b8e1342
--- /dev/null
+++ b/client/js/util/optimized_resize.js
@@ -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};
diff --git a/client/js/util/views.js b/client/js/util/views.js
index 656ab04..10e6b5c 100644
--- a/client/js/util/views.js
+++ b/client/js/util/views.js
@@ -141,19 +141,27 @@ function makeColorInput(options) {
'label', {class: 'color'}, colorInput + textInput);
}
+function makePostLink(id) {
+ return makeNonVoidElement('a', {
+ 'href': '/post/' + id,
+ }, '@' + id);
+}
+
function makeTagLink(name) {
- let category = null;
- try {
- category = tags.getTagByName(name).category;
- } catch (e) {
- category = 'unknown';
- }
+ 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,
});
diff --git a/client/js/views/home_view.js b/client/js/views/home_view.js
index 9f7c3b6..1cf81cf 100644
--- a/client/js/views/home_view.js
+++ b/client/js/views/home_view.js
@@ -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,
+ ];
+ });
+ }
}
}

© 2015 - 2026 Jakob L. Kreuze