diff --git a/src/classes/repositories/class-tainacan-collections.php b/src/classes/repositories/class-tainacan-collections.php index 8a39617cd..d547cfefa 100644 --- a/src/classes/repositories/class-tainacan-collections.php +++ b/src/classes/repositories/class-tainacan-collections.php @@ -132,7 +132,7 @@ class Collections extends Repository { 'title' => __( 'Enabled view modes', 'tainacan' ), 'type' => 'array', 'description' => __( 'Which visualization modes will be available for the public to choose from', 'tainacan' ), - 'default' => [ 'table', 'cards' ], + 'default' => [ 'table', 'cards', 'masonry' ], 'items' => [ 'type' => 'string' ], //'validation' => v::stringType(), ], diff --git a/src/classes/theme-helper/class-tainacan-theme-helper.php b/src/classes/theme-helper/class-tainacan-theme-helper.php index 051499878..5c422ff8c 100644 --- a/src/classes/theme-helper/class-tainacan-theme-helper.php +++ b/src/classes/theme-helper/class-tainacan-theme-helper.php @@ -2485,5 +2485,23 @@ class Theme_Helper { 'requires_thumbnail' => false, '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' => '', + 'type' => 'component', + 'implements_skeleton' => true, + 'placeholder_template' => '' + ]); } } diff --git a/src/views/admin/components/lists/items-list.vue b/src/views/admin/components/lists/items-list.vue index da167dd1d..043399001 100644 --- a/src/views/admin/components/lists/items-list.vue +++ b/src/views/admin/components/lists/items-list.vue @@ -1979,6 +1979,181 @@ + + + @@ -2674,6 +2849,12 @@ export default { 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/_view-mode-cards.scss"; @import "../../scss/_view-mode-masonry.scss"; + @import "../../scss/_view-mode-mosaic.scss"; @import "../../scss/_view-mode-grid.scss"; @import "../../scss/_view-mode-records.scss"; @import "../../scss/_view-mode-list.scss"; diff --git a/src/views/admin/pages/lists/items-page.vue b/src/views/admin/pages/lists/items-page.vue index 6a7578bc1..8f9738e7d 100644 --- a/src/views/admin/pages/lists/items-page.vue +++ b/src/views/admin/pages/lists/items-page.vue @@ -226,13 +226,13 @@ show: 500, 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, placement: 'auto-start', popperClass: ['tainacan-tooltip', 'tooltip', isRepositoryLevel ? 'tainacan-repository-tooltip' : ''] }" :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" aria-role="list" trap-focus> @@ -383,12 +383,15 @@ {{ $i18n.get('label_cards') }} + + + + + {{ $i18n.get('label_mosaic') }} + = 0 ? currentAdminViewMode : 'table'; + return ['table', 'cards', 'records', 'grid', 'masonry', 'list', 'map', 'mosaic'].indexOf(currentAdminViewMode) >= 0 ? currentAdminViewMode : 'table'; }, orderByName() { const metadatumName = this.$orderByHelper.getOrderByMetadatumName({ @@ -994,6 +1008,7 @@ existingViewMode == 'list' || existingViewMode == 'grid' || existingViewMode == 'masonry'|| + existingViewMode == 'mosaic'|| existingViewMode == 'map') this.$eventBusSearch.setInitialAdminViewMode(this.$userPrefs.get(prefsAdminViewMode)); else diff --git a/src/views/admin/scss/_view-mode-cards.scss b/src/views/admin/scss/_view-mode-cards.scss index 8197ab5f7..53a9ff59f 100644 --- a/src/views/admin/scss/_view-mode-cards.scss +++ b/src/views/admin/scss/_view-mode-cards.scss @@ -140,7 +140,7 @@ .metadata-title { flex-shrink: 0; padding: 0.6em 7em 0.5em 2.75em; - min-height: 40px; + min-height: 1.5em; position: relative; font-size: 1em !important; text-overflow: ellipsis; diff --git a/src/views/admin/scss/_view-mode-list.scss b/src/views/admin/scss/_view-mode-list.scss index 533184739..b643e0071 100644 --- a/src/views/admin/scss/_view-mode-list.scss +++ b/src/views/admin/scss/_view-mode-list.scss @@ -27,6 +27,7 @@ :deep(img) { height: auto; + max-width: 100%; } &:hover { @@ -111,7 +112,7 @@ .metadata-title { flex-shrink: 0; padding: 0.5em 7em 0.5em 2.75em; - min-height: 40px; + min-height: 1.5em; position: relative; font-size: 1em !important; text-overflow: ellipsis; diff --git a/src/views/admin/scss/_view-mode-map.scss b/src/views/admin/scss/_view-mode-map.scss index 90250ff87..d703b768a 100644 --- a/src/views/admin/scss/_view-mode-map.scss +++ b/src/views/admin/scss/_view-mode-map.scss @@ -264,6 +264,7 @@ z-index: 1; } .metadata-title { + box-sizing: border-box; flex-shrink: 0; padding: 0.25em 1.125em; font-size: 1.0em !important; diff --git a/src/views/admin/scss/_view-mode-masonry.scss b/src/views/admin/scss/_view-mode-masonry.scss index c3a5e8855..b301b1b5e 100644 --- a/src/views/admin/scss/_view-mode-masonry.scss +++ b/src/views/admin/scss/_view-mode-masonry.scss @@ -110,6 +110,7 @@ :deep(img) { height: auto; + max-width: 100%; } &:hover:not(.skeleton) { @@ -191,7 +192,7 @@ flex-shrink: 0; margin: 0px 6px 0px 24px; padding: 8px 1em; - min-height: 41px; + min-height: calc(1em + 8px); cursor: pointer; position: relative; diff --git a/src/views/admin/scss/_view-mode-mosaic.scss b/src/views/admin/scss/_view-mode-mosaic.scss new file mode 100644 index 000000000..6a81447e8 --- /dev/null +++ b/src/views/admin/scss/_view-mode-mosaic.scss @@ -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; + } + } + } + } +} \ No newline at end of file diff --git a/src/views/admin/scss/_view-mode-records.scss b/src/views/admin/scss/_view-mode-records.scss index 6d58fcfb1..0e53b5bc6 100644 --- a/src/views/admin/scss/_view-mode-records.scss +++ b/src/views/admin/scss/_view-mode-records.scss @@ -75,6 +75,7 @@ :deep(img) { height: auto; + max-width: 100%; } &:hover { @@ -150,7 +151,7 @@ flex-shrink: 0; padding: 0.5em 7em 0.5em 2.75em; font-size: 1.0em !important; - min-height: 40px; + min-height: 1.5em; position: relative; text-overflow: ellipsis; white-space: nowrap; diff --git a/src/views/gutenberg-blocks/blocks/dynamic-items-list/theme.js b/src/views/gutenberg-blocks/blocks/dynamic-items-list/theme.js index be4258f7e..fd782b60b 100644 --- a/src/views/gutenberg-blocks/blocks/dynamic-items-list/theme.js +++ b/src/views/gutenberg-blocks/blocks/dynamic-items-list/theme.js @@ -28,7 +28,7 @@ export default (element) => { let registeredViewModes = ( tainacan_blocks && tainacan_blocks.registered_view_modes && tainacan_blocks.registered_view_modes.length ) ? 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. let possibleViewModes = registeredViewModes.filter((aViewMode) => aViewMode === 'slideshow'); diff --git a/src/views/gutenberg-blocks/blocks/faceted-search/theme-search/components/view-mode-mosaic.vue b/src/views/gutenberg-blocks/blocks/faceted-search/theme-search/components/view-mode-mosaic.vue new file mode 100644 index 000000000..9b3a61a15 --- /dev/null +++ b/src/views/gutenberg-blocks/blocks/faceted-search/theme-search/components/view-mode-mosaic.vue @@ -0,0 +1,137 @@ + + + + + + + diff --git a/src/views/gutenberg-blocks/blocks/faceted-search/theme-search/scss/_layout.scss b/src/views/gutenberg-blocks/blocks/faceted-search/theme-search/scss/_layout.scss index e28c61ec8..250ae221e 100644 --- a/src/views/gutenberg-blocks/blocks/faceted-search/theme-search/scss/_layout.scss +++ b/src/views/gutenberg-blocks/blocks/faceted-search/theme-search/scss/_layout.scss @@ -179,6 +179,7 @@ width: 100%; max-width: 100% !important; margin: 0; + box-sizing: border-box; } .filters-components-list { diff --git a/src/views/gutenberg-blocks/blocks/faceted-search/theme.js b/src/views/gutenberg-blocks/blocks/faceted-search/theme.js index adbea16c2..5b9b05ccd 100644 --- a/src/views/gutenberg-blocks/blocks/faceted-search/theme.js +++ b/src/views/gutenberg-blocks/blocks/faceted-search/theme.js @@ -77,7 +77,7 @@ export default (element) => { const registeredViewModes = ( tainacan_plugin && tainacan_plugin.registered_view_modes && tainacan_plugin.registered_view_modes.length ) ? 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. let possibleViewModes = registeredViewModes; diff --git a/src/views/tainacan-i18n.php b/src/views/tainacan-i18n.php index ca131e628..7b81b20f0 100644 --- a/src/views/tainacan-i18n.php +++ b/src/views/tainacan-i18n.php @@ -336,6 +336,7 @@ return apply_filters( 'tainacan-i18n', [ 'label_grid' => __( 'Thumbnails', 'tainacan' ), 'label_table' => __( 'Table', 'tainacan' ), 'label_cards' => __( 'Cards', 'tainacan' ), + 'label_mosaic' => __( 'Mosaic', 'tainacan' ), /* translators: The 'records' view mode, in the sense of a catalog file */ 'label_records' => __( 'Records', 'tainacan' ), 'label_masonry' => __( 'Masonry', 'tainacan' ),