[FIX] mail: search for GIFs should match input
Before this PR, RPC in the search GIFs form could resolve in the wrong order and display outdated result. This PR introduce the use of useSequential hook witch ensure that irrelevant RPC are canceled. closes odoo/odoo#133125 X-original-commit: 39fe84706e02ff7828d5098c9ec56aff274cba64 Signed-off-by: Alexandre Kühn (aku) <aku@odoo.com>
This commit is contained in:
committed by
Alexandre Kühn
parent
0c3e66340d
commit
3bd65eadfa
@@ -2,7 +2,7 @@
|
||||
|
||||
import { useStore } from "@mail/core/common/messaging_hook";
|
||||
import { removeFromArrayWithPredicate } from "@mail/utils/common/arrays";
|
||||
import { useOnBottomScrolled } from "@mail/utils/common/hooks";
|
||||
import { useOnBottomScrolled, useSequential } from "@mail/utils/common/hooks";
|
||||
import { markEventHandled } from "@web/core/utils/misc";
|
||||
|
||||
import { Component, onWillStart, useRef, useState } from "@odoo/owl";
|
||||
@@ -78,6 +78,7 @@ export class GifPicker extends Component {
|
||||
this.orm = useService("orm");
|
||||
this.store = useStore();
|
||||
this.userService = useService("user");
|
||||
this.sequential = useSequential();
|
||||
useAutofocus();
|
||||
useOnBottomScrolled(
|
||||
"scroller",
|
||||
@@ -85,7 +86,7 @@ export class GifPicker extends Component {
|
||||
if (!this.state.showCategories) {
|
||||
this.state.loadingGif = true;
|
||||
if (!this.showFavorite) {
|
||||
this.searchDebounced();
|
||||
this.search();
|
||||
} else {
|
||||
this.loadFavoritesDebounced(this.offset);
|
||||
}
|
||||
@@ -109,19 +110,18 @@ export class GifPicker extends Component {
|
||||
loadingGif: false,
|
||||
loadingError: false,
|
||||
evenGif: {
|
||||
/** @type {TenorGif[]} */
|
||||
gifs: [],
|
||||
/** @type {Map<Number, TenorGif>} */
|
||||
gifs: new Map(),
|
||||
/** Size, in pixel, of the column. */
|
||||
columnSize: 0,
|
||||
},
|
||||
oddGif: {
|
||||
/** @type {TenorGif[]} */
|
||||
gifs: [],
|
||||
/** @type {Map<Number, TenorGif>} */
|
||||
gifs: new Map(),
|
||||
/** Size, in pixel, of the column. */
|
||||
columnSize: 0,
|
||||
},
|
||||
});
|
||||
this.searchDebounced = useDebounced(this.search, 200);
|
||||
this.loadFavoritesDebounced = useDebounced(this.loadFavorites, 200);
|
||||
onWillStart(() => {
|
||||
this.loadCategories();
|
||||
@@ -165,7 +165,6 @@ export class GifPicker extends Component {
|
||||
if (!this.state.searchTerm) {
|
||||
return;
|
||||
}
|
||||
this.state.loadingGif = true;
|
||||
try {
|
||||
const params = {
|
||||
country: this.userService.lang.slice(3, 5),
|
||||
@@ -175,19 +174,25 @@ export class GifPicker extends Component {
|
||||
if (this.next) {
|
||||
params.position = this.next;
|
||||
}
|
||||
const { results, next } = await this.rpc("/discuss/gif/search", params, {
|
||||
silent: true,
|
||||
const res = await this.sequential(() => {
|
||||
this.state.loadingGif = true;
|
||||
const res = this.rpc("/discuss/gif/search", params, {
|
||||
silent: true,
|
||||
});
|
||||
this.state.loadingGif = false;
|
||||
return res;
|
||||
});
|
||||
if (results) {
|
||||
if (res) {
|
||||
const { next, results } = res;
|
||||
this.next = next;
|
||||
for (const gif of results) {
|
||||
this.pushGif(gif);
|
||||
}
|
||||
this.state.loadingError = false;
|
||||
}
|
||||
} catch {
|
||||
this.state.loadingError = true;
|
||||
}
|
||||
this.state.loadingGif = false;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -195,10 +200,10 @@ export class GifPicker extends Component {
|
||||
*/
|
||||
pushGif(gif) {
|
||||
if (this.state.evenGif.columnSize <= this.state.oddGif.columnSize) {
|
||||
this.state.evenGif.gifs.push(gif);
|
||||
this.state.evenGif.gifs.set(gif.id, gif);
|
||||
this.state.evenGif.columnSize += gif.media_formats.tinygif.dims[1];
|
||||
} else {
|
||||
this.state.oddGif.gifs.push(gif);
|
||||
this.state.oddGif.gifs.set(gif.id, gif);
|
||||
this.state.oddGif.columnSize += gif.media_formats.tinygif.dims[1];
|
||||
}
|
||||
}
|
||||
@@ -206,7 +211,7 @@ export class GifPicker extends Component {
|
||||
onInput() {
|
||||
this.clear();
|
||||
this.state.loadingGif = true;
|
||||
this.searchDebounced();
|
||||
this.search();
|
||||
if (this.state.searchTerm) {
|
||||
this.closeCategories();
|
||||
} else {
|
||||
@@ -227,9 +232,9 @@ export class GifPicker extends Component {
|
||||
}
|
||||
|
||||
clear() {
|
||||
this.state.evenGif.gifs = [];
|
||||
this.state.evenGif.gifs.clear();
|
||||
this.state.evenGif.columnSize = 0;
|
||||
this.state.oddGif.gifs = [];
|
||||
this.state.oddGif.gifs.clear();
|
||||
this.state.oddGif.columnSize = 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -79,13 +79,13 @@
|
||||
<i
|
||||
t-if="!store.guest"
|
||||
class="position-absolute top-0 end-0 me-3 mt-3 p-2 o-bg-black bg-opacity-75 fa cursor-pointer opacity-0"
|
||||
t-att-class="{ 'o-text-white fa-star-o': !isFavorite(gif), 'fa-star o-starred': isFavorite(gif) }"
|
||||
t-on-click="() => this.onClickFavorite(gif)"
|
||||
t-att-class="{ 'o-text-white fa-star-o': !isFavorite(gif_value), 'fa-star o-starred': isFavorite(gif_value) }"
|
||||
t-on-click="() => this.onClickFavorite(gif_value)"
|
||||
/>
|
||||
<img
|
||||
class="img-fluid"
|
||||
t-att-src="gif.media_formats.tinygif.url"
|
||||
t-on-click="() => this.onClickGif(gif)"
|
||||
t-att-src="gif_value.media_formats.tinygif.url"
|
||||
t-on-click="() => this.onClickGif(gif_value)"
|
||||
loading="lazy"
|
||||
alt="GIF"
|
||||
/>
|
||||
|
||||
@@ -439,22 +439,32 @@ export function useSequential() {
|
||||
let inProgress = false;
|
||||
let nextFunction;
|
||||
let nextResolve;
|
||||
let nextReject;
|
||||
async function call() {
|
||||
const resolve = nextResolve;
|
||||
const reject = nextReject;
|
||||
const func = nextFunction;
|
||||
nextResolve = undefined;
|
||||
nextReject = undefined;
|
||||
nextFunction = undefined;
|
||||
inProgress = true;
|
||||
const data = await func();
|
||||
try {
|
||||
const data = await func();
|
||||
resolve(data);
|
||||
} catch (e) {
|
||||
reject(e);
|
||||
}
|
||||
inProgress = false;
|
||||
resolve(data);
|
||||
if (nextFunction && nextResolve) {
|
||||
call();
|
||||
}
|
||||
}
|
||||
return (func) => {
|
||||
nextResolve?.();
|
||||
const prom = new Promise((resolve) => (nextResolve = resolve));
|
||||
const prom = new Promise((resolve, reject) => {
|
||||
nextResolve = resolve;
|
||||
nextReject = reject;
|
||||
});
|
||||
nextFunction = func;
|
||||
if (!inProgress) {
|
||||
call();
|
||||
|
||||
@@ -201,3 +201,22 @@ QUnit.test(
|
||||
await contains(".o-mail-Composer-footer .o-discuss-GifPicker");
|
||||
}
|
||||
);
|
||||
|
||||
QUnit.test("Searching for a GIF with a failling RPC should display an error", async (assert) => {
|
||||
const pyEnv = await startServer();
|
||||
const channelId = pyEnv["discuss.channel"].create({ name: "" });
|
||||
const { openDiscuss } = await start({
|
||||
mockRPC(route) {
|
||||
if (route === "/discuss/gif/search") {
|
||||
throw new Error("Rpc failed");
|
||||
}
|
||||
if (route === "/discuss/gif/categories") {
|
||||
return rpc.categories;
|
||||
}
|
||||
},
|
||||
});
|
||||
await openDiscuss(channelId);
|
||||
await click("button[aria-label='GIFs']");
|
||||
await insertText("input[placeholder='Search for a gif']", "search");
|
||||
await contains(".o-discuss-GifPicker-error");
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user