Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(lxlweb): Decorated suggestions (LWS-321) #1210

Merged
merged 14 commits into from
Feb 5, 2025
Merged
2 changes: 1 addition & 1 deletion lxl-web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@
"prettier-plugin-svelte": "^3.2.6",
"prettier-plugin-tailwindcss": "^0.6.6",
"supersearch": "^0.0.1",
"svelte": "^5.1.9",
"svelte": "^5.18.0",
"svelte-check": "^4.0.4",
"tailwindcss": "^3.4.10",
"tslib": "^2.6.3",
Expand Down
77 changes: 67 additions & 10 deletions lxl-web/src/lib/assets/json/display-web.json
Original file line number Diff line number Diff line change
Expand Up @@ -275,14 +275,19 @@
"@id": "Agent-web-card",
"@type": "fresnel:Lens",
"classLensDomain": "Agent",
"showProperties": [
"nationality",
"disambiguatingDescription",
"activityStartDate",
"activityEndDate",
"hasOccupation",
"fieldOfActivity"
]
"showProperties": []
},
"Concept": {
"@id": "Concept-web-card",
"@type": "fresnel:Lens",
"classLensDomain": "Concept",
"showProperties": []
},
"Language": {
"@id": "Language-web-card",
"@type": "fresnel:Lens",
"classLensDomain": "Language",
"showProperties": []
},
"Instance": {
"@id": "Instance-web-card",
Expand All @@ -301,6 +306,24 @@
"@type": "fresnel:Lens",
"classLensDomain": "Work",
"showProperties": ["translationOf"]
},
"Agent": {
"@id": "Agent-web-card-header-extra",
"@type": "fresnel:Lens",
"classLensDomain": "Agent",
"showProperties": []
},
"Concept": {
"@id": "Concept-web-card-header-extra",
"@type": "fresnel:Lens",
"classLensDomain": "Concept",
"showProperties": []
},
"Language": {
"@id": "Language-web-card-header-extra",
"@type": "fresnel:Lens",
"classLensDomain": "Language",
"showProperties": []
}
}
},
Expand All @@ -320,11 +343,34 @@
"classLensDomain": "Instance",
"showProperties": ["hasTitle", "responsibilityStatement", "identifier", "publication"]
},
"Agent": {
"@id": "Agent-web-card-footer",
"@type": "fresnel:Lens",
"classLensDomain": "Agent",
"showProperties": [
"disambiguatingDescription",
"activityStartDate",
"activityEndDate",
"nationality"
]
},
"Person": {
"@id": "Person-web-card-footer",
"@type": "fresnel:Lens",
"classLensDomain": "Instance",
"showProperties": ["nationality"]
"classLensDomain": "Person",
"showProperties": ["hasOccupation", "nationality"]
},
"Concept": {
"@id": "Concept-web-card-footer",
"@type": "fresnel:Lens",
"classLensDomain": "Concept",
"showProperties": ["inScheme"]
},
"Language": {
"@id": "Language-web-card-footer",
"@type": "fresnel:Lens",
"classLensDomain": "Language",
"showProperties": []
}
}
}
Expand Down Expand Up @@ -407,6 +453,16 @@
"fresnel:contentFirst": "-"
}
},
"Agent-activityEndDate-format": {
"@id": "Agent-activityEndDate-format",
"@type": "fresnel:Format",
"fresnel:classFormatDomain": ["Agent"],
"fresnel:propertyFormatDomain": ["activityEndDate"],
"fresnel:propertyFormat": {
"fresnel:contentBefore": "-",
"fresnel:contentFirst": "-"
}
},
"ProvisionActivity-colon-before-agent-format": {
"@id": "ProvisionActivity-colon-before-agent-format",
"@type": "fresnel:Format",
Expand Down Expand Up @@ -443,6 +499,7 @@
"@type": "fresnel:Format",
"fresnel:classFormatDomain": ["Title"],
"fresnel:propertyFormatDomain": ["subtitle", "titleRemainder"],
"fresnel:propertyStyle": ["font-normal"],
"fresnel:propertyFormat": {
"fresnel:contentBefore": " : ",
"fresnel:contentFirst": ""
Expand Down
202 changes: 202 additions & 0 deletions lxl-web/src/lib/components/SuggestionCard.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
<script lang="ts">
import type { SearchResultItem } from '$lib/types/search';
import { LensType } from '$lib/types/xl';
import { ShowLabelsOptions } from '$lib/types/decoratedData';
import { LxlLens } from '$lib/types/display';
import { relativizeUrl } from '$lib/utils/http';
import getInstanceData from '$lib/utils/getInstanceData';
import getTypeIcon from '$lib/utils/getTypeIcon';
import placeholder from '$lib/assets/img/placeholder.svg';
import DecoratedData from '$lib/components/DecoratedData.svelte';
import { page } from '$app/stores';
import SearchItemDebug from '$lib/components/find/SearchItemDebug.svelte';
import EsExplain from '$lib/components/find/EsExplain.svelte';

type Props = {
item: SearchResultItem;
cellId: string;
isFocused: boolean;
};

const { item, cellId, isFocused }: Props = $props();

const itemId = $derived(relativizeUrl(item['@id']));
const titleId = $derived(`card-title-${itemId}`);
const footerId = $derived(`card-footer-${itemId}`);

let showDebugExplain = $state(false);
</script>

<div class="suggestion-card-container">
<article class="suggestion-card relative grid w-full gap-x-4 px-4 pb-2 pt-2 font-normal">
<a
id={cellId}
role="gridcell"
class={['card-link absolute h-full w-full hover:bg-main', isFocused && 'bg-site-header/40']}
href={itemId}
aria-labelledby={titleId}
aria-describedby={`${footerId}`}
></a>
<div class="card-image">
<div class="pointer-events-none relative flex">
{#if item.image}
<img
src={item.image.url}
width={item.image.widthṔx}
height={item.image.heightPx}
alt={$page.data.t('general.latestInstanceCover')}
class={[
'aspect-square object-contain object-top',
item['@type'] === 'Person' && 'rounded-full'
]}
/>
{#if item['@type'] !== 'Text' && item['@type'] !== 'Person' && getTypeIcon(item['@type'])}
{@const SvelteComponent = getTypeIcon(item['@type'])}
<div class="absolute -left-2 -top-2">
<div class="rounded-md bg-main/80 p-1.5">
<SvelteComponent class="h-3 w-3 text-icon-strong" />
</div>
</div>
{/if}
{:else}
<div class="flex items-center justify-center">
<img
src={placeholder}
alt=""
class={[
'object-contain object-top',
item['@type'] === 'Person' ? 'rounded-full' : 'rounded-sm'
]}
/>
{#if getTypeIcon(item['@type'])}
{@const SvelteComponent_1 = getTypeIcon(item['@type'])}
<SvelteComponent_1 class="absolute text-lg text-icon" />
{/if}
</div>
{/if}
</div>
</div>
<div class="card-content pointer-events-none z-10 overflow-hidden">
<header class="card-header" id={titleId}>
<hgroup class="flex">
<h2
class="card-header-title flex items-baseline overflow-hidden whitespace-nowrap text-link"
>
<span class="overflow-hidden text-ellipsis text-3-cond-bold">
<DecoratedData data={item['card-heading']} showLabels={ShowLabelsOptions.Never} />
</span>
<!-- first contributor -->
{#if item[LxlLens.CardBody]?._display?.[0]?.contribution}
<span class="divider text-2-regular">&nbsp;{'•'}&nbsp;</span>
<span class="flex-shrink-0 text-2-regular">
<DecoratedData
data={item[LxlLens.CardBody]?._display[0]}
showLabels={ShowLabelsOptions.Never}
allowLinks={false}
truncate={true}
/>
</span>
{/if}
</h2>
</hgroup>
<!-- header extra skipped -->
</header>
<footer class="card-footer mt-auto text-xs text-secondary" id={footerId}>
<span class="font-bold">
{item.typeStr}
</span>
<span class="divider">{' • '}</span>
{#each item[LensType.WebCardFooter]?._display as obj}
{#if 'hasInstance' in obj}
<span class="divider">{' • '}</span>
{@const instances = getInstanceData(obj.hasInstance)}
{#if instances?.years}
<span>
{#if instances.count > 1}
{instances?.count}
{$page.data.t('search.editions')}
{`(${instances.years})`}
{:else}
{instances.years}
{/if}
</span>
{/if}
{:else}
<span>
<DecoratedData data={obj} showLabels={ShowLabelsOptions.Never} allowLinks={false} />
</span>
{/if}
{/each}
</footer>
</div>
{#if item._debug}
{#key item._debug}
<button
type="button"
class="card-debug z-10 cursor-crosshair select-text self-start text-left"
onclick={() => {
showDebugExplain = !showDebugExplain;
}}
>
<SearchItemDebug debugInfo={item._debug} />
</button>
{#if showDebugExplain}
<div id="explain" class="z-10 col-span-full row-start-2 cursor-crosshair pt-4">
<EsExplain explain={item._debug.score.explain} />
</div>
{/if}
{/key}
{/if}
</article>
</div>

<style scoped lang="postcss">
.suggestion-card-container {
container-type: inline-size;
}

.suggestion-card {
grid-template-areas: 'image content debug';
grid-template-columns: 40px 1fr auto;
}

.card-image {
grid-area: image;
}

.card-content {
grid-area: content;
}

.card-debug {
grid-area: debug;
}

.card-header-title {
/* hide contributor remainder */
& :global(.remainder) {
@apply hidden;
}

/* hide role */
& :global([data-property='role']),
:global(._contentBefore:has(+ [data-property='role'])),
:global([data-property='role'] + ._contentAfter) {
@apply hidden;
}
}

.card-footer {
/* hide dangling divider • */
& .divider {
@apply hidden;
}
& :global(.divider:has(+ span)) {
@apply inline;
}

@container (min-width: 768px) {
@apply text-sm;
}
}
</style>
Loading
Loading