mirror of
https://github.com/Stremio/stremio-web.git
synced 2026-03-11 21:27:05 +00:00
Merge branch 'development' into feat/manage-streaming-urls
This commit is contained in:
commit
e7099767c4
53 changed files with 1629 additions and 156 deletions
BIN
images/calendar_placeholder.png
Normal file
BIN
images/calendar_placeholder.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 202 KiB |
14
package-lock.json
generated
14
package-lock.json
generated
|
|
@ -51,6 +51,7 @@
|
|||
"@stylistic/eslint-plugin-jsx": "^2.9.0",
|
||||
"@types/hat": "^0.0.4",
|
||||
"@types/react": "^18.2.9",
|
||||
"@types/react-dom": "^18.3.0",
|
||||
"babel-loader": "8.2.3",
|
||||
"clean-webpack-plugin": "4.0.0",
|
||||
"copy-webpack-plugin": "9.0.1",
|
||||
|
|
@ -3142,6 +3143,7 @@
|
|||
"version": "7.24.1",
|
||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.1.tgz",
|
||||
"integrity": "sha512-+BIznRzyqBf+2wCTxcKE3wDjfGeCoVE61KSHGpkzqrLi8qxqFwBeUFyId2cxkTmm55fzDGnm0+yCxaxygrLUnQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"regenerator-runtime": "^0.14.0"
|
||||
},
|
||||
|
|
@ -3152,7 +3154,8 @@
|
|||
"node_modules/@stremio/stremio-core-web/node_modules/regenerator-runtime": {
|
||||
"version": "0.14.1",
|
||||
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz",
|
||||
"integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw=="
|
||||
"integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@stremio/stremio-icons": {
|
||||
"version": "5.4.0",
|
||||
|
|
@ -3556,6 +3559,15 @@
|
|||
"csstype": "^3.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/react-dom": {
|
||||
"version": "18.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.0.tgz",
|
||||
"integrity": "sha512-EhwApuTmMBmXuFOikhQLIBUn6uFg81SwLMOAUgodJF14SOBOCMdU04gDoYi0WOJJHD144TL32z4yDqCW3dnkQg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/react": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/resolve": {
|
||||
"version": "1.17.1",
|
||||
"dev": true,
|
||||
|
|
|
|||
|
|
@ -55,6 +55,7 @@
|
|||
"@stylistic/eslint-plugin-jsx": "^2.9.0",
|
||||
"@types/hat": "^0.0.4",
|
||||
"@types/react": "^18.2.9",
|
||||
"@types/react-dom": "^18.3.0",
|
||||
"babel-loader": "8.2.3",
|
||||
"clean-webpack-plugin": "4.0.0",
|
||||
"copy-webpack-plugin": "9.0.1",
|
||||
|
|
|
|||
|
|
@ -23,6 +23,10 @@ const routerViewsConfig = [
|
|||
...routesRegexp.library,
|
||||
component: routes.Library
|
||||
},
|
||||
{
|
||||
...routesRegexp.calendar,
|
||||
component: routes.Calendar
|
||||
},
|
||||
{
|
||||
...routesRegexp.continuewatching,
|
||||
component: routes.Library
|
||||
|
|
|
|||
|
|
@ -91,6 +91,7 @@ html {
|
|||
min-height: 480px;
|
||||
font-family: 'PlusJakartaSans', 'sans-serif';
|
||||
overflow: auto;
|
||||
overscroll-behavior: none;
|
||||
|
||||
body {
|
||||
width: 100%;
|
||||
|
|
|
|||
102
src/common/BottomSheet/BottomSheet.less
Normal file
102
src/common/BottomSheet/BottomSheet.less
Normal file
|
|
@ -0,0 +1,102 @@
|
|||
// Copyright (C) 2017-2024 Smart code 203358507
|
||||
|
||||
@import (reference) '~stremio/common/screen-sizes.less';
|
||||
|
||||
.bottom-sheet {
|
||||
z-index: 99;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
|
||||
.backdrop {
|
||||
z-index: 0;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background-color: var(--primary-background-color);
|
||||
opacity: 0.8;
|
||||
transition: opacity 0.1s ease-out;
|
||||
cursor: pointer;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
}
|
||||
|
||||
.container {
|
||||
z-index: 1;
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
max-height: calc(100% - var(--horizontal-nav-bar-size));
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1.5rem;
|
||||
padding-bottom: 1rem;
|
||||
border-radius: 2rem 2rem 0 0;
|
||||
background-color: var(--modal-background-color);
|
||||
box-shadow: var(--outer-glow);
|
||||
overflow: hidden;
|
||||
|
||||
&:not(.dragging) {
|
||||
transition: transform 0.1s ease-out;
|
||||
}
|
||||
|
||||
.heading {
|
||||
position: relative;
|
||||
|
||||
.handle {
|
||||
position: relative;
|
||||
height: 2.5rem;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
&::after {
|
||||
content: "";
|
||||
height: 0.3rem;
|
||||
width: 3rem;
|
||||
border-radius: 1rem;
|
||||
background-color: var(--primary-foreground-color);
|
||||
opacity: 0.3;
|
||||
}
|
||||
}
|
||||
|
||||
.title {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0 1rem;
|
||||
padding-left: 1.5rem;
|
||||
font-size: 1.25rem;
|
||||
font-weight: 600;
|
||||
color: var(--primary-foreground-color);
|
||||
}
|
||||
}
|
||||
|
||||
.content {
|
||||
position: relative;
|
||||
overflow-y: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (min-width: @xsmall) {
|
||||
.bottom-sheet {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (orientation: landscape) {
|
||||
.bottom-sheet {
|
||||
.container {
|
||||
max-width: 90%;
|
||||
}
|
||||
}
|
||||
}
|
||||
87
src/common/BottomSheet/BottomSheet.tsx
Normal file
87
src/common/BottomSheet/BottomSheet.tsx
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
// Copyright (C) 2017-2024 Smart code 203358507
|
||||
|
||||
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { createPortal } from 'react-dom';
|
||||
import classNames from 'classnames';
|
||||
import useBinaryState from 'stremio/common/useBinaryState';
|
||||
import styles from './BottomSheet.less';
|
||||
|
||||
const CLOSE_THRESHOLD = 100;
|
||||
|
||||
type Props = {
|
||||
children: JSX.Element,
|
||||
title: string,
|
||||
show?: boolean,
|
||||
onClose: () => void,
|
||||
};
|
||||
|
||||
const BottomSheet = ({ children, title, show, onClose }: Props) => {
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const [startOffset, setStartOffset] = useState(0);
|
||||
const [offset, setOffset] = useState(0);
|
||||
|
||||
const [opened, open, close] = useBinaryState();
|
||||
|
||||
const containerStyle = useMemo(() => ({
|
||||
transform: `translateY(${offset}px)`
|
||||
}), [offset]);
|
||||
|
||||
const containerHeight = () => containerRef.current?.offsetHeight ?? 0;
|
||||
|
||||
const onCloseRequest = () => setOffset(containerHeight());
|
||||
|
||||
const onTouchStart = ({ touches }: React.TouchEvent<HTMLDivElement>) => {
|
||||
const { clientY } = touches[0];
|
||||
setStartOffset(clientY);
|
||||
};
|
||||
|
||||
const onTouchMove = useCallback(({ touches }: React.TouchEvent<HTMLDivElement>) => {
|
||||
const { clientY } = touches[0];
|
||||
setOffset(Math.max(0, clientY - startOffset));
|
||||
}, [startOffset]);
|
||||
|
||||
const onTouchEnd = () => {
|
||||
setOffset((offset) => offset > CLOSE_THRESHOLD ? containerHeight() : 0);
|
||||
setStartOffset(0);
|
||||
};
|
||||
|
||||
const onTransitionEnd = useCallback(() => {
|
||||
(offset === containerHeight()) && close();
|
||||
}, [offset]);
|
||||
|
||||
useEffect(() => {
|
||||
setOffset(0);
|
||||
show ? open() : close();
|
||||
}, [show]);
|
||||
|
||||
useEffect(() => {
|
||||
!opened && onClose();
|
||||
}, [opened]);
|
||||
|
||||
return opened && createPortal((
|
||||
<div className={styles['bottom-sheet']}>
|
||||
<div className={styles['backdrop']} onClick={onCloseRequest} />
|
||||
<div
|
||||
ref={containerRef}
|
||||
className={classNames(styles['container'], { [styles['dragging']]: startOffset }, 'animation-slide-up')}
|
||||
style={containerStyle}
|
||||
onTouchStart={onTouchStart}
|
||||
onTouchMove={onTouchMove}
|
||||
onTouchEnd={onTouchEnd}
|
||||
onTransitionEnd={onTransitionEnd}
|
||||
>
|
||||
<div className={styles['heading']}>
|
||||
<div className={styles['handle']} />
|
||||
<div className={styles['title']}>
|
||||
{title}
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles['content']} onClick={onCloseRequest}>
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
), document.body);
|
||||
};
|
||||
|
||||
export default BottomSheet;
|
||||
4
src/common/BottomSheet/index.ts
Normal file
4
src/common/BottomSheet/index.ts
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
// Copyright (C) 2017-2024 Smart code 203358507
|
||||
|
||||
import BottomSheet from './BottomSheet';
|
||||
export default BottomSheet;
|
||||
|
|
@ -20,14 +20,16 @@
|
|||
background-color: transparent;
|
||||
user-select: none;
|
||||
overflow: hidden;
|
||||
opacity: 0.6;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--overlay-color);
|
||||
transition: background-color 0.1s ease-out;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
&.active {
|
||||
font-weight: 700;
|
||||
opacity: 1;
|
||||
background-color: var(--quaternary-accent-color);
|
||||
transition: background-color 0.1s ease-in;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,5 @@
|
|||
// Copyright (C) 2017-2024 Smart code 203358507
|
||||
|
||||
@mask-width: 10%;
|
||||
|
||||
.chips {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
|
|
@ -9,17 +7,4 @@
|
|||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
gap: 1rem;
|
||||
overflow-x: auto;
|
||||
|
||||
&.left {
|
||||
mask-image: linear-gradient(90deg, rgba(0, 0, 0, 1) calc(100% - @mask-width), rgba(0, 0, 0, 0) 100%);
|
||||
}
|
||||
|
||||
&.right {
|
||||
mask-image: linear-gradient(90deg, rgba(0, 0, 0, 0) 0%, rgba(0, 0, 0, 1) @mask-width);
|
||||
}
|
||||
|
||||
&.center {
|
||||
mask-image: linear-gradient(90deg, rgba(0, 0, 0, 0) 0%, rgba(0, 0, 0, 1) @mask-width, rgba(0, 0, 0, 1) calc(100% - @mask-width), rgba(0, 0, 0, 0) 100%);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
// Copyright (C) 2017-2024 Smart code 203358507
|
||||
|
||||
import React, { memo, useEffect, useRef, useState } from 'react';
|
||||
import classNames from 'classnames';
|
||||
import React, { memo } from 'react';
|
||||
import HorizontalScroll from '../HorizontalScroll';
|
||||
import Chip from './Chip';
|
||||
import styles from './Chips.less';
|
||||
|
||||
|
|
@ -16,28 +16,9 @@ type Props = {
|
|||
onSelect: (value: string) => {},
|
||||
};
|
||||
|
||||
const SCROLL_THRESHOLD = 1;
|
||||
|
||||
const Chips = memo(({ options, selected, onSelect }: Props) => {
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
const [scrollPosition, setScrollPosition] = useState('left');
|
||||
|
||||
useEffect(() => {
|
||||
const onScroll = ({ target }: Event) => {
|
||||
const { scrollLeft, scrollWidth, offsetWidth} = target as HTMLDivElement;
|
||||
const position =
|
||||
(scrollLeft - SCROLL_THRESHOLD) <= 0 ? 'left' :
|
||||
(scrollLeft + offsetWidth + SCROLL_THRESHOLD) >= scrollWidth ? 'right' :
|
||||
'center';
|
||||
setScrollPosition(position);
|
||||
};
|
||||
|
||||
ref.current?.addEventListener('scroll', onScroll);
|
||||
return () => ref.current?.removeEventListener('scroll', onScroll);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div ref={ref} className={classNames(styles['chips'], [styles[scrollPosition]])}>
|
||||
<HorizontalScroll className={styles['chips']}>
|
||||
{
|
||||
options.map(({ label, value }) => (
|
||||
<Chip
|
||||
|
|
@ -49,7 +30,7 @@ const Chips = memo(({ options, selected, onSelect }: Props) => {
|
|||
/>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
</HorizontalScroll>
|
||||
);
|
||||
});
|
||||
|
||||
|
|
|
|||
20
src/common/HorizontalScroll/HorizontalScroll.less
Normal file
20
src/common/HorizontalScroll/HorizontalScroll.less
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
// Copyright (C) 2017-2024 Smart code 203358507
|
||||
|
||||
@mask-width: 10%;
|
||||
|
||||
.horizontal-scroll {
|
||||
position: relative;
|
||||
overflow-x: auto;
|
||||
|
||||
&.left {
|
||||
mask-image: linear-gradient(90deg, rgba(0, 0, 0, 1) calc(100% - @mask-width), rgba(0, 0, 0, 0) 100%);
|
||||
}
|
||||
|
||||
&.right {
|
||||
mask-image: linear-gradient(90deg, rgba(0, 0, 0, 0) 0%, rgba(0, 0, 0, 1) @mask-width);
|
||||
}
|
||||
|
||||
&.center {
|
||||
mask-image: linear-gradient(90deg, rgba(0, 0, 0, 0) 0%, rgba(0, 0, 0, 1) @mask-width, rgba(0, 0, 0, 1) calc(100% - @mask-width), rgba(0, 0, 0, 0) 100%);
|
||||
}
|
||||
}
|
||||
40
src/common/HorizontalScroll/HorizontalScroll.tsx
Normal file
40
src/common/HorizontalScroll/HorizontalScroll.tsx
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
// Copyright (C) 2017-2024 Smart code 203358507
|
||||
|
||||
import React, { useRef, useEffect, useState } from 'react';
|
||||
import classNames from 'classnames';
|
||||
import styles from './HorizontalScroll.less';
|
||||
|
||||
const SCROLL_THRESHOLD = 1;
|
||||
|
||||
type Props = {
|
||||
className: string,
|
||||
children: React.ReactNode,
|
||||
};
|
||||
|
||||
const HorizontalScroll = ({ className, children }: Props) => {
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
const [scrollPosition, setScrollPosition] = useState('left');
|
||||
|
||||
useEffect(() => {
|
||||
const onScroll = ({ target }: Event) => {
|
||||
const { scrollLeft, scrollWidth, offsetWidth } = target as HTMLDivElement;
|
||||
|
||||
setScrollPosition(() => (
|
||||
(scrollLeft - SCROLL_THRESHOLD) <= 0 ? 'left' :
|
||||
(scrollLeft + offsetWidth + SCROLL_THRESHOLD) >= scrollWidth ? 'right' :
|
||||
'center'
|
||||
));
|
||||
};
|
||||
|
||||
ref.current?.addEventListener('scroll', onScroll);
|
||||
return () => ref.current?.removeEventListener('scroll', onScroll);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div ref={ref} className={classNames(styles['horizontal-scroll'], className, [styles[scrollPosition]])}>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default HorizontalScroll;
|
||||
4
src/common/HorizontalScroll/index.ts
Normal file
4
src/common/HorizontalScroll/index.ts
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
// Copyright (C) 2017-2024 Smart code 203358507
|
||||
|
||||
import HorizontalScroll from './HorizontalScroll';
|
||||
export default HorizontalScroll;
|
||||
|
|
@ -10,6 +10,7 @@ const TABS = [
|
|||
{ id: 'board', label: 'Board', icon: 'home', href: '#/' },
|
||||
{ id: 'discover', label: 'Discover', icon: 'discover', href: '#/discover' },
|
||||
{ id: 'library', label: 'Library', icon: 'library', href: '#/library' },
|
||||
{ id: 'calendar', label: 'Calendar', icon: 'calendar', href: '#/calendar' },
|
||||
{ id: 'addons', label: 'ADDONS', icon: 'addons', href: '#/addons' },
|
||||
{ id: 'settings', label: 'SETTINGS', icon: 'settings', href: '#/settings' },
|
||||
];
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@
|
|||
align-items: center;
|
||||
gap: 1rem;
|
||||
width: var(--vertical-nav-bar-size);
|
||||
padding: 1rem 0;
|
||||
background-color: transparent;
|
||||
overflow-y: auto;
|
||||
scrollbar-width: none;
|
||||
|
|
@ -18,16 +19,8 @@
|
|||
}
|
||||
|
||||
.nav-tab-button {
|
||||
width: calc(var(--vertical-nav-bar-size) - 1.5rem);
|
||||
height: calc(var(--vertical-nav-bar-size) - 1.5rem);
|
||||
|
||||
&:first-child {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
width: calc(var(--vertical-nav-bar-size) - 1.2rem);
|
||||
height: calc(var(--vertical-nav-bar-size) - 1.2rem);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -45,12 +38,18 @@
|
|||
.nav-tab-button {
|
||||
flex: none;
|
||||
|
||||
&:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-height: @minimum) {
|
||||
.vertical-nav-bar-container {
|
||||
.nav-tab-button {
|
||||
&:last-child {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,44 +0,0 @@
|
|||
// Copyright (C) 2017-2023 Smart code 203358507
|
||||
|
||||
const React = require('react');
|
||||
const PropTypes = require('prop-types');
|
||||
const classnames = require('classnames');
|
||||
const { default: Icon } = require('@stremio/stremio-icons/react');
|
||||
const Button = require('stremio/common/Button');
|
||||
const styles = require('./styles');
|
||||
|
||||
const PaginationInput = ({ className, label, dataset, onSelect, ...props }) => {
|
||||
const prevNextButtonOnClick = React.useCallback((event) => {
|
||||
if (typeof onSelect === 'function') {
|
||||
onSelect({
|
||||
type: 'change-page',
|
||||
value: event.currentTarget.dataset.value,
|
||||
dataset: dataset,
|
||||
reactEvent: event,
|
||||
nativeEvent: event.nativeEvent
|
||||
});
|
||||
}
|
||||
}, [dataset, onSelect]);
|
||||
return (
|
||||
<div {...props} className={classnames(className, styles['pagination-input-container'])} >
|
||||
<Button className={styles['prev-button-container']} title={'Previous page'} data-value={'prev'} onClick={prevNextButtonOnClick}>
|
||||
<Icon className={styles['icon']} name={'chevron-back'} />
|
||||
</Button>
|
||||
<div className={styles['label-container']} title={label}>
|
||||
<div className={styles['label']}>{label}</div>
|
||||
</div>
|
||||
<Button className={styles['next-button-container']} title={'Next page'} data-value={'next'} onClick={prevNextButtonOnClick}>
|
||||
<Icon className={styles['icon']} name={'chevron-forward'} />
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
PaginationInput.propTypes = {
|
||||
className: PropTypes.string,
|
||||
label: PropTypes.string,
|
||||
dataset: PropTypes.object,
|
||||
onSelect: PropTypes.func
|
||||
};
|
||||
|
||||
module.exports = PaginationInput;
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
// Copyright (C) 2017-2023 Smart code 203358507
|
||||
|
||||
const PaginationInput = require('./PaginationInput');
|
||||
|
||||
module.exports = PaginationInput;
|
||||
|
|
@ -1,42 +0,0 @@
|
|||
// Copyright (C) 2017-2023 Smart code 203358507
|
||||
|
||||
@import (reference) '~@stremio/stremio-colors/less/stremio-colors.less';
|
||||
|
||||
.pagination-input-container {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
border-radius: var(--border-radius);
|
||||
|
||||
.prev-button-container, .next-button-container {
|
||||
flex: none;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-color: var(--overlay-color);
|
||||
|
||||
.icon {
|
||||
display: block;
|
||||
color: var(--primary-foreground-color);
|
||||
}
|
||||
}
|
||||
|
||||
.label-container {
|
||||
flex: 1;
|
||||
align-self: stretch;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-color: var(--overlay-color);
|
||||
|
||||
.label {
|
||||
flex: none;
|
||||
min-width: 1.2rem;
|
||||
max-width: 3rem;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
text-align: center;
|
||||
font-weight: 500;
|
||||
color: var(--primary-foreground-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -19,4 +19,23 @@
|
|||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
:global(.animation-slide-up) {
|
||||
:local {
|
||||
animation-name: slide-up;
|
||||
}
|
||||
|
||||
animation-timing-function: ease-out;
|
||||
animation-duration: 0.1s;
|
||||
}
|
||||
|
||||
@keyframes slide-up {
|
||||
0% {
|
||||
transform: translateY(100%);
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: translateY(0%);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
// Copyright (C) 2017-2023 Smart code 203358507
|
||||
|
||||
const AddonDetailsModal = require('./AddonDetailsModal');
|
||||
const { default: BottomSheet } = require('./BottomSheet');
|
||||
const Button = require('./Button');
|
||||
const Toggle = require('./Toggle');
|
||||
const { default: Chips } = require('./Chips');
|
||||
|
|
@ -17,7 +18,7 @@ const ModalDialog = require('./ModalDialog');
|
|||
const Multiselect = require('./Multiselect');
|
||||
const { default: MultiselectMenu } = require('./MultiselectMenu');
|
||||
const { HorizontalNavBar, VerticalNavBar } = require('./NavBar');
|
||||
const PaginationInput = require('./PaginationInput');
|
||||
const { default: HorizontalScroll } = require('./HorizontalScroll');
|
||||
const { PlatformProvider, usePlatform } = require('./Platform');
|
||||
const PlayIconCircleCentered = require('./PlayIconCircleCentered');
|
||||
const Popup = require('./Popup');
|
||||
|
|
@ -51,6 +52,7 @@ const { default: Checkbox } = require('./Checkbox');
|
|||
|
||||
module.exports = {
|
||||
AddonDetailsModal,
|
||||
BottomSheet,
|
||||
Button,
|
||||
Toggle,
|
||||
Chips,
|
||||
|
|
@ -67,8 +69,8 @@ module.exports = {
|
|||
Multiselect,
|
||||
MultiselectMenu,
|
||||
HorizontalNavBar,
|
||||
HorizontalScroll,
|
||||
VerticalNavBar,
|
||||
PaginationInput,
|
||||
PlatformProvider,
|
||||
usePlatform,
|
||||
PlayIconCircleCentered,
|
||||
|
|
|
|||
|
|
@ -17,6 +17,10 @@ const routesRegexp = {
|
|||
regexp: /^\/library(?:\/([^/]*))?$/,
|
||||
urlParamsNames: ['type']
|
||||
},
|
||||
calendar: {
|
||||
regexp: /^\/calendar(?:\/([^/]*)\/([^/]*))?$/,
|
||||
urlParamsNames: ['year', 'month']
|
||||
},
|
||||
continuewatching: {
|
||||
regexp: /^\/continuewatching(?:\/([^/]*))?$/,
|
||||
urlParamsNames: ['type']
|
||||
|
|
|
|||
1
src/modules.d.ts
vendored
1
src/modules.d.ts
vendored
|
|
@ -3,4 +3,5 @@ declare module '*.less' {
|
|||
export = resource;
|
||||
}
|
||||
|
||||
declare module 'stremio/common';
|
||||
declare module 'stremio/common/Button';
|
||||
|
|
|
|||
43
src/routes/Calendar/Calendar.less
Normal file
43
src/routes/Calendar/Calendar.less
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
// Copyright (C) 2017-2024 Smart code 203358507
|
||||
|
||||
@import (reference) '~stremio/common/screen-sizes.less';
|
||||
|
||||
.calendar {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: transparent;
|
||||
|
||||
.content {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 0.5rem;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding: 0 0 2rem 2rem;
|
||||
|
||||
.main {
|
||||
flex: auto;
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: @minimum) {
|
||||
.calendar {
|
||||
.content {
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: @small) and (orientation: landscape) {
|
||||
.calendar {
|
||||
.content {
|
||||
padding: 0 0 0 1rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
75
src/routes/Calendar/Calendar.tsx
Normal file
75
src/routes/Calendar/Calendar.tsx
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
// Copyright (C) 2017-2024 Smart code 203358507
|
||||
|
||||
import React, { useMemo, useState } from 'react';
|
||||
import { MainNavBars, BottomSheet, useProfile, withCoreSuspender } from 'stremio/common';
|
||||
import Selector from './Selector';
|
||||
import Table from './Table';
|
||||
import List from './List';
|
||||
import Details from './Details';
|
||||
import Placeholder from './Placeholder';
|
||||
import useCalendar from './useCalendar';
|
||||
import useCalendarDate from './useCalendarDate';
|
||||
import styles from './Calendar.less';
|
||||
|
||||
type Props = {
|
||||
urlParams: UrlParams,
|
||||
};
|
||||
|
||||
const Calendar = ({ urlParams }: Props) => {
|
||||
const calendar = useCalendar(urlParams);
|
||||
const profile = useProfile();
|
||||
|
||||
const { toDayMonth } = useCalendarDate(profile);
|
||||
|
||||
const [selected, setSelected] = useState<CalendarDate | null>(null);
|
||||
|
||||
const detailsTitle = useMemo(() => toDayMonth(selected), [selected, toDayMonth]);
|
||||
|
||||
const onDetailsClose = () => {
|
||||
setSelected(null);
|
||||
};
|
||||
|
||||
return (
|
||||
<MainNavBars className={styles['calendar']} route={'calendar'}>
|
||||
{
|
||||
profile.auth !== null ?
|
||||
<div className={styles['content']}>
|
||||
<div className={styles['main']}>
|
||||
<Selector
|
||||
selected={calendar.selected}
|
||||
selectable={calendar.selectable}
|
||||
profile={profile}
|
||||
/>
|
||||
<Table
|
||||
items={calendar.items}
|
||||
selected={selected}
|
||||
monthInfo={calendar.monthInfo}
|
||||
onChange={setSelected}
|
||||
/>
|
||||
</div>
|
||||
<List
|
||||
items={calendar.items}
|
||||
selected={selected}
|
||||
monthInfo={calendar.monthInfo}
|
||||
profile={profile}
|
||||
onChange={setSelected}
|
||||
/>
|
||||
<BottomSheet title={detailsTitle} show={selected} onClose={onDetailsClose}>
|
||||
<Details
|
||||
selected={selected}
|
||||
items={calendar.items}
|
||||
/>
|
||||
</BottomSheet>
|
||||
</div>
|
||||
:
|
||||
<Placeholder />
|
||||
}
|
||||
</MainNavBars>
|
||||
);
|
||||
};
|
||||
|
||||
const CalendarFallback = () => (
|
||||
<MainNavBars className={styles['calendar']} />
|
||||
);
|
||||
|
||||
export default withCoreSuspender(Calendar, CalendarFallback);
|
||||
60
src/routes/Calendar/Details/Details.less
Normal file
60
src/routes/Calendar/Details/Details.less
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
// Copyright (C) 2017-2024 Smart code 203358507
|
||||
|
||||
.details {
|
||||
position: relative;
|
||||
|
||||
.video {
|
||||
flex: none;
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 1rem;
|
||||
padding: 0 1.5rem;
|
||||
height: 4rem;
|
||||
font-size: 1rem;
|
||||
font-weight: 500;
|
||||
color: var(--primary-foreground-color);
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
|
||||
.name {
|
||||
flex: auto;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.info {
|
||||
flex: none;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.icon {
|
||||
flex: none;
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
padding: 0.5rem;
|
||||
border-radius: 50%;
|
||||
color: var(--primary-foreground-color);
|
||||
}
|
||||
|
||||
&:hover, &:active {
|
||||
background-color: var(--overlay-color);
|
||||
|
||||
.icon {
|
||||
display: block;
|
||||
background-color: var(--secondary-accent-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.placeholder {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 10rem;
|
||||
font-size: 1rem;
|
||||
color: var(--primary-foreground-color);
|
||||
}
|
||||
}
|
||||
45
src/routes/Calendar/Details/Details.tsx
Normal file
45
src/routes/Calendar/Details/Details.tsx
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
// Copyright (C) 2017-2024 Smart code 203358507
|
||||
|
||||
import React, { useMemo } from 'react';
|
||||
import Icon from '@stremio/stremio-icons/react';
|
||||
import Button from 'stremio/common/Button';
|
||||
import styles from './Details.less';
|
||||
|
||||
type Props = {
|
||||
selected: CalendarDate | null,
|
||||
items: CalendarItem[],
|
||||
};
|
||||
|
||||
const Details = ({ selected, items }: Props) => {
|
||||
const videos = useMemo(() => {
|
||||
return items.find(({ date }) => date.day === selected?.day)?.items ?? [];
|
||||
}, [selected, items]);
|
||||
|
||||
return (
|
||||
<div className={styles['details']}>
|
||||
{
|
||||
videos.map(({ id, name, season, episode, deepLinks }) => (
|
||||
<Button className={styles['video']} key={id} href={deepLinks.metaDetailsStreams}>
|
||||
<div className={styles['name']}>
|
||||
{name}
|
||||
</div>
|
||||
<div className={styles['info']}>
|
||||
S{season}E{episode}
|
||||
</div>
|
||||
<Icon className={styles['icon']} name={'play'} />
|
||||
</Button>
|
||||
))
|
||||
}
|
||||
{
|
||||
!videos.length ?
|
||||
<div className={styles['placeholder']}>
|
||||
No new episodes for this day
|
||||
</div>
|
||||
:
|
||||
null
|
||||
}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Details;
|
||||
4
src/routes/Calendar/Details/index.ts
Normal file
4
src/routes/Calendar/Details/index.ts
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
// Copyright (C) 2017-2024 Smart code 203358507
|
||||
|
||||
import Details from './Details';
|
||||
export default Details;
|
||||
98
src/routes/Calendar/List/Item/Item.less
Normal file
98
src/routes/Calendar/List/Item/Item.less
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
// Copyright (C) 2017-2024 Smart code 203358507
|
||||
|
||||
.item {
|
||||
flex: none;
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background-color: var(--overlay-color);
|
||||
border-radius: var(--border-radius);
|
||||
border: 0.15rem solid transparent;
|
||||
transition: border-color 0.1s ease-out;
|
||||
|
||||
.heading {
|
||||
flex: none;
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 3.5rem;
|
||||
font-size: 1rem;
|
||||
font-weight: 500;
|
||||
color: var(--primary-foreground-color);
|
||||
padding: 0 1rem;
|
||||
}
|
||||
|
||||
.body {
|
||||
flex: auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.video {
|
||||
flex: none;
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 1rem;
|
||||
height: 3rem;
|
||||
padding: 0 1rem;
|
||||
font-size: 1rem;
|
||||
font-weight: 500;
|
||||
color: var(--primary-foreground-color);
|
||||
|
||||
&:last-child {
|
||||
border-radius: 0 0 var(--border-radius) var(--border-radius);
|
||||
}
|
||||
|
||||
.name {
|
||||
flex: auto;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.info {
|
||||
flex: none;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.icon {
|
||||
flex: none;
|
||||
display: none;
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
padding: 0.5rem;
|
||||
border-radius: 50%;
|
||||
color: var(--primary-foreground-color);
|
||||
background-color: var(--secondary-accent-color);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: var(--overlay-color);
|
||||
|
||||
.info {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.icon {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.today {
|
||||
.heading {
|
||||
background-color: var(--primary-accent-color);
|
||||
}
|
||||
}
|
||||
|
||||
&.active {
|
||||
border-color: var(--primary-foreground-color);
|
||||
}
|
||||
|
||||
&:not(.active):hover {
|
||||
border-color: var(--overlay-color);
|
||||
}
|
||||
}
|
||||
68
src/routes/Calendar/List/Item/Item.tsx
Normal file
68
src/routes/Calendar/List/Item/Item.tsx
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
// Copyright (C) 2017-2024 Smart code 203358507
|
||||
|
||||
import React, { useEffect, useMemo, useRef } from 'react';
|
||||
import Icon from '@stremio/stremio-icons/react';
|
||||
import classNames from 'classnames';
|
||||
import { Button } from 'stremio/common';
|
||||
import useCalendarDate from '../../useCalendarDate';
|
||||
import styles from './Item.less';
|
||||
|
||||
type Props = {
|
||||
selected: CalendarDate | null,
|
||||
monthInfo: CalendarMonthInfo,
|
||||
date: CalendarDate,
|
||||
items: CalendarContentItem[],
|
||||
profile: Profile,
|
||||
onClick: (date: CalendarDate) => void,
|
||||
};
|
||||
|
||||
const Item = ({ selected, monthInfo, date, items, profile, onClick }: Props) => {
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
const { toDayMonth } = useCalendarDate(profile);
|
||||
|
||||
const [active, today] = useMemo(() => [
|
||||
date.day === selected?.day,
|
||||
date.day === monthInfo.today,
|
||||
], [selected, monthInfo, date]);
|
||||
|
||||
const onItemClick = () => {
|
||||
onClick && onClick(date);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
active && ref.current?.scrollIntoView({
|
||||
block: 'start',
|
||||
behavior: 'smooth',
|
||||
});
|
||||
}, [active]);
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={ref}
|
||||
className={classNames(styles['item'], { [styles['active']]: active, [styles['today']]: today })}
|
||||
key={date.day}
|
||||
onClick={onItemClick}
|
||||
>
|
||||
<div className={styles['heading']}>
|
||||
{toDayMonth(date)}
|
||||
</div>
|
||||
<div className={styles['body']}>
|
||||
{
|
||||
items.map(({ id, name, season, episode, deepLinks }) => (
|
||||
<Button className={styles['video']} key={id} href={deepLinks.metaDetailsStreams}>
|
||||
<div className={styles['name']}>
|
||||
{name}
|
||||
</div>
|
||||
<div className={styles['info']}>
|
||||
S{season}E{episode}
|
||||
</div>
|
||||
<Icon className={styles['icon']} name={'play'} />
|
||||
</Button>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Item;
|
||||
5
src/routes/Calendar/List/Item/index.ts
Normal file
5
src/routes/Calendar/List/Item/index.ts
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
// Copyright (C) 2017-2024 Smart code 203358507
|
||||
|
||||
import Item from './Item';
|
||||
|
||||
export default Item;
|
||||
37
src/routes/Calendar/List/List.less
Normal file
37
src/routes/Calendar/List/List.less
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
// Copyright (C) 2017-2024 Smart code 203358507
|
||||
|
||||
@import (reference) '~stremio/common/screen-sizes.less';
|
||||
|
||||
.list {
|
||||
flex: none;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
width: 20rem;
|
||||
padding: 0 1rem;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: @small) and (orientation: portrait) {
|
||||
.list {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: @medium) and (orientation: landscape) {
|
||||
.list {
|
||||
width: 20rem;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: @small) and (orientation: landscape) {
|
||||
.list {
|
||||
width: 17rem;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: @xsmall) and (orientation: landscape) {
|
||||
.list {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
38
src/routes/Calendar/List/List.tsx
Normal file
38
src/routes/Calendar/List/List.tsx
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
// Copyright (C) 2017-2024 Smart code 203358507
|
||||
|
||||
import React, { useMemo } from 'react';
|
||||
import Item from './Item';
|
||||
import styles from './List.less';
|
||||
|
||||
type Props = {
|
||||
items: CalendarItem[],
|
||||
selected: CalendarDate | null,
|
||||
monthInfo: CalendarMonthInfo,
|
||||
profile: Profile,
|
||||
onChange: (date: CalendarDate) => void,
|
||||
};
|
||||
|
||||
const List = ({ items, selected, monthInfo, profile, onChange }: Props) => {
|
||||
const filteredItems = useMemo(() => {
|
||||
return items.filter(({ items }) => items.length);
|
||||
}, [items]);
|
||||
|
||||
return (
|
||||
<div className={styles['list']}>
|
||||
{
|
||||
filteredItems.map((item) => (
|
||||
<Item
|
||||
key={item.date.day}
|
||||
{...item}
|
||||
selected={selected}
|
||||
monthInfo={monthInfo}
|
||||
profile={profile}
|
||||
onClick={onChange}
|
||||
/>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default List;
|
||||
5
src/routes/Calendar/List/index.ts
Normal file
5
src/routes/Calendar/List/index.ts
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
// Copyright (C) 2017-2024 Smart code 203358507
|
||||
|
||||
import List from './List';
|
||||
|
||||
export default List;
|
||||
99
src/routes/Calendar/Placeholder/Placeholder.less
Normal file
99
src/routes/Calendar/Placeholder/Placeholder.less
Normal file
|
|
@ -0,0 +1,99 @@
|
|||
// Copyright (C) 2017-2024 Smart code 203358507
|
||||
|
||||
@import (reference) '~stremio/common/screen-sizes.less';
|
||||
|
||||
.placeholder {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
overflow-y: auto;
|
||||
|
||||
.title {
|
||||
flex: none;
|
||||
font-size: 1.75rem;
|
||||
font-weight: 400;
|
||||
text-align: center;
|
||||
color: var(--primary-foreground-color);
|
||||
margin-bottom: 1rem;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.image {
|
||||
flex: none;
|
||||
height: 14rem;
|
||||
margin: 1.5rem 0;
|
||||
}
|
||||
|
||||
.overview {
|
||||
flex: none;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: 4rem;
|
||||
margin-bottom: 3rem;
|
||||
|
||||
.point {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: 1.5rem;
|
||||
width: 18rem;
|
||||
|
||||
.icon {
|
||||
flex: none;
|
||||
height: 3.25rem;
|
||||
width: 3.25rem;
|
||||
color: var(--primary-foreground-color);
|
||||
opacity: 0.3;
|
||||
}
|
||||
|
||||
.text {
|
||||
flex: auto;
|
||||
font-size: 1.1rem;
|
||||
font-size: 500;
|
||||
color: var(--primary-foreground-color);
|
||||
opacity: 0.9;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.button {
|
||||
flex: none;
|
||||
justify-content: center;
|
||||
height: 4rem;
|
||||
line-height: 4rem;
|
||||
padding: 0 5rem;
|
||||
font-size: 1.1rem;
|
||||
color: var(--primary-foreground-color);
|
||||
text-align: center;
|
||||
border-radius: 3.5rem;
|
||||
background-color: var(--overlay-color);
|
||||
|
||||
&:hover {
|
||||
outline: var(--focus-outline-size) solid var(--primary-foreground-color);
|
||||
background-color: transparent;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: @minimum) {
|
||||
.placeholder {
|
||||
padding: 1rem 2rem;
|
||||
|
||||
.image {
|
||||
height: 10rem;
|
||||
}
|
||||
|
||||
.overview {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.button {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
43
src/routes/Calendar/Placeholder/Placeholder.tsx
Normal file
43
src/routes/Calendar/Placeholder/Placeholder.tsx
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
// Copyright (C) 2017-2024 Smart code 203358507
|
||||
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import Icon from '@stremio/stremio-icons/react';
|
||||
import { Button, Image } from 'stremio/common';
|
||||
import styles from './Placeholder.less';
|
||||
|
||||
const Placeholder = () => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<div className={styles['placeholder']}>
|
||||
<div className={styles['title']}>
|
||||
{t('CALENDAR_NOT_LOGGED_IN')}
|
||||
</div>
|
||||
<Image
|
||||
className={styles['image']}
|
||||
src={require('/images/calendar_placeholder.png')}
|
||||
alt={' '}
|
||||
/>
|
||||
<div className={styles['overview']}>
|
||||
<div className={styles['point']}>
|
||||
<Icon className={styles['icon']} name={'megaphone'} />
|
||||
<div className={styles['text']}>
|
||||
{t('NOT_LOGGED_IN_NOTIFICATIONS')}
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles['point']}>
|
||||
<Icon className={styles['icon']} name={'calendar-thin'} />
|
||||
<div className={styles['text']}>
|
||||
{t('NOT_LOGGED_IN_CALENDAR')}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Button className={styles['button']} href={'#/intro?form=login'}>
|
||||
{t('LOG_IN')}
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Placeholder;
|
||||
5
src/routes/Calendar/Placeholder/index.ts
Normal file
5
src/routes/Calendar/Placeholder/index.ts
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
// Copyright (C) 2017-2024 Smart code 203358507
|
||||
|
||||
import Placeholder from './Placeholder';
|
||||
|
||||
export default Placeholder;
|
||||
92
src/routes/Calendar/Selector/Selector.less
Normal file
92
src/routes/Calendar/Selector/Selector.less
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
// Copyright (C) 2017-2024 Smart code 203358507
|
||||
|
||||
@import (reference) '~stremio/common/screen-sizes.less';
|
||||
|
||||
.selector {
|
||||
flex: none;
|
||||
position: relative;
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 0 1rem;
|
||||
|
||||
.prev, .next {
|
||||
position: relative;
|
||||
height: 3rem;
|
||||
width: 6rem;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 0.5rem;
|
||||
border-radius: 0.5rem;
|
||||
transition: background-color 0.1s ease-out;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
|
||||
.label, .icon {
|
||||
color: var(--primary-foreground-color);
|
||||
opacity: 0.5;
|
||||
transition: opacity 0.1s ease-out;
|
||||
}
|
||||
|
||||
.label {
|
||||
font-size: 1rem;
|
||||
font-weight: 500;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.icon {
|
||||
height: 1.5rem;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
.label, .icon {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
background-color: var(--overlay-color);
|
||||
}
|
||||
}
|
||||
|
||||
.prev {
|
||||
padding-left: 0.5rem;
|
||||
padding-right: 1.25rem;
|
||||
}
|
||||
|
||||
.next {
|
||||
padding-left: 1.25rem;
|
||||
padding-right: 0.5rem;
|
||||
}
|
||||
|
||||
.selected {
|
||||
position: relative;
|
||||
width: 8.5rem;
|
||||
text-align: center;
|
||||
|
||||
.year {
|
||||
font-size: 1rem;
|
||||
font-weight: 500;
|
||||
line-height: 100%;
|
||||
color: var(--primary-foreground-color);
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.month {
|
||||
font-size: 1.5rem;
|
||||
font-weight: 500;
|
||||
color: var(--primary-foreground-color);
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: @small) {
|
||||
.selector {
|
||||
justify-content: space-between;
|
||||
}
|
||||
}
|
||||
62
src/routes/Calendar/Selector/Selector.tsx
Normal file
62
src/routes/Calendar/Selector/Selector.tsx
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
// Copyright (C) 2017-2024 Smart code 203358507
|
||||
|
||||
import React, { useCallback, useMemo } from 'react';
|
||||
import Icon from '@stremio/stremio-icons/react';
|
||||
import { Button } from 'stremio/common';
|
||||
import useCalendarDate from '../useCalendarDate';
|
||||
import styles from './Selector.less';
|
||||
|
||||
type Props = {
|
||||
selected: CalendarSelected,
|
||||
selectable: CalendarSelectable,
|
||||
profile: Profile,
|
||||
};
|
||||
|
||||
const Selector = ({ selected, selectable, profile }: Props) => {
|
||||
const { toMonth } = useCalendarDate(profile);
|
||||
|
||||
const [prev, next] = useMemo(() => (
|
||||
[selectable.prev, selectable.next]
|
||||
), [selectable]);
|
||||
|
||||
const onPrev = useCallback(() => {
|
||||
window.location.href = prev.deepLinks.calendar;
|
||||
}, [prev]);
|
||||
|
||||
const onNext = useCallback(() => {
|
||||
window.location.href = next.deepLinks.calendar;
|
||||
}, [next]);
|
||||
|
||||
return (
|
||||
<div className={styles['selector']}>
|
||||
<Button className={styles['prev']} onClick={onPrev}>
|
||||
<Icon
|
||||
className={styles['icon']}
|
||||
name={'chevron-back'}
|
||||
/>
|
||||
<div className={styles['label']}>
|
||||
{toMonth(prev, 'short')}
|
||||
</div>
|
||||
</Button>
|
||||
<div className={styles['selected']}>
|
||||
<div className={styles['year']}>
|
||||
{selected?.year}
|
||||
</div>
|
||||
<div className={styles['month']}>
|
||||
{toMonth(selected, 'long')}
|
||||
</div>
|
||||
</div>
|
||||
<Button className={styles['next']} onClick={onNext}>
|
||||
<div className={styles['label']}>
|
||||
{toMonth(next, 'short')}
|
||||
</div>
|
||||
<Icon
|
||||
className={styles['icon']}
|
||||
name={'chevron-forward'}
|
||||
/>
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Selector;
|
||||
4
src/routes/Calendar/Selector/index.ts
Normal file
4
src/routes/Calendar/Selector/index.ts
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
// Copyright (C) 2017-2024 Smart code 203358507
|
||||
|
||||
import Selector from './Selector';
|
||||
export default Selector;
|
||||
177
src/routes/Calendar/Table/Cell/Cell.less
Normal file
177
src/routes/Calendar/Table/Cell/Cell.less
Normal file
|
|
@ -0,0 +1,177 @@
|
|||
// Copyright (C) 2017-2024 Smart code 203358507
|
||||
|
||||
@import (reference) '~stremio/common/screen-sizes.less';
|
||||
|
||||
.cell {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
gap: 0.5rem;
|
||||
background-color: var(--overlay-color);
|
||||
border: 0.15rem solid transparent;
|
||||
overflow: hidden;
|
||||
cursor: pointer;
|
||||
transition: border-color 0.1s ease-out;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
|
||||
&:first-child {
|
||||
border-radius: var(--border-radius) 0 0 0;
|
||||
}
|
||||
|
||||
&:nth-child(7) {
|
||||
border-radius: 0 var(--border-radius) 0 0;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
border-radius: 0 0 var(--border-radius) 0;
|
||||
}
|
||||
|
||||
.heading {
|
||||
flex: none;
|
||||
position: relative;
|
||||
height: 3rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 1rem;
|
||||
|
||||
.day {
|
||||
flex: none;
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 2rem;
|
||||
width: 2rem;
|
||||
border-radius: 100%;
|
||||
font-size: 1rem;
|
||||
font-weight: 500;
|
||||
color: var(--primary-foreground-color);
|
||||
}
|
||||
}
|
||||
|
||||
.items {
|
||||
flex: 0 1 10rem;
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 1rem;
|
||||
padding: 0 1rem 1rem 1rem;
|
||||
|
||||
.item {
|
||||
flex: none;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
aspect-ratio: 2 / 3;
|
||||
border-radius: var(--border-radius);
|
||||
|
||||
.icon {
|
||||
flex: none;
|
||||
z-index: 1;
|
||||
position: absolute;
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
padding: 0.5rem;
|
||||
border-radius: 50%;
|
||||
color: var(--primary-foreground-color);
|
||||
background-color: var(--secondary-accent-color);
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.poster {
|
||||
flex: auto;
|
||||
z-index: 0;
|
||||
position: relative;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
object-fit: cover;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.icon, .poster {
|
||||
transition: opacity 0.1s ease-out;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
.icon {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.poster {
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.more {
|
||||
display: none;
|
||||
flex: none;
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
padding: 0.5rem;
|
||||
align-self: center;
|
||||
color: var(--primary-foreground-color);
|
||||
}
|
||||
|
||||
&.today {
|
||||
.heading {
|
||||
.day {
|
||||
background-color: var(--primary-accent-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.active {
|
||||
border-color: var(--primary-foreground-color);
|
||||
}
|
||||
|
||||
&:not(.active):hover {
|
||||
border-color: var(--overlay-color);
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (orientation: portrait) {
|
||||
.cell {
|
||||
.heading {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.items {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.more {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: @small) and (orientation: landscape) {
|
||||
.cell {
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
|
||||
.items {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.more {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-height: @xxsmall) and (orientation: landscape) {
|
||||
.cell {
|
||||
.items {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.more {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
}
|
||||
61
src/routes/Calendar/Table/Cell/Cell.tsx
Normal file
61
src/routes/Calendar/Table/Cell/Cell.tsx
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
// Copyright (C) 2017-2024 Smart code 203358507
|
||||
|
||||
import React, { useMemo } from 'react';
|
||||
import Icon from '@stremio/stremio-icons/react';
|
||||
import classNames from 'classnames';
|
||||
import { Button, Image, HorizontalScroll } from 'stremio/common';
|
||||
import styles from './Cell.less';
|
||||
|
||||
type Props = {
|
||||
selected: CalendarDate | null,
|
||||
monthInfo: CalendarMonthInfo,
|
||||
date: CalendarDate,
|
||||
items: CalendarContentItem[],
|
||||
onClick: (date: CalendarDate) => void,
|
||||
};
|
||||
|
||||
const Cell = ({ selected, monthInfo, date, items, onClick }: Props) => {
|
||||
const [active, today] = useMemo(() => [
|
||||
date.day === selected?.day,
|
||||
date.day === monthInfo.today,
|
||||
], [selected, monthInfo, date]);
|
||||
|
||||
const onCellClick = () => {
|
||||
onClick && onClick(date);
|
||||
};
|
||||
|
||||
return (
|
||||
<Button
|
||||
className={classNames(styles['cell'], { [styles['active']]: active, [styles['today']]: today })}
|
||||
onClick={onCellClick}
|
||||
>
|
||||
<div className={styles['heading']}>
|
||||
<div className={styles['day']}>
|
||||
{date.day}
|
||||
</div>
|
||||
</div>
|
||||
<HorizontalScroll className={styles['items']}>
|
||||
{
|
||||
items.map(({ id, name, poster, deepLinks }) => (
|
||||
<Button key={id} className={styles['item']} href={deepLinks.metaDetailsStreams}>
|
||||
<Icon className={styles['icon']} name={'play'} />
|
||||
<Image
|
||||
className={styles['poster']}
|
||||
src={poster}
|
||||
alt={name}
|
||||
/>
|
||||
</Button>
|
||||
))
|
||||
}
|
||||
</HorizontalScroll>
|
||||
{
|
||||
items.length > 0 ?
|
||||
<Icon className={styles['more']} name={'more-horizontal'} />
|
||||
:
|
||||
null
|
||||
}
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
|
||||
export default Cell;
|
||||
5
src/routes/Calendar/Table/Cell/index.ts
Normal file
5
src/routes/Calendar/Table/Cell/index.ts
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
// Copyright (C) 2017-2024 Smart code 203358507
|
||||
|
||||
import Cell from './Cell';
|
||||
|
||||
export default Cell;
|
||||
65
src/routes/Calendar/Table/Table.less
Normal file
65
src/routes/Calendar/Table/Table.less
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
// Copyright (C) 2017-2024 Smart code 203358507
|
||||
|
||||
@import (reference) '~stremio/common/screen-sizes.less';
|
||||
|
||||
.table {
|
||||
flex: auto;
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.week {
|
||||
flex: none;
|
||||
position: relative;
|
||||
height: 3rem;
|
||||
width: 100%;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(7, 1fr);
|
||||
align-items: center;
|
||||
|
||||
.day {
|
||||
position: relative;
|
||||
padding: 0.5rem;
|
||||
font-size: 1rem;
|
||||
font-weight: 500;
|
||||
color: var(--primary-foreground-color);
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
|
||||
.long {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.short {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.grid {
|
||||
flex: auto;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(7, 1fr);
|
||||
gap: 1px;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: @xsmall) {
|
||||
.table {
|
||||
.week {
|
||||
.day {
|
||||
.long {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.short {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
62
src/routes/Calendar/Table/Table.tsx
Normal file
62
src/routes/Calendar/Table/Table.tsx
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
// Copyright (C) 2017-2024 Smart code 203358507
|
||||
|
||||
import React, { useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import styles from './Table.less';
|
||||
import Cell from './Cell/Cell';
|
||||
|
||||
const WEEK_DAYS = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday'];
|
||||
|
||||
type Props = {
|
||||
items: CalendarItem[],
|
||||
selected: CalendarDate | null,
|
||||
monthInfo: CalendarMonthInfo,
|
||||
onChange: (date: CalendarDate) => void,
|
||||
};
|
||||
|
||||
const Table = ({ items, selected, monthInfo, onChange }: Props) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const cellsOffset = useMemo(() => {
|
||||
return Array.from(Array(monthInfo.firstWeekday).keys());
|
||||
}, [monthInfo]);
|
||||
|
||||
return (
|
||||
<div className={styles['table']}>
|
||||
<div className={styles['week']}>
|
||||
{
|
||||
WEEK_DAYS.map((day) => (
|
||||
<div className={styles['day']} key={day}>
|
||||
<span className={styles['long']}>
|
||||
{t(day)}
|
||||
</span>
|
||||
<span className={styles['short']}>
|
||||
{t(day).slice(0, 3)}
|
||||
</span>
|
||||
</div>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
<div className={styles['grid']}>
|
||||
{
|
||||
cellsOffset.map((day) => (
|
||||
<span key={day} />
|
||||
))
|
||||
}
|
||||
{
|
||||
items.map((item) => (
|
||||
<Cell
|
||||
key={item.date.day}
|
||||
{...item}
|
||||
selected={selected}
|
||||
monthInfo={monthInfo}
|
||||
onClick={onChange}
|
||||
/>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Table;
|
||||
5
src/routes/Calendar/Table/index.ts
Normal file
5
src/routes/Calendar/Table/index.ts
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
// Copyright (C) 2017-2024 Smart code 203358507
|
||||
|
||||
import Table from './Table';
|
||||
|
||||
export default Table;
|
||||
5
src/routes/Calendar/index.ts
Normal file
5
src/routes/Calendar/index.ts
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
// Copyright (C) 2017-2024 Smart code 203358507
|
||||
|
||||
import Calendar from './Calendar';
|
||||
|
||||
export default Calendar;
|
||||
27
src/routes/Calendar/useCalendar.ts
Normal file
27
src/routes/Calendar/useCalendar.ts
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
// Copyright (C) 2017-2024 Smart code 203358507
|
||||
|
||||
import React from 'react';
|
||||
import { useModelState } from 'stremio/common';
|
||||
|
||||
const useCalendar = (urlParams: UrlParams) => {
|
||||
const action = React.useMemo(() => {
|
||||
const args = urlParams.year && urlParams.month ? {
|
||||
year: parseInt(urlParams.year),
|
||||
month: parseInt(urlParams.month),
|
||||
day: urlParams.day ? parseInt(urlParams.day) : null,
|
||||
} : null;
|
||||
|
||||
return {
|
||||
action: 'Load',
|
||||
args: {
|
||||
model: 'Calendar',
|
||||
args,
|
||||
},
|
||||
};
|
||||
}, [urlParams]);
|
||||
|
||||
const calendar = useModelState({ model: 'calendar', action }) as Calendar;
|
||||
return calendar;
|
||||
};
|
||||
|
||||
export default useCalendar;
|
||||
50
src/routes/Calendar/useCalendarDate.ts
Normal file
50
src/routes/Calendar/useCalendarDate.ts
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
import { useCallback } from 'react';
|
||||
|
||||
const useCalendarDate = (profile: Profile) => {
|
||||
const toMonth = useCallback((calendarDate: CalendarDate | CalendarSelectableDate | null, format: 'short' | 'long'): string => {
|
||||
if (!calendarDate) return '';
|
||||
|
||||
const date = new Date();
|
||||
date.setDate(1);
|
||||
date.setMonth(calendarDate.month - 1);
|
||||
|
||||
return date.toLocaleString(profile.settings.interfaceLanguage, {
|
||||
month: format,
|
||||
});
|
||||
}, [profile.settings]);
|
||||
|
||||
const toMonthYear = useCallback((calendarDate: CalendarDate | null): string => {
|
||||
if (!calendarDate) return '';
|
||||
|
||||
const date = new Date();
|
||||
date.setDate(1);
|
||||
date.setMonth(calendarDate.month - 1);
|
||||
date.setFullYear(calendarDate.year);
|
||||
|
||||
return date.toLocaleString(profile.settings.interfaceLanguage, {
|
||||
month: 'long',
|
||||
year: 'numeric',
|
||||
});
|
||||
}, [profile.settings]);
|
||||
|
||||
const toDayMonth = useCallback((calendarDate: CalendarDate | null): string => {
|
||||
if (!calendarDate) return '';
|
||||
|
||||
const date = new Date();
|
||||
date.setDate(calendarDate.day);
|
||||
date.setMonth(calendarDate.month - 1);
|
||||
|
||||
return date.toLocaleString(profile.settings.interfaceLanguage, {
|
||||
day: 'numeric',
|
||||
month: 'short',
|
||||
});
|
||||
}, [profile.settings]);
|
||||
|
||||
return {
|
||||
toMonth,
|
||||
toMonthYear,
|
||||
toDayMonth,
|
||||
};
|
||||
};
|
||||
|
||||
export default useCalendarDate;
|
||||
|
|
@ -11,13 +11,6 @@
|
|||
multiselect-label: label;
|
||||
}
|
||||
|
||||
:import('~stremio/common/PaginationInput/styles.less') {
|
||||
pagination-prev-button-container: prev-button-container;
|
||||
pagination-next-button-container: next-button-container;
|
||||
pagination-button-icon: icon;
|
||||
pagination-label: label;
|
||||
}
|
||||
|
||||
:import('~stremio/common/ModalDialog/styles.less') {
|
||||
selectable-inputs-modal-container: modal-dialog-container;
|
||||
selectable-inputs-modal-content: modal-dialog-content;
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ const Addons = require('./Addons');
|
|||
const Board = require('./Board');
|
||||
const Discover = require('./Discover');
|
||||
const Library = require('./Library');
|
||||
const Calendar = require('./Calendar').default;
|
||||
const MetaDetails = require('./MetaDetails');
|
||||
const NotFound = require('./NotFound');
|
||||
const Search = require('./Search');
|
||||
|
|
@ -16,6 +17,7 @@ module.exports = {
|
|||
Board,
|
||||
Discover,
|
||||
Library,
|
||||
Calendar,
|
||||
MetaDetails,
|
||||
NotFound,
|
||||
Search,
|
||||
|
|
|
|||
|
|
@ -35,10 +35,15 @@ function KeyboardShortcuts() {
|
|||
}
|
||||
case 'Digit4': {
|
||||
event.preventDefault();
|
||||
window.location = '#/addons';
|
||||
window.location = '#/calendar';
|
||||
break;
|
||||
}
|
||||
case 'Digit5': {
|
||||
event.preventDefault();
|
||||
window.location = '#/addons';
|
||||
break;
|
||||
}
|
||||
case 'Digit6': {
|
||||
event.preventDefault();
|
||||
window.location = '#/settings';
|
||||
break;
|
||||
|
|
|
|||
55
src/types/models/Calendar.d.ts
vendored
Normal file
55
src/types/models/Calendar.d.ts
vendored
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
type CalendarDeepLinks = {
|
||||
calendar: string,
|
||||
};
|
||||
|
||||
type CalendarItemDeepLinks = {
|
||||
metaDetailsStreams: string,
|
||||
};
|
||||
|
||||
type CalendarSelectableDate = {
|
||||
month: number,
|
||||
year: number,
|
||||
selected: boolean,
|
||||
deepLinks: CalendarDeepLinks,
|
||||
};
|
||||
|
||||
type CalendarSelectable = {
|
||||
prev: CalendarSelectableDate,
|
||||
next: CalendarSelectableDate,
|
||||
};
|
||||
|
||||
type CalendarDate = {
|
||||
day: number,
|
||||
month: number,
|
||||
year: number,
|
||||
};
|
||||
|
||||
type CalendarSelected = CalendarDate | null;
|
||||
|
||||
type CalendarMonthInfo = {
|
||||
today: number | null,
|
||||
days: number,
|
||||
firstWeekday: number,
|
||||
};
|
||||
|
||||
type CalendarContentItem = {
|
||||
id: string,
|
||||
name: string,
|
||||
poster?: string,
|
||||
title: string,
|
||||
season?: number,
|
||||
episode?: number,
|
||||
deepLinks: CalendarItemDeepLinks,
|
||||
};
|
||||
|
||||
type CalendarItem = {
|
||||
date: CalendarDate,
|
||||
items: CalendarContentItem[],
|
||||
};
|
||||
|
||||
type Calendar = {
|
||||
selectable: CalendarSelectable,
|
||||
selected: CalendarSelected,
|
||||
monthInfo: CalendarMonthInfo,
|
||||
items: CalendarItem[],
|
||||
};
|
||||
Loading…
Reference in a new issue