@smartsoft001-mobilems/objects-shell-angular 2.129.0 → 2.131.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { Injectable, inject, contentChild, ChangeDetectionStrategy, Component, NgModule, PLATFORM_ID, isDevMode, Directive, signal, effect, viewChild, computed, untracked, input, output, linkedSignal } from '@angular/core';
|
|
2
|
+
import { Injectable, inject, contentChild, ChangeDetectionStrategy, Component, NgModule, PLATFORM_ID, isDevMode, Directive, signal, effect, viewChild, computed, untracked, input, output, linkedSignal, DestroyRef } from '@angular/core';
|
|
3
3
|
import { FILE_SERVICE_CONFIG, PaginationMode, ListMode } from '@smartsoft001/angular';
|
|
4
4
|
import { CrudFacade, initialState as initialState$1, getReducer as getReducer$1, CrudService, CrudConfig, CrudFullConfig, CrudCoreModule, CrudModule, CrudListPageBaseComponent, CrudItemPageBaseComponent } from '@smartsoft001/crud-shell-angular';
|
|
5
|
-
import { CrudBaseService, FiltersBaseComponent, DICTIONARY_PAGE_SIZE, FileUrlService, provideInheritedSharedConfig, SharedModule, SeoService, TranslationService, MuseumSeoService, WcagChangesService, createSafeTimeouts, MasonryGridComponent, createCatalog, createCatalogLoadMore, toPlainText, namedEntryParts, serializeCatalogFilter, createCycleTagSearch, mergeSelectedTags, dictionaryFilterItems, dictionaryEntryKey, dictionaryEntries, SHARE_LINK_COPIED_TIMEOUT, ShareLinkBaseComponent } from '@smartsoft001-mobilems/angular';
|
|
5
|
+
import { CrudBaseService, FiltersBaseComponent, DICTIONARY_PAGE_SIZE, FileUrlService, provideInheritedSharedConfig, SharedModule, SeoService, TranslationService, MuseumSeoService, WcagChangesService, createSafeTimeouts, MasonryGridComponent, createCatalog, createCatalogLoadMore, toPlainText, namedEntryParts, serializeCatalogFilter, createCycleTagSearch, mergeSelectedTags, dictionaryFilterItems, dictionaryEntryKey, dictionaryEntries, SHARE_LINK_COPIED_TIMEOUT, ShareLinkBaseComponent, createLazyLoader } from '@smartsoft001-mobilems/angular';
|
|
6
6
|
import { NgTemplateOutlet, CommonModule, isPlatformServer, isPlatformBrowser, DOCUMENT } from '@angular/common';
|
|
7
7
|
import { firstValueFrom, retry, timer, throwError, map, tap, catchError, of, expand, reduce, switchMap, from, mergeMap, toArray, filter, takeUntil } from 'rxjs';
|
|
8
8
|
import { createFeatureSelector } from '@ngrx/store';
|
|
@@ -10,6 +10,7 @@ import { toSignal, toObservable, rxResource } from '@angular/core/rxjs-interop';
|
|
|
10
10
|
import { ActivatedRoute } from '@angular/router';
|
|
11
11
|
import { TranslateService } from '@ngx-translate/core';
|
|
12
12
|
import { capitalize } from '@smartsoft001/utils';
|
|
13
|
+
import { DomSanitizer } from '@angular/platform-browser';
|
|
13
14
|
|
|
14
15
|
class ObjectsService extends CrudBaseService {
|
|
15
16
|
getUrlNameForDetails() {
|
|
@@ -1741,9 +1742,1001 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.3", ngImpor
|
|
|
1741
1742
|
type: Directive
|
|
1742
1743
|
}], propDecorators: { filter: [{ type: i0.Input, args: [{ isSignal: true, alias: "filter", required: false }] }] } });
|
|
1743
1744
|
|
|
1745
|
+
/**
|
|
1746
|
+
* Logic-only base of an object media tab (MOB-1801), from Muzeum Podlasia's
|
|
1747
|
+
* `components/tabs`: the `item` and `formFeatureTypes` inputs and `models` —
|
|
1748
|
+
* the object's `additionalImages` of the given form feature types, sorted by
|
|
1749
|
+
* `position`. The tab keeps its template, styles and `host`.
|
|
1750
|
+
*
|
|
1751
|
+
* `mediaTabFilterModels(models)` is a hook (identity by default) that a tab
|
|
1752
|
+
* overrides to narrow the list further.
|
|
1753
|
+
*
|
|
1754
|
+
* ```ts
|
|
1755
|
+
* export class OtherFilesTabComponent extends ObjectMediaTabBaseComponent<IAnimationObject> {}
|
|
1756
|
+
* ```
|
|
1757
|
+
*/
|
|
1758
|
+
class ObjectMediaTabBaseComponent {
|
|
1759
|
+
constructor() {
|
|
1760
|
+
this.mediaTabFileUrl = inject(FileUrlService);
|
|
1761
|
+
this.mediaTabTranslate = inject(TranslateService);
|
|
1762
|
+
/** The object whose media the tab shows. */
|
|
1763
|
+
this.item = input.required(/* @ts-ignore */
|
|
1764
|
+
...(ngDevMode ? [{ debugName: "item" }] : /* istanbul ignore next */ []));
|
|
1765
|
+
/** The form feature types (`FormFeatureType`) the tab shows. */
|
|
1766
|
+
this.formFeatureTypes = input.required(/* @ts-ignore */
|
|
1767
|
+
...(ngDevMode ? [{ debugName: "formFeatureTypes" }] : /* istanbul ignore next */ []));
|
|
1768
|
+
/** The object's media of `formFeatureTypes`, sorted by `position`, then through `mediaTabFilterModels`. */
|
|
1769
|
+
this.models = computed(() => {
|
|
1770
|
+
const obj = this.item();
|
|
1771
|
+
let filtered = [];
|
|
1772
|
+
if (obj.additionalImages && Array.isArray(obj.additionalImages)) {
|
|
1773
|
+
filtered = obj.additionalImages
|
|
1774
|
+
.filter((img) => this.formFeatureTypes().includes(img?.formFeature))
|
|
1775
|
+
.sort((a, b) => (a?.position ?? 0) - (b?.position ?? 0));
|
|
1776
|
+
}
|
|
1777
|
+
return this.mediaTabFilterModels(filtered);
|
|
1778
|
+
}, /* @ts-ignore */
|
|
1779
|
+
...(ngDevMode ? [{ debugName: "models" }] : /* istanbul ignore next */ []));
|
|
1780
|
+
}
|
|
1781
|
+
/** Narrows the sorted list. Identity by default. */
|
|
1782
|
+
mediaTabFilterModels(models) {
|
|
1783
|
+
return models;
|
|
1784
|
+
}
|
|
1785
|
+
/** @nocollapse */ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: ObjectMediaTabBaseComponent, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
1786
|
+
/** @nocollapse */ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.1.3", type: ObjectMediaTabBaseComponent, isStandalone: true, inputs: { item: { classPropertyName: "item", publicName: "item", isSignal: true, isRequired: true, transformFunction: null }, formFeatureTypes: { classPropertyName: "formFeatureTypes", publicName: "formFeatureTypes", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0 }); }
|
|
1787
|
+
}
|
|
1788
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: ObjectMediaTabBaseComponent, decorators: [{
|
|
1789
|
+
type: Directive
|
|
1790
|
+
}], propDecorators: { item: [{ type: i0.Input, args: [{ isSignal: true, alias: "item", required: true }] }], formFeatureTypes: [{ type: i0.Input, args: [{ isSignal: true, alias: "formFeatureTypes", required: true }] }] } });
|
|
1791
|
+
|
|
1792
|
+
/**
|
|
1793
|
+
* Logic-only base of the media tabs' download button, from Muzeum Podlasia's
|
|
1794
|
+
* `download-button`: `download()` fetches `url`, saves the blob as `filename`
|
|
1795
|
+
* through a temporary `<a download>` and revokes the object URL. It runs only
|
|
1796
|
+
* from a click, so never on the server. The button keeps its template.
|
|
1797
|
+
*
|
|
1798
|
+
* ```ts
|
|
1799
|
+
* export class DownloadButtonComponent extends ObjectDownloadButtonBaseComponent {}
|
|
1800
|
+
* ```
|
|
1801
|
+
*/
|
|
1802
|
+
class ObjectDownloadButtonBaseComponent {
|
|
1803
|
+
constructor() {
|
|
1804
|
+
this.downloadButtonDocument = inject(DOCUMENT);
|
|
1805
|
+
/** The file URL. An empty URL does nothing. */
|
|
1806
|
+
this.url = input.required(/* @ts-ignore */
|
|
1807
|
+
...(ngDevMode ? [{ debugName: "url" }] : /* istanbul ignore next */ []));
|
|
1808
|
+
/** The name the file is saved under. */
|
|
1809
|
+
this.filename = input.required(/* @ts-ignore */
|
|
1810
|
+
...(ngDevMode ? [{ debugName: "filename" }] : /* istanbul ignore next */ []));
|
|
1811
|
+
/** `true` while the file is being fetched. */
|
|
1812
|
+
this.isDownloading = signal(false, /* @ts-ignore */
|
|
1813
|
+
...(ngDevMode ? [{ debugName: "isDownloading" }] : /* istanbul ignore next */ []));
|
|
1814
|
+
}
|
|
1815
|
+
async download() {
|
|
1816
|
+
const url = this.url();
|
|
1817
|
+
const filename = this.filename();
|
|
1818
|
+
if (!url)
|
|
1819
|
+
return;
|
|
1820
|
+
this.isDownloading.set(true);
|
|
1821
|
+
try {
|
|
1822
|
+
const response = await fetch(url);
|
|
1823
|
+
const blob = await response.blob();
|
|
1824
|
+
const blobUrl = URL.createObjectURL(blob);
|
|
1825
|
+
const link = this.downloadButtonDocument.createElement('a');
|
|
1826
|
+
link.href = blobUrl;
|
|
1827
|
+
link.download = filename;
|
|
1828
|
+
link.click();
|
|
1829
|
+
URL.revokeObjectURL(blobUrl);
|
|
1830
|
+
}
|
|
1831
|
+
catch (error) {
|
|
1832
|
+
console.error('Failed to download file:', error);
|
|
1833
|
+
}
|
|
1834
|
+
finally {
|
|
1835
|
+
this.isDownloading.set(false);
|
|
1836
|
+
}
|
|
1837
|
+
}
|
|
1838
|
+
/** @nocollapse */ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: ObjectDownloadButtonBaseComponent, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
1839
|
+
/** @nocollapse */ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.1.3", type: ObjectDownloadButtonBaseComponent, isStandalone: true, inputs: { url: { classPropertyName: "url", publicName: "url", isSignal: true, isRequired: true, transformFunction: null }, filename: { classPropertyName: "filename", publicName: "filename", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0 }); }
|
|
1840
|
+
}
|
|
1841
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: ObjectDownloadButtonBaseComponent, decorators: [{
|
|
1842
|
+
type: Directive
|
|
1843
|
+
}], propDecorators: { url: [{ type: i0.Input, args: [{ isSignal: true, alias: "url", required: true }] }], filename: [{ type: i0.Input, args: [{ isSignal: true, alias: "filename", required: true }] }] } });
|
|
1844
|
+
|
|
1845
|
+
/**
|
|
1846
|
+
* Logic-only base of the object map tab, from Muzeum Podlasia's `map-tab`:
|
|
1847
|
+
* the object's `archeoMap` coordinates. The tab keeps its template (the map
|
|
1848
|
+
* component and its zoom).
|
|
1849
|
+
*
|
|
1850
|
+
* ```ts
|
|
1851
|
+
* export class MapTabComponent extends ObjectMapTabBaseComponent {}
|
|
1852
|
+
* ```
|
|
1853
|
+
*/
|
|
1854
|
+
class ObjectMapTabBaseComponent {
|
|
1855
|
+
constructor() {
|
|
1856
|
+
/** The object whose `archeoMap` the tab shows. */
|
|
1857
|
+
this.item = input.required(/* @ts-ignore */
|
|
1858
|
+
...(ngDevMode ? [{ debugName: "item" }] : /* istanbul ignore next */ []));
|
|
1859
|
+
this.mapTabArcheoMap = computed(() => {
|
|
1860
|
+
const obj = this.item();
|
|
1861
|
+
return obj?.archeoMap;
|
|
1862
|
+
}, /* @ts-ignore */
|
|
1863
|
+
...(ngDevMode ? [{ debugName: "mapTabArcheoMap" }] : /* istanbul ignore next */ []));
|
|
1864
|
+
/** Truthy (the longitude string) when both coordinates are set. */
|
|
1865
|
+
this.hasCoordinates = computed(() => {
|
|
1866
|
+
const map = this.mapTabArcheoMap();
|
|
1867
|
+
return map?.latitude && map?.longitude;
|
|
1868
|
+
}, /* @ts-ignore */
|
|
1869
|
+
...(ngDevMode ? [{ debugName: "hasCoordinates" }] : /* istanbul ignore next */ []));
|
|
1870
|
+
/** `parseFloat(latitude)`, `0` when missing. */
|
|
1871
|
+
this.latitude = computed(() => {
|
|
1872
|
+
const map = this.mapTabArcheoMap();
|
|
1873
|
+
return map?.latitude ? parseFloat(map.latitude) : 0;
|
|
1874
|
+
}, /* @ts-ignore */
|
|
1875
|
+
...(ngDevMode ? [{ debugName: "latitude" }] : /* istanbul ignore next */ []));
|
|
1876
|
+
/** `parseFloat(longitude)`, `0` when missing. */
|
|
1877
|
+
this.longitude = computed(() => {
|
|
1878
|
+
const map = this.mapTabArcheoMap();
|
|
1879
|
+
return map?.longitude ? parseFloat(map.longitude) : 0;
|
|
1880
|
+
}, /* @ts-ignore */
|
|
1881
|
+
...(ngDevMode ? [{ debugName: "longitude" }] : /* istanbul ignore next */ []));
|
|
1882
|
+
}
|
|
1883
|
+
/** @nocollapse */ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: ObjectMapTabBaseComponent, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
1884
|
+
/** @nocollapse */ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.1.3", type: ObjectMapTabBaseComponent, isStandalone: true, inputs: { item: { classPropertyName: "item", publicName: "item", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0 }); }
|
|
1885
|
+
}
|
|
1886
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: ObjectMapTabBaseComponent, decorators: [{
|
|
1887
|
+
type: Directive
|
|
1888
|
+
}], propDecorators: { item: [{ type: i0.Input, args: [{ isSignal: true, alias: "item", required: true }] }] } });
|
|
1889
|
+
|
|
1890
|
+
/**
|
|
1891
|
+
* Logic-only base of an object tab with native `<audio>` players (MOB-1801):
|
|
1892
|
+
* the file URL, title, aria label and download file name of each recording,
|
|
1893
|
+
* shared by `ObjectAudioTabBaseComponent` and
|
|
1894
|
+
* `ObjectAudioDescriptionTabBaseComponent`. The fallback label comes from
|
|
1895
|
+
* `audioMediaTabLabelKey`, read lazily, so a subclass overrides it with a
|
|
1896
|
+
* field.
|
|
1897
|
+
*
|
|
1898
|
+
* ```ts
|
|
1899
|
+
* protected override readonly audioMediaTabLabelKey = 'DETAILS_PAGE.tabs.audio';
|
|
1900
|
+
* ```
|
|
1901
|
+
*/
|
|
1902
|
+
class ObjectAudioMediaTabBaseComponent extends ObjectMediaTabBaseComponent {
|
|
1903
|
+
constructor() {
|
|
1904
|
+
super(...arguments);
|
|
1905
|
+
/** Translation key of the player label; read lazily, so a subclass can override it. */
|
|
1906
|
+
this.audioMediaTabLabelKey = 'OBJECTS.filters.multimedia.audio';
|
|
1907
|
+
}
|
|
1908
|
+
/** The recording's file URL (`noCache`). */
|
|
1909
|
+
getAudioUrl(audio) {
|
|
1910
|
+
return this.mediaTabFileUrl.get(audio, 'noCache');
|
|
1911
|
+
}
|
|
1912
|
+
/** Alt text in the current language, then the description, then the label key. */
|
|
1913
|
+
getAudioTitle(audio) {
|
|
1914
|
+
const currentLang = this.mediaTabTranslate.currentLang;
|
|
1915
|
+
const altText = currentLang === 'en' ? audio.altTextEn : audio.altText;
|
|
1916
|
+
return (altText ||
|
|
1917
|
+
audio.description ||
|
|
1918
|
+
this.mediaTabTranslate.instant(this.audioMediaTabLabelKey));
|
|
1919
|
+
}
|
|
1920
|
+
/** `<label>: <title>`. */
|
|
1921
|
+
getAudioAriaLabel(audio) {
|
|
1922
|
+
const title = this.getAudioTitle(audio);
|
|
1923
|
+
const audioLabel = this.mediaTabTranslate.instant(this.audioMediaTabLabelKey);
|
|
1924
|
+
return `${audioLabel}: ${title}`;
|
|
1925
|
+
}
|
|
1926
|
+
/** The object title, alt text, description or `audio`, plus the extension (`mp3` by default). */
|
|
1927
|
+
getDownloadFilename(audio) {
|
|
1928
|
+
const name = this.item()?.title || audio.altText || audio.description || 'audio';
|
|
1929
|
+
const ext = audio.extension || 'mp3';
|
|
1930
|
+
return `${name}.${ext}`;
|
|
1931
|
+
}
|
|
1932
|
+
/** @nocollapse */ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: ObjectAudioMediaTabBaseComponent, deps: null, target: i0.ɵɵFactoryTarget.Directive }); }
|
|
1933
|
+
/** @nocollapse */ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.1.3", type: ObjectAudioMediaTabBaseComponent, isStandalone: true, usesInheritance: true, ngImport: i0 }); }
|
|
1934
|
+
}
|
|
1935
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: ObjectAudioMediaTabBaseComponent, decorators: [{
|
|
1936
|
+
type: Directive
|
|
1937
|
+
}] });
|
|
1938
|
+
|
|
1939
|
+
/**
|
|
1940
|
+
* Logic-only base of the object audio tab (MOB-1801). Muzeum Podlasia renders
|
|
1941
|
+
* the article player (`app-widget-audio` on `WidgetAudioBaseComponent`,
|
|
1942
|
+
* MOB-1805 / MOB-1872) through `widgetItem` and `trackName`; sztuki-2 and the
|
|
1943
|
+
* template render a native `<audio>` with a download button through
|
|
1944
|
+
* `getAudioUrl`, `getAudioTitle`, `getAudioAriaLabel` and
|
|
1945
|
+
* `getDownloadFilename` (`ObjectAudioMediaTabBaseComponent`, label
|
|
1946
|
+
* `DETAILS_PAGE.tabs.audio`), with `playableOnly: false`. The tab keeps its
|
|
1947
|
+
* template, styles and `host`.
|
|
1948
|
+
*
|
|
1949
|
+
* ```ts
|
|
1950
|
+
* export class AudioTabComponent extends ObjectAudioTabBaseComponent {
|
|
1951
|
+
* constructor() {
|
|
1952
|
+
* super(); // sztuki-2 / template: super({ playableOnly: false })
|
|
1953
|
+
* }
|
|
1954
|
+
* }
|
|
1955
|
+
* ```
|
|
1956
|
+
*/
|
|
1957
|
+
class ObjectAudioTabBaseComponent extends ObjectAudioMediaTabBaseComponent {
|
|
1958
|
+
constructor(options = {}) {
|
|
1959
|
+
super();
|
|
1960
|
+
this.audioMediaTabLabelKey = 'DETAILS_PAGE.tabs.audio';
|
|
1961
|
+
this.audioTabOptions = options;
|
|
1962
|
+
}
|
|
1963
|
+
/** With `playableOnly` (default) only recordings with a file. */
|
|
1964
|
+
mediaTabFilterModels(models) {
|
|
1965
|
+
if (this.audioTabOptions.playableOnly === false)
|
|
1966
|
+
return models;
|
|
1967
|
+
// Only recordings with a file — the "Audio 1, 2…" numbering counts what
|
|
1968
|
+
// is shown (MOB-1872 review).
|
|
1969
|
+
return models.filter((audio) => !!audio.filePath);
|
|
1970
|
+
}
|
|
1971
|
+
/**
|
|
1972
|
+
* The recording in the audio widget's shape: the file in `file`, the
|
|
1973
|
+
* heading from the description (without one — a player without a heading,
|
|
1974
|
+
* as in the mock-up).
|
|
1975
|
+
*/
|
|
1976
|
+
widgetItem(audio) {
|
|
1977
|
+
return {
|
|
1978
|
+
file: audio,
|
|
1979
|
+
title: audio.description ?? '',
|
|
1980
|
+
};
|
|
1981
|
+
}
|
|
1982
|
+
/**
|
|
1983
|
+
* The recording's name for screen readers (the player's button and
|
|
1984
|
+
* slider): the alt text in the current language or the description;
|
|
1985
|
+
* several recordings without names — "Audio 1", "Audio 2"… (MOB-1872
|
|
1986
|
+
* review); a single one — `null`.
|
|
1987
|
+
*/
|
|
1988
|
+
trackName(audio, index) {
|
|
1989
|
+
const altText = this.mediaTabTranslate.currentLang === 'en'
|
|
1990
|
+
? audio.altTextEn
|
|
1991
|
+
: audio.altText;
|
|
1992
|
+
const name = (altText || audio.description || '').trim();
|
|
1993
|
+
if (name)
|
|
1994
|
+
return name;
|
|
1995
|
+
return this.models().length > 1
|
|
1996
|
+
? `${this.mediaTabTranslate.instant(this.audioMediaTabLabelKey)} ${index + 1}`
|
|
1997
|
+
: null;
|
|
1998
|
+
}
|
|
1999
|
+
/** @nocollapse */ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: ObjectAudioTabBaseComponent, deps: "invalid", target: i0.ɵɵFactoryTarget.Directive }); }
|
|
2000
|
+
/** @nocollapse */ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.1.3", type: ObjectAudioTabBaseComponent, isStandalone: true, usesInheritance: true, ngImport: i0 }); }
|
|
2001
|
+
}
|
|
2002
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: ObjectAudioTabBaseComponent, decorators: [{
|
|
2003
|
+
type: Directive
|
|
2004
|
+
}], ctorParameters: () => [{ type: undefined }] });
|
|
2005
|
+
|
|
2006
|
+
/**
|
|
2007
|
+
* Logic-only base of the object audio description tab (MOB-1801), from Muzeum
|
|
2008
|
+
* Podlasia's `audio-description-tab.component.ts` (sztuki-2 and the template
|
|
2009
|
+
* differ by one template class only): the main image above the players and
|
|
2010
|
+
* the names of each recording (`ObjectAudioMediaTabBaseComponent`, label
|
|
2011
|
+
* `OBJECTS.filters.multimedia.audio`). The tab keeps its template, styles and `host`.
|
|
2012
|
+
*
|
|
2013
|
+
* ```ts
|
|
2014
|
+
* export class AudioDescriptionTabComponent extends ObjectAudioDescriptionTabBaseComponent {}
|
|
2015
|
+
* ```
|
|
2016
|
+
*/
|
|
2017
|
+
class ObjectAudioDescriptionTabBaseComponent extends ObjectAudioMediaTabBaseComponent {
|
|
2018
|
+
constructor() {
|
|
2019
|
+
super(...arguments);
|
|
2020
|
+
/** The object's main image (`big` = full resolution without cache); `null` without one or for the no-image placeholder. */
|
|
2021
|
+
this.mainImageUrl = computed(() => {
|
|
2022
|
+
const obj = this.item();
|
|
2023
|
+
if (obj?.image) {
|
|
2024
|
+
const url = this.mediaTabFileUrl.get(obj.image, 'big');
|
|
2025
|
+
if (url && !url.includes('assets/no-image.svg')) {
|
|
2026
|
+
return url;
|
|
2027
|
+
}
|
|
2028
|
+
}
|
|
2029
|
+
return null;
|
|
2030
|
+
}, /* @ts-ignore */
|
|
2031
|
+
...(ngDevMode ? [{ debugName: "mainImageUrl" }] : /* istanbul ignore next */ []));
|
|
2032
|
+
}
|
|
2033
|
+
/** @nocollapse */ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: ObjectAudioDescriptionTabBaseComponent, deps: null, target: i0.ɵɵFactoryTarget.Directive }); }
|
|
2034
|
+
/** @nocollapse */ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.1.3", type: ObjectAudioDescriptionTabBaseComponent, isStandalone: true, usesInheritance: true, ngImport: i0 }); }
|
|
2035
|
+
}
|
|
2036
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: ObjectAudioDescriptionTabBaseComponent, decorators: [{
|
|
2037
|
+
type: Directive
|
|
2038
|
+
}] });
|
|
2039
|
+
|
|
2040
|
+
/**
|
|
2041
|
+
* Logic-only base of the object documents tab (MOB-1801), from Muzeum
|
|
2042
|
+
* Podlasia's `documents-tab.component.ts` (sztuki-2 and the template differ by
|
|
2043
|
+
* one template class only): each document's URL, display name and download
|
|
2044
|
+
* file name. The tab keeps its template, styles and `host`.
|
|
2045
|
+
*
|
|
2046
|
+
* ```ts
|
|
2047
|
+
* export class DocumentsTabComponent extends ObjectDocumentsTabBaseComponent {}
|
|
2048
|
+
* ```
|
|
2049
|
+
*/
|
|
2050
|
+
class ObjectDocumentsTabBaseComponent extends ObjectMediaTabBaseComponent {
|
|
2051
|
+
/** The document's file URL (`noCache`). */
|
|
2052
|
+
getDocumentUrl(doc) {
|
|
2053
|
+
return this.mediaTabFileUrl.get(doc, 'noCache');
|
|
2054
|
+
}
|
|
2055
|
+
/** Alt text in the current language, then the description, then the upper-case extension (`PDF` by default). */
|
|
2056
|
+
getDocumentDisplayName(doc) {
|
|
2057
|
+
const currentLang = this.mediaTabTranslate.currentLang;
|
|
2058
|
+
const altText = currentLang === 'en' ? doc.altTextEn : doc.altText;
|
|
2059
|
+
if (altText)
|
|
2060
|
+
return altText;
|
|
2061
|
+
if (doc.description)
|
|
2062
|
+
return doc.description;
|
|
2063
|
+
// Show the extension name when there is no description
|
|
2064
|
+
const extension = doc.extension || 'pdf';
|
|
2065
|
+
return extension.toUpperCase();
|
|
2066
|
+
}
|
|
2067
|
+
/** Alt text in the current language, description or `document`, plus the extension (`pdf` by default). */
|
|
2068
|
+
getDocumentFileName(doc) {
|
|
2069
|
+
const currentLang = this.mediaTabTranslate.currentLang;
|
|
2070
|
+
const altText = currentLang === 'en' ? doc.altTextEn : doc.altText;
|
|
2071
|
+
const name = altText || doc.description || 'document';
|
|
2072
|
+
const extension = doc.extension || 'pdf';
|
|
2073
|
+
return `${name}.${extension}`;
|
|
2074
|
+
}
|
|
2075
|
+
/** @nocollapse */ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: ObjectDocumentsTabBaseComponent, deps: null, target: i0.ɵɵFactoryTarget.Directive }); }
|
|
2076
|
+
/** @nocollapse */ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.1.3", type: ObjectDocumentsTabBaseComponent, isStandalone: true, usesInheritance: true, ngImport: i0 }); }
|
|
2077
|
+
}
|
|
2078
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: ObjectDocumentsTabBaseComponent, decorators: [{
|
|
2079
|
+
type: Directive
|
|
2080
|
+
}] });
|
|
2081
|
+
|
|
2082
|
+
/**
|
|
2083
|
+
* Creates the selector state of a media tab, 1:1 from Muzeum Podlasia's
|
|
2084
|
+
* animation tab. `fallbackLabel` gets the 0-based index of a model without
|
|
2085
|
+
* `altText` and `description`. When `item()` becomes a different object (a new
|
|
2086
|
+
* `id`), the first model is selected; an object that only re-emits keeps the
|
|
2087
|
+
* user's choice. `item` and `models` are functions (`() => this.item()`), read
|
|
2088
|
+
* lazily, so a tab that redeclares `item` still works. Call it in an injection context (a field initializer),
|
|
2089
|
+
* because it registers an `effect`.
|
|
2090
|
+
*/
|
|
2091
|
+
function createMediaTabPicker(item, models, fallbackLabel) {
|
|
2092
|
+
const selectedKey = signal(null, /* @ts-ignore */
|
|
2093
|
+
...(ngDevMode ? [{ debugName: "selectedKey" }] : /* istanbul ignore next */ []));
|
|
2094
|
+
const options = computed(() => {
|
|
2095
|
+
return models().map((model, index) => ({
|
|
2096
|
+
key: String(model.id),
|
|
2097
|
+
value: model?.altText ?? model?.description ?? fallbackLabel(index),
|
|
2098
|
+
}));
|
|
2099
|
+
}, /* @ts-ignore */
|
|
2100
|
+
...(ngDevMode ? [{ debugName: "options" }] : /* istanbul ignore next */ []));
|
|
2101
|
+
const selected = computed(() => {
|
|
2102
|
+
const list = models();
|
|
2103
|
+
if (list.length === 0)
|
|
2104
|
+
return null;
|
|
2105
|
+
const key = selectedKey();
|
|
2106
|
+
if (key) {
|
|
2107
|
+
const found = list.find((model) => String(model.id) === key);
|
|
2108
|
+
if (found)
|
|
2109
|
+
return found;
|
|
2110
|
+
}
|
|
2111
|
+
return list[0];
|
|
2112
|
+
}, /* @ts-ignore */
|
|
2113
|
+
...(ngDevMode ? [{ debugName: "selected" }] : /* istanbul ignore next */ []));
|
|
2114
|
+
const selectedOption = computed(() => {
|
|
2115
|
+
const model = selected();
|
|
2116
|
+
const list = options();
|
|
2117
|
+
if (model) {
|
|
2118
|
+
const found = list.find((option) => option.key === String(model.id));
|
|
2119
|
+
if (found)
|
|
2120
|
+
return found;
|
|
2121
|
+
}
|
|
2122
|
+
return list[0] || { key: '', value: '' };
|
|
2123
|
+
}, /* @ts-ignore */
|
|
2124
|
+
...(ngDevMode ? [{ debugName: "selectedOption" }] : /* istanbul ignore next */ []));
|
|
2125
|
+
let lastItemId = null;
|
|
2126
|
+
effect(() => {
|
|
2127
|
+
const currentItem = item();
|
|
2128
|
+
const list = models();
|
|
2129
|
+
// Only reset the selected key when the item changes (a different object)
|
|
2130
|
+
if (currentItem?.id !== lastItemId) {
|
|
2131
|
+
lastItemId = currentItem?.id ?? null;
|
|
2132
|
+
if (list.length > 0) {
|
|
2133
|
+
selectedKey.set(String(list[0].id));
|
|
2134
|
+
}
|
|
2135
|
+
else {
|
|
2136
|
+
selectedKey.set(null);
|
|
2137
|
+
}
|
|
2138
|
+
}
|
|
2139
|
+
});
|
|
2140
|
+
return { selectedKey, options, selected, selectedOption };
|
|
2141
|
+
}
|
|
2142
|
+
|
|
2143
|
+
/**
|
|
2144
|
+
* Logic-only base of an object tab with one video at a time and a dropdown
|
|
2145
|
+
* (MOB-1801), shared by `ObjectAnimationTabBaseComponent` and
|
|
2146
|
+
* `ObjectVideoTabBaseComponent`, 1:1 from Muzeum Podlasia's animation and
|
|
2147
|
+
* video tabs: the selector state, the player reset on a change and the
|
|
2148
|
+
* player helpers. The translation keys and the download fallback name are
|
|
2149
|
+
* abstract fields, read lazily.
|
|
2150
|
+
*/
|
|
2151
|
+
class ObjectVideoMediaTabBaseComponent extends ObjectMediaTabBaseComponent {
|
|
2152
|
+
constructor() {
|
|
2153
|
+
super(...arguments);
|
|
2154
|
+
/** The template's native `<video #videoPlayer>`, if it has one. */
|
|
2155
|
+
this.videoPlayer = viewChild('videoPlayer', /* @ts-ignore */
|
|
2156
|
+
...(ngDevMode ? [{ debugName: "videoPlayer" }] : /* istanbul ignore next */ []));
|
|
2157
|
+
this.videoMediaTabPicker = createMediaTabPicker(() => this.item(), () => this.models(), (index) => `${this.mediaTabTranslate.instant(this.videoMediaTabOptionLabelKey)} ${index + 1}`);
|
|
2158
|
+
/** The chosen video's key; the first video of each new object. */
|
|
2159
|
+
this.selectedVideoKey = this.videoMediaTabPicker.selectedKey;
|
|
2160
|
+
/** Dropdown options: alt text, description or `<label> <n>`. */
|
|
2161
|
+
this.videoOptions = this.videoMediaTabPicker.options;
|
|
2162
|
+
/** The chosen video, the first one by default. */
|
|
2163
|
+
this.selectedVideo = this.videoMediaTabPicker.selected;
|
|
2164
|
+
/** The dropdown option of `selectedVideo`. */
|
|
2165
|
+
this.selectedVideoOption = this.videoMediaTabPicker.selectedOption;
|
|
2166
|
+
}
|
|
2167
|
+
/** Selects a video; a native `#videoPlayer` is paused and rewound. */
|
|
2168
|
+
videoChange(key) {
|
|
2169
|
+
this.selectedVideoKey.set(key);
|
|
2170
|
+
// Reset video playback
|
|
2171
|
+
const player = this.videoPlayer()?.nativeElement;
|
|
2172
|
+
if (player) {
|
|
2173
|
+
player.pause();
|
|
2174
|
+
player.currentTime = 0;
|
|
2175
|
+
}
|
|
2176
|
+
}
|
|
2177
|
+
/** The video's file URL (`noCache`). */
|
|
2178
|
+
getVideoUrl(video) {
|
|
2179
|
+
return this.mediaTabFileUrl.get(video, 'noCache');
|
|
2180
|
+
}
|
|
2181
|
+
/** Alt text in the current language, then the description, then the title key. */
|
|
2182
|
+
getVideoTitle(video) {
|
|
2183
|
+
const currentLang = this.mediaTabTranslate.currentLang;
|
|
2184
|
+
const altText = currentLang === 'en' ? video.altTextEn : video.altText;
|
|
2185
|
+
return (altText ||
|
|
2186
|
+
video.description ||
|
|
2187
|
+
this.mediaTabTranslate.instant(this.videoMediaTabTitleKey));
|
|
2188
|
+
}
|
|
2189
|
+
/** `<title key>: <title>`. */
|
|
2190
|
+
getVideoAriaLabel(video) {
|
|
2191
|
+
const title = this.getVideoTitle(video);
|
|
2192
|
+
const videoLabel = this.mediaTabTranslate.instant(this.videoMediaTabTitleKey);
|
|
2193
|
+
return `${videoLabel}: ${title}`;
|
|
2194
|
+
}
|
|
2195
|
+
/** The object title, alt text, description or the fallback name, plus the extension (`mp4` by default). */
|
|
2196
|
+
getDownloadFilename(video) {
|
|
2197
|
+
const name = this.item()?.title ||
|
|
2198
|
+
video.altText ||
|
|
2199
|
+
video.description ||
|
|
2200
|
+
this.videoMediaTabDownloadName;
|
|
2201
|
+
const ext = video.extension || 'mp4';
|
|
2202
|
+
return `${name}.${ext}`;
|
|
2203
|
+
}
|
|
2204
|
+
/** @nocollapse */ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: ObjectVideoMediaTabBaseComponent, deps: null, target: i0.ɵɵFactoryTarget.Directive }); }
|
|
2205
|
+
/** @nocollapse */ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.2.0", version: "22.1.3", type: ObjectVideoMediaTabBaseComponent, isStandalone: true, viewQueries: [{ propertyName: "videoPlayer", first: true, predicate: ["videoPlayer"], descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0 }); }
|
|
2206
|
+
}
|
|
2207
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: ObjectVideoMediaTabBaseComponent, decorators: [{
|
|
2208
|
+
type: Directive
|
|
2209
|
+
}], propDecorators: { videoPlayer: [{ type: i0.ViewChild, args: ['videoPlayer', { isSignal: true }] }] } });
|
|
2210
|
+
|
|
2211
|
+
/**
|
|
2212
|
+
* Logic-only base of the object animation tab (MOB-1801), 1:1 from Muzeum
|
|
2213
|
+
* Podlasia (sztuki-2 and the template are identical): options without a name
|
|
2214
|
+
* are `OBJECTS.filters.multimedia.animation <n>`, the player is titled with
|
|
2215
|
+
* `OBJECTS.filters.multimedia.video`, the download falls back to
|
|
2216
|
+
* `animation.mp4`, and `videoChange` pauses and rewinds `#videoPlayer`. The
|
|
2217
|
+
* tab keeps its template, styles and `host`.
|
|
2218
|
+
*
|
|
2219
|
+
* ```ts
|
|
2220
|
+
* export class AnimationTabComponent extends ObjectAnimationTabBaseComponent {}
|
|
2221
|
+
* ```
|
|
2222
|
+
*/
|
|
2223
|
+
class ObjectAnimationTabBaseComponent extends ObjectVideoMediaTabBaseComponent {
|
|
2224
|
+
constructor() {
|
|
2225
|
+
super(...arguments);
|
|
2226
|
+
this.videoMediaTabOptionLabelKey = 'OBJECTS.filters.multimedia.animation';
|
|
2227
|
+
this.videoMediaTabTitleKey = 'OBJECTS.filters.multimedia.video';
|
|
2228
|
+
this.videoMediaTabDownloadName = 'animation';
|
|
2229
|
+
}
|
|
2230
|
+
/** @nocollapse */ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: ObjectAnimationTabBaseComponent, deps: null, target: i0.ɵɵFactoryTarget.Directive }); }
|
|
2231
|
+
/** @nocollapse */ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.1.3", type: ObjectAnimationTabBaseComponent, isStandalone: true, usesInheritance: true, ngImport: i0 }); }
|
|
2232
|
+
}
|
|
2233
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: ObjectAnimationTabBaseComponent, decorators: [{
|
|
2234
|
+
type: Directive
|
|
2235
|
+
}] });
|
|
2236
|
+
|
|
2237
|
+
/**
|
|
2238
|
+
* Logic-only base of the object video tab (MOB-1801). Options, title and
|
|
2239
|
+
* aria label use `DETAILS_PAGE.tabs.video`. Muzeum Podlasia renders the app's
|
|
2240
|
+
* video player (a new source resets it — a still frame with a play button,
|
|
2241
|
+
* no playback), so its template has no `#videoPlayer` and `videoChange` only
|
|
2242
|
+
* selects the video. Sztuki-2 and the template render a native
|
|
2243
|
+
* `<video #videoPlayer>`, which `videoChange` pauses and rewinds, and a
|
|
2244
|
+
* download button named by `getDownloadFilename` (fallback `video.mp4`). The
|
|
2245
|
+
* tab keeps its template, styles and `host`.
|
|
2246
|
+
*
|
|
2247
|
+
* ```ts
|
|
2248
|
+
* export class VideoTabComponent extends ObjectVideoTabBaseComponent {}
|
|
2249
|
+
* ```
|
|
2250
|
+
*/
|
|
2251
|
+
class ObjectVideoTabBaseComponent extends ObjectVideoMediaTabBaseComponent {
|
|
2252
|
+
constructor() {
|
|
2253
|
+
super(...arguments);
|
|
2254
|
+
this.videoMediaTabOptionLabelKey = 'DETAILS_PAGE.tabs.video';
|
|
2255
|
+
this.videoMediaTabTitleKey = 'DETAILS_PAGE.tabs.video';
|
|
2256
|
+
this.videoMediaTabDownloadName = 'video';
|
|
2257
|
+
}
|
|
2258
|
+
/** @nocollapse */ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: ObjectVideoTabBaseComponent, deps: null, target: i0.ɵɵFactoryTarget.Directive }); }
|
|
2259
|
+
/** @nocollapse */ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.1.3", type: ObjectVideoTabBaseComponent, isStandalone: true, usesInheritance: true, ngImport: i0 }); }
|
|
2260
|
+
}
|
|
2261
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: ObjectVideoTabBaseComponent, decorators: [{
|
|
2262
|
+
type: Directive
|
|
2263
|
+
}] });
|
|
2264
|
+
|
|
2265
|
+
/**
|
|
2266
|
+
* Logic-only base of the object virtual walk tab (MOB-1801), 1:1 from Muzeum
|
|
2267
|
+
* Podlasia (sztuki-2 and the template are identical): the walk dropdown, the
|
|
2268
|
+
* trusted iframe source and the fullscreen toggle of `#walkContainer`. The
|
|
2269
|
+
* `fullscreenchange` listener is registered in the browser only, through the
|
|
2270
|
+
* injected `DOCUMENT`, and removed on destroy. The tab keeps its template,
|
|
2271
|
+
* styles and `host`.
|
|
2272
|
+
*
|
|
2273
|
+
* ```ts
|
|
2274
|
+
* export class VirtualWalkTabComponent extends ObjectVirtualWalkTabBaseComponent {
|
|
2275
|
+
* constructor() {
|
|
2276
|
+
* super();
|
|
2277
|
+
* }
|
|
2278
|
+
* }
|
|
2279
|
+
* ```
|
|
2280
|
+
*/
|
|
2281
|
+
class ObjectVirtualWalkTabBaseComponent extends ObjectMediaTabBaseComponent {
|
|
2282
|
+
constructor() {
|
|
2283
|
+
super();
|
|
2284
|
+
this.virtualWalkTabSanitizer = inject(DomSanitizer);
|
|
2285
|
+
this.virtualWalkTabDocument = inject(DOCUMENT);
|
|
2286
|
+
/** The template's `#iframeContainer`. */
|
|
2287
|
+
this.iframeContainer = viewChild('iframeContainer', /* @ts-ignore */
|
|
2288
|
+
...(ngDevMode ? [{ debugName: "iframeContainer" }] : /* istanbul ignore next */ []));
|
|
2289
|
+
/** The template's `#walkContainer`, the element shown fullscreen. */
|
|
2290
|
+
this.walkContainer = viewChild('walkContainer', /* @ts-ignore */
|
|
2291
|
+
...(ngDevMode ? [{ debugName: "walkContainer" }] : /* istanbul ignore next */ []));
|
|
2292
|
+
this.virtualWalkTabPicker = createMediaTabPicker(() => this.item(), () => this.models(), (index) => `${this.mediaTabTranslate.instant('OBJECTS.filters.multimedia.virtualWalk')} ${index + 1}`);
|
|
2293
|
+
/** The chosen walk's key; the first walk of each new object. */
|
|
2294
|
+
this.selectedWalkKey = this.virtualWalkTabPicker.selectedKey;
|
|
2295
|
+
/** Dropdown options: alt text, description or `OBJECTS.filters.multimedia.virtualWalk <n>`. */
|
|
2296
|
+
this.walkOptions = this.virtualWalkTabPicker.options;
|
|
2297
|
+
/** The chosen walk, the first one by default. */
|
|
2298
|
+
this.selectedWalk = this.virtualWalkTabPicker.selected;
|
|
2299
|
+
/** The dropdown option of `selectedWalk`. */
|
|
2300
|
+
this.selectedWalkOption = this.virtualWalkTabPicker.selectedOption;
|
|
2301
|
+
/** The selected walk's `iframeSource`, trusted; `null` without one. */
|
|
2302
|
+
this.safeIframeUrl = computed(() => {
|
|
2303
|
+
const walk = this.selectedWalk();
|
|
2304
|
+
if (!walk?.iframeSource)
|
|
2305
|
+
return null;
|
|
2306
|
+
return this.virtualWalkTabSanitizer.bypassSecurityTrustResourceUrl(walk.iframeSource);
|
|
2307
|
+
}, /* @ts-ignore */
|
|
2308
|
+
...(ngDevMode ? [{ debugName: "safeIframeUrl" }] : /* istanbul ignore next */ []));
|
|
2309
|
+
/** Whether the document is in fullscreen (follows `fullscreenchange`, e.g. Escape). */
|
|
2310
|
+
this.isFullscreen = signal(false, /* @ts-ignore */
|
|
2311
|
+
...(ngDevMode ? [{ debugName: "isFullscreen" }] : /* istanbul ignore next */ []));
|
|
2312
|
+
if (isPlatformBrowser(inject(PLATFORM_ID))) {
|
|
2313
|
+
// Listen for fullscreen changes (e.g. when the user presses Escape)
|
|
2314
|
+
const doc = this.virtualWalkTabDocument;
|
|
2315
|
+
const onFullscreenChange = () => {
|
|
2316
|
+
this.isFullscreen.set(!!doc.fullscreenElement);
|
|
2317
|
+
};
|
|
2318
|
+
doc.addEventListener('fullscreenchange', onFullscreenChange);
|
|
2319
|
+
inject(DestroyRef).onDestroy(() => {
|
|
2320
|
+
doc.removeEventListener('fullscreenchange', onFullscreenChange);
|
|
2321
|
+
});
|
|
2322
|
+
}
|
|
2323
|
+
}
|
|
2324
|
+
/** Selects a walk. */
|
|
2325
|
+
walkChange(key) {
|
|
2326
|
+
this.selectedWalkKey.set(key);
|
|
2327
|
+
}
|
|
2328
|
+
/** Alt text in the current language, then the description, then `OBJECTS.filters.multimedia.virtualWalk`. */
|
|
2329
|
+
getWalkTitle(walk) {
|
|
2330
|
+
const currentLang = this.mediaTabTranslate.currentLang;
|
|
2331
|
+
const altText = currentLang === 'en' ? walk.altTextEn : walk.altText;
|
|
2332
|
+
return (altText ||
|
|
2333
|
+
walk.description ||
|
|
2334
|
+
this.mediaTabTranslate.instant('OBJECTS.filters.multimedia.virtualWalk'));
|
|
2335
|
+
}
|
|
2336
|
+
/** Shows `#walkContainer` fullscreen, or leaves fullscreen. */
|
|
2337
|
+
toggleFullscreen() {
|
|
2338
|
+
const container = this.walkContainer();
|
|
2339
|
+
if (!container)
|
|
2340
|
+
return;
|
|
2341
|
+
const element = container.nativeElement;
|
|
2342
|
+
const doc = this.virtualWalkTabDocument;
|
|
2343
|
+
if (!doc.fullscreenElement) {
|
|
2344
|
+
element.requestFullscreen().catch((err) => {
|
|
2345
|
+
console.error('Error attempting to enable fullscreen:', err);
|
|
2346
|
+
});
|
|
2347
|
+
}
|
|
2348
|
+
else {
|
|
2349
|
+
doc.exitFullscreen();
|
|
2350
|
+
}
|
|
2351
|
+
}
|
|
2352
|
+
/** @nocollapse */ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: ObjectVirtualWalkTabBaseComponent, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
2353
|
+
/** @nocollapse */ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.2.0", version: "22.1.3", type: ObjectVirtualWalkTabBaseComponent, isStandalone: true, viewQueries: [{ propertyName: "iframeContainer", first: true, predicate: ["iframeContainer"], descendants: true, isSignal: true }, { propertyName: "walkContainer", first: true, predicate: ["walkContainer"], descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0 }); }
|
|
2354
|
+
}
|
|
2355
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: ObjectVirtualWalkTabBaseComponent, decorators: [{
|
|
2356
|
+
type: Directive
|
|
2357
|
+
}], ctorParameters: () => [], propDecorators: { iframeContainer: [{ type: i0.ViewChild, args: ['iframeContainer', { isSignal: true }] }], walkContainer: [{ type: i0.ViewChild, args: ['walkContainer', { isSignal: true }] }] } });
|
|
2358
|
+
|
|
2359
|
+
/**
|
|
2360
|
+
* Logic-only base of the object 3D model tab (MOB-1801), 1:1 from Muzeum
|
|
2361
|
+
* Podlasia: the model list, the lazily loaded `online-3d-viewer`, the model
|
|
2362
|
+
* switch, the camera reset, the wireframe and the fullscreen toggle. The tab
|
|
2363
|
+
* keeps its template, styles and `host`, and returns its own
|
|
2364
|
+
* `import('online-3d-viewer')` from `loadModel3dLibrary()` — the framework does
|
|
2365
|
+
* not depend on the library, and the viewer is never loaded on the server.
|
|
2366
|
+
* Podlasie's model file URI panel (MOB-981) stays in Podlasie and follows
|
|
2367
|
+
* `selectedModel`.
|
|
2368
|
+
*
|
|
2369
|
+
* ```ts
|
|
2370
|
+
* export class Model3dTabComponent extends ObjectModel3dTabBaseComponent {
|
|
2371
|
+
* constructor() {
|
|
2372
|
+
* super(); // sztuki-2 / template: super({ loadableOnly: false })
|
|
2373
|
+
* }
|
|
2374
|
+
*
|
|
2375
|
+
* protected loadModel3dLibrary() {
|
|
2376
|
+
* return import('online-3d-viewer');
|
|
2377
|
+
* }
|
|
2378
|
+
* }
|
|
2379
|
+
* ```
|
|
2380
|
+
*/
|
|
2381
|
+
class ObjectModel3dTabBaseComponent extends ObjectMediaTabBaseComponent {
|
|
2382
|
+
constructor(options = {}) {
|
|
2383
|
+
super();
|
|
2384
|
+
/** The template's `#viewer3d`, the viewer's canvas container. */
|
|
2385
|
+
this.viewer3d = viewChild('viewer3d', /* @ts-ignore */
|
|
2386
|
+
...(ngDevMode ? [{ debugName: "viewer3d" }] : /* istanbul ignore next */ []));
|
|
2387
|
+
/** The chosen model's index, bound with `ngModel` (may arrive as a string). */
|
|
2388
|
+
this.selectedModelIndex = 0;
|
|
2389
|
+
/** The chosen model's index as a signal, next to `selectedModelIndex` (`onModelChange`). */
|
|
2390
|
+
this.selectedModel = signal(0, /* @ts-ignore */
|
|
2391
|
+
...(ngDevMode ? [{ debugName: "selectedModel" }] : /* istanbul ignore next */ []));
|
|
2392
|
+
/** A model is loading. */
|
|
2393
|
+
this.isLoading = signal(false, /* @ts-ignore */
|
|
2394
|
+
...(ngDevMode ? [{ debugName: "isLoading" }] : /* istanbul ignore next */ []));
|
|
2395
|
+
/** The load error, `null` without one. */
|
|
2396
|
+
this.error = signal(null, /* @ts-ignore */
|
|
2397
|
+
...(ngDevMode ? [{ debugName: "error" }] : /* istanbul ignore next */ []));
|
|
2398
|
+
/** The edges are shown. */
|
|
2399
|
+
this.isWireframe = signal(false, /* @ts-ignore */
|
|
2400
|
+
...(ngDevMode ? [{ debugName: "isWireframe" }] : /* istanbul ignore next */ []));
|
|
2401
|
+
/** Whether the document is in fullscreen (follows `fullscreenchange`, e.g. Escape). */
|
|
2402
|
+
this.isFullscreen = signal(false, /* @ts-ignore */
|
|
2403
|
+
...(ngDevMode ? [{ debugName: "isFullscreen" }] : /* istanbul ignore next */ []));
|
|
2404
|
+
this.model3dTabDocument = inject(DOCUMENT);
|
|
2405
|
+
this.model3dTabPlatformId = inject(PLATFORM_ID);
|
|
2406
|
+
this.model3dTabTimeouts = createSafeTimeouts();
|
|
2407
|
+
/**
|
|
2408
|
+
* `online-3d-viewer` (+ `three`, ~1.1 MB) is loaded only on the first
|
|
2409
|
+
* init — a static import put the library in the chunk of the whole objects
|
|
2410
|
+
* module, loaded on the `/obiekty` list and every card, also without a 3D
|
|
2411
|
+
* model (Lighthouse, MOB-1850, MOB-1895). One load is shared between
|
|
2412
|
+
* concurrent calls. Created on first use.
|
|
2413
|
+
*/
|
|
2414
|
+
this.model3dTabLibraryLoader = null;
|
|
2415
|
+
/** The number of the latest init — an older call after `await` does not overwrite a newer one. */
|
|
2416
|
+
this.model3dTabInitToken = 0;
|
|
2417
|
+
this.model3dTabViewer = null;
|
|
2418
|
+
this.model3dTabOptions = options;
|
|
2419
|
+
effect(() => {
|
|
2420
|
+
const viewerElement = this.viewer3d();
|
|
2421
|
+
const models = this.models();
|
|
2422
|
+
if (viewerElement && models.length > 0) {
|
|
2423
|
+
this.model3dTabTimeouts.later(() => void this.model3dTabInitViewer(), 100);
|
|
2424
|
+
}
|
|
2425
|
+
});
|
|
2426
|
+
if (isPlatformBrowser(this.model3dTabPlatformId)) {
|
|
2427
|
+
// Listen for fullscreen changes (e.g. when the user presses Escape)
|
|
2428
|
+
const doc = this.model3dTabDocument;
|
|
2429
|
+
const onFullscreenChange = () => {
|
|
2430
|
+
const wasFullscreen = this.isFullscreen();
|
|
2431
|
+
this.isFullscreen.set(!!doc.fullscreenElement);
|
|
2432
|
+
// Reinitialize the viewer when leaving fullscreen to fix sizing issues
|
|
2433
|
+
if (wasFullscreen && !doc.fullscreenElement) {
|
|
2434
|
+
this.model3dTabTimeouts.later(() => void this.model3dTabInitViewer(), 100);
|
|
2435
|
+
}
|
|
2436
|
+
};
|
|
2437
|
+
doc.addEventListener('fullscreenchange', onFullscreenChange);
|
|
2438
|
+
inject(DestroyRef).onDestroy(() => {
|
|
2439
|
+
doc.removeEventListener('fullscreenchange', onFullscreenChange);
|
|
2440
|
+
});
|
|
2441
|
+
}
|
|
2442
|
+
}
|
|
2443
|
+
/** With `loadableOnly` (default) only models with a file to load. */
|
|
2444
|
+
mediaTabFilterModels(models) {
|
|
2445
|
+
if (this.model3dTabOptions.loadableOnly === false)
|
|
2446
|
+
return models;
|
|
2447
|
+
// Only models with a file to load — the same condition as the tab
|
|
2448
|
+
// counter, so the default model does not end in "No files found"
|
|
2449
|
+
// (MOB-1872 review).
|
|
2450
|
+
return models.filter((model) => (model.files ?? []).some((file) => !!file?.filePath));
|
|
2451
|
+
}
|
|
2452
|
+
/** Follows the `selectedModelIndex` dropdown: sets `selectedModel` and loads that model. */
|
|
2453
|
+
onModelChange() {
|
|
2454
|
+
this.selectedModel.set(Number(this.selectedModelIndex) || 0);
|
|
2455
|
+
this.model3dTabLoadModel(this.selectedModelIndex);
|
|
2456
|
+
}
|
|
2457
|
+
/** Fits the model to the window with the Y axis up. A no-op before the library is loaded. */
|
|
2458
|
+
resetCamera() {
|
|
2459
|
+
const library = this.model3dTabLibraryLoader?.loaded;
|
|
2460
|
+
if (this.model3dTabViewer && library) {
|
|
2461
|
+
// Reset the camera to fit the model and reset rotation
|
|
2462
|
+
const viewerInstance = this.model3dTabViewer.GetViewer();
|
|
2463
|
+
// Reset the up vector to default (Y-up)
|
|
2464
|
+
viewerInstance.SetUpVector(library.Direction.Y, false);
|
|
2465
|
+
// Get bounding sphere and fit to window
|
|
2466
|
+
const boundingSphere = viewerInstance.GetBoundingSphere(() => true);
|
|
2467
|
+
viewerInstance.AdjustClippingPlanesToSphere(boundingSphere);
|
|
2468
|
+
viewerInstance.FitSphereToWindow(boundingSphere, true); // Use animation for smooth transition
|
|
2469
|
+
}
|
|
2470
|
+
}
|
|
2471
|
+
/** Shows or hides the edges. A no-op before the library is loaded. */
|
|
2472
|
+
toggleWireframe() {
|
|
2473
|
+
const library = this.model3dTabLibraryLoader?.loaded;
|
|
2474
|
+
if (!this.model3dTabViewer || !library)
|
|
2475
|
+
return;
|
|
2476
|
+
const newWireframeState = !this.isWireframe();
|
|
2477
|
+
this.isWireframe.set(newWireframeState);
|
|
2478
|
+
const edgeSettings = new library.EdgeSettings(newWireframeState, new library.RGBColor(0, 0, 0), 1);
|
|
2479
|
+
const viewerInstance = this.model3dTabViewer.GetViewer();
|
|
2480
|
+
viewerInstance.SetEdgeSettings(edgeSettings);
|
|
2481
|
+
}
|
|
2482
|
+
/** Shows the parent of `#viewer3d` fullscreen, or leaves fullscreen. */
|
|
2483
|
+
toggleFullscreen() {
|
|
2484
|
+
const viewerElement = this.viewer3d();
|
|
2485
|
+
if (!viewerElement)
|
|
2486
|
+
return;
|
|
2487
|
+
const container = viewerElement.nativeElement.parentElement;
|
|
2488
|
+
const doc = this.model3dTabDocument;
|
|
2489
|
+
if (!doc.fullscreenElement) {
|
|
2490
|
+
container.requestFullscreen().catch((err) => {
|
|
2491
|
+
console.error('Error attempting to enable fullscreen:', err);
|
|
2492
|
+
});
|
|
2493
|
+
}
|
|
2494
|
+
else {
|
|
2495
|
+
doc.exitFullscreen();
|
|
2496
|
+
}
|
|
2497
|
+
}
|
|
2498
|
+
model3dTabLibrary() {
|
|
2499
|
+
this.model3dTabLibraryLoader ??= createLazyLoader(() => this.loadModel3dLibrary());
|
|
2500
|
+
return this.model3dTabLibraryLoader;
|
|
2501
|
+
}
|
|
2502
|
+
async model3dTabInitViewer() {
|
|
2503
|
+
if (!isPlatformBrowser(this.model3dTabPlatformId))
|
|
2504
|
+
return;
|
|
2505
|
+
const viewerElement = this.viewer3d();
|
|
2506
|
+
if (!viewerElement)
|
|
2507
|
+
return;
|
|
2508
|
+
const models = this.models();
|
|
2509
|
+
if (models.length === 0)
|
|
2510
|
+
return;
|
|
2511
|
+
const token = ++this.model3dTabInitToken;
|
|
2512
|
+
this.isLoading.set(true);
|
|
2513
|
+
this.error.set(null);
|
|
2514
|
+
let library;
|
|
2515
|
+
try {
|
|
2516
|
+
library = await this.model3dTabLibrary().load();
|
|
2517
|
+
}
|
|
2518
|
+
catch (loadError) {
|
|
2519
|
+
// The library chunk did not arrive (network, old deploy) — a message
|
|
2520
|
+
// instead of an endless loader; the next entry to the tab retries.
|
|
2521
|
+
if (token === this.model3dTabInitToken) {
|
|
2522
|
+
this.isLoading.set(false);
|
|
2523
|
+
this.error.set('3D viewer failed to load');
|
|
2524
|
+
}
|
|
2525
|
+
console.error('online-3d-viewer', loadError);
|
|
2526
|
+
return;
|
|
2527
|
+
}
|
|
2528
|
+
// A newer call came in meanwhile, or the component is gone.
|
|
2529
|
+
if (token !== this.model3dTabInitToken || !this.viewer3d())
|
|
2530
|
+
return;
|
|
2531
|
+
// Clean up existing viewer
|
|
2532
|
+
if (this.model3dTabViewer) {
|
|
2533
|
+
this.model3dTabViewer.Destroy();
|
|
2534
|
+
this.model3dTabViewer = null;
|
|
2535
|
+
}
|
|
2536
|
+
const parentDiv = viewerElement.nativeElement;
|
|
2537
|
+
parentDiv.innerHTML = ''; // Clear previous content
|
|
2538
|
+
// Initialize the viewer with callback for proper loading state
|
|
2539
|
+
this.model3dTabViewer = new library.EmbeddedViewer(parentDiv, {
|
|
2540
|
+
backgroundColor: new library.RGBAColor(245, 245, 245, 255),
|
|
2541
|
+
defaultColor: new library.RGBColor(200, 200, 200),
|
|
2542
|
+
edgeSettings: new library.EdgeSettings(false, new library.RGBColor(0, 0, 0), 1),
|
|
2543
|
+
onModelLoaded: () => {
|
|
2544
|
+
this.isLoading.set(false);
|
|
2545
|
+
},
|
|
2546
|
+
});
|
|
2547
|
+
this.model3dTabLoadModel(this.selectedModelIndex);
|
|
2548
|
+
}
|
|
2549
|
+
model3dTabLoadModel(index) {
|
|
2550
|
+
if (!this.model3dTabViewer)
|
|
2551
|
+
return;
|
|
2552
|
+
const models = this.models();
|
|
2553
|
+
if (index < 0 || index >= models.length)
|
|
2554
|
+
return;
|
|
2555
|
+
const model = models[index];
|
|
2556
|
+
this.isLoading.set(true);
|
|
2557
|
+
this.error.set(null);
|
|
2558
|
+
// Only files with a URL — an empty entry in `files[]` broke the 3D viewer
|
|
2559
|
+
// and blocked the model's valid files (MOB-1872 review).
|
|
2560
|
+
const fileUrls = (model.files ?? [])
|
|
2561
|
+
.map((file) => file?.filePath)
|
|
2562
|
+
.filter((url) => !!url);
|
|
2563
|
+
if (fileUrls.length === 0) {
|
|
2564
|
+
this.error.set('No files found for this model');
|
|
2565
|
+
this.isLoading.set(false);
|
|
2566
|
+
return;
|
|
2567
|
+
}
|
|
2568
|
+
// Load the model - callbacks onModelLoaded/onModelLoadFailed handle loading state
|
|
2569
|
+
this.model3dTabViewer.LoadModelFromUrlList(fileUrls);
|
|
2570
|
+
}
|
|
2571
|
+
/** @nocollapse */ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: ObjectModel3dTabBaseComponent, deps: "invalid", target: i0.ɵɵFactoryTarget.Directive }); }
|
|
2572
|
+
/** @nocollapse */ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.2.0", version: "22.1.3", type: ObjectModel3dTabBaseComponent, isStandalone: true, viewQueries: [{ propertyName: "viewer3d", first: true, predicate: ["viewer3d"], descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0 }); }
|
|
2573
|
+
}
|
|
2574
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: ObjectModel3dTabBaseComponent, decorators: [{
|
|
2575
|
+
type: Directive
|
|
2576
|
+
}], ctorParameters: () => [{ type: undefined }], propDecorators: { viewer3d: [{ type: i0.ViewChild, args: ['viewer3d', { isSignal: true }] }] } });
|
|
2577
|
+
|
|
2578
|
+
/**
|
|
2579
|
+
* Logic-only base of the sztuki-2 / template `media-sections`: an object's
|
|
2580
|
+
* multimedia as landing-page sections (Figma), each with a heading like
|
|
2581
|
+
* „Galeria zdjęć”; a section without data is not rendered. Order: HD photos →
|
|
2582
|
+
* 3D model → animation → video → audio → virtual walk → audio description →
|
|
2583
|
+
* map → other files. The component keeps its template, heading class and
|
|
2584
|
+
* tab imports.
|
|
2585
|
+
*
|
|
2586
|
+
* ```ts
|
|
2587
|
+
* export class MediaSectionsComponent extends ObjectMediaSectionsBaseComponent {
|
|
2588
|
+
* readonly sectionHeadingClass = '…';
|
|
2589
|
+
* }
|
|
2590
|
+
* ```
|
|
2591
|
+
*/
|
|
2592
|
+
class ObjectMediaSectionsBaseComponent {
|
|
2593
|
+
constructor() {
|
|
2594
|
+
this.mediaSectionsFileUrl = inject(FileUrlService);
|
|
2595
|
+
this.mediaSectionsMedia = inject(ObjectMediaService);
|
|
2596
|
+
/** The object whose multimedia the sections show. */
|
|
2597
|
+
this.item = input.required(/* @ts-ignore */
|
|
2598
|
+
...(ngDevMode ? [{ debugName: "item" }] : /* istanbul ignore next */ []));
|
|
2599
|
+
/**
|
|
2600
|
+
* Multimedia already shown in the object card's gallery frame (the video /
|
|
2601
|
+
* 3D tabs, MOB-1961) — not repeated in the sections. Default `[]`.
|
|
2602
|
+
*/
|
|
2603
|
+
this.exclude = input([], /* @ts-ignore */
|
|
2604
|
+
...(ngDevMode ? [{ debugName: "exclude" }] : /* istanbul ignore next */ []));
|
|
2605
|
+
/** `[IMAGES_HD]` */
|
|
2606
|
+
this.imagesHDTypes = [FormFeatureType.IMAGES_HD];
|
|
2607
|
+
/** `[MODEL_3D]` */
|
|
2608
|
+
this.model3DTypes = [FormFeatureType.MODEL_3D];
|
|
2609
|
+
/** `[ANIMATION]` */
|
|
2610
|
+
this.animationTypes = [FormFeatureType.ANIMATION];
|
|
2611
|
+
/** `[AUDIO_DESKRYPCJA]` */
|
|
2612
|
+
this.adTypes = [FormFeatureType.AUDIO_DESKRYPCJA];
|
|
2613
|
+
/** `[VIRTUAL_WALK]` */
|
|
2614
|
+
this.virtualWalkTypes = [FormFeatureType.VIRTUAL_WALK];
|
|
2615
|
+
/** `[VIDEO]` */
|
|
2616
|
+
this.videoTypes = [FormFeatureType.VIDEO];
|
|
2617
|
+
/** `[AUDIO]` */
|
|
2618
|
+
this.audioTypes = [FormFeatureType.AUDIO];
|
|
2619
|
+
/** `[OTHER]` */
|
|
2620
|
+
this.otherTypes = [FormFeatureType.OTHER];
|
|
2621
|
+
/** HD photo thumbnails (without the main image) — fullscreen on click. */
|
|
2622
|
+
this.hdThumbnailUrls = computed(() => this.mediaSectionsMedia.thumbnailUrls(this.item(), this.imagesHDTypes, false), /* @ts-ignore */
|
|
2623
|
+
...(ngDevMode ? [{ debugName: "hdThumbnailUrls" }] : /* istanbul ignore next */ []));
|
|
2624
|
+
/** HD photo gallery items (without the main image) for the fullscreen. */
|
|
2625
|
+
this.hdGalleryItems = computed(() => this.mediaSectionsMedia.galleryItems(this.item(), this.imagesHDTypes, false), /* @ts-ignore */
|
|
2626
|
+
...(ngDevMode ? [{ debugName: "hdGalleryItems" }] : /* istanbul ignore next */ []));
|
|
2627
|
+
/** Whether the HD fullscreen gallery is open. */
|
|
2628
|
+
this.hdFullscreenOpen = signal(false, /* @ts-ignore */
|
|
2629
|
+
...(ngDevMode ? [{ debugName: "hdFullscreenOpen" }] : /* istanbul ignore next */ []));
|
|
2630
|
+
/** The photo the HD fullscreen gallery shows. */
|
|
2631
|
+
this.hdFullscreenIndex = signal(0, /* @ts-ignore */
|
|
2632
|
+
...(ngDevMode ? [{ debugName: "hdFullscreenIndex" }] : /* istanbul ignore next */ []));
|
|
2633
|
+
/** The object title without HTML tags (alt text). */
|
|
2634
|
+
this.altTitle = computed(() => (this.item()?.title ?? '').replace(/<\/?[^>]+(>|$)/g, ''), /* @ts-ignore */
|
|
2635
|
+
...(ngDevMode ? [{ debugName: "altTitle" }] : /* istanbul ignore next */ []));
|
|
2636
|
+
/** HD photos section: any HD thumbnail. */
|
|
2637
|
+
this.showPhotosHD = computed(() => this.hdThumbnailUrls().length > 0, /* @ts-ignore */
|
|
2638
|
+
...(ngDevMode ? [{ debugName: "showPhotosHD" }] : /* istanbul ignore next */ []));
|
|
2639
|
+
/** 3D model section: a real file URL, unless excluded. */
|
|
2640
|
+
this.showModel3D = computed(() => !this.exclude().includes('model3d') &&
|
|
2641
|
+
this.mediaSectionsHasContent(this.model3DTypes), /* @ts-ignore */
|
|
2642
|
+
...(ngDevMode ? [{ debugName: "showModel3D" }] : /* istanbul ignore next */ []));
|
|
2643
|
+
/** Animation section: a real file URL. */
|
|
2644
|
+
this.showAnimation = computed(() => this.mediaSectionsHasContent(this.animationTypes), /* @ts-ignore */
|
|
2645
|
+
...(ngDevMode ? [{ debugName: "showAnimation" }] : /* istanbul ignore next */ []));
|
|
2646
|
+
/**
|
|
2647
|
+
* Video section: a file with a `filePath` (`FileUrlService` gives a
|
|
2648
|
+
* placeholder for non-image extensions), unless excluded.
|
|
2649
|
+
*/
|
|
2650
|
+
this.showVideo = computed(() => !this.exclude().includes('video') &&
|
|
2651
|
+
this.mediaSectionsMedia.hasFiles(this.item(), this.videoTypes), /* @ts-ignore */
|
|
2652
|
+
...(ngDevMode ? [{ debugName: "showVideo" }] : /* istanbul ignore next */ []));
|
|
2653
|
+
/** Audio section: a file with a `filePath`. */
|
|
2654
|
+
this.showAudio = computed(() => this.mediaSectionsMedia.hasFiles(this.item(), this.audioTypes), /* @ts-ignore */
|
|
2655
|
+
...(ngDevMode ? [{ debugName: "showAudio" }] : /* istanbul ignore next */ []));
|
|
2656
|
+
/** Virtual walk section: a walk with an `iframeSource` (not a file URL). */
|
|
2657
|
+
this.showVirtualWalk = computed(() => {
|
|
2658
|
+
const obj = this.item();
|
|
2659
|
+
if (!obj?.additionalImages)
|
|
2660
|
+
return false;
|
|
2661
|
+
const virtualWalkItems = obj.additionalImages.filter((img) => this.virtualWalkTypes.includes(img?.formFeature));
|
|
2662
|
+
// Any virtual walk item with an iframeSource
|
|
2663
|
+
return virtualWalkItems.some((item) => item.iframeSource);
|
|
2664
|
+
}, /* @ts-ignore */
|
|
2665
|
+
...(ngDevMode ? [{ debugName: "showVirtualWalk" }] : /* istanbul ignore next */ []));
|
|
2666
|
+
/** The number of audio description files with a real URL. */
|
|
2667
|
+
this.audioDescriptionCount = computed(() => {
|
|
2668
|
+
const obj = this.item();
|
|
2669
|
+
if (!obj?.additionalImages)
|
|
2670
|
+
return 0;
|
|
2671
|
+
const audioItems = obj.additionalImages.filter((img) => {
|
|
2672
|
+
if (!this.adTypes.includes(img?.formFeature))
|
|
2673
|
+
return false;
|
|
2674
|
+
const url = this.mediaSectionsFileUrl.get(img);
|
|
2675
|
+
return url && !url.includes('assets/no-image.svg');
|
|
2676
|
+
});
|
|
2677
|
+
return audioItems.length;
|
|
2678
|
+
}, /* @ts-ignore */
|
|
2679
|
+
...(ngDevMode ? [{ debugName: "audioDescriptionCount" }] : /* istanbul ignore next */ []));
|
|
2680
|
+
/** Audio description section: any audio description. */
|
|
2681
|
+
this.showAudioDescription = computed(() => this.audioDescriptionCount() > 0, /* @ts-ignore */
|
|
2682
|
+
...(ngDevMode ? [{ debugName: "showAudioDescription" }] : /* istanbul ignore next */ []));
|
|
2683
|
+
/** Map section: both `archeoMap` coordinates set. */
|
|
2684
|
+
this.showMap = computed(() => {
|
|
2685
|
+
const obj = this.item();
|
|
2686
|
+
return !!(obj?.archeoMap?.latitude && obj?.archeoMap?.longitude);
|
|
2687
|
+
}, /* @ts-ignore */
|
|
2688
|
+
...(ngDevMode ? [{ debugName: "showMap" }] : /* istanbul ignore next */ []));
|
|
2689
|
+
/** Other files section (PDF, etc.): a real file URL. */
|
|
2690
|
+
this.showOther = computed(() => this.mediaSectionsHasContent(this.otherTypes), /* @ts-ignore */
|
|
2691
|
+
...(ngDevMode ? [{ debugName: "showOther" }] : /* istanbul ignore next */ []));
|
|
2692
|
+
/** Whether any section is visible. */
|
|
2693
|
+
this.hasVisibleSections = computed(() => this.showPhotosHD() ||
|
|
2694
|
+
this.showModel3D() ||
|
|
2695
|
+
this.showAnimation() ||
|
|
2696
|
+
this.showVideo() ||
|
|
2697
|
+
this.showAudio() ||
|
|
2698
|
+
this.showVirtualWalk() ||
|
|
2699
|
+
this.showAudioDescription() ||
|
|
2700
|
+
this.showMap() ||
|
|
2701
|
+
this.showOther(), /* @ts-ignore */
|
|
2702
|
+
...(ngDevMode ? [{ debugName: "hasVisibleSections" }] : /* istanbul ignore next */ []));
|
|
2703
|
+
}
|
|
2704
|
+
/** Opens the HD fullscreen gallery at the clicked thumbnail. */
|
|
2705
|
+
openHdFullscreen(index) {
|
|
2706
|
+
this.hdFullscreenIndex.set(index);
|
|
2707
|
+
this.hdFullscreenOpen.set(true);
|
|
2708
|
+
}
|
|
2709
|
+
/**
|
|
2710
|
+
* Whether an additional file of the types has a URL from `FileUrlService`
|
|
2711
|
+
* that is not the no-image placeholder. Reads `item()`, so the calling
|
|
2712
|
+
* `computed` tracks it.
|
|
2713
|
+
*/
|
|
2714
|
+
mediaSectionsHasContent(types) {
|
|
2715
|
+
const obj = this.item();
|
|
2716
|
+
if (!obj)
|
|
2717
|
+
return false;
|
|
2718
|
+
// Check additional images
|
|
2719
|
+
if (obj.additionalImages && Array.isArray(obj.additionalImages)) {
|
|
2720
|
+
const filtered = obj.additionalImages.filter((img) => types.includes(img?.formFeature));
|
|
2721
|
+
for (const img of filtered) {
|
|
2722
|
+
const url = this.mediaSectionsFileUrl.get(img);
|
|
2723
|
+
if (url && !url.includes('assets/no-image.svg')) {
|
|
2724
|
+
return true;
|
|
2725
|
+
}
|
|
2726
|
+
}
|
|
2727
|
+
}
|
|
2728
|
+
return false;
|
|
2729
|
+
}
|
|
2730
|
+
/** @nocollapse */ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: ObjectMediaSectionsBaseComponent, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
2731
|
+
/** @nocollapse */ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.1.3", type: ObjectMediaSectionsBaseComponent, isStandalone: true, inputs: { item: { classPropertyName: "item", publicName: "item", isSignal: true, isRequired: true, transformFunction: null }, exclude: { classPropertyName: "exclude", publicName: "exclude", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0 }); }
|
|
2732
|
+
}
|
|
2733
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: ObjectMediaSectionsBaseComponent, decorators: [{
|
|
2734
|
+
type: Directive
|
|
2735
|
+
}], propDecorators: { item: [{ type: i0.Input, args: [{ isSignal: true, alias: "item", required: true }] }], exclude: [{ type: i0.Input, args: [{ isSignal: true, alias: "exclude", required: false }] }] } });
|
|
2736
|
+
|
|
1744
2737
|
/**
|
|
1745
2738
|
* Generated bundle index. Do not edit.
|
|
1746
2739
|
*/
|
|
1747
2740
|
|
|
1748
|
-
export { ASK_ABOUT_OBJECT_KEYS, AdvancedFilterType, ComponentsModule, FiltersComponent, FormFeatureType, ListComponent, MULTIMEDIA_DICTIONARY, OBJECTS_CATALOG_DEFAULTS, OBJECTS_CATALOG_SEO, OBJECTS_FILTER_SINGLE_VALUE_LABELS, OBJECTS_LIMIT_OPTIONS, OBJECTS_NEW_TYPE_SORT_OPTIONS, OBJECTS_SHARE_COPIED_TIMEOUT, OBJECTS_SORT_OPTIONS, OBJECTS_THEME_TAGS_FILTER_KEY, ObjectDetailsBaseComponent, ObjectMediaService, ObjectsAdvancedFiltersBaseComponent, ObjectsCatalogBaseComponent, ObjectsDataService, ObjectsFacade, ObjectsFiltersBarBaseComponent, ObjectsItemComponent, ObjectsListComponent, ObjectsModule, ObjectsSearchPanelBaseComponent, ObjectsService, ObjectsShareResultsBaseComponent, ObjectsSharedModule, SERVICES, askAboutObjectHref, bibliography, buildObjectsResultsUrl, descriptionHtml, getObjectsState, getReducer, initialState, inventoryValues, objectTitle, objectsSortFromKey, objectsSortKey, relatedArticles, tagValues };
|
|
2741
|
+
export { ASK_ABOUT_OBJECT_KEYS, AdvancedFilterType, ComponentsModule, FiltersComponent, FormFeatureType, ListComponent, MULTIMEDIA_DICTIONARY, OBJECTS_CATALOG_DEFAULTS, OBJECTS_CATALOG_SEO, OBJECTS_FILTER_SINGLE_VALUE_LABELS, OBJECTS_LIMIT_OPTIONS, OBJECTS_NEW_TYPE_SORT_OPTIONS, OBJECTS_SHARE_COPIED_TIMEOUT, OBJECTS_SORT_OPTIONS, OBJECTS_THEME_TAGS_FILTER_KEY, ObjectAnimationTabBaseComponent, ObjectAudioDescriptionTabBaseComponent, ObjectAudioMediaTabBaseComponent, ObjectAudioTabBaseComponent, ObjectDetailsBaseComponent, ObjectDocumentsTabBaseComponent, ObjectDownloadButtonBaseComponent, ObjectMapTabBaseComponent, ObjectMediaSectionsBaseComponent, ObjectMediaService, ObjectMediaTabBaseComponent, ObjectModel3dTabBaseComponent, ObjectVideoMediaTabBaseComponent, ObjectVideoTabBaseComponent, ObjectVirtualWalkTabBaseComponent, ObjectsAdvancedFiltersBaseComponent, ObjectsCatalogBaseComponent, ObjectsDataService, ObjectsFacade, ObjectsFiltersBarBaseComponent, ObjectsItemComponent, ObjectsListComponent, ObjectsModule, ObjectsSearchPanelBaseComponent, ObjectsService, ObjectsShareResultsBaseComponent, ObjectsSharedModule, SERVICES, askAboutObjectHref, bibliography, buildObjectsResultsUrl, descriptionHtml, getObjectsState, getReducer, initialState, inventoryValues, objectTitle, objectsSortFromKey, objectsSortKey, relatedArticles, tagValues };
|
|
1749
2742
|
//# sourceMappingURL=smartsoft001-mobilems-objects-shell-angular.mjs.map
|