Skip to content
Snippets Groups Projects
Commit e1450d28 authored by Eliot Berriot's avatar Eliot Berriot
Browse files

Merge branch 'webpack-tweaking' into 'develop'

Improve front-end performance

See merge request funkwhale/funkwhale!499
parents 4c2730bc 4f44a5ab
No related branches found
No related tags found
No related merge requests found
Showing
with 210 additions and 273 deletions
......@@ -93,5 +93,6 @@ po/*.po
docs/swagger
_build
front/src/translations.json
front/src/translations/*.json
front/locales/en_US/LC_MESSAGES/app.po
*.prof
......@@ -58,15 +58,6 @@ class RavenDSN(types.StringPreference):
field_kwargs = {"required": False}
@global_preferences_registry.register
class RavenEnabled(types.BooleanPreference):
show_in_api = True
section = raven
name = "front_enabled"
default = False
verbose_name = "Report front-end errors with Raven"
@global_preferences_registry.register
class InstanceNodeinfoEnabled(types.BooleanPreference):
show_in_api = False
......
Improved front-end performance by stripping unused dependencies, reducing bundle size
and enabling gzip compression
Enable gzip compression [manual action suggested]
-------------------------------------------------
Gzip compression will be enabled on new instances by default
and will reduce the amount of bandwidth consumed by your instance.
If you with to benefit from gzip compression on your instance,
edit your reverse proxy virtualhost file (located at ``/etc/nginx/sites-available/funkwhale.conf``) and add the following snippet
in the server block, then reload your nginx server::
server {
# ... exiting configuration
# compression settings
gzip on;
gzip_comp_level 5;
gzip_min_length 256;
gzip_proxied any;
gzip_vary on;
gzip_types
application/atom+xml
application/javascript
application/json
application/ld+json
application/activity+json
application/manifest+json
application/rss+xml
application/vnd.geo+json
application/vnd.ms-fontobject
application/x-font-ttf
application/x-web-app-manifest+json
application/xhtml+xml
application/xml
font/opentype
image/bmp
image/svg+xml
image/x-icon
text/cache-manifest
text/css
text/plain
text/vcard
text/vnd.rim.location.xloc
text/vtt
text/x-component
text/x-cross-domain-policy;
# end of compression settings
}
......@@ -29,6 +29,40 @@ server {
# HSTS
add_header Strict-Transport-Security "max-age=31536000";
# compression settings
gzip on;
gzip_comp_level 5;
gzip_min_length 256;
gzip_proxied any;
gzip_vary on;
gzip_types
application/atom+xml
application/javascript
application/json
application/ld+json
application/activity+json
application/manifest+json
application/rss+xml
application/vnd.geo+json
application/vnd.ms-fontobject
application/x-font-ttf
application/x-web-app-manifest+json
application/xhtml+xml
application/xml
font/opentype
image/bmp
image/svg+xml
image/x-icon
text/cache-manifest
text/css
text/plain
text/vcard
text/vnd.rim.location.xloc
text/vtt
text/x-component
text/x-cross-domain-policy;
location / {
include /etc/nginx/funkwhale_proxy.conf;
proxy_pass http://fw/;
......
......@@ -43,6 +43,41 @@ server {
root ${FUNKWHALE_FRONTEND_PATH};
# compression settings
gzip on;
gzip_comp_level 5;
gzip_min_length 256;
gzip_proxied any;
gzip_vary on;
gzip_types
application/atom+xml
application/javascript
application/json
application/ld+json
application/activity+json
application/manifest+json
application/rss+xml
application/vnd.geo+json
application/vnd.ms-fontobject
application/x-font-ttf
application/x-web-app-manifest+json
application/xhtml+xml
application/xml
font/opentype
image/bmp
image/svg+xml
image/x-icon
text/cache-manifest
text/css
text/plain
text/vcard
text/vnd.rim.location.xloc
text/vtt
text/x-component
text/x-cross-domain-policy;
# end of compression settings
location / {
include /etc/nginx/funkwhale_proxy.conf;
# this is needed if you have file import via upload enabled
......
......@@ -43,6 +43,39 @@ http {
charset utf-8;
client_max_body_size 30M;
include /etc/nginx/funkwhale_proxy.conf;
# compression settings
gzip on;
gzip_comp_level 5;
gzip_min_length 256;
gzip_proxied any;
gzip_vary on;
gzip_types
application/atom+xml
application/javascript
application/json
application/ld+json
application/activity+json
application/manifest+json
application/rss+xml
application/vnd.geo+json
application/vnd.ms-fontobject
application/x-font-ttf
application/x-web-app-manifest+json
application/xhtml+xml
application/xml
font/opentype
image/bmp
image/svg+xml
image/x-icon
text/cache-manifest
text/css
text/plain
text/vcard
text/vnd.rim.location.xloc
text/vtt
text/x-component
text/x-cross-domain-policy;
location /front/ {
proxy_pass http://funkwhale-front/front/;
......
......@@ -182,7 +182,7 @@ work. We will store the necessary files in the ``/srv/funkwhale/custom`` directo
mkdir custom
cat <<EOF > custom/settings.json
{
"additionalStylesheets": ["/custom/custom.css"]
"additionalStylesheets": ["/front/custom/custom.css"]
}
EOF
cat <<EOF > custom/custom.css
......@@ -194,7 +194,7 @@ work. We will store the necessary files in the ``/srv/funkwhale/custom`` directo
By executing the previous commands, you will end up with two files in your ``/srv/funkwhale/custom``
directory:
- ``settings.json`` will tell the front-end what stylesheets you want to load (``/custom/custom.css`` in this example)
- ``settings.json`` will tell the front-end what stylesheets you want to load (``/front/custom/custom.css`` in this example)
- ``custom.css`` will hold your custom CSS
The last step to make this work is to ensure both files are served by the reverse proxy.
......
......@@ -3,7 +3,7 @@
"version": "0.1.0",
"private": true,
"scripts": {
"serve": "scripts/i18n-compile.sh && vue-cli-service serve --port ${VUE_PORT:-8000} --host ${VUE_HOST:-0.0.0.0}",
"serve": "vue-cli-service serve --port ${VUE_PORT:-8000} --host ${VUE_HOST:-0.0.0.0}",
"build": "scripts/i18n-compile.sh && vue-cli-service build",
"lint": "vue-cli-service lint",
"i18n-extract": "scripts/i18n-extract.sh",
......@@ -20,8 +20,7 @@
"lodash": "^4.17.10",
"masonry-layout": "^4.2.2",
"moment": "^2.22.2",
"raven-js": "^3.26.4",
"semantic-ui-css": "^2.3.3",
"semantic-ui-css": "^2.4.1",
"showdown": "^1.8.6",
"vue": "^2.5.17",
"vue-gettext": "^2.1.0",
......@@ -49,7 +48,8 @@
"node-sass": "^4.9.3",
"sass-loader": "^7.1.0",
"sinon": "^6.1.5",
"vue-template-compiler": "^2.5.17"
"vue-template-compiler": "^2.5.17",
"webpack-bundle-size-analyzer": "^3.0.0"
},
"eslintConfig": {
"root": true,
......
{
"additionalStylesheets": ["/custom.css"]
"additionalStylesheets": [
"/front/custom.css"
]
}
#!/bin/bash -eux
locales=$(tail -n +2 src/locales.js | sed -e 's/export default //' | jq '.locales[].code' | xargs echo)
find locales -name '*.po' | xargs $(yarn bin)/gettext-compile --output src/translations.json
locales=$(tail -n +2 src/locales.js | sed -e 's/export default //' | jq '.locales[].code' | grep -v 'en_US' | xargs echo)
for locale in $locales; do
find "locales/$locale" -name '*.po' | $(yarn bin)/gettext-compile locales/$locale/LC_MESSAGES/app.po --output src/translations/$locale.json
done
# find locales -name '*.po' | xargs $(yarn bin)/gettext-compile --output src/translations.json
......@@ -43,10 +43,6 @@
:version="version"
@show:shortcuts-modal="showShortcutsModal = !showShortcutsModal"
></app-footer>
<raven
v-if="$store.state.instance.settings.raven.front_enabled.value"
:dsn="$store.state.instance.settings.raven.front_dsn.value"
></raven>
<playlist-modal v-if="$store.state.auth.authenticated"></playlist-modal>
<shortcuts-modal @update:show="showShortcutsModal = $event" :show="showShortcutsModal"></shortcuts-modal>
<GlobalEvents @keydown.h.exact="showShortcutsModal = !showShortcutsModal"/>
......@@ -56,18 +52,16 @@
<script>
import axios from 'axios'
import _ from 'lodash'
import _ from '@/lodash'
import {mapState} from 'vuex'
import { WebSocketBridge } from 'django-channels'
import GlobalEvents from '@/components/utils/global-events'
import translations from '@/translations'
import Sidebar from '@/components/Sidebar'
import AppFooter from '@/components/Footer'
import Raven from '@/components/Raven'
import ServiceMessages from '@/components/ServiceMessages'
import locales from './locales'
import PlaylistModal from '@/components/playlists/PlaylistModal'
import ShortcutsModal from '@/components/ShortcutsModal'
......@@ -76,7 +70,6 @@ export default {
components: {
Sidebar,
AppFooter,
Raven,
PlaylistModal,
ShortcutsModal,
GlobalEvents,
......@@ -139,7 +132,7 @@ export default {
},
autodetectLanguage () {
let userLanguage = navigator.language || navigator.userLanguage
let available = _.keys(translations)
let available = locales.locales.map(e => { return e.code })
let matching = available.filter((a) => {
return userLanguage.replace('-', '_') === a
})
......@@ -223,181 +216,5 @@ export default {
</script>
<style lang="scss">
// we do the import here instead in main.js
// as resolve order is not deterministric in webpack
// and we end up with CSS rules not applied,
// see https://github.com/webpack/webpack/issues/215
@import "semantic/semantic.css";
@import "style/vendor/media";
html,
body {
@include media("<desktop") {
font-size: 90%;
}
}
#app {
font-family: "Avenir", Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.instance-chooser {
margin-top: 2em;
}
.main.pusher,
.footer {
@include media(">desktop") {
margin-left: 350px !important;
margin-top: 50px;
}
transform: none !important;
}
.main.pusher > .ui.secondary.menu {
margin-left: 0;
margin-right: 0;
border: none;
box-shadow: inset 0px -2px 0px 0px rgba(34, 36, 38, 0.15);
.ui.item {
border: none;
border-bottom-style: none;
margin-bottom: 0px;
&.active {
box-shadow: inset 0px -2px 0px 0px #000;
}
}
@include media(">tablet") {
padding: 0 2.5rem;
}
@include media(">desktop") {
position: fixed;
left: 350px;
right: 0px;
top: 0px;
z-index: 99;
}
background-color: white;
.item {
padding-top: 1.5em;
padding-bottom: 1.5em;
}
}
.service-messages {
position: fixed;
bottom: 1em;
left: 1em;
@include media(">desktop") {
left: 350px;
}
}
.main-pusher {
padding: 1.5rem 0;
}
.ui.stripe.segment,
#footer {
padding: 2em;
@include media(">tablet") {
padding: 4em;
}
}
.ellipsis {
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
}
.ui.small.text.container {
max-width: 500px !important;
}
.button.icon.tiny {
padding: 0.5em !important;
}
.sidebar {
.logo {
path {
fill: white;
}
}
}
.discrete {
color: rgba(0, 0, 0, 0.87);
}
.link {
cursor: pointer;
}
.ui.really.basic.button {
&:not(:focus) {
box-shadow: none !important;
background-color: none !important;
}
}
.floated.buttons .button ~ .dropdown {
border-left: none;
}
.ui.icon.header .circular.icon {
display: flex;
justify-content: center;
}
.segment-content .button {
margin: 0.5em;
}
a {
cursor: pointer;
}
.segment.hidden {
display: none;
}
button.reset {
border: none;
margin: 0;
padding: 0;
width: auto;
overflow: visible;
background: transparent;
/* inherit font & color from ancestor */
color: inherit;
font: inherit;
/* Normalize `line-height`. Cannot be changed from `normal` in Firefox 4+. */
line-height: normal;
/* Corrects font smoothing for webkit */
-webkit-font-smoothing: inherit;
-moz-osx-font-smoothing: inherit;
/* Corrects inability to style clickable `input` types in iOS */
-webkit-appearance: none;
text-align: inherit;
}
.ui.table > caption {
font-weight: bold;
padding: 0.5em;
text-align: left;
}
[role="button"] {
cursor: pointer;
}
.left.floated {
float: left;
}
.right.floated {
float: right;
}
@import "style/_main";
</style>
front/src/assets/audio/default-cover.png

