[FIX] web_editor: video selector component adjustments for Knowledge

This commit will apply the following changes:
1. Introduce a new `VideoIframe` component for the `VideoSelector`
   component. With that change, one can easily mock the `VideoIframe`
   component in a tour and replace the iframe of the video with
   alternative content. It will then be possible to test the whole video
   integration flow with a tour without relying on Third-Party services.
2. Enhance the Python video parser to include the video id and the video
   parameters of the url being parsed. These new information will be
   used for the new video behavior of Knowledge.
3. Call the callback function notifying that the video url has changed
   when the textarea is emptied (see: `selectMedia`).
4. Authorize non-internal users to call the /web_editor/video_url/data
   route. This will ensure that portal users will be able to integrate
   videos in their Knowledge articles.
5. Apply some minor UI and UX adjustments:
   - The label next to the toggle buttons will now be properly aligned.
   - The textarea to input the video url will be automatically focused.

task-3297215

closes odoo/odoo#127452

Related: odoo/enterprise#40683
Signed-off-by: Damien Abeloos (abd) <abd@odoo.com>
This commit is contained in:
Julien Banken
2023-09-13 15:34:27 +00:00
parent 1c5961ceed
commit 95e6d0199b
5 changed files with 58 additions and 15 deletions
-2
View File
@@ -230,8 +230,6 @@ class Web_Editor(http.Controller):
def video_url_data(self, video_url, autoplay=False, loop=False,
hide_controls=False, hide_fullscreen=False, hide_yt_logo=False,
hide_dm_logo=False, hide_dm_share=False):
if not request.env.user._is_internal():
raise werkzeug.exceptions.Forbidden()
return get_video_url_data(
video_url, autoplay=autoplay, loop=loop,
hide_controls=hide_controls, hide_fullscreen=hide_fullscreen,
@@ -1,7 +1,7 @@
/** @odoo-module **/
import { _t } from "@web/core/l10n/translation";
import { useService } from '@web/core/utils/hooks';
import { useAutofocus, useService } from '@web/core/utils/hooks';
import { debounce } from '@web/core/utils/timing';
import { Component, useState, useRef, onMounted, onWillStart } from "@odoo/owl";
@@ -9,6 +9,13 @@ import { Component, useState, useRef, onMounted, onWillStart } from "@odoo/owl";
class VideoOption extends Component {}
VideoOption.template = 'web_editor.VideoOption';
class VideoIframe extends Component {
static template = 'web_editor.VideoIframe';
static props = {
src: { type: String },
};
}
export class VideoSelector extends Component {
setup() {
this.rpc = useService('rpc');
@@ -98,6 +105,8 @@ export class VideoSelector extends Component {
}));
});
useAutofocus();
this.onChangeUrl = debounce((ev) => this.updateVideo(ev.target.value), 500);
}
@@ -130,6 +139,12 @@ export class VideoSelector extends Component {
this.state.options = [];
this.state.platform = null;
this.state.errorMessage = '';
/**
* When the url input is emptied, we need to call the `selectMedia`
* callback function to notify the other components that the media
* has changed.
*/
this.props.selectMedia({});
return;
}
@@ -150,7 +165,13 @@ export class VideoSelector extends Component {
options[option.id] = option.value;
}
}
const { embed_url: src, platform } = await this._getVideoURLData(url, options);
const {
embed_url: src,
video_id: videoId,
params,
platform
} = await this._getVideoURLData(url, options);
if (!src) {
this.state.errorMessage = _t("The provided url is not valid");
@@ -173,7 +194,13 @@ export class VideoSelector extends Component {
}
this.state.src = src;
this.props.selectMedia({ id: src, src });
this.props.selectMedia({
id: src,
src,
platform,
videoId,
params
});
if (platform !== this.state.platform) {
this.state.platform = platform;
this.state.options = newOptions;
@@ -213,6 +240,7 @@ VideoSelector.mediaExtraClasses = [];
VideoSelector.tagNames = ['IFRAME', 'DIV'];
VideoSelector.template = 'web_editor.VideoSelector';
VideoSelector.components = {
VideoIframe,
VideoOption,
};
VideoSelector.defaultProps = {
@@ -3,27 +3,37 @@
<t t-name="web_editor.VideoOption">
<div class="mb-1">
<label class="o_switch" t-on-change="props.onChangeOption">
<input type="checkbox" t-att-checked="props.value"/><span/>
<label class="d-flex align-items-start gap-2 cursor-pointer" t-on-change="props.onChangeOption">
<div class="o_switch flex-shrink-0">
<input type="checkbox" t-att-checked="props.value"/>
<span/>
</div>
<span t-esc="props.label"/>
<span t-if="props.description" class="small text-muted ms-auto" t-esc="props.description"/>
<span t-if="props.description" class="text-muted" t-esc="props.description"/>
</label>
</div>
</t>
<t t-name="web_editor.VideoIframe">
<iframe t-att-src="this.props.src"
class="o_video_dialog_iframe mw-100 mh-100 overflow-hidden shadow"
width="1280" height="720"
allowfullscreen="allowfullscreen" frameborder="0"/>
</t>
<t t-name="web_editor.VideoSelector">
<div class="row">
<div class="col mt-4 o_video_dialog_form">
<div class="mb-2">
<label class="col-form-label" for="o_video_text">
<label for="o_video_text">
<b>Video code </b>(URL or Embed)
</label>
<div class="text-start">
<small class="text-muted">Accepts <b><i>Youtube</i></b>, <b><i>Vimeo</i></b>, <b><i>Dailymotion</i></b> and <b><i>Youku</i></b> videos</small>
</div>
<textarea t-model="state.urlInput" class="form-control" id="o_video_text" placeholder="Copy-paste your URL or embed code here" t-on-input="onChangeUrl" t-att-class="{ 'is-valid': state.urlInput and !this.state.errorMessage, 'is-invalid': state.urlInput and this.state.errorMessage }"/>
<textarea t-ref="autofocus" t-model="state.urlInput" class="form-control" id="o_video_text" placeholder="Copy-paste your URL or embed code here" t-on-input="onChangeUrl" t-att-class="{ 'is-valid': state.urlInput and !this.state.errorMessage, 'is-invalid': state.urlInput and this.state.errorMessage }"/>
</div>
<div t-if="shownOptions.length" class="o_video_dialog_options mt-4">
<div t-if="shownOptions.length" class="o_video_dialog_options">
<VideoOption t-foreach="shownOptions" t-as="option" t-key="option.id"
value="option.value"
onChangeOption="() => this.onChangeOption(option.id)"
@@ -46,7 +56,9 @@
<div t-if="this.state.src and !this.state.errorMessage" class="o_video_dialog_preview_text mb-2">Preview</div>
<div class="media_iframe_video">
<div class="media_iframe_video_size"/>
<iframe t-if="this.state.src and !this.state.errorMessage" height="720" width="1280" class="o_video_dialog_iframe mw-100 mh-100 overflow-hidden shadow" allowfullscreen="allowfullscreen" frameborder="0" t-att-src="this.state.src"/>
<VideoIframe
t-if="this.state.src and !this.state.errorMessage"
src="this.state.src"/>
<div t-if="this.state.errorMessage" class="alert alert-warning o_video_dialog_iframe mw-100 mh-100 mb-2 mt-2" t-esc="this.state.errorMessage"/>
</div>
</div>
+1 -1
View File
@@ -68,7 +68,7 @@ class TestVideoUtils(common.BaseCase):
self.assertEqual('B6dXGTxggTG', tools.get_video_source_data(TestVideoUtils.urls['instagram'])[1])
def test_get_video_url_data(self):
self.assertEqual(2, len(tools.get_video_url_data(TestVideoUtils.urls['youtube'])))
self.assertEqual(4, len(tools.get_video_url_data(TestVideoUtils.urls['youtube'])))
#youtube
self.assertEqual('youtube', tools.get_video_url_data(TestVideoUtils.urls['youtube'])['platform'])
#vimeo
+7 -2
View File
@@ -52,7 +52,7 @@ def get_video_source_data(video_url):
def get_video_url_data(video_url, autoplay=False, loop=False, hide_controls=False, hide_fullscreen=False, hide_yt_logo=False, hide_dm_logo=False, hide_dm_share=False):
""" Computes the platform name and embed_url from given URL
""" Computes the platform name, the embed_url, the video id and the video params of the given URL
(or error message in case of invalid URL).
"""
source = get_video_source_data(video_url)
@@ -115,7 +115,12 @@ def get_video_url_data(video_url, autoplay=False, loop=False, hide_controls=Fals
if params:
embed_url = f'{embed_url}?{url_encode(params)}'
return {'platform': platform, 'embed_url': embed_url}
return {
'platform': platform,
'embed_url': embed_url,
'video_id': video_id,
'params': params
}
def get_video_embed_code(video_url):