Autocolors

WIP on a new feature: trying to guess current Peertube theme's colors,
and apply them to ConverseJS.
This commit is contained in:
John Livingston 2021-11-19 16:45:10 +01:00
parent fbb2e8345c
commit 8999133dcc
No known key found for this signature in database
GPG Key ID: B17B5640CE66CDBC
7 changed files with 208 additions and 3 deletions

View File

@ -5,10 +5,12 @@
### Features
* UI/UX improvements. Now using a custom ConverseJS build.
* Auto color detection: when using ConverseJS, the plugin tries to guess colors to apply to its theme.
### Breaking changes
* If you have some CSS customization for the plugin, it may be broken.
* Auto color detection can have bad result for some Peertube theme. If so, you can disable it in the settings.
## v4.0.3

View File

@ -227,6 +227,14 @@ function register ({ registerHook, registerSettingsScript, peertubeHelpers }: Re
options.formValues['chat-type'] === ('builtin-converse' as ChatType) ||
options.formValues['chat-type'] === ('builtin-prosody' as ChatType)
)
case 'converse-autocolors':
return !(
(
options.formValues['chat-type'] === ('builtin-converse' as ChatType) ||
options.formValues['chat-type'] === ('builtin-prosody' as ChatType)
) &&
options.formValues['converse-theme'] === 'peertube'
)
case 'chat-uri':
case 'chat-type-help-external-uri':
return options.formValues['chat-type'] !== ('external-uri' as ChatType)

View File

@ -1,5 +1,6 @@
import { videoHasWebchat } from 'shared/lib/video'
import type { ChatType } from 'shared/lib/types'
import { videoHasWebchat } from 'shared/lib/video'
import { AutoColors, isAutoColorsAvailable, areAutoColorsValid } from 'shared/lib/autocolors'
interface VideoWatchLoadedHookOptions {
videojs: any
@ -60,9 +61,78 @@ function register ({ registerHook, peertubeHelpers }: RegisterOptions): void {
logger.error('No iframe uri')
return null
}
if (isAutoColorsAvailable(settings['chat-type'] as ChatType, settings['converse-theme'])) {
logger.info('We have to try to compute autocolors.')
try {
const autocolors = computeAutoColors()
if (autocolors) {
const url = new URL(iframeUri, window.location.origin)
for (const p in autocolors) {
url.searchParams.set('_ac_' + p, autocolors[p as keyof AutoColors])
}
iframeUri = url.href
}
} catch (err) {
logger.error(`Failed computing autocolors: '${err as string}'`)
}
}
return iframeUri
}
function computeAutoColors (): AutoColors | null {
if (!window.getComputedStyle) {
logger.warn('[AutoColors] getComputedStyle is not available, aborting.')
return null
}
// Searching for one of these button:
const button = document.querySelector('.publish-button, .peertube-button-link')
if (!button) {
logger.warn('[AutoColors] Cant find a button, aborting.')
return null
}
const buttonStyles = window.getComputedStyle(button)
logger.info('[AutoColors] found button styles')
const main = document.querySelector('#content')
if (!main) {
logger.warn('[AutoColors] Cant find main, aborting.')
return null
}
const mainStyles = window.getComputedStyle(main)
logger.info('[AutoColors] found main styles')
const menu = document.querySelector('.menu-link')
if (!menu) {
logger.warn('[AutoColors] Cant find menu, aborting.')
return null
}
const menuStyles = window.getComputedStyle(menu)
logger.info('[AutoColors] found menu styles')
const autocolors: AutoColors = {
mainForeground: mainStyles.color,
mainBackground: mainStyles.backgroundColor,
greyForeground: '#000',
greyBackground: '#000',
menuForeground: menuStyles.color,
menuBackground: menuStyles.backgroundColor,
inputForeground: '#000',
inputBackground: '#000',
buttonForeground: buttonStyles.color,
buttonBackground: buttonStyles.backgroundColor,
link: '#000',
linkHover: '#000'
}
const autoColorsTest = areAutoColorsValid(autocolors)
if (autoColorsTest !== true) {
logger.warn('[AutoColors] Computed colors are not valid, dropping. Invalid values: ' + autoColorsTest.join(', '))
return null
}
return autocolors
}
function displayButton (
buttonContainer: HTMLElement,
name: string,

View File

@ -8,6 +8,7 @@
<link type="text/css" rel="stylesheet" media="screen" href="{{BASE_STATIC_URL}}conversejs/converse.min.css" />
<script type="text/javaScript" src="{{BASE_STATIC_URL}}conversejs/converse.min.js"></script>
<script type="text/javascript" src="{{BASE_STATIC_URL}}static/builtin.js"></script>
{{CONVERSEJS_AUTOCOLORS}}
</head>
<body class="converse-fullscreen theme-peertube">
<noscript>You need to enable JavaScript to run the Converse.js chat app.</noscript>

View File

@ -8,6 +8,7 @@ import { asyncMiddleware } from '../middlewares/async'
import { getProsodyDomain } from '../prosody/config/domain'
import { getAPIKey } from '../apikey'
import { getChannelInfosById, getChannelNameById } from '../database/channel'
import { isAutoColorsAvailable, areAutoColorsValid, AutoColors } from '../../../shared/lib/autocolors'
import * as path from 'path'
const bodyParser = require('body-parser')
const got = require('got')
@ -42,7 +43,7 @@ async function initWebchatRouter (options: RegisterServerOptions): Promise<Route
'chat-type', 'chat-room', 'chat-server',
'chat-bosh-uri', 'chat-ws-uri',
'prosody-room-type',
'converse-theme'
'converse-theme', 'converse-autocolors'
])
const chatType: ChatType = (settings['chat-type'] ?? 'disabled') as ChatType
@ -149,6 +150,53 @@ async function initWebchatRouter (options: RegisterServerOptions): Promise<Route
}
room = room.replace(/{{CHANNEL_NAME}}/g, channelName)
}
let autocolorsStyles = ''
if (
settings['converse-autocolors'] &&
isAutoColorsAvailable(settings['chat-type'] as ChatType, settings['converse-theme'] as string)
) {
peertubeHelpers.logger.debug('Trying to load AutoColors...')
const autocolors: AutoColors = {
mainForeground: req.query._ac_mainForeground?.toString() ?? '',
mainBackground: req.query._ac_mainBackground?.toString() ?? '',
greyForeground: req.query._ac_greyForeground?.toString() ?? '',
greyBackground: req.query._ac_greyBackground?.toString() ?? '',
menuForeground: req.query._ac_menuForeground?.toString() ?? '',
menuBackground: req.query._ac_menuBackground?.toString() ?? '',
inputForeground: req.query._ac_inputForeground?.toString() ?? '',
inputBackground: req.query._ac_inputBackground?.toString() ?? '',
buttonForeground: req.query._ac_buttonForeground?.toString() ?? '',
buttonBackground: req.query._ac_buttonBackground?.toString() ?? '',
link: req.query._ac_link?.toString() ?? '',
linkHover: req.query._ac_linkHover?.toString() ?? ''
}
const autoColorsTest = areAutoColorsValid(autocolors)
if (autoColorsTest === true) {
autocolorsStyles = `
<style>
:root {
--peertube-main-foreground: ${autocolors.mainForeground};
--peertube-main-background: ${autocolors.mainBackground};
--peertube-grey-foreground: ${autocolors.greyForeground};
--peertube-grey-background: ${autocolors.greyBackground};
--peertube-menu-foreground: ${autocolors.menuForeground};
--peertube-menu-background: ${autocolors.menuBackground};
--peertube-input-foreground: ${autocolors.inputForeground};
--peertube-input-background: ${autocolors.inputBackground};
--peertube-button-foreground: ${autocolors.buttonForeground};
--peertube-button-background: ${autocolors.buttonBackground};
--peertube-link: ${autocolors.link};
--peertube-link-hover: ${autocolors.linkHover};
}
</style>
`
} else {
peertubeHelpers.logger.error('Provided AutoColors are invalid.', autoColorsTest)
}
} else {
peertubeHelpers.logger.debug('No AutoColors.')
}
// ... then inject it in the page.
page = page.replace(/{{ROOM}}/g, room)
page = page.replace(/{{BOSH_SERVICE_URL}}/g, boshUri)
@ -156,6 +204,7 @@ async function initWebchatRouter (options: RegisterServerOptions): Promise<Route
page = page.replace(/{{AUTHENTICATION_URL}}/g, authenticationUrl)
page = page.replace(/{{ADVANCEDCONTROLS}}/g, advancedControls ? 'true' : 'false')
page = page.replace(/{{CONVERSEJS_THEME}}/g, converseJSTheme)
page = page.replace(/{{CONVERSEJS_AUTOCOLORS}}/g, autocolorsStyles)
res.status(200)
res.type('html')