66.8 KiB | W: | H:

front/src/assets/audio/default-cover.png

21.1 KiB | W: | H:

front/src/assets/audio/default-cover.png
front/src/assets/audio/default-cover.png
front/src/assets/audio/default-cover.png
front/src/assets/audio/default-cover.png
  • 2-up
  • Swipe
  • Onion skin
......@@ -27,9 +27,9 @@
<p>{{ instance.short_description.value }}</p>
</div>
<div
v-if="instance.long_description.value"
v-if="markdown && instance.long_description.value"
class="ui middle aligned stackable text container"
v-html="$options.filters.markdown(instance.long_description.value)">
v-html="markdown.makeHtml(instance.long_description.value)">
</div>
</section>
</main>
......@@ -43,8 +43,17 @@ export default {
components: {
Stats
},
data () {
return {
markdown: null
}
},
created () {
this.$store.dispatch("instance/fetchSettings")
let self = this
import('showdown').then(module => {
self.markdown = new module.default.Converter()
})
},
computed: {
...mapState({
......
......@@ -20,7 +20,7 @@
<div class="ui form">
<div class="ui field">
<label><translate>Change language</translate></label>
<select class="ui dropdown" v-model="$language.current">
<select class="ui dropdown" :value="$language.current" @change="updateLanguage($event.target.value)">
<option v-for="(language, key) in $language.available" :key="key" :value="key">{{ language }}</option>
</select>
</div>
......@@ -60,7 +60,9 @@
</template>
<script>
import Vue from "vue"
import { mapState } from "vuex"
import axios from 'axios'
export default {
props: ["version"],
......@@ -74,6 +76,13 @@ export default {
if (confirm) {
this.$store.commit("instance/instanceUrl", null)
}
},
updateLanguage(value) {
let self = this
import(`../translations/${value}.json`).then((response) =>{
Vue.$translations[value] = response.default[value]
self.$language.current = value
})
}
},
computed: {
......
......@@ -24,7 +24,7 @@
</template>
<script>
import _ from "lodash"
import _ from "@/lodash"
export default {
props: {
......
<template>
<div class="raven"></div>
</template>
<script>
import Raven from 'raven-js'
import RavenVue from 'raven-js/plugins/vue'
import Vue from 'vue'
import logger from '@/logging'
export default {
props: ['dsn'],
created () {
Raven.uninstall()
this.setUp()
},
destroyed () {
Raven.uninstall()
},
methods: {
setUp () {
Raven.uninstall()
logger.default.info('Installing raven...')
Raven.config(this.dsn).addPlugin(RavenVue, Vue).install()
}
},
watch: {
dsn: function () {
this.setUp()
}
}
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped >
.raven {
display: none;
}
</style>
......@@ -16,8 +16,8 @@
<div class="menu-area">
<div class="ui compact fluid two item inverted menu">
<a class="active item" role="button" @click.prevent.stop="selectedTab = 'library'" data-tab="library"><translate>Browse</translate></a>
<a class="item" role="button" @click.prevent.stop="selectedTab = 'queue'" data-tab="queue">
<a :class="[{active: selectedTab === 'library'}, 'item']" role="button" @click.prevent.stop="selectedTab = 'library'" data-tab="library"><translate>Browse</translate></a>
<a :class="[{active: selectedTab === 'queue'}, 'item']" role="button" @click.prevent.stop="selectedTab = 'queue'" data-tab="queue">
<translate>Queue</translate>&nbsp;
<template v-if="queue.tracks.length === 0">
<translate>(empty)</translate>
......@@ -29,7 +29,7 @@
</div>
</div>
<div class="tabs">
<section class="ui bottom attached active tab" data-tab="library" :aria-label="labels.mainMenu">
<section :class="['ui', 'bottom', 'attached', {active: selectedTab === 'library'}, 'tab']" :aria-label="labels.mainMenu">
<nav class="ui inverted vertical large fluid menu" role="navigation" :aria-label="labels.mainMenu">
<div class="item">
<header class="header"><translate>My account</translate></header>
......@@ -39,7 +39,7 @@
<translate :translate-params="{username: $store.state.auth.username}">
Logged in as %{ username }
</translate>
<img class="ui right floated circular tiny avatar image" v-if="$store.state.auth.profile.avatar.square_crop" :src="$store.getters['instance/absoluteUrl']($store.state.auth.profile.avatar.square_crop)" />
<img class="ui right floated circular tiny avatar image" v-if="$store.state.auth.profile.avatar.square_crop" v-lazy="$store.getters['instance/absoluteUrl']($store.state.auth.profile.avatar.square_crop)" />
</router-link>
<router-link class="item" v-if="$store.state.auth.authenticated" :to="{path: '/settings'}"><i class="setting icon"></i><translate>Settings</translate></router-link>
<router-link class="item" v-if="$store.state.auth.authenticated" :to="{name: 'notifications'}">
......@@ -113,7 +113,7 @@
</div>
</div>
</div>
<section class="ui bottom attached tab" data-tab="queue">
<section :class="['ui', 'bottom', 'attached', {active: selectedTab === 'queue'}, 'tab']">
<table class="ui compact inverted very basic fixed single line unstackable table">
<draggable v-model="tracks" element="tbody" @update="reorder">
<tr
......@@ -188,11 +188,6 @@ export default {
fetchInterval: null
}
},
mounted() {
$(this.$el)
.find(".menu .item")
.tab()
},
destroy() {
if (this.fetchInterval) {
clearInterval(this.fetchInterval)
......@@ -206,10 +201,8 @@ export default {
labels() {
let mainMenu = this.$gettext("Main menu")
let selectTrack = this.$gettext("Play this track")
let pendingRequests = this.$gettext("Pending import requests")
let pendingFollows = this.$gettext("Pending follow requests")
return {
pendingRequests,
pendingFollows,
mainMenu,
selectTrack
......
......@@ -29,7 +29,7 @@
</template>
<script>
import _ from 'lodash'
import _ from '@/lodash'
import axios from 'axios'
import logger from '@/logging'
import AlbumCard from '@/components/audio/album/Card'
......
......@@ -4,7 +4,7 @@
<script>
import {mapState} from 'vuex'
import _ from 'lodash'
import _ from '@/lodash'
import url from '@/utils/url'
import {Howl} from 'howler'
......
......@@ -11,7 +11,7 @@
<div class="ui loader"></div>
</div>
<div class="card" v-for="album in albums" :key="album.id">
<div :class="['ui', 'image', 'with-overlay', {'default-cover': !album.cover.original}]" :style="getImageStyle(album)">
<div :class="['ui', 'image', 'with-overlay', {'default-cover': !album.cover.original}]" v-lazy:background-image="getImageUrl(album)">
<play-button class="play-overlay" :icon-only="true" :is-playable="album.is_playable" :button-classes="['ui', 'circular', 'large', 'orange', 'icon', 'button']" :album="album.id"></play-button>
</div>
<div class="content">
......@@ -36,7 +36,7 @@
</template>
<script>
import _ from 'lodash'
import _ from '@/lodash'
import axios from 'axios'
import PlayButton from '@/components/audio/PlayButton'
......@@ -87,17 +87,15 @@ export default {
this.offset = Math.max(this.offset - this.limit, 0)
}
},
getImageStyle (album) {
getImageUrl (album) {
let url = '../../../assets/audio/default-cover.png'
if (album.cover.original) {
url = this.$store.getters['instance/absoluteUrl'](album.cover.medium_square_crop)
} else {
return {}
}
return {
'background-image': `url("${url}")`
return null
}
return url
}
},
watch: {
......@@ -108,10 +106,10 @@ export default {
}
</script>
<style scoped lang="scss">
@import '../../../style/vendor/media';
@import "../../../style/vendor/media";
.default-cover {
background-image: url('../../../assets/audio/default-cover.png') !important;
background-image: url("../../../assets/audio/default-cover.png") !important;
}
.wrapper {
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment