);
}
}
+Calendar.propTypes = {
+ metaItems: PropTypes.arrayOf(PropTypes.shape({
+ poster: PropTypes.string,
+ name: PropTypes.string.isRequired,
+ videos: PropTypes.arrayOf(PropTypes.shape({
+ id: PropTypes.string.isRequired,
+ name: PropTypes.string.isRequired,
+ description: PropTypes.string,
+ released: PropTypes.PropTypes.instanceOf(Date),
+ season: PropTypes.number,
+ episode: PropTypes.number
+ }))
+ }))
+};
+Calendar.defaultProps = {
+ metaItems: [
+ {
+ poster: 'https://images.metahub.space/poster/medium/tt2306299/img',
+ name: 'Vikings',
+ videos: [
+ { id: '1', episode: 16, name: 'Homeland', description: 'Bjorn achieves one of Ragnars dreams. Back in Kattegat, Ivar hatches a new plan while preparing for a divine arrival. In Iceland, a settler returns in a terrible state. King Alfred faces his greatest threat yet.', released: new Date(2019, 2, 1), season: 5 },
+ { id: '2', episode: 17, name: 'The Buddha', description: 'Bjorn achieves one of Ragnars dreams. Back in Kattegat, Ivar hatches a new plan while preparing for a divine arrival. In Iceland, a settler returns in a terrible state. King Alfred faces his greatest threat yet.', released: new Date(2019, 2, 2), season: 5 },
+ { id: '3', episode: 18, name: 'Boneless', description: 'Bjorn achieves one of Ragnars dreams. Back in Kattegat, Ivar hatches a new plan while preparing for a divine arrival. In Iceland, a settler returns in a terrible state. King Alfred faces his greatest threat yet.', released: new Date(2019, 2, 3), season: 5 },
+ { id: '4', episode: 19, name: 'Revenge', description: 'Bjorn achieves one of Ragnars dreams. Back in Kattegat, Ivar hatches a new plan while preparing for a divine arrival. In Iceland, a settler returns in a terrible state. King Alfred faces his greatest threat yet.', released: new Date(2019, 2, 4), season: 5 },
+ { id: '5', episode: 20, name: 'Ragnarok', description: 'Bjorn achieves one of Ragnars dreams. Back in Kattegat, Ivar hatches a new plan while preparing for a divine arrival. In Iceland, a settler returns in a terrible state. King Alfred faces his greatest threat yet.', released: new Date(2019, 2, 5), season: 5 }
+ ]
+ },
+ {
+ poster: 'https://images.metahub.space/poster/medium/tt1520211/img',
+ name: 'The Walking Dead',
+ videos: [
+ { id: '6', episode: 16, name: 'Sing me a song', description: 'Bjorn achieves one of Ragnars dreams. Back in Kattegat, Ivar hatches a new plan while preparing for a divine arrival. In Iceland, a settler returns in a terrible state. King Alfred faces his greatest threat yet.', released: new Date(2019, 2, 7), season: 5 },
+ { id: '7', episode: 17, name: 'Say yes', description: 'Bjorn achieves one of Ragnars dreams. Back in Kattegat, Ivar hatches a new plan while preparing for a divine arrival. In Iceland, a settler returns in a terrible state. King Alfred faces his greatest threat yet.', released: new Date(2019, 2, 12), season: 5 },
+ { id: '8', episode: 18, name: 'Bury me here', description: 'Bjorn achieves one of Ragnars dreams. Back in Kattegat, Ivar hatches a new plan while preparing for a divine arrival. In Iceland, a settler returns in a terrible state. King Alfred faces his greatest threat yet.', released: new Date(2019, 2, 19), season: 5 },
+ { id: '9', episode: 19, name: 'Honor', description: 'Bjorn achieves one of Ragnars dreams. Back in Kattegat, Ivar hatches a new plan while preparing for a divine arrival. In Iceland, a settler returns in a terrible state. King Alfred faces his greatest threat yet.', released: new Date(2019, 4, 3), season: 5 },
+ { id: '10', episode: 20, name: 'The Bridge', description: 'Bjorn achieves one of Ragnars dreams. Back in Kattegat, Ivar hatches a new plan while preparing for a divine arrival. In Iceland, a settler returns in a terrible state. King Alfred faces his greatest threat yet.', released: new Date(2019, 5, 10), season: 5 }
+ ]
+ },
+ {
+ poster: 'https://images.metahub.space/poster/medium/tt0944947/img',
+ name: 'Game of Thrones',
+ videos: [
+ { id: '11', episode: 16, name: 'The North Remembers', description: 'Bjorn achieves one of Ragnars dreams. Back in Kattegat, Ivar hatches a new plan while preparing for a divine arrival. In Iceland, a settler returns in a terrible state. King Alfred faces his greatest threat yet.', released: new Date(2019, 2, 4), season: 5 },
+ { id: '12', episode: 17, name: 'Garden of Bones', description: 'Bjorn achieves one of Ragnars dreams. Back in Kattegat, Ivar hatches a new plan while preparing for a divine arrival. In Iceland, a settler returns in a terrible state. King Alfred faces his greatest threat yet.', released: new Date(2019, 2, 10), season: 5 },
+ { id: '13', episode: 18, name: 'Dragonstone', description: 'Bjorn achieves one of Ragnars dreams. Back in Kattegat, Ivar hatches a new plan while preparing for a divine arrival. In Iceland, a settler returns in a terrible state. King Alfred faces his greatest threat yet.', released: new Date(2019, 3, 17), season: 5 },
+ { id: '14', episode: 19, name: 'Stormborn', description: 'Bjorn achieves one of Ragnars dreams. Back in Kattegat, Ivar hatches a new plan while preparing for a divine arrival. In Iceland, a settler returns in a terrible state. King Alfred faces his greatest threat yet.', released: new Date(2019, 4, 3), season: 5 },
+ { id: '15', episode: 20, name: 'Eastwatch', description: 'Bjorn achieves one of Ragnars dreams. Back in Kattegat, Ivar hatches a new plan while preparing for a divine arrival. In Iceland, a settler returns in a terrible state. King Alfred faces his greatest threat yet.', released: new Date(2019, 5, 10), season: 5 }
+ ]
+ },
+ {
+ poster: 'https://images.metahub.space/poster/medium/tt0411008/img',
+ name: 'Lost',
+ videos: [
+ { id: '16', episode: 16, name: 'The Lie', description: 'Bjorn achieves one of Ragnars dreams. Back in Kattegat, Ivar hatches a new plan while preparing for a divine arrival. In Iceland, a settler returns in a terrible state. King Alfred faces his greatest threat yet.', released: new Date(2019, 2, 4), season: 5 },
+ { id: '17', episode: 17, name: '316', description: 'Bjorn achieves one of Ragnars dreams. Back in Kattegat, Ivar hatches a new plan while preparing for a divine arrival. In Iceland, a settler returns in a terrible state. King Alfred faces his greatest threat yet.', released: new Date(2019, 2, 10), season: 5 },
+ { id: '18', episode: 18, name: 'Dead Is Dead', description: 'Bjorn achieves one of Ragnars dreams. Back in Kattegat, Ivar hatches a new plan while preparing for a divine arrival. In Iceland, a settler returns in a terrible state. King Alfred faces his greatest threat yet.', released: new Date(2019, 2, 17), season: 5 },
+ { id: '19', episode: 19, name: 'Racon', description: 'Bjorn achieves one of Ragnars dreams. Back in Kattegat, Ivar hatches a new plan while preparing for a divine arrival. In Iceland, a settler returns in a terrible state. King Alfred faces his greatest threat yet.', released: new Date(2019, 4, 3), season: 5 },
+ { id: '20', episode: 20, name: 'Sundown', description: 'Bjorn achieves one of Ragnars dreams. Back in Kattegat, Ivar hatches a new plan while preparing for a divine arrival. In Iceland, a settler returns in a terrible state. King Alfred faces his greatest threat yet.', released: new Date(2019, 2, 10), season: 5 }
+ ]
+ },
+ {
+ poster: 'https://images.metahub.space/poster/medium/tt0813715/img',
+ name: 'Heroes',
+ videos: [
+ { id: '21', episode: 16, name: 'Genesis', description: 'Bjorn achieves one of Ragnars dreams. Back in Kattegat, Ivar hatches a new plan while preparing for a divine arrival. In Iceland, a settler returns in a terrible state. King Alfred faces his greatest threat yet.', released: new Date(2019, 3, 3), season: 5 },
+ { id: '22', episode: 17, name: 'Six Months Ago', description: 'Bjorn achieves one of Ragnars dreams. Back in Kattegat, Ivar hatches a new plan while preparing for a divine arrival. In Iceland, a settler returns in a terrible state. King Alfred faces his greatest threat yet.', released: new Date(2019, 3, 10), season: 5 },
+ { id: '23', episode: 18, name: 'Fallout', description: 'Bjorn achieves one of Ragnars dreams. Back in Kattegat, Ivar hatches a new plan while preparing for a divine arrival. In Iceland, a settler returns in a terrible state. King Alfred faces his greatest threat yet.', released: new Date(2019, 3, 17), season: 5 },
+ { id: '24', episode: 19, name: 'Parasite', description: 'Bjorn achieves one of Ragnars dreams. Back in Kattegat, Ivar hatches a new plan while preparing for a divine arrival. In Iceland, a settler returns in a terrible state. King Alfred faces his greatest threat yet.', released: new Date(2019, 3, 8), season: 5 },
+ { id: '25', episode: 20, name: 'The Wall', description: 'Bjorn achieves one of Ragnars dreams. Back in Kattegat, Ivar hatches a new plan while preparing for a divine arrival. In Iceland, a settler returns in a terrible state. King Alfred faces his greatest threat yet.', released: new Date(2019, 5, 10), season: 5 }
+ ]
+ }
+ ]
+};
+
export default Calendar;
diff --git a/src/routes/Calendar/styles.less b/src/routes/Calendar/styles.less
new file mode 100644
index 000000000..8afda4cb6
--- /dev/null
+++ b/src/routes/Calendar/styles.less
@@ -0,0 +1,335 @@
+.calendar-container {
+ --spacing: 16px;
+ --videos-scroll-container-width: 270px;
+ font-size: 14px;
+}
+
+.calendar-container {
+ padding: var(--spacing);
+ height: 100%;
+ display: flex;
+ flex-direction: row;
+ background-color: var(--color-background);
+
+ .calendar {
+ padding-right: calc(var(--spacing) * 0.5);
+ flex: 1;
+
+ .month-buttons-container {
+ padding-top: calc(var(--spacing) * 0.5);
+ height: 3.5em;
+ text-align: center;
+
+ .month-button {
+ padding: calc(var(--spacing) * 0.25);
+ width: 6em;
+ display: inline-block;
+ font-size: 1.7em;
+ line-height: 1.2em;
+ overflow: hidden;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+ vertical-align: middle;
+ border: calc(var(--focusable-border-size) * 0.5) solid transparent;
+ color: var(--color-surfacelighter);
+ cursor: pointer;
+
+ &:focus {
+ border-color: var(--color-surfacelighter);
+ }
+
+ &:first-child, &:last-child {
+ font-size: 1.4em;
+ color: var(--color-surface);
+
+ &:hover {
+ border-color: transparent;
+ color: var(--color-surfacelight);
+ }
+ }
+
+ &:not(:first-child):not(:last-child) {
+ margin: 0 var(--spacing);
+ }
+ }
+ }
+
+ .week-days {
+ height: 3.5em;
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+
+ .day {
+ padding-right: calc(var(--spacing) * 0.5);
+ flex: 1;
+ font-size: 1.2em;
+ line-height: 1.2em;
+ text-align: center;
+ overflow: hidden;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+ color: var(--color-secondarylighter);
+ }
+ }
+
+ .month-days {
+ width: 100%;
+ height: 36.5em;
+ display: table;
+ table-layout: fixed;
+ border-spacing: var(--focusable-border-size);
+
+ .week {
+ display: table-row;
+
+ .pad {
+ display: table-cell;
+ }
+
+ .day {
+ display: table-cell;
+ position: relative;
+ z-index: 0;
+ border: calc(var(--focusable-border-size) * 0.5) solid transparent;
+ background-color: var(--color-backgroundlighter);
+ cursor: pointer;
+
+ .date {
+ position: absolute;
+ z-index: 1;
+ top: 0;
+ left: 0;
+ right: 0;
+ padding: 0.4em 0;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 1.4em;
+ color: var(--color-secondarylighter);
+
+ &.today {
+ color: var(--color-surfacelighter);
+ background-color: var(--color-primary);
+ }
+ }
+
+ .posters-container {
+ position: absolute;
+ z-index: 0;
+ right: 0;
+ bottom: 0;
+ left: 0;
+
+ .poster {
+ display: none;
+ width: calc(100% / 4);
+ background-size: cover;
+ background-repeat: no-repeat;
+ background-position: center;
+
+ &.past {
+ opacity: 0.4;
+ }
+
+ &:nth-child(-n+4) {
+ display: inline-block;
+ }
+
+ &:before {
+ content: "";
+ display: inline-block;
+ padding-top: calc(100% * var(--poster-shape-ratio));
+ }
+ }
+ }
+
+ &:hover, &:focus {
+ border-color: var(--color-surfacelighter);
+ }
+
+ &.selected {
+ border-color: var(--color-primary);
+ background-color: var(--color-backgroundlighter60);
+
+ &:focus {
+ border-color: var(--color-surfacelighter);
+ }
+
+ &:hover {
+ border-color: var(--color-primary);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ .videos-scroll-container {
+ padding-right: calc(var(--spacing) * 0.5);
+ width: var(--videos-scroll-container-width);
+ overflow-x: hidden;
+ overflow-y: auto;
+ scroll-behavior: smooth;
+
+ .videos-container {
+ border: var(--focusable-border-size) solid var(--color-background);
+ background-color: var(--color-backgroundlighter);
+
+ .date {
+ padding: var(--spacing);
+ font-size: 1.2em;
+ color: var(--color-primarylight);
+ cursor: pointer;
+ }
+
+ .video {
+ padding: var(--spacing);
+ border: calc(var(--focusable-border-size) * 0.5) solid transparent;
+ cursor: pointer;
+
+ .main-info {
+ display: flex;
+ flex-direction: row;
+ justify-content: space-between;
+
+ .meta-item-name {
+ flex: 3;
+ line-height: 1.2em;
+ overflow: hidden;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+ color: var(--color-surface);
+ }
+
+ .video-number {
+ flex: 1;
+ line-height: 1.2em;
+ text-align: end;
+ overflow: hidden;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+ color: var(--color-surface);
+ }
+ }
+
+ .name {
+ width: 100%;
+ display: none;
+ padding: calc(var(--spacing) * 0.5) 0;
+ overflow-wrap: break-word;
+ color: var(--color-surfacelighter);
+ }
+
+ .description {
+ display: none;
+ padding: var(--spacing) 0;
+ line-height: 1.2em;
+ border-bottom: calc(var(--focusable-border-size) * 0.5) solid var(--color-background);
+ color: var(--color-surfacelighter);
+ }
+
+ .watch-button-container {
+ display: none;
+ margin: calc(var(--spacing) * 0.5);
+ padding: var(--spacing);
+ border: calc(var(--focusable-border-size) * 0.5) solid transparent;
+ cursor: pointer;
+
+ .watch-button {
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+ justify-content: center;
+
+ .icon {
+ margin-right: 1em;
+ width: 1em;
+ fill: var(--color-surfacelight);
+ }
+
+ .label {
+ max-width: 9em;
+ word-break: break-all; //Firefox doesn't support { break-word }
+ word-break: break-word;
+ color: var(--color-surfacelight);
+ }
+ }
+
+ &:focus {
+ border: calc(var(--focusable-border-size) * 0.5) solid var(--color-surfacelighter);
+ }
+
+ &:hover {
+ border-color: transparent;
+
+ .watch-button {
+ .icon {
+ fill: var(--color-surfacelighter);
+ }
+
+ .label {
+ color: var(--color-surfacelighter);
+ }
+ }
+ }
+ }
+
+ &.selected {
+ padding-bottom: 0;
+ background-color: var(--color-primarydark);
+ cursor: default;
+
+ .main-info {
+ .meta-item-name {
+ white-space: unset;
+ overflow-wrap: break-word;
+ }
+
+ .video-number {
+ white-space: unset;
+ overflow-wrap: break-word;
+ }
+ }
+
+ .name {
+ display: block;
+ }
+
+ .description {
+ display: block;
+ }
+
+ .watch-button-container {
+ display: block;
+ }
+
+ &:hover {
+ background-color: var(--color-primarydark);
+ }
+ }
+
+ &.today {
+ background-color: var(--color-primarydarker40);
+ }
+
+ &:focus {
+ border-color: var(--color-surfacelighter);
+ }
+
+ &:hover {
+ border-color: transparent;
+ background-color: var(--color-primarydark60);
+ }
+ }
+
+ &.selected {
+ border-color: var(--color-primary);
+ }
+
+ &:not(:last-child) {
+ margin-bottom: calc(var(--spacing) * 0.5);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/routes/Detail/StreamsList/StreamsList.js b/src/routes/Detail/StreamsList/StreamsList.js
index c590e09c9..a70faa00b 100644
--- a/src/routes/Detail/StreamsList/StreamsList.js
+++ b/src/routes/Detail/StreamsList/StreamsList.js
@@ -4,12 +4,28 @@ import Icon from 'stremio-icons/dom';
import Stream from './Stream';
import styles from './styles';
+const renderStreamPlaceholders = () => {
+ const streamPlaceholders = [];
+ for (let placeholderNumber = 0; placeholderNumber < 20; placeholderNumber++) {
+ streamPlaceholders.push(
+