Merge pull request #901 from tainacan/feature/900

Creates Mosaic View Mode #900
This commit is contained in:
Mateus Machado Luna 2024-06-21 16:09:52 -03:00 committed by GitHub
commit 1fe3617d2a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
15 changed files with 595 additions and 16 deletions

View File

@ -132,7 +132,7 @@ class Collections extends Repository {
'title' => __( 'Enabled view modes', 'tainacan' ), 'title' => __( 'Enabled view modes', 'tainacan' ),
'type' => 'array', 'type' => 'array',
'description' => __( 'Which visualization modes will be available for the public to choose from', 'tainacan' ), 'description' => __( 'Which visualization modes will be available for the public to choose from', 'tainacan' ),
'default' => [ 'table', 'cards' ], 'default' => [ 'table', 'cards', 'masonry' ],
'items' => [ 'type' => 'string' ], 'items' => [ 'type' => 'string' ],
//'validation' => v::stringType(), //'validation' => v::stringType(),
], ],

View File

@ -2485,5 +2485,23 @@ class Theme_Helper {
'requires_thumbnail' => false, 'requires_thumbnail' => false,
'placeholder_template' => $map_view_mode_placeholder 'placeholder_template' => $map_view_mode_placeholder
]); ]);
$this->register_view_mode('mosaic', [
'label' => __('Mosaic', 'tainacan'),
'dynamic_metadata' => false,
'description' => __('A mosaic view, similar to Flickr and Google Photos, which will display images without cropping.', 'tainacan'),
'icon' => '<span class="icon"><i class="tainacan-icon tainacan-icon-viewmasonry tainacan-icon-rotate-90 tainacan-icon-1-25em"></i></span>',
'type' => 'component',
'implements_skeleton' => true,
'placeholder_template' => '<ul style="list-style: none;width: 100%; height: auto; display: flex; gap: 24px 0; flex-wrap: wrap;">' .
array_reduce( range(0,11), function($container, $i) {
$container .= '<li style="flex-grow: 1; max-width: 35%; width: ' . ($i % 2 == 0 ? rand(100, 180) : rand(90, 170)) . 'px; height: 120px ; background-color: var(--tainacan-block-gray1, #f2f2f2); margin: 0; padding: 5px;">
<div style="width: 100%;height: 100%; background-color: var(--tainacan-block-gray2, #dbdbdb);margin-bottom: 10px;"></div>
<div style="width: 100%;height: 10px; background-color: var(--tainacan-block-gray3, #a5a5a5);"></div>
</li>';
return $container;
}) .
'</ul>'
]);
} }
} }

View File

