Skip to content

Commit

Permalink
Theme config does not take effect (#155)
Browse files Browse the repository at this point in the history
* fix issue theme config not working
* remove token from world theme api call as it not needed
* fix code review: use django built-in function, handle exception
* update log error when exception occurs
  • Loading branch information
lcduong authored Jul 22, 2024
1 parent 8b37785 commit fb5bfe6
Show file tree
Hide file tree
Showing 10 changed files with 141 additions and 38 deletions.
1 change: 1 addition & 0 deletions server/venueless/api/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,5 @@
re_path("worlds/(?P<world_id>[^/]+)/schedule_update/?$", views.schedule_update),
re_path("worlds/(?P<world_id>[^/]+)/delete_user/?$", views.delete_user),
path("worlds/<str:world_id>/", include(world_router.urls)),
path("worlds/<str:world_id>/theme", views.WorldThemeView.as_view()),
]
26 changes: 25 additions & 1 deletion server/venueless/api/views.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import logging
from contextlib import suppress
from urllib.parse import urlparse

from asgiref.sync import async_to_sync
from django.core import exceptions
from django.db import transaction
from django.shortcuts import get_object_or_404
from django.utils.timezone import now
from rest_framework import viewsets
from rest_framework.decorators import api_view, permission_classes
Expand All @@ -20,7 +22,9 @@
from venueless.core.models import Channel, User
from venueless.core.services.world import notify_schedule_change, notify_world_change

from ..core.models import Room
from ..core.models import Room, World

logger = logging.getLogger(__name__)


class RoomViewSet(viewsets.ModelViewSet):
Expand Down Expand Up @@ -82,6 +86,26 @@ def patch(self, request, **kwargs):
return Response(serializer.data)


class WorldThemeView(APIView):

permission_classes = []

def get(self, request, **kwargs):
"""
Retrieve theme config of a world
@param request: request obj
@param kwargs: world_id
@return: theme data of a world
"""
try:
world = get_object_or_404(World, id=kwargs["world_id"])
return Response(WorldSerializer(world).data['config']['theme'])
except KeyError:
logger.error("error happened when trying to get theme data of world: %s", kwargs["world_id"])
return Response("error happened when trying to get theme data of world: " + kwargs["world_id"], status=503)



def get_domain(path):
if not path:
return ""
Expand Down
4 changes: 3 additions & 1 deletion server/venueless/graphs/views.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import io
import re
import logging
from collections import Counter, defaultdict
from datetime import timedelta
Expand Down Expand Up @@ -50,7 +51,8 @@ class GraphView(View):

@cached_property
def world(self):
return get_object_or_404(World, domain=self.request.headers["Host"])
world_domain = re.sub(r":\d+$", "", self.request.get_host())
return get_object_or_404(World, domain=world_domain)

@cached_property
def fig(self):
Expand Down
25 changes: 15 additions & 10 deletions server/venueless/live/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,8 @@ def source(self):

