carousel, display logs
This commit is contained in:
@ -174,16 +174,16 @@ a.navbar-item.is-active {
|
||||
--highlight-color-2-alpha: rgb(0, 0, 254, 0.7);
|
||||
--highlight-color-2-grey: rgba(50, 200, 200, 1);
|
||||
|
||||
--header-height: 30em;
|
||||
--header-height: 30rem;
|
||||
|
||||
--heading-height: 30em;
|
||||
--heading-height: 30rem;
|
||||
--heading-title-bg-color: rgba(255, 255, 0, 1);
|
||||
--heading-bg-color: var(--highlight-color);
|
||||
--heading-bg-highlight-color: var(--highlight-color-2);
|
||||
--heading-font-family: default;
|
||||
|
||||
--preview-cover-size: 18em;
|
||||
--preview-cover-small-size: 10em;
|
||||
--preview-cover-size: 24rem;
|
||||
--preview-cover-small-size: 10rem;
|
||||
|
||||
--player-panel-bg: var(--highlight-color);
|
||||
--player-bar-bg: var(--highlight-color);
|
||||
@ -262,6 +262,9 @@ h1, h2, h3, h4, h5, h6, .heading, .title, .subtitle {
|
||||
.no-border { border: 0px !important; }
|
||||
|
||||
// -- colors
|
||||
.highlight-color { color: var(--highlight-color); }
|
||||
.highlight-color-2 { color: var(--highlight-color-2); }
|
||||
|
||||
.is-success {
|
||||
background-color: $green !important;
|
||||
border-color: $green-dark !important;
|
||||
@ -328,25 +331,6 @@ h1, h2, h3, h4, h5, h6, .heading, .title, .subtitle {
|
||||
border-color: var(--highlight-color-2-alpha);
|
||||
}
|
||||
|
||||
.actions &, &.action {
|
||||
background-color: var(--highlight-color);
|
||||
justify-content: center;
|
||||
min-width: 2rem;
|
||||
|
||||
.not-selected { opacity: 0.6; }
|
||||
|
||||
|
||||
.icon { margin: 0em !important; }
|
||||
|
||||
label {
|
||||
margin-left: $mp-2;
|
||||
}
|
||||
|
||||
&:hover, .selected {
|
||||
color: var(--highlight-color-2) !important;
|
||||
}
|
||||
}
|
||||
|
||||
.dropdown-trigger {
|
||||
border-radius: 1.5em;
|
||||
}
|
||||
@ -366,9 +350,34 @@ h1, h2, h3, h4, h5, h6, .heading, .title, .subtitle {
|
||||
}
|
||||
|
||||
.actions {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: $mp-3;
|
||||
justify-content: right;
|
||||
|
||||
&.no-label label {
|
||||
display: none;
|
||||
}
|
||||
|
||||
button, .action {
|
||||
background-color: var(--highlight-color);
|
||||
justify-content: center;
|
||||
min-width: 2rem;
|
||||
|
||||
.not-selected { opacity: 0.6; }
|
||||
|
||||
|
||||
.icon { margin: 0em !important; }
|
||||
|
||||
label {
|
||||
margin-left: $mp-2;
|
||||
}
|
||||
|
||||
&:hover, .selected {
|
||||
color: var(--highlight-color-2) !important;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@ -379,6 +388,7 @@ h1, h2, h3, h4, h5, h6, .heading, .title, .subtitle {
|
||||
&.is-3 {
|
||||
margin-top: $mp-3;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.heading {
|
||||
@ -406,6 +416,10 @@ h1, h2, h3, h4, h5, h6, .heading, .title, .subtitle {
|
||||
display: flex;
|
||||
background-color: var(--highlight-color);
|
||||
|
||||
&:empty {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.nav-item {
|
||||
padding: $mp-3;
|
||||
flex-grow: 1;
|
||||
@ -513,16 +527,11 @@ nav li {
|
||||
background-size: cover;
|
||||
margin-bottom: $mp-6 !important;
|
||||
|
||||
&.preview-card {
|
||||
&:not(.wide) {
|
||||
max-width: 30em;
|
||||
}
|
||||
}
|
||||
|
||||
&.preview-item {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
// FIXME: remove
|
||||
&.columns, .headings.columns {
|
||||
margin-left: 0em;
|
||||
margin-right: 0em;
|
||||
@ -553,123 +562,21 @@ nav li {
|
||||
}
|
||||
}
|
||||
|
||||
.preview.comment {
|
||||
.title { font-size: $text-size-bigger; }
|
||||
.subtitle { font-size: $text-size; }
|
||||
}
|
||||
|
||||
|
||||
.list-item {
|
||||
width: 100%;
|
||||
|
||||
&:not(:first-child) {
|
||||
margin-top: calc($mp-4 / 2);
|
||||
}
|
||||
|
||||
.headings {
|
||||
padding-top: 0em;
|
||||
margin-bottom: $mp-4 !important;
|
||||
background-color: var(--heading-bg-color);
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.media-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: var(--preview-cover-small-size);
|
||||
|
||||
.content {
|
||||
flex-grow: 1;
|
||||
margin-bottom: auto;
|
||||
}
|
||||
|
||||
.actions {
|
||||
flex-grow: unset;
|
||||
text-align: right;
|
||||
margin-top: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ---- cards
|
||||
.preview-wide {
|
||||
height: var(--preview-cover-size);
|
||||
|
||||
&:not(.header) .headings {
|
||||
box-shadow: 0em 0em 1em rgba(0,0,0,0.2);
|
||||
}
|
||||
|
||||
& .headings {
|
||||
width: var(--preview-cover-size);
|
||||
flex-grow: 0;
|
||||
margin-right: $mp-4;
|
||||
}
|
||||
|
||||
& .content {
|
||||
font-size: $text-size-big;
|
||||
flex-grow: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.preview-card {
|
||||
height: var(--preview-cover-size);
|
||||
min-width: var(--preview-cover-size);
|
||||
|
||||
&:not(.header) {
|
||||
box-shadow: 0em 0em 1em rgba(0,0,0,0.2);
|
||||
}
|
||||
|
||||
.card-grid & {
|
||||
min-width: unset;
|
||||
}
|
||||
|
||||
.actions {
|
||||
position: absolute;
|
||||
bottom: $mp-3;
|
||||
right: $mp-3;
|
||||
|
||||
label {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.preview-cover {
|
||||
background-size: cover;
|
||||
background-color: transparent !important;
|
||||
height: var(--preview-cover-size);
|
||||
width: var(--preview-cover-size);
|
||||
min-width: var(--preview-cover-size);
|
||||
|
||||
&.small {
|
||||
min-width: unset;
|
||||
height: var(--preview-cover-small-size);
|
||||
width: var(--preview-cover-small-size) !important;
|
||||
min-width: var(--preview-cover-small-size);
|
||||
}
|
||||
}
|
||||
|
||||
.preview-card-headings {
|
||||
width: 100%;
|
||||
min-width: var(--preview-cover-size);
|
||||
min-height: 100%;
|
||||
|
||||
padding-top: $mp-3;
|
||||
|
||||
& > div:not(:last-child),
|
||||
& .column > div {
|
||||
margin-bottom: $mp-3;
|
||||
}
|
||||
|
||||
preview-header:not(.no-cover) & .heading {
|
||||
margin-bottom: $mp-3;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
.preview-header {
|
||||
width: 100%;
|
||||
|
||||
@ -695,6 +602,120 @@ nav li {
|
||||
}
|
||||
|
||||
|
||||
// ---- specific
|
||||
.preview.comment {
|
||||
.title { font-size: $text-size-bigger; }
|
||||
.subtitle { font-size: $text-size; }
|
||||
}
|
||||
|
||||
|
||||
// ---- list
|
||||
.list-item {
|
||||
width: 100%;
|
||||
|
||||
&:not(:first-child) {
|
||||
margin-top: calc($mp-4 / 2);
|
||||
}
|
||||
|
||||
.headings {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
padding-top: 0em;
|
||||
margin-bottom: $mp-4 !important;
|
||||
background-color: var(--heading-bg-color);
|
||||
|
||||
.title {
|
||||
flex-grow: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.media-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.list-item:not(.no-cover) & {
|
||||
min-height: var(--preview-cover-small-size);
|
||||
}
|
||||
|
||||
.content {
|
||||
flex-grow: 1;
|
||||
margin-bottom: auto;
|
||||
}
|
||||
|
||||
.actions {
|
||||
flex-grow: unset;
|
||||
text-align: right;
|
||||
margin-top: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ---- wide
|
||||
.preview-wide {
|
||||
height: var(--preview-cover-size);
|
||||
|
||||
&:not(.header) .headings {
|
||||
box-shadow: 0em 0em 1em rgba(0,0,0,0.2);
|
||||
}
|
||||
|
||||
& .headings {
|
||||
width: var(--preview-cover-size);
|
||||
flex-grow: 0;
|
||||
margin-right: $mp-4;
|
||||
}
|
||||
|
||||
& .content {
|
||||
font-size: $text-size-big;
|
||||
flex-grow: 1;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ---- card
|
||||
.preview-card {
|
||||
height: var(--preview-cover-size);
|
||||
width: var(--preview-cover-size);
|
||||
|
||||
&:not(.header) {
|
||||
box-shadow: 0em 0em 1em rgba(0,0,0,0.2);
|
||||
}
|
||||
|
||||
.card-grid & {
|
||||
min-width: unset;
|
||||
}
|
||||
|
||||
.actions {
|
||||
position: absolute;
|
||||
bottom: $mp-3;
|
||||
right: $mp-3;
|
||||
|
||||
label {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.preview-card-headings {
|
||||
padding-top: $mp-3;
|
||||
|
||||
& > div:not(:last-child),
|
||||
& .column > div {
|
||||
margin-bottom: $mp-3;
|
||||
}
|
||||
|
||||
preview-header:not(.no-cover) & .heading {
|
||||
margin-bottom: $mp-3;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
.header {
|
||||
background-size: cover;
|
||||
|
||||
@ -723,34 +744,54 @@ nav li {
|
||||
}
|
||||
|
||||
|
||||
// -- program grid
|
||||
// ---- card grid
|
||||
.card-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr 1fr;
|
||||
gap: $mp-4;
|
||||
}
|
||||
|
||||
.a-carousel-container {
|
||||
width: 100%;
|
||||
gap: $mp-4;
|
||||
transition: margin-left 1s;
|
||||
|
||||
@media screen and (max-width: $screen-wide) {
|
||||
.preview-card:not(.preview-header) {
|
||||
height: 20em !important;
|
||||
> * {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.card-grid .preview-card {
|
||||
height: 20em;
|
||||
.a-carousel-button-container {
|
||||
button, .button {
|
||||
z-index:1000;
|
||||
position: absolute;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
top: calc(var(--preview-cover-size) - $text-size-medium);
|
||||
|
||||
&.prev { left: -$mp-3e; }
|
||||
&.next { right: -$mp-3e; }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ---- responsive
|
||||
@media screen and (max-width: $screen-wide) {
|
||||
:root {
|
||||
--preview-cover-size: 18em;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: $screen-normal) {
|
||||
.card-grid {
|
||||
grid-template-columns: 1fr 1fr;
|
||||
|
||||
.preview-card:nth-child(3) {
|
||||
display: none;
|
||||
}
|
||||
.page .container {
|
||||
margin-left: $mp-4;
|
||||
margin-right: $mp-4;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: $screen-small) {
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ---- player
|
||||
|
184
assets/src/components/ACarousel.vue
Normal file
184
assets/src/components/ACarousel.vue
Normal file
@ -0,0 +1,184 @@
|
||||
<template>
|
||||
<div class="a-carousel-button-container" v-if="showPrevButton">
|
||||
<button :class="[buttonClass, 'prev']" aria-label="Go left" @click="prev">
|
||||
<span class="icon">
|
||||
<i :class="leftButtonIcon"></i>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="a-carousel-button-container" v-if="showNextButton">
|
||||
<button :class="[buttonClass, 'next']" aria-label="Go left" @click="next">
|
||||
<span class="icon">
|
||||
<i :class="rightButtonIcon"></i>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
<div ref="viewport" class="a-carousel-viewport">
|
||||
<section ref="container" :class="['a-carousel-container', containerClass]">
|
||||
<slot name="default"></slot>
|
||||
</section>
|
||||
</div>
|
||||
</template>
|
||||
<style scoped>
|
||||
.a-carousel-viewport {
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.a-carousel-container {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: left;
|
||||
}
|
||||
|
||||
.a-carousel-container > * {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
</style>
|
||||
<script>
|
||||
import {ref} from 'vue'
|
||||
|
||||
export default {
|
||||
setup() {
|
||||
return {
|
||||
viewport: ref(null),
|
||||
container: ref(null),
|
||||
}
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
cards: [],
|
||||
index: 0,
|
||||
refresh_: 0,
|
||||
}
|
||||
},
|
||||
|
||||
props: {
|
||||
cardSelector: {type: String, default: ''},
|
||||
containerClass: {type: String, default: ''},
|
||||
buttonClass: {type: String, default: 'button'},
|
||||
leftButtonIcon: {type: String, default: "fas fa-chevron-left"},
|
||||
rightButtonIcon: {type: String, default: "fas fa-chevron-right"},
|
||||
},
|
||||
|
||||
computed: {
|
||||
card() { return this.cards()[this.index] },
|
||||
showPrevButton() {
|
||||
return this.index > 0
|
||||
},
|
||||
showNextButton() {
|
||||
if(!this.cards || this.cards.length <= 1)
|
||||
return false
|
||||
|
||||
let { count } = this.visibility
|
||||
return (this.index + count) < this.cards.length
|
||||
},
|
||||
|
||||
visibility() {
|
||||
// force refresh on index
|
||||
[this.index, this.refresh_]
|
||||
|
||||
if(!this.cards)
|
||||
return {min: -1, max: -1, count: 0};
|
||||
|
||||
const vOff = this.offset(this.$refs.viewport)
|
||||
var [min, max] = [-1, -1]
|
||||
for(let at=0; at < this.cards.length; at++) {
|
||||
const card = this.cards[at]
|
||||
const cOff = this.offset(card)
|
||||
const visible = vOff.min <= cOff.min && vOff.max >= cOff.max
|
||||
if(visible) {
|
||||
if(min === -1)
|
||||
min = parseInt(at)
|
||||
max = parseInt(at)
|
||||
}
|
||||
}
|
||||
if(max !== -1)
|
||||
max++
|
||||
return {
|
||||
min, max,
|
||||
count: (min !== -1) ? max-min : 0
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
offset(el, parent=null) {
|
||||
const rect = el.getBoundingClientRect()
|
||||
const off = {min: rect.left, max: rect.right }
|
||||
if(parent === null)
|
||||
return off
|
||||
|
||||
const pOff = this.offset(parent)
|
||||
return {
|
||||
min: off.min - pOff.min,
|
||||
max: off.max - pOff.max,
|
||||
}
|
||||
},
|
||||
|
||||
getCards() {
|
||||
if(!this.cardSelector)
|
||||
return this.$refs.container.children
|
||||
return this.$refs.container.querySelectorAll(this.cardSelector)
|
||||
},
|
||||
|
||||
selectIndex(index, relative=false) {
|
||||
if(relative)
|
||||
index = this.index + index
|
||||
|
||||
index = Math.min(this.cards.length, index)
|
||||
const el = this.cards[index]
|
||||
const elOff = this.offset(el, this.$refs.container)
|
||||
this.$refs.container.style.marginLeft = `-${elOff.min}px`
|
||||
this.index = index;
|
||||
return el
|
||||
},
|
||||
|
||||
next() {
|
||||
if(!this.visibility.count)
|
||||
return
|
||||
let {count} = this.visibility
|
||||
let at = Math.min(
|
||||
count === 1 ? this.index+count : this.index+count-1,
|
||||
this.cards.length-count
|
||||
)
|
||||
this.selectIndex(at)
|
||||
},
|
||||
|
||||
prev() {
|
||||
if(!this.visibility.count)
|
||||
return
|
||||
const {min, count} = this.visibility
|
||||
let at = Math.max(0, min-count)
|
||||
if(min < 0 || count <= 0)
|
||||
return
|
||||
this.selectIndex(at)
|
||||
},
|
||||
|
||||
refresh() {
|
||||
this.cards = this.getCards()
|
||||
this.selectIndex(this.index)
|
||||
this.refresh_++
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
mounted() {
|
||||
this.observer = [
|
||||
new MutationObserver(() => this.refresh()),
|
||||
new ResizeObserver(() => this.refresh())
|
||||
]
|
||||
this.observer[0].observe(this.$refs.container, {"childList": true})
|
||||
this.observer[1].observe(this.$refs.container)
|
||||
this.refresh()
|
||||
},
|
||||
|
||||
unmounted() {
|
||||
for(var observer of this.observers)
|
||||
observer.disconnect()
|
||||
}
|
||||
}
|
||||
</script>
|
@ -1,4 +1,5 @@
|
||||
import AAutocomplete from './AAutocomplete.vue'
|
||||
import ACarousel from './ACarousel.vue'
|
||||
import ADropdown from "./ADropdown.vue"
|
||||
import AEpisode from './AEpisode.vue'
|
||||
import AList from './AList.vue'
|
||||
@ -15,7 +16,7 @@ import AStreamer from './AStreamer.vue'
|
||||
* Core components
|
||||
*/
|
||||
export const base = {
|
||||
AAutocomplete, ADropdown, AEpisode, AList, APage, APlayer, APlaylist,
|
||||
AAutocomplete, ACarousel, ADropdown, AEpisode, AList, APage, APlayer, APlaylist,
|
||||
AProgress, ASoundItem,
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user