@ -1979,6 +1979,181 @@
</l-control> </l-control>
</l-map> </l-map>
</div> </div>
<!-- MOSAIC VIEW MODE -->
<ul
v-if="viewMode == 'mosaic'"
:class="{
'hide-items-selection': $adminOptions.hideItemsListSelection
}"
class="tainacan-mosaic-container">
<li
v-for="(item, index) of items"
:key="index"
:data-tainacan-item-id="item.id"
:style="{
'--tainacan-mosaic-item-width': getAcceptableWidthBasedOnRatio(item['thumbnail'], 'tainacan-large-full', 300),
'--tainacan-mosaic-item-height': $thumbHelper.getHeight(item['thumbnail'], 'tainacan-large-full', 300)
}">
<div
:class="{
'selected-mosaic-item': getSelectedItemChecked(item.id) == true
}"
class="tainacan-mosaic-item"
@click.left="onClickItem($event, item)"
@click.right="onRightClickItem($event, item)">
<!-- Thumbnail -->
<blur-hash-image
v-if="item.thumbnail != undefined"
class="tainacan-mosaic-item-thumbnail"
:width="$thumbHelper.getWidth(item['thumbnail'], 'tainacan-large-full', 320)"
:height="$thumbHelper.getHeight(item['thumbnail'], 'tainacan-large-full', 320)"
:hash="$thumbHelper.getBlurhashString(item['thumbnail'], 'tainacan-large-full')"
:src="$thumbHelper.getSrc(item['thumbnail'], 'tainacan-large-full', item.document_mimetype)"
:srcset="$thumbHelper.getSrcSet(item['thumbnail'], 'tainacan-large-full', item.document_mimetype)"
:alt="item.thumbnail_alt ? item.thumbnail_alt : $i18n.get('label_thumbnail')"
:transition-duration="500"
/>
<!-- Checkbox -->
<!-- TODO: Remove v-if="collectionId" from this element when the bulk edit in repository is done -->
<div
v-if="collectionId && !$adminOptions.hideItemsListSelection && ($adminOptions.itemsSingleSelectionMode || $adminOptions.itemsMultipleSelectionMode || (collection && collection.current_user_can_bulk_edit))"
:class="{ 'is-selecting': isSelectingItems }"
class="mosaic-item-checkbox">
<label
tabindex="0"
:class="(!$adminOptions.itemsSingleSelectionMode ? 'b-checkbox checkbox' : 'b-radio radio') + ' is-small'">
<input
v-if="!$adminOptions.itemsSingleSelectionMode"
type="checkbox"
:checked="getSelectedItemChecked(item.id)"
@input="setSelectedItemChecked(item.id)">
<input
v-else
v-model="singleItemSelection"
type="radio"
name="item-single-selection"
:value="item.id">
<span class="check" />
<span class="control-label" />
<span class="sr-only">{{ $i18n.get('label_select_item') }}</span>
</label>
</div>
<!-- Title -->
<div
class="metadata-title"
:style="{
'padding-left': !collectionId || !($adminOptions.itemsSingleSelectionMode || $adminOptions.itemsMultipleSelectionMode || (collection && collection.current_user_can_bulk_edit)) || $adminOptions.itemsSearchSelectionMode ? '0 !important' : (isOnAllItemsTabs ? '0.5em' : '1em')
}">
<p
v-tooltip="{
delay: {
show: 750,
hide: 100,
},
content: item.title != undefined ? item.title : '',
html: true,
placement: 'auto-start',
popperClass: ['tainacan-tooltip', 'tooltip', isRepositoryLevel ? 'tainacan-repository-tooltip' : '']
}">
<span
v-if="isOnAllItemsTabs && $statusHelper.hasIcon(item.status)"
v-tooltip="{
content: $i18n.get('status_' + item.status),
autoHide: true,
popperClass: ['tainacan-tooltip', 'tooltip', isRepositoryLevel ? 'tainacan-repository-tooltip' : ''],
placement: 'auto-start'
}"
class="icon has-text-gray">
<i
class="tainacan-icon tainacan-icon-1em"
:class="$statusHelper.getIcon(item.status)"
/>
</span>
{{ item.title != undefined ? item.title : '' }}
</p>
</div>
<!-- Actions -->
<div
v-if="item.current_user_can_edit && !$adminOptions.hideItemsListActionAreas"
class="actions-area"
:label="$i18n.get('label_actions')">
<a
v-if="!isOnTrash"
id="button-edit"
:aria-label="$i18n.getFrom('items','edit_item')"
@click.prevent.stop="goToItemEditPage(item)">
<span
v-tooltip="{
content: $i18n.get('edit'),
autoHide: true,
placement: 'auto',
popperClass: ['tainacan-tooltip', 'tooltip', isRepositoryLevel ? 'tainacan-repository-tooltip' : '']
}"
class="icon">
<i class="has-text-secondary tainacan-icon tainacan-icon-1-25em tainacan-icon-edit" />
</span>
</a>
<a
v-if="isOnTrash"
:aria-lavel="$i18n.get('label_button_untrash')"
@click.prevent.stop="untrashOneItem(item.id)">
<span
v-tooltip="{
content: $i18n.get('label_recover_from_trash'),
autoHide: true,
placement: 'auto',
popperClass: ['tainacan-tooltip', 'tooltip', isRepositoryLevel ? 'tainacan-repository-tooltip' : '']
}"
class="icon">
<i class="has-text-secondary tainacan-icon tainacan-icon-1-25em tainacan-icon-undo" />
</span>
</a>
<a
v-if="item.current_user_can_delete"
id="button-delete"
:aria-label="$i18n.get('label_button_delete')"
@click.prevent.stop="deleteOneItem(item.id)">
<span
v-tooltip="{
content: isOnTrash ? $i18n.get('label_delete_permanently') : $i18n.get('delete'),
autoHide: true,
placement: 'auto',
popperClass: ['tainacan-tooltip', 'tooltip', isRepositoryLevel ? 'tainacan-repository-tooltip' : '']
}"
class="icon">
<i
:class="{ 'tainacan-icon-delete': !isOnTrash, 'tainacan-icon-deleteforever': isOnTrash }"
class="has-text-secondary tainacan-icon tainacan-icon-1-25em" />
</span>
</a>
<a
v-if="!isOnTrash"
id="button-open-external"
:aria-label="$i18n.getFrom('items','view_item')"
target="_blank"
:href="item.url"
@click.stop="">
<span
v-tooltip="{
content: $i18n.get('label_item_page_on_website'),
autoHide: true,
popperClass: ['tainacan-tooltip', 'tooltip', isRepositoryLevel ? 'tainacan-repository-tooltip' : ''],
placement: 'auto',
html: true
}"
class="icon">
<i class="tainacan-icon tainacan-icon-1-125em tainacan-icon-openurl" />
</span>
</a>
</div>
</div>
</li>
</ul>
</div> </div>
</div> </div>
</template> </template>
@ -2674,6 +2849,12 @@ export default {
duration: 3000 duration: 3000
}); });
} }
},
getAcceptableWidthBasedOnRatio(thumbnail, size, defaultSize) {
const width = this.$thumbHelper.getWidth(thumbnail, size, defaultSize);
const height = this.$thumbHelper.getHeight(thumbnail, size, defaultSize);
return (width / height) > 0.7 ? width : ( height * 0.7 );
} }
} }
} }
@ -2685,6 +2866,7 @@ export default {
@import "../../scss/_tables.scss"; @import "../../scss/_tables.scss";
@import "../../scss/_view-mode-cards.scss"; @import "../../scss/_view-mode-cards.scss";
@import "../../scss/_view-mode-masonry.scss"; @import "../../scss/_view-mode-masonry.scss";
@import "../../scss/_view-mode-mosaic.scss";
@import "../../scss/_view-mode-grid.scss"; @import "../../scss/_view-mode-grid.scss";
@import "../../scss/_view-mode-records.scss"; @import "../../scss/_view-mode-records.scss";
@import "../../scss/_view-mode-list.scss"; @import "../../scss/_view-mode-list.scss";

View File

@ -226,13 +226,13 @@
show: 500, show: 500,
hide: 300, hide: 300,
}, },
content: (totalItems <= 0 || adminViewMode == 'grid'|| adminViewMode == 'cards' || adminViewMode == 'masonry') ? (adminViewMode == 'grid'|| adminViewMode == 'cards' || adminViewMode == 'masonry') ? $i18n.get('info_current_view_mode_metadata_not_allowed') : $i18n.get('info_cant_select_metadata_without_items') : '', content: (totalItems <= 0 || adminViewMode == 'grid'|| adminViewMode == 'cards' || adminViewMode == 'masonry' || adminViewMode == 'mosaic') ? (adminViewMode == 'grid'|| adminViewMode == 'cards' || adminViewMode == 'masonry' || adminViewMode == 'mosaic') ? $i18n.get('info_current_view_mode_metadata_not_allowed') : $i18n.get('info_cant_select_metadata_without_items') : '',
autoHide: false, autoHide: false,
placement: 'auto-start', placement: 'auto-start',
popperClass: ['tainacan-tooltip', 'tooltip', isRepositoryLevel ? 'tainacan-repository-tooltip' : ''] popperClass: ['tainacan-tooltip', 'tooltip', isRepositoryLevel ? 'tainacan-repository-tooltip' : '']
}" }"
:mobile-modal="true" :mobile-modal="true"
:disabled="totalItems <= 0 || adminViewMode == 'grid'|| adminViewMode == 'cards' || adminViewMode == 'masonry'" :disabled="totalItems <= 0 || adminViewMode == 'grid'|| adminViewMode == 'cards' || adminViewMode == 'masonry' || adminViewMode == 'mosaic'"
class="show metadata-options-dropdown" class="show metadata-options-dropdown"
aria-role="list" aria-role="list"
trap-focus> trap-focus>
@ -383,12 +383,15 @@
<span class="view-mode-icon icon is-small gray-icon"> <span class="view-mode-icon icon is-small gray-icon">
<i <i
v-if="adminViewMode !== 'map'" v-if="adminViewMode !== 'map'"
:class="{'tainacan-icon-viewtable' : ( adminViewMode == 'table' || adminViewMode == undefined), :class="{
'tainacan-icon-viewtable' : ( adminViewMode == 'table' || adminViewMode == undefined),
'tainacan-icon-viewcards' : adminViewMode == 'cards', 'tainacan-icon-viewcards' : adminViewMode == 'cards',
'tainacan-icon-viewminiature' : adminViewMode == 'grid', 'tainacan-icon-viewminiature' : adminViewMode == 'grid',
'tainacan-icon-viewrecords' : adminViewMode == 'records', 'tainacan-icon-viewrecords' : adminViewMode == 'records',
'tainacan-icon-viewlist' : adminViewMode == 'list', 'tainacan-icon-viewlist' : adminViewMode == 'list',
'tainacan-icon-viewmasonry' : adminViewMode == 'masonry' }" 'tainacan-icon-viewmasonry' : adminViewMode == 'masonry' || adminViewMode == 'mosaic',
'tainacan-icon-rotate-90' : adminViewMode == 'mosaic'
}"
class="tainacan-icon tainacan-icon-1-25em" /> class="tainacan-icon tainacan-icon-1-25em" />
<svg <svg
v-else v-else
@ -426,6 +429,17 @@
</span> </span>
<span>{{ $i18n.get('label_cards') }}</span> <span>{{ $i18n.get('label_cards') }}</span>
</b-dropdown-item> </b-dropdown-item>
<b-dropdown-item
aria-controls="items-list-results"
role="button"
:class="{ 'is-active': adminViewMode == 'mosaic' }"
:value="'mosaic'"
aria-role="listitem">
<span class="icon gray-icon">
<i class="tainacan-icon tainacan-icon-viewmasonry tainacan-icon-rotate-90" />
</span>
<span>{{ $i18n.get('label_mosaic') }}</span>
</b-dropdown-item>
<b-dropdown-item <b-dropdown-item
v-if="!collection || (collection && collection.hide_items_thumbnail_on_lists != 'yes')" v-if="!collection || (collection && collection.hide_items_thumbnail_on_lists != 'yes')"
aria-controls="items-list-results" aria-controls="items-list-results"
@ -773,7 +787,7 @@
}, },
adminViewMode() { adminViewMode() {
const currentAdminViewMode = this.getAdminViewMode(); const currentAdminViewMode = this.getAdminViewMode();
return ['table', 'cards', 'records', 'grid', 'masonry', 'list', 'map'].indexOf(currentAdminViewMode) >= 0 ? currentAdminViewMode : 'table'; return ['table', 'cards', 'records', 'grid', 'masonry', 'list', 'map', 'mosaic'].indexOf(currentAdminViewMode) >= 0 ? currentAdminViewMode : 'table';
}, },
orderByName() { orderByName() {
const metadatumName = this.$orderByHelper.getOrderByMetadatumName({ const metadatumName = this.$orderByHelper.getOrderByMetadatumName({
@ -994,6 +1008,7 @@
existingViewMode == 'list' || existingViewMode == 'list' ||
existingViewMode == 'grid' || existingViewMode == 'grid' ||
existingViewMode == 'masonry'|| existingViewMode == 'masonry'||
existingViewMode == 'mosaic'||
existingViewMode == 'map') existingViewMode == 'map')
this.$eventBusSearch.setInitialAdminViewMode(this.$userPrefs.get(prefsAdminViewMode)); this.$eventBusSearch.setInitialAdminViewMode(this.$userPrefs.get(prefsAdminViewMode));
else else

View File

@ -140,7 +140,7 @@
.metadata-title { .metadata-title {
flex-shrink: 0; flex-shrink: 0;
padding: 0.6em 7em 0.5em 2.75em; padding: 0.6em 7em 0.5em 2.75em;
min-height: 40px; min-height: 1.5em;
position: relative; position: relative;
font-size: 1em !important; font-size: 1em !important;
text-overflow: ellipsis; text-overflow: ellipsis;

View File

@ -27,6 +27,7 @@
:deep(img) { :deep(img) {
height: auto; height: auto;
max-width: 100%;
} }
&:hover { &:hover {
@ -111,7 +112,7 @@
.metadata-title { .metadata-title {
flex-shrink: 0; flex-shrink: 0;
padding: 0.5em 7em 0.5em 2.75em; padding: 0.5em 7em 0.5em 2.75em;
min-height: 40px; min-height: 1.5em;
position: relative; position: relative;
font-size: 1em !important; font-size: 1em !important;
text-overflow: ellipsis; text-overflow: ellipsis;

View File

@ -264,6 +264,7 @@
z-index: 1; z-index: 1;
} }
.metadata-title { .metadata-title {
box-sizing: border-box;
flex-shrink: 0; flex-shrink: 0;
padding: 0.25em 1.125em; padding: 0.25em 1.125em;
font-size: 1.0em !important; font-size: 1.0em !important;

View File

@ -110,6 +110,7 @@
:deep(img) { :deep(img) {
height: auto; height: auto;
max-width: 100%;
} }
&:hover:not(.skeleton) { &:hover:not(.skeleton) {
@ -191,7 +192,7 @@
flex-shrink: 0; flex-shrink: 0;
margin: 0px 6px 0px 24px; margin: 0px 6px 0px 24px;
padding: 8px 1em; padding: 8px 1em;
min-height: 41px; min-height: calc(1em + 8px);
cursor: pointer; cursor: pointer;
position: relative; position: relative;

View File

@ -0,0 +1,221 @@
.tainacan-mosaic-container,
.tainacan-mosaic-container--skeleton {
--tainacan-mosaic-view-mode-gap: 12px;
--tainacan-mosaic-view-mode-min-height: 180px;
display: flex;
flex-wrap: wrap;
grid-gap: calc(2em + 16px) var(--tainacan-mosaic-view-mode-gap, 12px);
list-style: none;
height: auto;
min-height: 0px; /* While most view modes set this to 50vh, this causes a bug for this one if there are few items to display */
margin: 0;
margin-bottom: calc(2em + 16px + var(--tainacan-container-padding));
padding: 0;
list-style: none;
animation-name: appear;
animation-duration: 0.5s;
&::after {
content: " ";
flex-grow: 1000000000;
}
& > li,
& > .skeleton {
flex-grow: calc(var(--tainacan-mosaic-item-width, 300) * (100000 / var(--tainacan-mosaic-item-height, 300)));
flex-basis: calc(var(--tainacan-mosaic-view-mode-min-height, 180) * (var(--tainacan-mosaic-item-width, 300) / var(--tainacan-mosaic-item-height, 300)));
aspect-ratio: var(--tainacan-mosaic-item-width, 300) / var(--tainacan-mosaic-item-height, 300);
position: relative;
margin: 0 !important;
padding: 0 !important;
:deep(img) {
position: absolute;
width: auto;
height: 100%;
}
:deep(canvas.child) {
aspect-ratio: var(--tainacan-mosaic-item-width, 300) / var(--tainacan-mosaic-item-height, 300);
}
&:hover:not(.skeleton) {
background-color: var(--tainacan-item-heading-hover-background-color);
}
&.selected-mosaic-item:not(.skeleton) {
background-color: var(--tainacan-turquoise1);
}
&:not(.skeleton) {
background-color: var(--tainacan-item-background-color);
}
.tainacan-mosaic-item {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
}
.mosaic-item-checkbox {
position: absolute;
margin-top: 8px;
margin-left: 1em;
z-index: 9;
}
.actions-area {
position: relative;
float: right;
width: 100%;
height: 0px;
display: flex;
justify-content: flex-end;
visibility: hidden;
overflow: hidden;
opacity: 0.0;
padding: 8px;
margin-top: -25px;
background-color: var(--tainacan-item-heading-hover-background-color);
transition: visibility 0.2s ease, opacity 0.3s ease, height 0.2s ease, margin-top 0.1s ease;
a {
margin-left: 8px;
opacity: 0;
transition: opacity 0.3s ease-in;
}
}
&:hover,
&:focus,
&:focus-within,
&:focus-visible,
& > a:focus,
& > a:focus-within,
& > a:focus-visible {
background: var(--tainacan-item-hover-background-color);
.actions-area {
visibility: visible;
opacity: 1.0;
height: 42px;
margin-top: 0px;
background-color: var(--tainacan-item-heading-hover-background-color);
a {
opacity: 1;
}
}
}
&.selected-mosaic-item {
.actions-area {
background-color: var(--tainacan-turquoise1);
}
}
.tainacan-mosaic-item-thumbnail {
background-size: cover;
background-color: var(--tainacan-item-background-color);
background-blend-mode: multiply;
border-radius: 0px;
min-width: 100%;
display: flex;
justify-content: center;
&:hover {
cursor: pointer;
}
}
.metadata-title {
display: flex;
align-items: center;
justify-content: space-between;
flex-shrink: 0;
margin: 0;
padding: 8px 1em;
min-height: calc(1em + 8px);
width: 100%;
cursor: pointer;
position: absolute;
top: 100%;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
p {
font-size: 0.875em !important;
color: var(--tainacan-heading-color) !important;
text-align: left !important;
margin-bottom: 0 !important;
line-height: 1.5em;
word-break: break-word;
margin: 0;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
}
.slideshow-icon {
color: var(--tainacan-info-color);
position: absolute;
right: 5px;
top: 8px;
transform: scale(0.0);
transition: transform 0.2s ease;
}
.icon:not(.slideshow-icon) {
float: left;
margin-top: -1px;
}
}
&:hover,
&:focus,
&:focus-within,
&:focus-visible,
& > a:focus,
& > a:focus-within,
& > a:focus-visible {
.metadata-title {
background: var(--tainacan-item-heading-hover-background-color);
.slideshow-icon {
transform: scale(1.0);
}
}
}
}
&.hide-items-selection {
.tainacan-mosaic-item {
&:hover { background-color: transparent; cursor: default; }
&:hover .tainacan-mosaic-item-thumbnail { cursor: default; }
}
}
&.has-title-inside {
gap: var(--tainacan-mosaic-view-mode-gap, 12px);
& > li {
.metadata-title {
top: unset;
bottom: 0;
white-space: wrap;
overflow: visible;
text-overflow: none;
p {
white-space: wrap;
overflow: visible;
text-overflow: none;
}
}
&:not(:hover):not(:focus):not(:focus-within):not(:focus-visible),
& > a:not(:hover):not(:focus):not(:focus-within):not(:focus-visible) {
.metadata-title {
display: none;
}
}
}
}
}

View File

@ -75,6 +75,7 @@
:deep(img) { :deep(img) {
height: auto; height: auto;
max-width: 100%;
} }
&:hover { &:hover {
@ -150,7 +151,7 @@
flex-shrink: 0; flex-shrink: 0;
padding: 0.5em 7em 0.5em 2.75em; padding: 0.5em 7em 0.5em 2.75em;
font-size: 1.0em !important; font-size: 1.0em !important;
min-height: 40px; min-height: 1.5em;
position: relative; position: relative;
text-overflow: ellipsis; text-overflow: ellipsis;
white-space: nowrap; white-space: nowrap;

View File

@ -28,7 +28,7 @@ export default (element) => {
let registeredViewModes = let registeredViewModes =
( tainacan_blocks && tainacan_blocks.registered_view_modes && tainacan_blocks.registered_view_modes.length ) ? ( tainacan_blocks && tainacan_blocks.registered_view_modes && tainacan_blocks.registered_view_modes.length ) ?
tainacan_blocks.registered_view_modes : tainacan_blocks.registered_view_modes :
[ 'table', 'cards', 'records', 'masonry', 'list', 'map' ]; [ 'table', 'cards', 'records', 'masonry', 'mosaic', 'list', 'map'];
// At first, we consider that all registered view modes are included. // At first, we consider that all registered view modes are included.
let possibleViewModes = registeredViewModes.filter((aViewMode) => aViewMode === 'slideshow'); let possibleViewModes = registeredViewModes.filter((aViewMode) => aViewMode === 'slideshow');

View File

@ -0,0 +1,137 @@
<template>
<div class="table-container">
<div class="table-wrapper">
<!-- Empty result placeholder, rendered in the parent component -->
<slot />
<!-- SKELETON LOADING -->
<div
v-if="isLoading"
class="tainacan-mosaic-container--skeleton">
<div
v-for="item in 12"
:key="item"
:style="{
'--tainacan-mosaic-item-width': randomHeightForMosaicItem(),
'--tainacan-mosaic-item-height': randomHeightForMosaicItem()
}"
class="skeleton" />
</div>
<!-- MOSAIC VIEW MODE -->
<ul
v-if="!isLoading"
class="tainacan-mosaic-container">
<li
v-for="(item, index) of items"
:key="index"
:style="{
'--tainacan-mosaic-item-width': getAcceptableWidthBasedOnRatio(item['thumbnail'], 'tainacan-large-full', 300),
'--tainacan-mosaic-item-height': $thumbHelper.getHeight(item['thumbnail'], 'tainacan-large-full', 300)
}"
:data-tainacan-item-id="item.id"
:aria-setsize="totalItems"
:aria-posinset="getPosInSet(index)">
<a
class="tainacan-mosaic-item"
:href="getItemLink(item.url, index)">
<!-- JS-side hook for extra content -->
<div
v-if="hasBeforeHook()"
class="faceted-search-hook faceted-search-hook-item-before"
v-html="getBeforeHook(item)" />
<!-- Thumbnail -->
<blur-hash-image
v-if="item.thumbnail != undefined"
class="tainacan-mosaic-item-thumbnail"
:width="$thumbHelper.getWidth(item['thumbnail'], 'tainacan-large-full', 320)"
:height="$thumbHelper.getHeight(item['thumbnail'], 'tainacan-large-full', 320)"
:hash="$thumbHelper.getBlurhashString(item['thumbnail'], 'tainacan-large-full')"
:src="$thumbHelper.getSrc(item['thumbnail'], 'tainacan-large-full', item.document_mimetype)"
:srcset="$thumbHelper.getSrcSet(item['thumbnail'], 'tainacan-large-full', item.document_mimetype)"
:alt="item.thumbnail_alt ? item.thumbnail_alt : $i18n.get('label_thumbnail')"
:transition-duration="500"
/>
<!-- Title -->
<div
class="metadata-title"
:style="isSlideshowViewModeEnabled ? 'padding-right: 2rem;' : ''">
<p
v-tooltip="{
delay: {
show: 750,
hide: 100,
},
content: item.title != undefined ? item.title : '',
html: true,
placement: 'auto-start',
popperClass: ['tainacan-tooltip', 'tooltip']
}"
v-html="item.title != undefined ? item.title : ''" />
<span
v-if="isSlideshowViewModeEnabled"
v-tooltip="{
delay: {
show: 500,
hide: 100,
},
content: $i18n.get('label_see_on_fullscreen'),
placement: 'auto-start',
popperClass: ['tainacan-tooltip', 'tooltip']
}"
class="icon slideshow-icon"
@click.prevent="starSlideshowFromHere(index)">
<i class="tainacan-icon tainacan-icon-viewgallery tainacan-icon-1-125em" />
</span>
</div>
<!-- JS-side hook for extra content -->
<div
v-if="hasAfterHook()"
class="faceted-search-hook faceted-search-hook-item-after"
v-html="getAfterHook(item)" />
</a>
</li>
</ul>
</div>
</div>
</template>
<script>
import { viewModesMixin } from '../js/view-modes-mixin.js';
export default {
name: 'ViewModeMosaic',
mixins: [
viewModesMixin
],
methods: {
randomHeightForMosaicItem() {
let min = 120;
let max = 280;
return Math.floor(Math.random()*(max-min+1)+min);
},
getAcceptableWidthBasedOnRatio(thumbnail, size, defaultSize) {
const width = this.$thumbHelper.getWidth(thumbnail, size, defaultSize);
const height = this.$thumbHelper.getHeight(thumbnail, size, defaultSize);
return (width / height) > 0.7 ? width : ( height * 0.7 );
}
}
}
</script>
<style lang="scss" scoped>
@import "../../../../../admin/scss/_view-mode-mosaic.scss";
</style>

View File

@ -179,6 +179,7 @@
width: 100%; width: 100%;
max-width: 100% !important; max-width: 100% !important;
margin: 0; margin: 0;
box-sizing: border-box;
} }
.filters-components-list { .filters-components-list {

View File

@ -77,7 +77,7 @@ export default (element) => {
const registeredViewModes = const registeredViewModes =
( tainacan_plugin && tainacan_plugin.registered_view_modes && tainacan_plugin.registered_view_modes.length ) ? ( tainacan_plugin && tainacan_plugin.registered_view_modes && tainacan_plugin.registered_view_modes.length ) ?
tainacan_plugin.registered_view_modes : tainacan_plugin.registered_view_modes :
[ 'table', 'cards', 'records', 'masonry', 'slideshow', 'list', 'map' ]; [ 'table', 'cards', 'records', 'masonry', 'mosaic', 'slideshow', 'list', 'map'];
// At first, we consider that all registered view modes are included. // At first, we consider that all registered view modes are included.
let possibleViewModes = registeredViewModes; let possibleViewModes = registeredViewModes;

View File

@ -336,6 +336,7 @@ return apply_filters( 'tainacan-i18n', [
'label_grid' => __( 'Thumbnails', 'tainacan' ), 'label_grid' => __( 'Thumbnails', 'tainacan' ),
'label_table' => __( 'Table', 'tainacan' ), 'label_table' => __( 'Table', 'tainacan' ),
'label_cards' => __( 'Cards', 'tainacan' ), 'label_cards' => __( 'Cards', 'tainacan' ),
'label_mosaic' => __( 'Mosaic', 'tainacan' ),
/* translators: The 'records' view mode, in the sense of a catalog file */ /* translators: The 'records' view mode, in the sense of a catalog file */
'label_records' => __( 'Records', 'tainacan' ), 'label_records' => __( 'Records', 'tainacan' ),
'label_masonry' => __( 'Masonry', 'tainacan' ), 'label_masonry' => __( 'Masonry', 'tainacan' ),