class ManifestView(View):
def get(self, request, *args, **kwargs):
world = get_object_or_404(World, domain=request.headers["Host"])
world_domain = re.sub(r":\d+$", "", request.get_host())
world = get_object_or_404(World, domain=world_domain)
# TODO: Allow to parametrize colors and logos
source = {
"name": world.title,
Expand Down Expand Up @@ -83,7 +84,8 @@ def _has_separate_short_domain(self):
def get(self, request, *args, **kwargs):
# Is this an anonymous invite to a room?
short_host = self._has_separate_short_domain
if short_host and request.headers["Host"] == short_host:
world_domain = re.sub(r":\d+$", "", request.get_host())
if short_host and world_domain == short_host:
# The sysadmin has set up a separate domain for short URLs
if request.path == "/":
# This must be a 200, not a 302 or 404, so the domain is considered "active"
Expand Down Expand Up @@ -120,14 +122,13 @@ def get(self, request, *args, **kwargs):
# We do not show a 404 since theoretically this could be a vlaid path recognized by
# the frontend router.
pass

try:
world = get_object_or_404(World, domain=request.headers["Host"])
world = get_object_or_404(World, domain=world_domain)
except OperationalError:
# We use connection pooling, so if the database server went away since the last connection
# terminated, Django won't know and we'll get an OperationalError. We just silently re-try
# once, since Django will then use a new connection.
world = get_object_or_404(World, domain=request.headers["Host"])
world = get_object_or_404(World, domain=world_domain)
source = sh.source
source = re.sub(
"<title>[^<]*</title>",
Expand All @@ -144,7 +145,7 @@ def get(self, request, *args, **kwargs):
"base": reverse("api:root", kwargs={"world_id": world.id}),
"socket": "{}://{}/ws/world/{}/".format(
settings.WEBSOCKET_PROTOCOL,
request.headers["Host"],
request.get_host(),
world.pk,
),
"upload": reverse("storage:upload"),
Expand Down Expand Up @@ -194,7 +195,8 @@ async def get(self, request, *args, **kwargs):
@method_decorator(cache_page(1 if settings.DEBUG else 60), name="dispatch")
class CustomCSSView(View):
def get(self, request, *args, **kwargs):
world = get_object_or_404(World, domain=request.headers["Host"])
world_domain = re.sub(r":\d+$", "", request.get_host())
world = get_object_or_404(World, domain=world_domain)
source = world.config.get("theme", {}).get("css", "")
return HttpResponse(source, content_type="text/css")

Expand All @@ -206,13 +208,15 @@ class BBBCSSView(TemplateView):

def get_context_data(self, **kwargs):
ctx = super().get_context_data()
ctx["world"] = get_object_or_404(World, domain=self.request.headers["Host"])
world_domain = re.sub(r":\d+$", "", self.request.get_host())
ctx["world"] = get_object_or_404(World, domain=world_domain)
return ctx


class ShortTokenView(View):
def get(self, request, token):
world = get_object_or_404(World, domain=self.request.headers["Host"])
world_domain = re.sub(r":\d+$", "", self.request.get_host())
world = get_object_or_404(World, domain=world_domain)
try:
st = ShortToken.objects.get(short_token=token, world=world)
return redirect(f"/#token={st.long_token}")
Expand All @@ -229,7 +233,8 @@ def dispatch(self, request, *args, **kwargs):

@cached_property
def world(self):
return get_object_or_404(World, domain=self.request.headers["Host"])
world_domain = re.sub(r":\d+$", "", self.request.get_host())
return get_object_or_404(World, domain=world_domain)

def post(self, request, *args, **kwargs):
data = json.loads(request.body)
Expand Down
4 changes: 3 additions & 1 deletion server/venueless/storage/views.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import logging
from io import BytesIO
import re

from asgiref.sync import async_to_sync
from django.core.exceptions import PermissionDenied, ValidationError
Expand Down Expand Up @@ -33,7 +34,8 @@ def dispatch(self, request, *args, **kwargs):

@cached_property
def world(self):
return get_object_or_404(World, domain=self.request.headers["Host"])
world_domain = re.sub(r":\d+$", "", self.request.get_host())
return get_object_or_404(World, domain=world_domain)

@cached_property
def user(self):
Expand Down
4 changes: 3 additions & 1 deletion server/venueless/zoom/views.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import datetime
from calendar import timegm
import re

import jwt
from django.conf import settings
Expand Down Expand Up @@ -53,7 +54,8 @@ def get_closest_zoom_lang(world):
class ZoomViewMixin:
@cached_property
def world(self):
w = get_object_or_404(World, domain=self.request.headers["Host"])
world_domain = re.sub(r":\d+$", "", self.request.get_host())
w = get_object_or_404(World, domain=world_domain)
if not settings.DEBUG and "zoom" not in w.feature_flags:
raise PermissionDenied("Feature disabled")
return w
Expand Down
11 changes: 11 additions & 0 deletions webapp/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,17 @@ if (ENV_DEVELOPMENT || !window.venueless) {
},
defaultLocale: 'en',
locales: ['en', 'de', 'pt_BR'],
theme: {
logo: {
url: "/eventyay-video-logo.svg",
fitToWidth: false
},
colors: {
primary: '#2185d0',
sidebar: '#2185d0',
bbb_background: '#ffffff',
}
}
}
} else {
// load from index.html as `window.venueless = {…}`
Expand Down
18 changes: 16 additions & 2 deletions webapp/src/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ import i18n, { init as i18nInit } from 'i18n'
import { emojiPlugin } from 'lib/emoji'
import features from 'features'
import config from 'config'
import {computeForegroundSidebarColor, getThemeConfig} from 'theme'
import theme from 'theme'

async function init ({token, inviteToken}) {
Vue.config.productionTip = false
Expand All @@ -36,14 +38,17 @@ async function init ({token, inviteToken}) {
Vue.use(emojiPlugin)
await i18nInit(Vue)
Vue.prototype.$features = features

try {
await setThemeConfig()
} catch (error) {
console.error("error happened when trying to load theme config: ", error)
}
const app = new Vue({
router,
store,
render: h => h('router-view')
}).$mount('#app')
window.vapp = app

store.commit('setUserLocale', i18n.resolvedLanguage)
store.dispatch('updateUserTimezone', localStorage.userTimezone || moment.tz.guess())

Expand Down Expand Up @@ -111,3 +116,12 @@ navigator.serviceWorker?.getRegistrations().then((registrations) => {
registration.unregister()
}
})

async function setThemeConfig(){
const themeData = await getThemeConfig()
theme.logo = themeData.logo
theme.identicons = themeData.identicons
theme.colors = themeData.colors
theme.streamOfflineImage = themeData.streamOfflineImage
computeForegroundSidebarColor(themeData.colors)
}
50 changes: 46 additions & 4 deletions webapp/src/theme.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,16 +40,16 @@ const firstReadable = function (colors, background = '#FFF', threshold = 4.5) {
return best
}

const CLR_PRIMARY_TEXT = {LIGHT: Color('rgba(0, 0, 0, .87)'), DARK: Color('rgba(255, 255, 255, 1)')}
const CLR_PRIMARY_TEXT = {LIGHT: Color('rgba(33, 133, 208, 1)'), DARK: Color('rgba(255, 255, 255, 1)')}
const CLR_SECONDARY_TEXT = {LIGHT: Color('rgba(0, 0, 0, .54)'), DARK: Color('rgba(255, 255, 255, .7)')}
const CLR_SECONDARY_TEXT_FALLBACK = {LIGHT: Color('rgba(0, 0, 0, .74)'), DARK: Color('rgba(255, 255, 255, .9)')}
const CLR_DISABLED_TEXT = {LIGHT: Color('rgba(0, 0, 0, .38)'), DARK: Color('rgba(255, 255, 255, .5)')}
const CLR_DIVIDERS = {LIGHT: Color('rgba(255, 255, 255, .63)'), DARK: Color('rgba(255, 255, 255, .63)')}

const DEFAULT_COLORS = {
primary: '#673ab7',
sidebar: '#180044',
bbb_background: '#333333',
primary: '#2185d0',
sidebar: '#2185d0',
bbb_background: '#ffffff',
}

const DEFAULT_LOGO = {
Expand Down Expand Up @@ -116,3 +116,45 @@ export { themeVariables, colors, DEFAULT_COLORS, DEFAULT_LOGO, DEFAULT_IDENTICON
export function computeForegroundColor (bgColor) {
return firstReadable([CLR_PRIMARY_TEXT.LIGHT, CLR_PRIMARY_TEXT.DARK], bgColor)
}

export function computeForegroundSidebarColor(colors) {
const configColors = {
primary: colors.primary,
sidebar: colors.sidebar,
bbb_background: colors.bbb_background,
}
const sbColors = Object.keys(DEFAULT_COLORS).reduce((acc, key) => (acc[key] = Color((configColors ?? DEFAULT_COLORS)[key]), acc), {})
sbColors.primaryDarken15 = sbColors.primary.darken(0.15)
sbColors.primaryDarken20 = sbColors.primary.darken(0.20)
sbColors.primaryAlpha60 = sbColors.primary.alpha(0.6)
sbColors.primaryAlpha50 = sbColors.primary.alpha(0.5)
sbColors.primaryAlpha18 = sbColors.primary.alpha(0.18)
sbColors.inputPrimaryBg = sbColors.primary
sbColors.inputPrimaryFg = firstReadable([CLR_PRIMARY_TEXT.LIGHT, CLR_PRIMARY_TEXT.DARK], sbColors.primary)
sbColors.inputPrimaryBgDarken = sbColors.primary.darken(0.15)
sbColors.inputSecondaryFg = sbColors.primary
sbColors.inputSecondaryFgAlpha = sbColors.primary.alpha(0.08)
sbColors.sidebarTextPrimary = firstReadable([CLR_PRIMARY_TEXT.LIGHT, CLR_PRIMARY_TEXT.DARK], sbColors.sidebar)
sbColors.sidebarTextSecondary = firstReadable([CLR_SECONDARY_TEXT.LIGHT, CLR_SECONDARY_TEXT_FALLBACK.LIGHT, CLR_SECONDARY_TEXT.DARK, CLR_SECONDARY_TEXT_FALLBACK.DARK], sbColors.sidebar)
sbColors.sidebarTextDisabled = firstReadable([CLR_DISABLED_TEXT.LIGHT, CLR_DISABLED_TEXT.DARK], sbColors.sidebar)
sbColors.sidebarActiveBg = firstReadable(['rgba(0, 0, 0, 0.08)', 'rgba(255, 255, 255, 0.4)'], sbColors.sidebar)
sbColors.sidebarActiveFg = firstReadable([CLR_PRIMARY_TEXT.LIGHT, CLR_PRIMARY_TEXT.DARK], sbColors.sidebar)
sbColors.sidebarHoverBg = firstReadable(['rgba(0, 0, 0, 0.12)', 'rgba(255, 255, 255, 0.3)'], sbColors.sidebar)
sbColors.sidebarHoverFg = firstReadable([CLR_PRIMARY_TEXT.LIGHT, CLR_PRIMARY_TEXT.DARK], sbColors.sidebar)


for (const [key, value] of Object.entries(sbColors)) {
themeVariables[`--clr-${kebabCase(key)}`] = value.string()
}
}

export async function getThemeConfig() {
const themeUrl = config.api.base + 'theme'
const response = await (await fetch(themeUrl, {
method: 'GET',
headers: {
'Content-Type': 'application/json'
}
})).json()
return response
}
36 changes: 18 additions & 18 deletions webapp/src/views/admin/config/schedule.vue
Original file line number Diff line number Diff line change
Expand Up @@ -7,30 +7,30 @@
.ui-form-body(v-if="config")
bunt-select(name="source", label="Schedule source", v-model="source", :options="sourceOptions")
template(v-if="source === 'pretalx'")
p To use pretalx for your event, enter the domain of the pretalx server you use and the short form name of your event. We'll then pull in the schedule automatically and keep it updated. You must be using pretalx version 2 or later.
bunt-input(name="domain", label="pretalx domain", v-model="config.pretalx.domain", placeholder="e.g. https://pretalx.com/", hint="must have the format https://…/", :validation="$v.config.pretalx.domain")
bunt-input(name="event", label="pretalx event slug", v-model="config.pretalx.event", placeholder="e.g. democon")
h2 Pretalx Connection
p To use eventyay-talk for your event, enter the domain of the eventyay-talk server you use and the short form name of your event. We'll then pull in the schedule automatically and keep it updated.
bunt-input(name="domain", label="eventyay-talk domain", v-model="config.pretalx.domain", placeholder="e.g. https://wikimania-dev.eventyay.com/", hint="must have the format https://…/", :validation="$v.config.pretalx.domain")
bunt-input(name="event", label="eventyay-talk event slug", v-model="config.pretalx.event", placeholder="e.g. wikimania")
h2 Eventyay-talk Connection
template(v-if="config.pretalx.connected")
p Your pretalx instance has successfully connected to venueless.
p(v-if="lastPush") Last time pretalx pushed a new schedule version: {{ lastPush }}
p Your eventyay-talk instance has successfully connected to system.
p(v-if="lastPush") Last time eventyay-talk pushed a new schedule version: {{ lastPush }}
template(v-else)
p To enable automatic schedule update pushes from pretalx to venueless, activate the pretalx-venueless plugin and complete the connection procedure.
h3 Step 1: Install and activate the pretalx-venueless plugin
.pretalx-status(v-if="isPretalxPluginInstalled") pretalx-venueless plugin has been detected!
p To enable automatic schedule update pushes from eventyay-talk to system, activate the eventyay-talk-video plugin and complete the connection procedure.
h3 Step 1: Install and activate the eventyay-talk-video plugin
.pretalx-status(v-if="isPretalxPluginInstalled") eventyay-talk-video plugin has been detected!
.pretalx-status.plugin-not-installed(v-if="!isPretalxPluginInstalled")
| pretalx-venueless plugin not installed/activated or domain + event not a valid pretalx instance.
| eventyay-talk-video plugin not installed/activated or domain + event not a valid eventyay-talk instance.
br
| Please install and activate the plugin in #[a(:href="`${pretalxDomain}orga/event/${config.pretalx.event}/settings/plugins`", target="_blank") your pretalx event plugin settings].
h3 Step 2: Connect pretalx to venueless
.pretalx-status(v-if="config.pretalx.connected") Pretalx-venueless connection active!
.pretalx-status.not-connected(v-else) Pretalx is not connected to venueless.
bunt-button#btn-pretalx-connect(:disabled="!isPretalxPluginInstalled", :loading="connecting", @click="startPretalxConnect") {{ !config.pretalx.connected ? 'Connect to pretalx' : 'Reconnect to pretalx' }}
| Please install and activate the plugin in #[a(:href="`${pretalxDomain}orga/event/${config.pretalx.event}/settings/plugins`", target="_blank") your eventyay-talk event plugin settings].
h3 Step 2: Connect eventyay-talk to system
.pretalx-status(v-if="config.pretalx.connected") Eventyay-talk-video connection active!
.pretalx-status.not-connected(v-else) Eventyay-talk is not connected to system.
bunt-button#btn-pretalx-connect(:disabled="!isPretalxPluginInstalled", :loading="connecting", @click="startPretalxConnect") {{ !config.pretalx.connected ? 'Connect to eventyay-talk' : 'Reconnect to eventyay-talk' }}
template(v-else-if="source === 'url'")
p To automatically load the schedule from an external system, enter an URL here. Note that the URL must be a JSON file compliant with the pretalx schedule widget API version 2.
p To automatically load the schedule from an external system, enter an URL here. Note that the URL must be a JSON file compliant with the eventyay-talk schedule widget API version 2.
bunt-input(name="url", label="JSON URL", v-model="config.pretalx.url", placeholder="e.g. https://website.com/event.json", :validation="$v.config.pretalx.url")
template(v-else-if="source === 'file'")
p If you don't use pretalx, you can upload your schedule as a Microsoft Excel file (XLSX) with a specific setup.
p If you don't use eventyay-talk, you can upload your schedule as a Microsoft Excel file (XLSX) with a specific setup.
p
a(href="/schedule_ex_en.xlsx", target="_blank") Download English sample file
| {{ " / " }}
Expand Down Expand Up @@ -71,7 +71,7 @@ export default {
sourceOptions () {
const sourceOptions = [
{id: null, label: 'No Schedule'},
{id: 'pretalx', label: 'Pretalx'},
{id: 'pretalx', label: 'Eventyay-talk'},
{id: 'file', label: 'File Upload'},
{id: 'url', label: 'External URL'},
]
Expand Down

0 comments on commit fb5bfe6

Please sign in to comment.