diff options
| author | rr- | 2016-05-29 12:28:52 +0200 |
|---|---|---|
| committer | rr- | 2016-05-29 12:41:22 +0200 |
| commit | 53fd6fb29bb489c112a87c5effaf6c650a1e5fba (patch) | |
| tree | a1503e48b6d6e3baf039076a4258a63cffe683f8 /client | |
| parent | 6d6cce20dd176e19401f4c4ae8f8c32eb7834cf1 (diff) | |
client/home: view featured post, add search form
Diffstat (limited to 'client')
| -rw-r--r-- | client/css/home.styl | 21 | ||||
| -rw-r--r-- | client/css/main.styl | 15 | ||||
| -rw-r--r-- | client/css/posts.styl | 29 | ||||
| -rw-r--r-- | client/html/home.tpl | 50 | ||||
| -rw-r--r-- | client/html/post_content.tpl | 30 | ||||
| -rw-r--r-- | client/js/controllers/home_controller.js | 11 | ||||
| -rw-r--r-- | client/js/controls/post_content_control.js | 79 | ||||
| -rw-r--r-- | client/js/util/optimized_resize.js | 33 | ||||
| -rw-r--r-- | client/js/util/views.js | 22 | ||||
| -rw-r--r-- | client/js/views/home_view.js | 40 |
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) %>) + • + Running <a class='version' href='https://github.com/rr-/szurubooru/commits/master'><%= ctx.version %></a> + • + 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, + ]; + }); + } } } |