Skip to content

Display links to things in local systems #1291

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

Open
wants to merge 19 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions lxl-web/src/app.css
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,15 @@
}
}

.btn-outlined {
color: var(--color-link);
font-size: var(--text-xs);
padding: calc(var(--spacing) * 1.5) calc(var(--spacing) * 6);
height: calc(var(--spacing) * 10);
border-radius: calc(infinity * 1px);
border: 1px solid var(--color-link);
}

.badge {
display: flex;
align-items: center;
Expand Down
32 changes: 32 additions & 0 deletions lxl-web/src/lib/assets/json/display-web.json
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,18 @@
}
}
},
"tokens": {
"@id": "tokens",
"@type": "fresnel:Group",
"lenses": {
"Publication": {
"@id": "Publication-tokens",
"@type": "fresnel:Lens",
"classLensDomain": "Publication",
"showProperties": ["agent", "year"]
}
}
},
"web-chips": {
"@id": "web-chips",
"@type": "fresnel:Group",
Expand Down Expand Up @@ -823,6 +835,26 @@
"fresnel:contentBefore": ""
}
},
"bibdb:PostalAddress-format": {
"@id": "bibdb:PostalAddress-format",
"@type": "fresnel:Format",
"fresnel:classFormatDomain": ["bibdb:PostalAddress"],
"fresnel:propertyFormatDomain": ["bibdb:streetAddress", "bibdb:postalCode", "bibdb:email"],
"fresnel:propertyFormat": {
"fresnel:contentBefore": "\n",
"fresnel:contentFirst": ""
}
},
"bibdb:PostalAddress-format2": {
"@id": "bibdb:PostalAddress-format2",
"@type": "fresnel:Format",
"fresnel:classFormatDomain": ["bibdb:PostalAddress"],
"fresnel:propertyFormatDomain": ["bibdb:addressLocality"],
"fresnel:propertyFormat": {
"fresnel:contentBefore": " ",
"fresnel:contentFirst": ""
}
},
"default-separators": {
"@id": "default-separators",
"@type": "fresnel:Format",
Expand Down
7 changes: 6 additions & 1 deletion lxl-web/src/lib/i18n/locales/en.js
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,12 @@ export default {
loanStatusFailed: 'Failed to get loan status',
available: 'Available',
unavailable: 'Not available',
map: 'map'
map: 'map',
linkToLocal: 'Show in local library catalog',
loanReserveLink: 'Loan/reserve',
linkToCatalog: 'Local library catalog',
linkToSite: 'Library website',
openingHoursEtc: 'Opening hours, address etc'
},
filterAlias: {
'alias-myLibraries': 'My Libraries'
Expand Down
7 changes: 6 additions & 1 deletion lxl-web/src/lib/i18n/locales/sv.js
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,12 @@ export default {
loanStatusFailed: 'Lånestatus kunde inte hämtas',
available: 'Tillgänglig',
unavailable: 'Ej tillgänglig',
map: 'karta'
map: 'karta',
linkToLocal: 'Visa i bibliotekets katalog',
loanReserveLink: 'Låna/reservera',
linkToCatalog: 'Bibliotekets lokala katalog',
linkToSite: 'Bibliotekets webbplats',
openingHoursEtc: 'Öppettider, adress m.m.'
},
filterAlias: {
'alias-myLibraries': 'Mina bibliotek'
Expand Down
15 changes: 14 additions & 1 deletion lxl-web/src/lib/types/holdings.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { DisplayDecorated } from './xl';
import type { DisplayDecorated, FramedData } from './xl';

export type BibIdObj = {
bibId: string;
Expand All @@ -7,6 +7,7 @@ export type BibIdObj = {
onr: string | null;
isbn: string[];
issn: string[];
str: string;
};

export type HoldingsByInstanceId = {
Expand All @@ -29,3 +30,15 @@ export type DecoratedHolder = {
sigel: string;
str: string;
};

export type FullHolderBySigel = {
[sigel: string]: FramedData;
};

export type ItemLinksForHolder = {
[sigel: string]: { [linkType: string]: string[] };
};

export type ItemLinksByBibId = {
[id: string]: ItemLinksForHolder;
};
22 changes: 22 additions & 0 deletions lxl-web/src/lib/types/xl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,28 @@ export enum Platform {
meta = 'meta'
}

export enum BibDb {
ils = 'bibdb:ils',
lopac = 'bibdb:lopac',
bibIdSearchUriByLang = 'bibdb:bibIdSearchUriByLang',
bibIdSearchUri = 'bibdb:bibIdSearchUri',
isbnSearchUri = 'bibdb:isbnSearchUri',
issnSearchUri = 'bibdb:issnSearchUri',
eodUri = 'bibdb:eodUri',
itemStatusUri = 'bibdb:itemStatusUri',
openingHours = 'bibdb:openingHours',
address = 'bibdb:address',
postalAddress = 'bibdb:PostalAddress',
visitingAddress = 'bibdb:VisitingAddress',
LinksToCatalog = 'linksToCatalog',
LinksToSite = 'linksToSite',
LinksToItem = 'linksToItem',
Address = 'address',
ItemStatus = 'itemStatus',
OpeningHours = 'openingHours',
LoanReserveLink = 'loanReserveLink'
}

export type ClassName = string;
export type PropertyName = string;
export type LangCode = string;
Expand Down
6 changes: 6 additions & 0 deletions lxl-web/src/lib/utils/holdersCache.svelte.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import type { FullHolderBySigel } from '$lib/types/holdings';

type Cache = {
holders: FullHolderBySigel;
};
export const holdersCache: Cache = $state({ holders: {} });
195 changes: 185 additions & 10 deletions lxl-web/src/lib/utils/holdings.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,21 @@
import { pushState } from '$app/navigation';
import isFnurgel from '$lib/utils/isFnurgel';
import type { BibIdObj, HoldersByType, HoldingsByInstanceId } from '$lib/types/holdings';
import { LensType, type FramedData } from '$lib/types/xl';
import type {
BibIdObj,
DecoratedHolder,
FullHolderBySigel,
HoldersByType,
HoldingsByInstanceId,
ItemLinksByBibId,
ItemLinksForHolder
} from '$lib/types/holdings';
import { LensType, type FramedData, JsonLd, BibDb } from '$lib/types/xl';
import type { LocaleCode } from '$lib/i18n/locales';
import type { LibraryItem, UserSettings } from '$lib/types/userSettings';
import { relativizeUrl } from '$lib/utils/http';
import { DisplayUtil, toString } from '$lib/utils/xl.js';
import getAtPath from '$lib/utils/getAtPath';
import { holdersCache } from '$lib/utils/holdersCache.svelte';

export function getHoldingsLink(url: URL, value: string) {
const newSearchParams = new URLSearchParams([...Array.from(url.searchParams.entries())]);
Expand Down Expand Up @@ -72,13 +82,20 @@ export function getHoldingsByInstanceId(
}, {});
}

export function getBibIdsByInstanceId(mainEntity, record): Record<string, BibIdObj> {
return mainEntity['@reverse']?.instanceOf?.reduce((acc, instanceOfItem) => {
const id = relativizeUrl(instanceOfItem['@id'])?.replace('#it', '');
export function getBibIdsByInstanceId(
mainEntity,
displayUtil: DisplayUtil,
record,
locale: LocaleCode
): Record<string, BibIdObj> {
return mainEntity['@reverse']?.instanceOf?.reduce((acc, instance) => {
const id = relativizeUrl(instance['@id'])?.replace('#it', '');

const bibId = instanceOfItem.meta?.controlNumber || record?.controlNumber;
const type = instanceOfItem['@type'];
const holders = instanceOfItem['@reverse']?.itemOf?.map((i) => i?.heldBy?.sigel);
const bibId = instance.meta?.controlNumber || record?.controlNumber;
const type = instance['@type'];
const holders = instance['@reverse']?.itemOf?.map((i) => i?.heldBy?.sigel);
const str =
toString(displayUtil.lensAndFormat(instance.publication[0], LensType.Token, locale)) || '';

// add Legacy Libris III system number for ONR param
let onr = null;
Expand All @@ -90,7 +107,7 @@ export function getBibIdsByInstanceId(mainEntity, record): Record<string, BibIdO

const isbn: string[] = [];
const issn: string[] = [];
instanceOfItem.identifiedBy?.forEach((el: { '@type': string; value: string }) => {
instance.identifiedBy?.forEach((el: { '@type': string; value: string }) => {
if (el['@type'] === 'ISBN') {
isbn.push(el.value);
}
Expand All @@ -111,7 +128,8 @@ export function getBibIdsByInstanceId(mainEntity, record): Record<string, BibIdO
holders,
onr,
isbn,
issn
issn,
str
}
};
}, {});
Expand Down Expand Up @@ -185,3 +203,160 @@ export function getMyLibsFromHoldings(
}
return Object.values(result);
}

export async function getFullHolderData(allHolders: DecoratedHolder[]): Promise<FullHolderBySigel> {
const holderBySigel: FullHolderBySigel = {};

for (const h of allHolders) {
const id = h.obj?.['@id'];
const libraryRes = await fetch(`${id}?framed=true`, {
headers: { Accept: 'application/ld+json' }
});
const resJson = await libraryRes.json();
const libraryMainEntity = resJson['mainEntity'] as FramedData;

if (libraryMainEntity) {
holderBySigel[h.sigel] = libraryMainEntity;
}
}
return holderBySigel;
}

export async function fetchHoldersIfAbsent(holdersByType: HoldersByType) {
const cachedHolders = holdersCache.holders;
const allHolders = Object.values(holdersByType).flat();
for (const h of allHolders) {
const id = h.obj?.['@id'];

if (h.sigel && cachedHolders && !cachedHolders[h.sigel]) {
const libraryRes = await fetch(`${id}?framed=true`, {
headers: { Accept: 'application/ld+json' }
});
const resJson = await libraryRes.json();
const libraryMainEntity = resJson['mainEntity'] as FramedData;
if (libraryMainEntity) {
cachedHolders[h.sigel] = libraryMainEntity;
}
}
}
}

export function getItemLinksByBibId(
bibIdsByInstanceId: Record<string, BibIdObj>,
locale: LocaleCode,
displayUtil: DisplayUtil
): ItemLinksByBibId {
const linksByInstanceId: ItemLinksByBibId = {};
for (const bibIdObj of Object.values(bibIdsByInstanceId)) {
const linksForHolder: ItemLinksForHolder = {};
console.log('bibIdObj', bibIdObj);
bibIdObj.holders?.forEach((sigel) => {
if (holdersCache.holders) {
const fullHolderData = holdersCache.holders[sigel];

const ilsPaths = [
[BibDb.ils, BibDb.bibIdSearchUri],
[BibDb.ils, BibDb.isbnSearchUri],
[BibDb.ils, BibDb.issnSearchUri]
];

const lopacPaths = [[BibDb.lopac, BibDb.bibIdSearchUriByLang]];

let linksToItem = getLinksToItemFor(bibIdObj, fullHolderData, ilsPaths, locale);
const lopacLinksItem = getLinksToItemFor(bibIdObj, fullHolderData, lopacPaths, locale);

const linkTemplateEod = getAtPath(fullHolderData, [BibDb.eodUri], []);
if (linkTemplateEod && linkTemplateEod.length !== 0) {
linksToItem = [linkTemplateEod.replace(/%BIB_*ID%/, bibIdObj.bibId), ...linksToItem];
}

//TODO: rename
const allLinks: { [linkType: string]: string[] } = {};

const itemStatusUri = getAtPath(fullHolderData, [BibDb.ils, BibDb.itemStatusUri], []);

if (itemStatusUri && itemStatusUri.length !== 0) {
allLinks[BibDb.ItemStatus] = [itemStatusUri];
}

const linksToCatalog: string[] = [];
const linkToCatalog = getAtPath(fullHolderData, [BibDb.ils, 'url'], undefined);
if (linkToCatalog && linkToCatalog.length !== 0) {
linksToCatalog.push(linkToCatalog);
allLinks[BibDb.LinksToCatalog] = linksToCatalog;
}

const linksToSite: string[] = [];
const linkToSite = getAtPath(fullHolderData, ['url', JsonLd.ID], undefined);
if (linkToSite && linkToSite.length !== 0) {
linksToSite.push(linkToSite);
allLinks[BibDb.LinksToSite] = linksToSite;
}

const openingHoursList: string[] = [];
const openingHours = getAtPath(fullHolderData, [BibDb.openingHours], undefined);
if (openingHours && openingHours !== '') {
openingHoursList.push(openingHours);
allLinks[BibDb.OpeningHours] = openingHoursList;
}

const addresses: string[] = [];
const address = getAtPath(fullHolderData, [BibDb.address, '*'], undefined);
const postalAddress = address.find((a) => a[JsonLd.TYPE] === BibDb.postalAddress);
const visitingAddress = address.find((a) => a[JsonLd.TYPE] === BibDb.visitingAddress);

if (address && address.length !== 0) {
addresses.push(
toString(displayUtil.lensAndFormat(visitingAddress, LensType.Card, locale)) || ''
);
addresses.push(
toString(displayUtil.lensAndFormat(postalAddress, LensType.Card, locale)) || ''
);
allLinks[BibDb.Address] = addresses;
}

if (linksToItem.length !== 0) {
allLinks[BibDb.LinksToItem] = linksToItem;
}

if (lopacLinksItem.length !== 0) {
allLinks[BibDb.LoanReserveLink] = lopacLinksItem;
}

if (Object.keys(allLinks).length !== 0) {
linksForHolder[sigel] = allLinks;
}
}
});
linksByInstanceId[bibIdObj.bibId] = linksForHolder;
}
return linksByInstanceId;
}

function getLinksToItemFor(
bibIdObj: BibIdObj,
fullHolderData: FramedData,
paths: string[][],
locale: LocaleCode
): string[] {
let linksToItem: string[] = [];
for (const path of paths) {
const linkTemplate = getAtPath(fullHolderData, path, []);
if (linkTemplate && linkTemplate.length !== 0) {
if (path.includes(BibDb.bibIdSearchUriByLang) && bibIdObj.bibId !== '') {
linksToItem = [linkTemplate[locale].replace(/%BIB_*ID%/, bibIdObj.bibId), ...linksToItem];
}
if (path.includes(BibDb.bibIdSearchUri) && bibIdObj.bibId !== '') {
// forms in the wild %BIB_ID%, %BIBID%, more???
linksToItem = [linkTemplate.replace(/%BIB_*ID%/, bibIdObj.bibId), ...linksToItem];
}
if (path.includes(BibDb.isbnSearchUri) && bibIdObj.isbn.length !== 0) {
linksToItem = [linkTemplate.replace(/%ISBN%/, bibIdObj.isbn), ...linksToItem];
}
if (path.includes(BibDb.issnSearchUri) && bibIdObj.issn.length !== 0) {
linksToItem = [linkTemplate.replace(/%ISSN%/, bibIdObj.issn), ...linksToItem];
}
}
}
return linksToItem;
}
Loading