mirror of
https://github.com/gramps-project/gramps-web.git
synced 2026-10-19 23:27:57 +08:00
9ab109c98d
* Refactor renderIcon function * Refactor citations list in source view * Convert source citation to editable list * Style tweak * Added custom event type to form select with form validation * Updating code in form select type to be compliant with translations and removed redundencies * Removed redundent code from mwc-textfield * Removing redundent disabled attribute from mwc-select * Remove custom type form on reset * Allow switching back to default type Co-authored-by: David Straub <straub@protonmail.com> Co-authored-by: Eric Kemmer <kemmer@usf.edu>
545 lines
15 KiB
JavaScript
545 lines
15 KiB
JavaScript
/* eslint-disable no-bitwise */
|
|
import {html} from 'lit'
|
|
import '@material/mwc-icon'
|
|
import dayjs from 'dayjs/esm'
|
|
import relativeTime from 'dayjs/esm/plugin/relativeTime'
|
|
|
|
import {asteriskIcon, crossIcon} from './icons.js'
|
|
import {additionalStrings} from './strings.js'
|
|
|
|
dayjs.extend(relativeTime)
|
|
|
|
const BASE_DIR = ''
|
|
|
|
export function translate(strings, s) {
|
|
if (s === undefined) {
|
|
return ''
|
|
}
|
|
if (s in strings) {
|
|
return strings[s].replace('_', '')
|
|
}
|
|
return s.replace('_', '')
|
|
}
|
|
|
|
export function personTitleFromProfile(personProfile) {
|
|
return `${personProfile.name_given || '…'} ${
|
|
personProfile.name_surname || '…'
|
|
}`
|
|
}
|
|
|
|
export function familyTitleFromProfile(familyProfile) {
|
|
return `${personTitleFromProfile(
|
|
familyProfile.father || {}
|
|
)} & ${personTitleFromProfile(familyProfile.mother || {})}`
|
|
}
|
|
|
|
export function citationTitleFromProfile(citationProfile) {
|
|
return `${citationProfile.source?.title || ''}
|
|
${citationProfile.page ? ` (${citationProfile.page})` : ''}`
|
|
}
|
|
|
|
export function eventTitleFromProfile(eventProfile, strings, date = true) {
|
|
if (eventProfile.summary) {
|
|
return html`${eventProfile.summary}${date && eventProfile.date
|
|
? ` (${eventProfile.date})`
|
|
: ''}`
|
|
}
|
|
// the code below is only kept for backward compatibility
|
|
const primary = translate(strings, 'Primary')
|
|
const family = translate(strings, 'Family')
|
|
const people =
|
|
eventProfile?.participants?.people.filter(
|
|
obj => obj.role === primary || obj.role === 'Primary'
|
|
) || []
|
|
const families =
|
|
eventProfile?.participants?.families.filter(
|
|
obj => obj.role === family || obj.role === 'Family'
|
|
) || []
|
|
const primaryPeople = `${people
|
|
.map(obj => personTitleFromProfile(obj.person))
|
|
.join(', ')}
|
|
${families.map(obj => familyTitleFromProfile(obj.family)).join(', ')}`
|
|
return html`${eventProfile.type}${primaryPeople.trim()
|
|
? `: ${primaryPeople}`
|
|
: ''}${eventProfile.date ? ` (${eventProfile.date})` : ''}`
|
|
}
|
|
|
|
export function renderPerson(personProfile) {
|
|
return html`
|
|
<span class="event">
|
|
<mwc-icon class="inline ${personProfile.sex === 'M' ? 'male' : 'female'}"
|
|
>person</mwc-icon
|
|
>
|
|
<a href="${BASE_DIR}/person/${personProfile.gramps_id}"
|
|
>${personProfile.name_given || '…'}
|
|
${personProfile.name_surname || '…'}</a
|
|
>
|
|
</span>
|
|
${personProfile?.birth?.date
|
|
? html` <span class="event"
|
|
><i>${asteriskIcon}</i> ${personProfile.birth.date}</span
|
|
>`
|
|
: ''}
|
|
${personProfile?.death?.date
|
|
? html` <span class="event"
|
|
><i>${crossIcon}</i> ${personProfile.death.date}</span
|
|
>`
|
|
: ''}
|
|
`
|
|
}
|
|
|
|
export function getName(obj, type) {
|
|
switch (type) {
|
|
case 'person':
|
|
return obj?.primary_name?.first_name
|
|
case 'event':
|
|
return obj.type
|
|
case 'family':
|
|
return ''
|
|
case 'place':
|
|
return obj?.name?.value || obj.title
|
|
case 'source':
|
|
return obj.title
|
|
case 'repository':
|
|
return obj.name
|
|
case 'citation':
|
|
return obj.page
|
|
case 'media':
|
|
return obj.desc
|
|
default:
|
|
return ''
|
|
}
|
|
}
|
|
|
|
export function showObject(type, obj, strings) {
|
|
switch (type) {
|
|
case 'person':
|
|
return html`
|
|
<mwc-icon class="inline ${obj.gender === 1 ? 'male' : 'female'}"
|
|
>person</mwc-icon
|
|
>
|
|
<a href="${BASE_DIR}/${type}/${obj.gramps_id}"
|
|
>${obj?.profile?.name_given || html`…`}
|
|
${obj?.profile?.name_surname || html`…`}
|
|
</a>
|
|
`
|
|
case 'family':
|
|
return html`
|
|
<mwc-icon class="inline">people</mwc-icon>
|
|
<a href="${BASE_DIR}/${type}/${obj.gramps_id}"
|
|
>${familyTitleFromProfile(obj.profile || {}) || type}
|
|
</a>
|
|
`
|
|
case 'event':
|
|
return html`
|
|
<mwc-icon class="inline">event</mwc-icon>
|
|
<a href="${BASE_DIR}/${type}/${obj.gramps_id}"
|
|
>${eventTitleFromProfile(obj.profile || {}, strings) || obj.type}
|
|
</a>
|
|
`
|
|
case 'place':
|
|
return html`
|
|
<mwc-icon class="inline">place</mwc-icon>
|
|
<a href="${BASE_DIR}/${type}/${obj.gramps_id}"
|
|
>${obj?.profile?.name || obj?.name?.value || obj.title || type}
|
|
</a>
|
|
`
|
|
case 'source':
|
|
return html`
|
|
<mwc-icon class="inline">bookmarks</mwc-icon>
|
|
<a href="${BASE_DIR}/${type}/${obj.gramps_id}"
|
|
>${getName(obj, type) || type}
|
|
</a>
|
|
`
|
|
case 'citation':
|
|
return html`
|
|
<mwc-icon class="inline">bookmark</mwc-icon>
|
|
<a href="${BASE_DIR}/${type}/${obj.gramps_id}"
|
|
>${citationTitleFromProfile(obj.profile || {}) || type}
|
|
</a>
|
|
`
|
|
case 'repository':
|
|
return html`
|
|
<mwc-icon class="inline">account_balance</mwc-icon>
|
|
<a href="${BASE_DIR}/${type}/${obj.gramps_id}"
|
|
>${getName(obj, type) || type}
|
|
</a>
|
|
`
|
|
case 'note':
|
|
return html`
|
|
<mwc-icon class="inline">sticky_note_2</mwc-icon>
|
|
<a href="${BASE_DIR}/${type}/${obj.gramps_id}"
|
|
>${translate(strings, obj.type) || type}
|
|
</a>
|
|
`
|
|
case 'media':
|
|
return html`
|
|
<mwc-icon class="inline">photo</mwc-icon>
|
|
<a href="${BASE_DIR}/media/${obj.gramps_id}"
|
|
>${getName(obj, type) || type}
|
|
</a>
|
|
`
|
|
case 'tag':
|
|
return html``
|
|
default:
|
|
return `unknown type: ${type}`
|
|
}
|
|
}
|
|
|
|
export const objectIcon = {
|
|
person: 'person',
|
|
family: 'people',
|
|
event: 'event',
|
|
place: 'place',
|
|
source: 'bookmarks',
|
|
citation: 'bookmark',
|
|
repository: 'account_balance',
|
|
note: 'sticky_note_2',
|
|
media: 'photo',
|
|
tag: 'label',
|
|
}
|
|
|
|
export const objectTypeToEndpoint = {
|
|
person: 'people',
|
|
family: 'families',
|
|
event: 'events',
|
|
place: 'places',
|
|
source: 'sources',
|
|
citation: 'citations',
|
|
repository: 'repositories',
|
|
note: 'notes',
|
|
media: 'media',
|
|
tag: 'tags',
|
|
}
|
|
|
|
export function objectDescription(type, obj, strings) {
|
|
switch (type) {
|
|
case 'person':
|
|
return html`${obj?.profile?.name_given || html`…`}
|
|
${obj?.profile?.name_surname || html`…`}`
|
|
case 'family':
|
|
return html`${familyTitleFromProfile(obj.profile || {}) || type}`
|
|
case 'event':
|
|
return html`${eventTitleFromProfile(obj.profile || {}, strings, false) ||
|
|
obj.type}`
|
|
case 'place':
|
|
return html`${obj?.profile?.name ||
|
|
obj?.name?.value ||
|
|
obj.title ||
|
|
type}`
|
|
case 'source':
|
|
return html`${getName(obj, type) || type}`
|
|
case 'citation':
|
|
return html`${citationTitleFromProfile(obj.profile || {}) || type}`
|
|
case 'repository':
|
|
return html`${getName(obj, type) || type}`
|
|
case 'note':
|
|
return html`${translate(strings, obj.type) || type}`
|
|
case 'media':
|
|
return html`${getName(obj, type) || type}`
|
|
case 'tag':
|
|
return html`${obj.name}`
|
|
default:
|
|
return `unknown type: ${type}`
|
|
}
|
|
}
|
|
|
|
export function objectDetail(type, obj, strings) {
|
|
switch (type) {
|
|
case 'person':
|
|
return `
|
|
${obj?.profile?.birth?.date ? `* ${obj.profile.birth.date}` : ''}${
|
|
obj?.profile?.birth?.place && obj?.profile?.birth?.date ? ', ' : ''
|
|
}${obj?.profile?.birth?.place || ''}
|
|
`
|
|
// case 'family':
|
|
// return ''
|
|
case 'event':
|
|
return `
|
|
${obj?.profile?.date || ''}${
|
|
obj?.profile?.place && obj?.profile?.date ? ', ' : ''
|
|
}${obj?.profile?.place || ''}
|
|
`
|
|
case 'place':
|
|
return `
|
|
${obj?.profile?.type ? obj.profile.type : ''}
|
|
`
|
|
// case 'source':
|
|
// return ''
|
|
// case 'citation':
|
|
// return ''
|
|
case 'repository':
|
|
return `
|
|
${obj.type ? translate(strings, obj.type) : ''}
|
|
`
|
|
case 'note':
|
|
return obj?.text?.string || ''
|
|
case 'media':
|
|
if (obj.mime.startsWith('audio')) {
|
|
return translate(strings, 'Audio')
|
|
}
|
|
if (obj.mime.startsWith('video')) {
|
|
return translate(strings, 'Video')
|
|
}
|
|
if (obj.mime.startsWith('image')) {
|
|
return translate(strings, 'Image')
|
|
}
|
|
if (obj.mime === 'application/pdf') {
|
|
return translate(strings, 'PDF')
|
|
}
|
|
return obj.mime || ''
|
|
// case 'tag':
|
|
// return ''
|
|
default:
|
|
return ''
|
|
}
|
|
}
|
|
|
|
export function prettyTimeDiffTimestamp(timestamp, locale) {
|
|
// pt_PT is the only locale we have to rename
|
|
const dayjsLocale = locale === 'pt_PT' ? 'pt' : locale
|
|
dayjs.locale(dayjsLocale.toLowerCase().replace('_', '-'))
|
|
return dayjs.unix(timestamp).fromNow()
|
|
}
|
|
|
|
export function debounce(func, wait) {
|
|
let timeout
|
|
return function executedFunction(...args) {
|
|
const later = () => {
|
|
clearTimeout(timeout)
|
|
func(...args)
|
|
}
|
|
clearTimeout(timeout)
|
|
timeout = setTimeout(later, wait)
|
|
}
|
|
}
|
|
|
|
export function getNameFromProfile(obj, type, strings) {
|
|
switch (type) {
|
|
case 'person':
|
|
return personTitleFromProfile(obj)
|
|
case 'event':
|
|
return eventTitleFromProfile(obj, strings)
|
|
case 'family':
|
|
return familyTitleFromProfile(obj)
|
|
case 'place':
|
|
return obj.name
|
|
case 'source':
|
|
return obj.title
|
|
case 'repository':
|
|
return obj.name
|
|
case 'citation':
|
|
return citationTitleFromProfile(obj)
|
|
case 'media':
|
|
return obj.desc
|
|
default:
|
|
return ''
|
|
}
|
|
}
|
|
|
|
export function fireEvent(target, name, detail) {
|
|
target.dispatchEvent(
|
|
new CustomEvent(name, {bubbles: true, composed: true, detail})
|
|
)
|
|
}
|
|
|
|
function uuidv4() {
|
|
return ([1e7] + -1e3 + -4e3 + -8e3 + -1e11).replace(/[018]/g, c =>
|
|
(
|
|
c ^
|
|
(crypto.getRandomValues(new Uint8Array(1))[0] & (15 >> (c / 4)))
|
|
).toString(16)
|
|
)
|
|
}
|
|
|
|
export function makeHandle() {
|
|
return uuidv4()
|
|
}
|
|
|
|
// Gregorian date to Julian day needed for Date.sortval
|
|
export function getSortval(year, month, day) {
|
|
return Math.ceil(2440587.5 + Date.UTC(year, month - 1, day) / 86400000)
|
|
}
|
|
|
|
export function getBrowserLanguage() {
|
|
// get browser language and replace all '-' with '_'
|
|
// since the strings from backend comes with underscore
|
|
const browserLang = navigator.language.replaceAll('-', '_')
|
|
if (browserLang in additionalStrings) {
|
|
return browserLang
|
|
}
|
|
if (browserLang.split('_')[0] in additionalStrings) {
|
|
return browserLang.split('_')[0]
|
|
}
|
|
return null
|
|
}
|
|
|
|
// return translated date span
|
|
export function dateSpanLocal(date1, date2, lang) {
|
|
const localStrings = {
|
|
ar: 'بين 1111 و 2222',
|
|
bg: 'между 1111 и 2222',
|
|
ca: 'entre 1111 i 2222',
|
|
cs: 'mezi 1111 a 2222',
|
|
da: 'mellem 1111 og 2222',
|
|
de: 'zwischen 1111 und 2222',
|
|
el: 'μεταξύ 1111 και 2222',
|
|
en: 'between 1111 and 2222',
|
|
en_GB: 'between 1111 and 2222',
|
|
es: 'entre 1111 y 2222',
|
|
fi: '1111 ja 2222 välillä',
|
|
fr: 'entre 1111 et 2222',
|
|
hr: 'između 1111 i 2222',
|
|
hu: '1111 és 2222 között',
|
|
is: 'milli 1111 og 2222',
|
|
it: 'tra 1111 e 2222',
|
|
ja: '1111と2222の間',
|
|
lt: 'tarp 1111 ir 2222',
|
|
nb: 'mellom 1111 og 2222',
|
|
nl: 'tussen 1111 en 2222',
|
|
nn: 'mellom 1111 og 2222',
|
|
pl: 'między 1111 a 2222',
|
|
pt_BR: 'entre 1111 e 2222',
|
|
pt_PT: 'entre 1111 e 2222',
|
|
ru: 'между 1111 и 2222',
|
|
sk: 'medzi 1111 a 2222',
|
|
sl: 'med 1111 in 2222',
|
|
sr: 'између 1111 и 2222',
|
|
sv: 'mellan 1111 och 2222',
|
|
uk: 'між 1111 та 2222',
|
|
zh_CN: ' 介于 1111 与 2222 之间',
|
|
zh_HK: ' 介于 1111 与 2222 之间',
|
|
zh_TW: ' 介于 1111 与 2222 之间',
|
|
}
|
|
let str = localStrings[lang]
|
|
if (!str) {
|
|
const candidates = Object.keys(localStrings).filter(key =>
|
|
key.startsWith(lang)
|
|
)
|
|
if (candidates.length === 0) {
|
|
return null
|
|
}
|
|
str = localStrings[candidates[0]]
|
|
}
|
|
return str.replace('1111', date1).replace('2222', date2)
|
|
}
|
|
|
|
export const personFilter = {
|
|
IsFemale: 'Females',
|
|
HasUnknownGender: 'People with unknown gender',
|
|
IsMale: 'Males',
|
|
HasAlternateName: 'People with an alternate name',
|
|
HasNickname: 'People with a nickname',
|
|
HaveAltFamilies: 'Adopted people',
|
|
HaveChildren: 'People with children',
|
|
IncompleteNames: 'People with incomplete names',
|
|
NeverMarried: 'People with no marriage records',
|
|
MultipleMarriages: 'People with multiple marriage records',
|
|
NoBirthdate: 'People without a known birth date',
|
|
NoDeathdate: 'People without a known death date',
|
|
PersonWithIncompleteEvent: 'People with incomplete events',
|
|
FamilyWithIncompleteEvent: 'Families with incomplete events',
|
|
PeoplePrivate: 'People marked private',
|
|
PeoplePublic: 'People not marked private',
|
|
MissingParent: 'People missing parents',
|
|
Disconnected: 'Disconnected people',
|
|
}
|
|
|
|
export const filterCounts = {
|
|
citations: {
|
|
HasGallery: 'Citations with <count> media',
|
|
HasNote: 'Citations having <count> notes',
|
|
},
|
|
events: {
|
|
HasGallery: 'Events with <count> media',
|
|
HasNote: 'Events having <count> notes',
|
|
HasSourceCount: 'Events with <count> sources',
|
|
},
|
|
families: {
|
|
HasGallery: 'Families with <count> media',
|
|
HasNote: 'Families having <count> notes',
|
|
HasSourceCount: 'Families with <count> sources',
|
|
},
|
|
media: {HasSourceCount: 'Media with <count> sources'},
|
|
notes: {},
|
|
people: {
|
|
HavePhotos: 'People with <count> media',
|
|
HasNote: 'People having <count> notes',
|
|
HasSourceCount: 'People with <count> sources',
|
|
HasAddress: 'People with <count> addresses',
|
|
HasAssociation: 'People with <count> associations',
|
|
},
|
|
places: {
|
|
HasGallery: 'Places with <count> media',
|
|
HasNote: 'Places having <count> notes',
|
|
HasSourceCount: 'Place with <count> sources',
|
|
},
|
|
repositories: {},
|
|
sources: {
|
|
HasGallery: 'Sources with <count> media',
|
|
HasNote: 'Sources having <count> notes',
|
|
HasRepository: 'Sources with <count> Repository references',
|
|
},
|
|
}
|
|
|
|
export const filterMime = {
|
|
'image/': 'Image',
|
|
'audio/': 'Audio',
|
|
'video/': 'Video',
|
|
'text/': 'Text',
|
|
'application/pdf': 'PDF',
|
|
}
|
|
|
|
export const reportCategoryIcon = {
|
|
0: 'description', // text
|
|
1: 'pie_chart', // draw
|
|
// 2: '', // code
|
|
// 3: '', // web
|
|
// 4: '', // book
|
|
5: 'account_tree', // graphviz
|
|
// 6: '', // tree
|
|
}
|
|
|
|
export function arrayEqual(A, B) {
|
|
return A.length > 0 && B.length > 0 && A.every(e => B.includes(e))
|
|
}
|
|
|
|
function _getMediaHandle(obj) {
|
|
if (obj.object_type === 'media') {
|
|
return obj.object.handle
|
|
}
|
|
if (obj.object?.media_list?.length) {
|
|
return obj.object.media_list[0].ref
|
|
}
|
|
return ''
|
|
}
|
|
|
|
function _getMediaRect(obj) {
|
|
if (obj.object?.media_list?.length) {
|
|
return obj.object.media_list[0].rect
|
|
}
|
|
return []
|
|
}
|
|
|
|
export function renderIcon(obj) {
|
|
const handle = _getMediaHandle(obj)
|
|
const rect = _getMediaRect(obj)
|
|
if (handle) {
|
|
return html`<grampsjs-img
|
|
handle="${handle}"
|
|
slot="graphic"
|
|
circle
|
|
square
|
|
size="70"
|
|
.rect="${rect}"
|
|
.mime=""
|
|
><mwc-icon class="placeholder"
|
|
>${objectIcon[obj.object_type]}</mwc-icon
|
|
></grampsjs-img
|
|
>`
|
|
}
|
|
return html`<mwc-icon slot="graphic"
|
|
>${objectIcon[obj.object_type]}</mwc-icon
|
|
>`
|
|
}
|