View File

@ -282,7 +282,7 @@ Example: height:400px;`,
label: 'ConverseJS theme',
type: 'select',
default: 'peertube' as ConverseJSTheme,
private: true,
private: false,
options: [
{ value: 'peertube', label: 'Peertube theme' },
{ value: 'default', label: 'Default ConverseJS theme' },
@ -291,6 +291,22 @@ Example: height:400px;`,
descriptionHTML: 'Please choose the converseJS theme you want to use.'
})
registerSetting({
name: 'converse-autocolors',
label: 'Automatic color detection',
type: 'input-checkbox',
default: true,
private: false,
descriptionHTML:
`Try to auto detect colors from user's current theme.<br>
When this settings is enabled, the plugin tries to auto-detect colors to apply to the chat theme.<br>
If this is not correctly working for some of your Peertube theme, you can disable this option.
You can report the bug on the official
<a href="https://github.com/JohnXLivingston/peertube-plugin-livechat/issues" target="_blank">
issue tracker
</a>. Don't forget to specify which theme is not working.`
})
// ********** Built-in Prosody advanced settings
registerSetting({
name: 'prosody-advanced',

59
shared/lib/autocolors.ts Normal file
View File

@ -0,0 +1,59 @@
import type { ChatType } from './types'
type AutoColorValue = string
interface AutoColors {
mainForeground: AutoColorValue
mainBackground: AutoColorValue
greyForeground: AutoColorValue
greyBackground: AutoColorValue
menuForeground: AutoColorValue
menuBackground: AutoColorValue
inputForeground: AutoColorValue
inputBackground: AutoColorValue
buttonForeground: AutoColorValue
buttonBackground: AutoColorValue
link: AutoColorValue
linkHover: AutoColorValue
}
/**
* @param chatType value of the settings 'chat-type'
* @param theme value of the settings 'converse-theme'
* @returns true if the theme can use autocolors
*/
function isAutoColorsAvailable (chatType: ChatType, theme: string): boolean {
if (chatType !== 'builtin-prosody' && chatType !== 'builtin-converse') {
return false
}
return theme === 'peertube' // currently the only theme that can handle autocolors.
}
/**
* @param autocolors
* @returns true if ok. Else a string array with invalid values.
*/
function areAutoColorsValid (autocolors: AutoColors): true | string[] {
const errors: string[] = []
for (const k in autocolors) {
const color = autocolors[k as keyof AutoColors]
// FIXME: there are missing cases. For now there are only basic values formats.
if (
!/^rgb\(\d{1,3},\s*\d{1,3},\s*\d{1,3}\)$/.test(color) &&
!/^rgba\(\d{1,3},\s*\d{1,3},\s*\d{1,3},\s*(1|0|0?.\d+)\)$/.test(color) &&
!/^#[0-9a-fA-F]{3,6}$/.test(color)
) {
errors.push(color)
}
}
if (errors.length) {
return errors
}
return true
}
export {
AutoColors,
isAutoColorsAvailable,
areAutoColorsValid
}