Skip to content

Commit

Permalink
[Enhancement #544] Simplify FTS results rendering.
Browse files Browse the repository at this point in the history
Do not load full term for each result, support all newly added fields.
  • Loading branch information
ledsoft committed Oct 17, 2024
1 parent bedcc29 commit f000068
Show file tree
Hide file tree
Showing 5 changed files with 102 additions and 231 deletions.
2 changes: 1 addition & 1 deletion src/component/search/label/SearchResults.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ export function mergeDuplicates(
// If the match field is the same there is no need to update other attributes, as the match is already
// marked in the snippet of the existing item
if (existing.snippetField !== r.snippetField) {
if (r.snippetField === "label") {
if (r.snippetField === "prefLabel") {
// Render label match first
existing.snippets.unshift(r.snippetText);
existing.snippetFields.unshift(r.snippetField);
Expand Down
177 changes: 45 additions & 132 deletions src/component/search/label/TermResultItem.tsx
Original file line number Diff line number Diff line change
@@ -1,150 +1,63 @@
import * as React from "react";
import { injectIntl } from "react-intl";
import withI18n, { HasI18n } from "../../hoc/withI18n";
import VocabularyUtils, { IRI } from "../../../util/VocabularyUtils";
import AssetLink from "../../misc/AssetLink";
import Term from "../../../model/Term";
import { connect } from "react-redux";
import { ThunkDispatch } from "../../../util/Types";
import { loadTermByIri } from "../../../action/AsyncActions";
import { useSelector } from "react-redux";
import { SearchResultItem } from "./SearchResults";
import AssetLabel from "../../misc/AssetLabel";
import AssetFactory from "../../../util/AssetFactory";
import TermItState from "../../../model/TermItState";
import FTSMatch from "./FTSMatch";
import TermBadge from "../../badge/TermBadge";
import { getTermPath } from "../../term/TermLink";
import User from "../../../model/User";
import { getLocalized } from "../../../model/MultilingualString";
import { getShortLocale } from "../../../util/IntlUtil";
import TermStateBadge from "../../term/state/TermStateBadge";
import { useI18n } from "../../hook/useI18n";
import { getResultDescription } from "./VocabularyResultItem";

interface TermResultItemOwnProps {
interface TermResultItemProps {
result: SearchResultItem;
}

interface TermResultItemDispatchProps {
loadTerm: (termIri: IRI) => Promise<Term | null>;
}

interface TermResultItemStateProps {
user: User;
}

interface TermResultItemProps
extends TermResultItemOwnProps,
TermResultItemDispatchProps,
TermResultItemStateProps,
HasI18n {}

interface TermResultItemState {
text: string | undefined;
}

export class TermResultItem extends React.Component<
TermResultItemProps,
TermResultItemState
> {
constructor(props: TermResultItemProps) {
super(props);
this.state = {
text: undefined,
};
}
const TermResultItem: React.FC<TermResultItemProps> = ({ result }) => {
const { i18n } = useI18n();
const user = useSelector((state: TermItState) => state.user);

public componentDidMount(): void {
const indexOfDefinition = this.getIndexOf("definition");
if (indexOfDefinition < 0) {
const iri = VocabularyUtils.create(this.props.result.iri);
this.props.loadTerm(iri).then((term) => {
if (term) {
this.setState({
text: term!.definition
? getLocalized(
term!.definition,
getShortLocale(this.props.locale)
)
: getLocalized(
term!.scopeNote,
getShortLocale(this.props.locale)
),
});
}
});
}
}

private getIndexOf(field: string) {
return this.props.result.snippetFields.indexOf(field);
}

public render() {
const i18n = this.props.i18n;
const result = this.props.result;
const t = {
iri: result.iri,
label: (
<>
<span className="search-result-title">{result.label}</span>
&nbsp;
{this.props.result.vocabulary ? (
<>
{i18n("search.results.vocabulary.from")}&nbsp;
<AssetLabel iri={result.vocabulary!.iri} />
</>
) : (
<></>
)}
</>
),
};

let text;
if (this.getIndexOf("definition") > -1) {
text = result.snippets[this.getIndexOf("definition")];
} else {
text = this.state.text;
}

if (text && text!.length > 200) {
text = text!.substring(0, 200) + " ...";
}

const asset = AssetFactory.createAsset(result);
return (
const t = {
iri: result.iri,
label: (
<>
<TermBadge className="search-result-badge" />
<TermStateBadge state={result.state} />
<AssetLink
asset={t}
path={getTermPath(asset as Term, this.props.user)}
tooltip={i18n("asset.link.tooltip")}
/>
<br />
<span className="search-result-snippet">
{this.getIndexOf("definition") > -1 ? (
<FTSMatch match={text || ""} />
) : (
text
)}
</span>
<span className="search-result-title">{result.label}</span>
&nbsp;
{result.vocabulary ? (
<>
{i18n("search.results.vocabulary.from")}&nbsp;
<AssetLabel iri={result.vocabulary!.iri} />
</>
) : (
<></>
)}
</>
);
}
}

export default connect<
TermResultItemStateProps,
TermResultItemDispatchProps,
TermResultItemOwnProps,
TermItState
>(
(state: TermItState) => {
return { user: state.user };
},
(dispatch: ThunkDispatch) => {
return {
loadTerm: (termIri: IRI) => dispatch(loadTermByIri(termIri)),
};
}
)(injectIntl(withI18n(TermResultItem)));
),
};
const fields = ["definition", "scopeNote"];

const description = getResultDescription(result, fields);

const asset = AssetFactory.createAsset(result);
return (
<>
<TermBadge className="search-result-badge" />
<TermStateBadge state={result.state} />
<AssetLink
asset={t}
path={getTermPath(asset as Term, user)}
tooltip={i18n("asset.link.tooltip")}
/>
<br />
<span className="search-result-snippet">
<FTSMatch match={description} />
</span>
</>
);
};

export default TermResultItem;
135 changes: 45 additions & 90 deletions src/component/search/label/VocabularyResultItem.tsx
Original file line number Diff line number Diff line change
@@ -1,110 +1,65 @@
import * as React from "react";
import { injectIntl } from "react-intl";
import withI18n, { HasI18n } from "../../hoc/withI18n";
import VocabularyUtils, { IRI } from "../../../util/VocabularyUtils";
import { connect } from "react-redux";
import { ThunkDispatch } from "../../../util/Types";
import { SearchResultItem } from "./SearchResults";
import TermItState from "../../../model/TermItState";
import FTSMatch from "./FTSMatch";
import { getRdfsResource } from "../../../action/AsyncActions";
import RdfsResource from "../../../model/RdfsResource";
import Vocabulary from "../../../model/Vocabulary";
import VocabularyLink from "../../vocabulary/VocabularyLink";
import AssetFactory from "../../../util/AssetFactory";
import VocabularyBadge from "../../badge/VocabularyBadge";
import { getLocalized } from "../../../model/MultilingualString";
import { getShortLocale } from "../../../util/IntlUtil";

interface VocabularyResultItemOwnProps {
interface VocabularyResultItemProps {
result: SearchResultItem;
}

interface VocabularyResultItemDispatchProps {
getResource: (iri: IRI) => Promise<RdfsResource | undefined>;
export function getSnippetFieldIndex(
result: SearchResultItem,
fieldName: string
) {
return result.snippetFields.indexOf(fieldName);
}

interface VocabularyResultItemStateProps {}

interface VocabularyResultItemProps
extends VocabularyResultItemOwnProps,
VocabularyResultItemDispatchProps,
VocabularyResultItemStateProps,
HasI18n {}

interface VocabularyResultItemState {
comment?: string | null;
}

export class VocabularyResultItem extends React.Component<
VocabularyResultItemProps,
VocabularyResultItemState
> {
constructor(props: VocabularyResultItemProps) {
super(props);
this.state = {
comment: null,
};
}

public componentDidMount(): void {
const indexOfComment = this.getIndexOf("comment");
if (indexOfComment < 0) {
const iri = VocabularyUtils.create(this.props.result.iri);
this.props.getResource(iri).then((resource) => {
if (resource) {
this.setState({
comment: getLocalized(
resource.comment,
getShortLocale(this.props.locale)
),
});
}
});
export function getResultDescription(
result: SearchResultItem,
fieldNames: string[]
) {
let snippetFieldIndex = -1;
for (let i = 0; i < fieldNames.length; i++) {
snippetFieldIndex = getSnippetFieldIndex(result, fieldNames[i]);
if (snippetFieldIndex >= 0) {
break;
}
}

private getIndexOf(field: string) {
return this.props.result.snippetFields.indexOf(field);
let text;
if (snippetFieldIndex >= 0) {
text = result.snippets[snippetFieldIndex];
} else {
text = result.description;
}

public render() {
let text;
if (this.getIndexOf("comment") > -1) {
text = this.props.result.snippets[this.getIndexOf("comment")];
} else {
text = this.state.comment || "";
}

if (text && text!.length > 200) {
text = text!.substring(0, 200) + " ...";
}

const res = this.props.result;
return (
<>
<VocabularyBadge className="search-result-badge" />
<span className="search-result-title">
<VocabularyLink
vocabulary={AssetFactory.createAsset(res) as Vocabulary}
/>
</span>
<br />
<span className="search-result-snippet">
<FTSMatch match={text || ""} />
</span>
</>
);
if (text && text!.length > 200) {
text = text!.substring(0, 200) + " ...";
}
return text || "";
}

export default connect<
VocabularyResultItemStateProps,
VocabularyResultItemDispatchProps,
VocabularyResultItemOwnProps,
TermItState
>(undefined, (dispatch: ThunkDispatch) => {
return {
getResource: (iri: IRI) => dispatch(getRdfsResource(iri)),
};
})(injectIntl(withI18n(VocabularyResultItem)));
const VocabularyResultItem: React.FC<VocabularyResultItemProps> = ({
result,
}) => {
const description = getResultDescription(result, ["description"]);

return (
<>
<VocabularyBadge className="search-result-badge" />
<span className="search-result-title">
<VocabularyLink
vocabulary={AssetFactory.createAsset(result) as Vocabulary}
/>
</span>
<br />
<span className="search-result-snippet">
<FTSMatch match={description} />
</span>
</>
);
};

export default VocabularyResultItem;
Loading

0 comments on commit f000068

Please sign in to comment.