diff --git a/res/css/_animations.sass b/res/css/_animations.sass index 03993488a66c49dd76d7fea702ebff5816520024..db3311c0760d8bba0687bcfee15c5199daa42c2a 100644 --- a/res/css/_animations.sass +++ b/res/css/_animations.sass @@ -23,4 +23,26 @@ to margin-top: 0 - opacity: 1 \ No newline at end of file + opacity: 1 + +@keyframes indeterminate + 0% + left: -35% + right: 100% + 60% + left: 100% + right: -90% + 100% + left: 100% + right: -90% + +@keyframes indeterminate-short + 0% + left: -200% + right: 100% + 60% + left: 107% + right: -8% + 100% + left: 107% + right: -8% \ No newline at end of file diff --git a/res/css/_content.sass b/res/css/_content.sass index a4d6c9e5f90da229f083a3ddd7bb48005775fb45..469fefed5395a9e5caafd6bdcc70077c43b9150f 100644 --- a/res/css/_content.sass +++ b/res/css/_content.sass @@ -14,6 +14,21 @@ padding-right: 0 padding-bottom: 0 + .error + text-align: center + height: 100% + margin: 4rem + animation-duration: 0.6s + animation-name: slidein + + img + max-width: 100% + + h1 + margin-top: 2rem + color: #777 + font-weight: normal + .buffer display: block margin-top: 0 diff --git a/res/css/_loading.sass b/res/css/_loading.sass new file mode 100644 index 0000000000000000000000000000000000000000..06b73b28a3a52c65d14f5c6a383ceae73e9820fe --- /dev/null +++ b/res/css/_loading.sass @@ -0,0 +1,38 @@ +.progress + position: absolute + height: 4px + display: block + background-color: rgba(255,255,255,0.2) + overflow: hidden + transition: opacity 400ms + bottom: 0 + left: 0 + right: 0 + + &:not(.visible) + opacity: 0 + + .indeterminate + background-color: rgba(255,255,255,0.8) + + &:before + content: '' + position: absolute + background-color: inherit + top: 0 + left: 0 + bottom: 0 + will-change: left, right + animation: indeterminate 4.2s cubic-bezier(0.65, 0.815, 0.735, 0.395) infinite + + &:after + content: '' + position: absolute + background-color: inherit + top: 0 + left: 0 + bottom: 0 + will-change: left, right + animation: indeterminate-short 4.2s cubic-bezier(0.165, 0.84, 0.44, 1) infinite + animation-delay: 2.25s + diff --git a/res/css/_nav.sass b/res/css/_nav.sass index 63755938312f9f689079319856212a3f9217f792..3a92ce032807e37d10e3d69cf40023fbbfa93c1f 100644 --- a/res/css/_nav.sass +++ b/res/css/_nav.sass @@ -189,6 +189,12 @@ .icon color: #757575 + .progress + background-color: rgba(117, 117, 117, 0.2) + + .indeterminate + background-color: rgba(117, 117, 117, 0.8) + .history transform: translateY(0) diff --git a/res/css/search.css b/res/css/search.css index f8fccc34f09c9b213a711df7daca411b45683585..66ec6bb91aff4c0d864f812d62082360eef6fd3a 100644 --- a/res/css/search.css +++ b/res/css/search.css @@ -61,6 +61,40 @@ body { visibility: hidden !important; display: none !important; } +.progress { + position: absolute; + height: 4px; + display: block; + background-color: rgba(255, 255, 255, 0.2); + overflow: hidden; + transition: opacity 400ms; + bottom: 0; + left: 0; + right: 0; } + .progress:not(.visible) { + opacity: 0; } + .progress .indeterminate { + background-color: rgba(255, 255, 255, 0.8); } + .progress .indeterminate:before { + content: ''; + position: absolute; + background-color: inherit; + top: 0; + left: 0; + bottom: 0; + will-change: left, right; + animation: indeterminate 4.2s cubic-bezier(0.65, 0.815, 0.735, 0.395) infinite; } + .progress .indeterminate:after { + content: ''; + position: absolute; + background-color: inherit; + top: 0; + left: 0; + bottom: 0; + will-change: left, right; + animation: indeterminate-short 4.2s cubic-bezier(0.165, 0.84, 0.44, 1) infinite; + animation-delay: 2.25s; } + .nav { position: fixed; left: 0; @@ -225,6 +259,10 @@ body { color: #757575; } .nav.focus .bar .container .icon { color: #757575; } + .nav.focus .bar .progress { + background-color: rgba(117, 117, 117, 0.2); } + .nav.focus .bar .progress .indeterminate { + background-color: rgba(117, 117, 117, 0.8); } .nav.focus .history { transform: translateY(0); } .nav.focus + .results { @@ -909,6 +947,18 @@ body { padding-left: 0; padding-right: 0; padding-bottom: 0; } } + .results .error { + text-align: center; + height: 100%; + margin: 4rem; + animation-duration: 0.6s; + animation-name: slidein; } + .results .error img { + max-width: 100%; } + .results .error h1 { + margin-top: 2rem; + color: #777; + font-weight: normal; } .results .buffer { display: block; margin-top: 0; @@ -1239,3 +1289,25 @@ body { to { margin-top: 0; opacity: 1; } } + +@keyframes indeterminate { + 0% { + left: -35%; + right: 100%; } + 60% { + left: 100%; + right: -90%; } + 100% { + left: 100%; + right: -90%; } } + +@keyframes indeterminate-short { + 0% { + left: -200%; + right: 100%; } + 60% { + left: 107%; + right: -8%; } + 100% { + left: 107%; + right: -8%; } } diff --git a/res/css/search.sass b/res/css/search.sass index f5db1ffe420543054694fc86d86138dccbec0571..1d11da7a872e1f90ea999a4a54aaa277598af26a 100644 --- a/res/css/search.sass +++ b/res/css/search.sass @@ -24,6 +24,7 @@ body visibility: hidden !important display: none !important +@import "loading" @import "nav" @import "searchoptions" @import "content" diff --git a/res/js/component/app.js b/res/js/component/app.js index d56ee1cc7001c3434443ac8344c4bf62462c757b..9f8a11025ad7a61254a9e32e43d9da78a6ac77d5 100644 --- a/res/js/component/app.js +++ b/res/js/component/app.js @@ -4,6 +4,7 @@ class App { this.navigation = new Navigation(); this.buffers = []; this.loadingQuery = 0; + this.error = null; if (Storage.exists('language')) { moment.locale(Storage.get('language')); } @@ -31,6 +32,7 @@ class App { } search(query, sender, buffer, network, before, since) { this.clear(); + this.navigation.loading.show(); this.navigation.input.blur(); this.navigation.historyView.resetNavigation(); this.navigation.historyView.add(new HistoryElement(query)); @@ -43,12 +45,16 @@ class App { load('web/search/', statehandler.parse()).then(result => { if (this.loadingQuery !== queryId) return; + this.navigation.loading.hide(); this.buffers = result.map(buffer => { return new Buffer(buffer.bufferid, buffer.buffername, buffer.networkname, buffer.hasmore, buffer.messages.map(msg => { return new Context(new Message(msg.messageid, msg.type, msg.time, msg.sender, msg.message, true)); })); }); this.buffers.forEach(buffer => this.insert(buffer)); + if (this.buffers.length === 0) { + this.showError(translation.error.none_found); + } }); } clear() { @@ -56,12 +62,19 @@ class App { const buffer = this.buffers.pop(); this.resultContainer.removeChild(buffer.elem); } + if (this.error) { + this.resultContainer.removeChild(this.error.elem); + } } clearAll() { this.clear(); this.navigation.historyView.clear(); statehandler.clear(); } + showError(text) { + this.error = new Error(text); + this.resultContainer.appendChild(this.error.elem); + } insert(buffer) { this.resultContainer.appendChild(buffer.elem); buffer.addEventListener('loadMore', () => this.bufferLoadMore(buffer)); diff --git a/res/js/component/app.jsx b/res/js/component/app.jsx index 115694d047d04e651e87b2919fe568210d634dde..65fde68e421111c5ab73aa5138a9c15317dc9cd1 100644 --- a/res/js/component/app.jsx +++ b/res/js/component/app.jsx @@ -6,6 +6,7 @@ class App { this.buffers = []; this.loadingQuery = 0; + this.error = null; if (Storage.exists('language')) { moment.locale(Storage.get('language')); @@ -32,6 +33,8 @@ class App { search(query, sender, buffer, network, before, since) { this.clear(); + this.navigation.loading.show(); + this.navigation.input.blur(); this.navigation.historyView.resetNavigation(); this.navigation.historyView.add(new HistoryElement(query)); @@ -47,12 +50,16 @@ class App { if (this.loadingQuery !== queryId) return; + this.navigation.loading.hide(); this.buffers = result.map((buffer) => { return new Buffer(buffer.bufferid, buffer.buffername, buffer.networkname, buffer.hasmore, buffer.messages.map((msg) => { return new Context(new Message(msg.messageid, msg.type, msg.time, msg.sender, msg.message, true)); })); }); this.buffers.forEach((buffer) => this.insert(buffer)); + if (this.buffers.length === 0) { + this.showError(translation.error.none_found); + } }); } @@ -61,6 +68,10 @@ class App { const buffer = this.buffers.pop(); this.resultContainer.removeChild(buffer.elem); } + + if (this.error) { + this.resultContainer.removeChild(this.error.elem); + } } clearAll() { @@ -69,6 +80,11 @@ class App { statehandler.clear(); } + showError(text) { + this.error = new Error(text); + this.resultContainer.appendChild(this.error.elem); + } + insert(buffer) { this.resultContainer.appendChild(buffer.elem); buffer.addEventListener("loadMore", () => this.bufferLoadMore(buffer)); diff --git a/res/js/component/error.js b/res/js/component/error.js new file mode 100644 index 0000000000000000000000000000000000000000..6633e396b6af0c48990d2cc7646141e711a31087 --- /dev/null +++ b/res/js/component/error.js @@ -0,0 +1,18 @@ +class Error { + constructor(text) { + this.render(text); + } + render(text) { + return this.elem = function () { + var $$a = document.createElement('div'); + $$a.setAttribute('class', 'error'); + var $$b = document.createElement('img'); + $$b.setAttribute('src', 'res/icons/error.png'); + $$a.appendChild($$b); + var $$c = document.createElement('h1'); + $$a.appendChild($$c); + $$c.appendChildren(text); + return $$a; + }.call(this); + } +} \ No newline at end of file diff --git a/res/js/component/error.jsx b/res/js/component/error.jsx new file mode 100644 index 0000000000000000000000000000000000000000..ddb9a7389b51d9ad06c4c23d9c5b7ab4f769fd2e --- /dev/null +++ b/res/js/component/error.jsx @@ -0,0 +1,14 @@ +class Error { + constructor(text) { + this.render(text); + } + + render(text) { + return this.elem = ( + <div className="error"> + <img src="res/icons/error.png"/> + <h1>{text}</h1> + </div> + ); + } +} \ No newline at end of file diff --git a/res/js/component/loading.js b/res/js/component/loading.js new file mode 100644 index 0000000000000000000000000000000000000000..158a3ae08112a6f523802cf9924431c9ce32e104 --- /dev/null +++ b/res/js/component/loading.js @@ -0,0 +1,21 @@ +class Loading { + constructor() { + this.render(); + } + render() { + return this.elem = function () { + var $$a = document.createElement('div'); + $$a.setAttribute('class', 'progress'); + var $$b = document.createElement('div'); + $$b.setAttribute('class', 'indeterminate'); + $$a.appendChild($$b); + return $$a; + }.call(this); + } + show() { + this.elem.classList.add('visible'); + } + hide() { + this.elem.classList.remove('visible'); + } +} \ No newline at end of file diff --git a/res/js/component/loading.jsx b/res/js/component/loading.jsx new file mode 100644 index 0000000000000000000000000000000000000000..8d72d35f8fda04edc795f421c74407dfa591bb1f --- /dev/null +++ b/res/js/component/loading.jsx @@ -0,0 +1,22 @@ +class Loading { + constructor() { + this.render(); + } + + render() { + return this.elem = ( + <div className="progress"> + <div className="indeterminate"> + </div> + </div> + ); + } + + show() { + this.elem.classList.add("visible"); + } + + hide() { + this.elem.classList.remove("visible"); + } +} \ No newline at end of file diff --git a/res/js/component/nav.js b/res/js/component/nav.js index fa2b31e0f43a14e484adfcdf81977a5ee108eb41..701376af5db3ed667bcbb3162c0e2bac2428bee9 100644 --- a/res/js/component/nav.js +++ b/res/js/component/nav.js @@ -52,6 +52,7 @@ class Navigation extends Component { $$i.appendChild($$j); var $$k = document.createTextNode('exit_to_app'); $$j.appendChild($$k); + $$b.appendChildren((this.loading = new Loading()).elem); $$a.appendChildren((this.historyView = new HistoryView()).elem); return $$a; }.call(this); diff --git a/res/js/component/nav.jsx b/res/js/component/nav.jsx index b7389c7305246ddb6d5f0d9085f3f0c9c46abc18..c0f064b94814c468177aaa3632836294341fe78b 100644 --- a/res/js/component/nav.jsx +++ b/res/js/component/nav.jsx @@ -36,6 +36,7 @@ class Navigation extends Component { className="icon">exit_to_app</a> </div> </div> + {(this.loading = new Loading()).elem} </div> {(this.historyView = new HistoryView()).elem} </div> diff --git a/templates/search.phtml b/templates/search.phtml index a72a637f71a853bb8432c6554bc9d8384b789df6..3b032a48081dc15f78fea4c675858a0e71a76910 100644 --- a/templates/search.phtml +++ b/templates/search.phtml @@ -42,6 +42,8 @@ <script src="res/js/component/nav.js"></script> <script src="res/js/component/message.js"></script> <script src="res/js/component/loadmore.js"></script> +<script src="res/js/component/error.js"></script> +<script src="res/js/component/loading.js"></script> <script src="res/js/component/context.js"></script> <script src="res/js/component/buffer.js"></script> <script src="res/js/component/app.js"></script> diff --git a/translations/de.json b/translations/de.json index e6fe6c122aa60e928adbb07ae89a7355c3144acf..8937f7110a9812d46de085b72e1051d852cea970 100644 --- a/translations/de.json +++ b/translations/de.json @@ -24,6 +24,9 @@ "error_unauthed": "Sie müssen angemeldet sein, um diese Seite zu nutzen." } }, + "error": { + "none_found": "Keine Ergebnisse gefunden" + }, "search": "Suchen", "logout": "Abmelden", "title": "QuasselSearch" diff --git a/translations/en.json b/translations/en.json index bfde2bd48ea9ef86655731a2fea57684fd481bbc..87a4f4e189d8808b2fe3abc09db33e6d26a79b86 100644 --- a/translations/en.json +++ b/translations/en.json @@ -24,6 +24,9 @@ "error_unauthed": "You need to be logged in to access this page." } }, + "error": { + "none_found": "No results found" + }, "search": "Search", "logout": "Logout", "title": "QuasselSearch"