mirror of
https://github.com/Stremio/stremio-web.git
synced 2026-04-21 11:42:05 +00:00
Merge branch 'development' of https://github.com/Stremio/stremio-web into feat/settings-transcoding-profile
This commit is contained in:
commit
0ad4d1dd0f
20 changed files with 340 additions and 74 deletions
129
package-lock.json
generated
129
package-lock.json
generated
|
|
@ -63,6 +63,8 @@
|
||||||
"postcss-loader": "6.2.0",
|
"postcss-loader": "6.2.0",
|
||||||
"readdirp": "3.6.0",
|
"readdirp": "3.6.0",
|
||||||
"terser-webpack-plugin": "5.2.4",
|
"terser-webpack-plugin": "5.2.4",
|
||||||
|
"ts-loader": "^9.5.1",
|
||||||
|
"typescript": "^5.4.2",
|
||||||
"webpack": "5.61.0",
|
"webpack": "5.61.0",
|
||||||
"webpack-cli": "4.9.1",
|
"webpack-cli": "4.9.1",
|
||||||
"webpack-dev-server": "^4.7.4",
|
"webpack-dev-server": "^4.7.4",
|
||||||
|
|
@ -13039,6 +13041,120 @@
|
||||||
"node": ">=6"
|
"node": ">=6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/ts-loader": {
|
||||||
|
"version": "9.5.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-9.5.1.tgz",
|
||||||
|
"integrity": "sha512-rNH3sK9kGZcH9dYzC7CewQm4NtxJTjSEVRJ2DyBZR7f8/wcta+iV44UPCXc5+nzDzivKtlzV6c9P4e+oFhDLYg==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"chalk": "^4.1.0",
|
||||||
|
"enhanced-resolve": "^5.0.0",
|
||||||
|
"micromatch": "^4.0.0",
|
||||||
|
"semver": "^7.3.4",
|
||||||
|
"source-map": "^0.7.4"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"typescript": "*",
|
||||||
|
"webpack": "^5.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/ts-loader/node_modules/ansi-styles": {
|
||||||
|
"version": "4.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
|
||||||
|
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"color-convert": "^2.0.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/ts-loader/node_modules/chalk": {
|
||||||
|
"version": "4.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
|
||||||
|
"integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"ansi-styles": "^4.1.0",
|
||||||
|
"supports-color": "^7.1.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/chalk/chalk?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/ts-loader/node_modules/color-convert": {
|
||||||
|
"version": "2.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
||||||
|
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"color-name": "~1.1.4"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=7.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/ts-loader/node_modules/color-name": {
|
||||||
|
"version": "1.1.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
|
||||||
|
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"node_modules/ts-loader/node_modules/has-flag": {
|
||||||
|
"version": "4.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
|
||||||
|
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
|
||||||
|
"dev": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/ts-loader/node_modules/semver": {
|
||||||
|
"version": "7.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz",
|
||||||
|
"integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"lru-cache": "^6.0.0"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"semver": "bin/semver.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/ts-loader/node_modules/source-map": {
|
||||||
|
"version": "0.7.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz",
|
||||||
|
"integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==",
|
||||||
|
"dev": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/ts-loader/node_modules/supports-color": {
|
||||||
|
"version": "7.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
|
||||||
|
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"has-flag": "^4.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/tslib": {
|
"node_modules/tslib": {
|
||||||
"version": "1.14.1",
|
"version": "1.14.1",
|
||||||
"license": "0BSD"
|
"license": "0BSD"
|
||||||
|
|
@ -13093,6 +13209,19 @@
|
||||||
"is-typedarray": "^1.0.0"
|
"is-typedarray": "^1.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/typescript": {
|
||||||
|
"version": "5.4.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.2.tgz",
|
||||||
|
"integrity": "sha512-+2/g0Fds1ERlP6JsakQQDXjZdZMM+rqpamFZJEKh4kwTIn3iDkgKtby0CeNd5ATNZ4Ry1ax15TMx0W2V+miizQ==",
|
||||||
|
"dev": true,
|
||||||
|
"bin": {
|
||||||
|
"tsc": "bin/tsc",
|
||||||
|
"tsserver": "bin/tsserver"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.17"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/unbox-primitive": {
|
"node_modules/unbox-primitive": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
|
|
||||||
|
|
@ -66,6 +66,8 @@
|
||||||
"postcss-loader": "6.2.0",
|
"postcss-loader": "6.2.0",
|
||||||
"readdirp": "3.6.0",
|
"readdirp": "3.6.0",
|
||||||
"terser-webpack-plugin": "5.2.4",
|
"terser-webpack-plugin": "5.2.4",
|
||||||
|
"ts-loader": "^9.5.1",
|
||||||
|
"typescript": "^5.4.2",
|
||||||
"webpack": "5.61.0",
|
"webpack": "5.61.0",
|
||||||
"webpack-cli": "4.9.1",
|
"webpack-cli": "4.9.1",
|
||||||
"webpack-dev-server": "^4.7.4",
|
"webpack-dev-server": "^4.7.4",
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,7 @@
|
||||||
--primary-accent-color: rgb(123, 91, 245);
|
--primary-accent-color: rgb(123, 91, 245);
|
||||||
--secondary-accent-color: rgba(34, 179, 101, 1);
|
--secondary-accent-color: rgba(34, 179, 101, 1);
|
||||||
--tertiary-accent-color: rgba(246, 199, 0, 1);
|
--tertiary-accent-color: rgba(246, 199, 0, 1);
|
||||||
|
--quaternary-accent-color: rgba(18, 69, 166, 1);
|
||||||
--overlay-color: rgba(255, 255, 255, 0.05);
|
--overlay-color: rgba(255, 255, 255, 0.05);
|
||||||
--modal-background-color: rgba(15, 13, 32, 1);
|
--modal-background-color: rgba(15, 13, 32, 1);
|
||||||
--outer-glow: 0px 0px 30px rgba(123, 91, 245, 0.37);
|
--outer-glow: 0px 0px 30px rgba(123, 91, 245, 0.37);
|
||||||
|
|
|
||||||
34
src/common/Chips/Chip/Chip.less
Normal file
34
src/common/Chips/Chip/Chip.less
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
// Copyright (C) 2017-2024 Smart code 203358507
|
||||||
|
|
||||||
|
@height: 2.75rem;
|
||||||
|
|
||||||
|
.chip {
|
||||||
|
flex: none;
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
height: @height;
|
||||||
|
font-size: 1rem;
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--primary-foreground-color);
|
||||||
|
white-space: nowrap;
|
||||||
|
text-transform: capitalize;
|
||||||
|
padding: 0 1.75rem;
|
||||||
|
border-radius: @height;
|
||||||
|
-webkit-tap-highlight-color: transparent;
|
||||||
|
background-color: transparent;
|
||||||
|
user-select: none;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: var(--overlay-color);
|
||||||
|
transition: background-color 0.1s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
font-weight: 700;
|
||||||
|
background-color: var(--quaternary-accent-color);
|
||||||
|
transition: background-color 0.1s ease-in;
|
||||||
|
}
|
||||||
|
}
|
||||||
45
src/common/Chips/Chip/Chip.tsx
Normal file
45
src/common/Chips/Chip/Chip.tsx
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
// Copyright (C) 2017-2024 Smart code 203358507
|
||||||
|
|
||||||
|
import React, { MouseEvent, memo, useCallback, useEffect, useRef } from 'react';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import Button from 'stremio/common/Button';
|
||||||
|
import styles from './Chip.less';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
label: string,
|
||||||
|
value: string,
|
||||||
|
active: boolean,
|
||||||
|
onSelect: (value: string) => void,
|
||||||
|
};
|
||||||
|
|
||||||
|
const Chip = memo(({ label, value, active, onSelect }: Props) => {
|
||||||
|
const ref = useRef<HTMLElement>(null);
|
||||||
|
|
||||||
|
const onClick = useCallback(({ currentTarget }: MouseEvent<HTMLElement>) => {
|
||||||
|
const value = currentTarget.dataset['value'];
|
||||||
|
value && onSelect(value);
|
||||||
|
}, [onselect]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
active && ref.current?.scrollIntoView({
|
||||||
|
block: 'nearest',
|
||||||
|
inline: 'center',
|
||||||
|
behavior: 'smooth',
|
||||||
|
});
|
||||||
|
}, [active]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
ref={ref}
|
||||||
|
key={value}
|
||||||
|
className={classNames(styles['chip'], { [styles['active']]: active })}
|
||||||
|
tabIndex={-1}
|
||||||
|
data-value={value}
|
||||||
|
onClick={onClick}
|
||||||
|
>
|
||||||
|
{label}
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
export default Chip;
|
||||||
4
src/common/Chips/Chip/index.ts
Normal file
4
src/common/Chips/Chip/index.ts
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
// Copyright (C) 2017-2024 Smart code 203358507
|
||||||
|
|
||||||
|
import Chip from './Chip';
|
||||||
|
export default Chip;
|
||||||
25
src/common/Chips/Chips.less
Normal file
25
src/common/Chips/Chips.less
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
// Copyright (C) 2017-2024 Smart code 203358507
|
||||||
|
|
||||||
|
@mask-width: 10%;
|
||||||
|
|
||||||
|
.chips {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
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%);
|
||||||
|
}
|
||||||
|
}
|
||||||
56
src/common/Chips/Chips.tsx
Normal file
56
src/common/Chips/Chips.tsx
Normal file
|
|
@ -0,0 +1,56 @@
|
||||||
|
// Copyright (C) 2017-2024 Smart code 203358507
|
||||||
|
|
||||||
|
import React, { memo, useEffect, useRef, useState } from 'react';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import Chip from './Chip';
|
||||||
|
import styles from './Chips.less';
|
||||||
|
|
||||||
|
type Option = {
|
||||||
|
label: string,
|
||||||
|
value: string,
|
||||||
|
};
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
options: Option[],
|
||||||
|
selected: string[],
|
||||||
|
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]])}>
|
||||||
|
{
|
||||||
|
options.map(({ label, value }) => (
|
||||||
|
<Chip
|
||||||
|
key={value}
|
||||||
|
label={label}
|
||||||
|
value={value}
|
||||||
|
active={selected.includes(value)}
|
||||||
|
onSelect={onSelect}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
export default Chips;
|
||||||
4
src/common/Chips/index.ts
Normal file
4
src/common/Chips/index.ts
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
// Copyright (C) 2017-2024 Smart code 203358507
|
||||||
|
|
||||||
|
import Chips from './Chips';
|
||||||
|
export default Chips;
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
const AddonDetailsModal = require('./AddonDetailsModal');
|
const AddonDetailsModal = require('./AddonDetailsModal');
|
||||||
const Button = require('./Button');
|
const Button = require('./Button');
|
||||||
const Checkbox = require('./Checkbox');
|
const Checkbox = require('./Checkbox');
|
||||||
|
const { default: Chips } = require('./Chips');
|
||||||
const ColorInput = require('./ColorInput');
|
const ColorInput = require('./ColorInput');
|
||||||
const ContinueWatchingItem = require('./ContinueWatchingItem');
|
const ContinueWatchingItem = require('./ContinueWatchingItem');
|
||||||
const DelayedRenderer = require('./DelayedRenderer');
|
const DelayedRenderer = require('./DelayedRenderer');
|
||||||
|
|
@ -50,6 +51,7 @@ module.exports = {
|
||||||
AddonDetailsModal,
|
AddonDetailsModal,
|
||||||
Button,
|
Button,
|
||||||
Checkbox,
|
Checkbox,
|
||||||
|
Chips,
|
||||||
ColorInput,
|
ColorInput,
|
||||||
ContinueWatchingItem,
|
ContinueWatchingItem,
|
||||||
DelayedRenderer,
|
DelayedRenderer,
|
||||||
|
|
|
||||||
5
src/modules.d.ts
vendored
5
src/modules.d.ts
vendored
|
|
@ -1,2 +1,3 @@
|
||||||
declare module '*';
|
declare module '*.less';
|
||||||
declare module 'classnames';
|
declare module 'stremio/common';
|
||||||
|
declare module 'stremio/common/*';
|
||||||
|
|
@ -3,9 +3,8 @@
|
||||||
const React = require('react');
|
const React = require('react');
|
||||||
const PropTypes = require('prop-types');
|
const PropTypes = require('prop-types');
|
||||||
const classnames = require('classnames');
|
const classnames = require('classnames');
|
||||||
const { default: Icon } = require('@stremio/stremio-icons/react');
|
|
||||||
const NotFound = require('stremio/routes/NotFound');
|
const NotFound = require('stremio/routes/NotFound');
|
||||||
const { Button, DelayedRenderer, Multiselect, MainNavBars, LibItem, Image, ModalDialog, useProfile, useNotifications, routesRegexp, useOnScrollToBottom, useBinaryState, withCoreSuspender } = require('stremio/common');
|
const { Button, Chips, DelayedRenderer, Multiselect, MainNavBars, LibItem, Image, useProfile, useNotifications, routesRegexp, useOnScrollToBottom, withCoreSuspender } = require('stremio/common');
|
||||||
const useLibrary = require('./useLibrary');
|
const useLibrary = require('./useLibrary');
|
||||||
const useSelectableInputs = require('./useSelectableInputs');
|
const useSelectableInputs = require('./useSelectableInputs');
|
||||||
const styles = require('./styles');
|
const styles = require('./styles');
|
||||||
|
|
@ -49,8 +48,7 @@ const Library = ({ model, urlParams, queryParams }) => {
|
||||||
const profile = useProfile();
|
const profile = useProfile();
|
||||||
const notifications = useNotifications();
|
const notifications = useNotifications();
|
||||||
const [library, loadNextPage] = useLibrary(model, urlParams, queryParams);
|
const [library, loadNextPage] = useLibrary(model, urlParams, queryParams);
|
||||||
const [typeSelect, sortSelect, hasNextPage] = useSelectableInputs(library);
|
const [typeSelect, sortChips, hasNextPage] = useSelectableInputs(library);
|
||||||
const [inputsModalOpen, openInputsModal, closeInputsModal] = useBinaryState(false);
|
|
||||||
const scrollContainerRef = React.useRef(null);
|
const scrollContainerRef = React.useRef(null);
|
||||||
const onScrollToBottom = React.useCallback(() => {
|
const onScrollToBottom = React.useCallback(() => {
|
||||||
if (hasNextPage) {
|
if (hasNextPage) {
|
||||||
|
|
@ -70,11 +68,7 @@ const Library = ({ model, urlParams, queryParams }) => {
|
||||||
model === 'continue_watching' || profile.auth !== null ?
|
model === 'continue_watching' || profile.auth !== null ?
|
||||||
<div className={styles['selectable-inputs-container']}>
|
<div className={styles['selectable-inputs-container']}>
|
||||||
<Multiselect {...typeSelect} className={styles['select-input-container']} />
|
<Multiselect {...typeSelect} className={styles['select-input-container']} />
|
||||||
<Multiselect {...sortSelect} className={styles['select-input-container']} />
|
<Chips {...sortChips} className={styles['select-input-container']} />
|
||||||
<div className={styles['spacing']} />
|
|
||||||
<Button className={styles['filter-container']} title={'All filters'} onClick={openInputsModal}>
|
|
||||||
<Icon className={styles['filter-icon']} name={'filters'} />
|
|
||||||
</Button>
|
|
||||||
</div>
|
</div>
|
||||||
:
|
:
|
||||||
null
|
null
|
||||||
|
|
@ -122,15 +116,6 @@ const Library = ({ model, urlParams, queryParams }) => {
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
{
|
|
||||||
inputsModalOpen ?
|
|
||||||
<ModalDialog title={'Library filters'} className={styles['selectable-inputs-modal']} onCloseRequest={closeInputsModal}>
|
|
||||||
<Multiselect {...typeSelect} className={styles['select-input-container']} />
|
|
||||||
<Multiselect {...sortSelect} className={styles['select-input-container']} />
|
|
||||||
</ModalDialog>
|
|
||||||
:
|
|
||||||
null
|
|
||||||
}
|
|
||||||
</MainNavBars>
|
</MainNavBars>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -46,28 +46,6 @@
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.filter-container {
|
|
||||||
flex: none;
|
|
||||||
display: none;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
width: 3rem;
|
|
||||||
height: 3rem;
|
|
||||||
border-radius: var(--border-radius);
|
|
||||||
background-color: var(--overlay-color);
|
|
||||||
|
|
||||||
.filter-icon {
|
|
||||||
flex: none;
|
|
||||||
width: 1.4rem;
|
|
||||||
height: 1.4rem;
|
|
||||||
color: var(--primary-foreground-color);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.spacing {
|
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.message-container {
|
.message-container {
|
||||||
|
|
@ -241,18 +219,6 @@
|
||||||
.library-content {
|
.library-content {
|
||||||
.selectable-inputs-container {
|
.selectable-inputs-container {
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
|
|
||||||
.select-input-container {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.spacing {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.filter-container {
|
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.meta-items-container {
|
.meta-items-container {
|
||||||
|
|
|
||||||
|
|
@ -18,8 +18,7 @@ const mapSelectableInputs = (library, t) => {
|
||||||
window.location = event.value;
|
window.location = event.value;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const sortSelect = {
|
const sortChips = {
|
||||||
title: t.string('SELECT_SORT'),
|
|
||||||
options: library.selectable.sorts
|
options: library.selectable.sorts
|
||||||
.map(({ sort, deepLinks }) => ({
|
.map(({ sort, deepLinks }) => ({
|
||||||
value: deepLinks.library,
|
value: deepLinks.library,
|
||||||
|
|
@ -28,11 +27,11 @@ const mapSelectableInputs = (library, t) => {
|
||||||
selected: library.selectable.sorts
|
selected: library.selectable.sorts
|
||||||
.filter(({ selected }) => selected)
|
.filter(({ selected }) => selected)
|
||||||
.map(({ deepLinks }) => deepLinks.library),
|
.map(({ deepLinks }) => deepLinks.library),
|
||||||
onSelect: (event) => {
|
onSelect: (value) => {
|
||||||
window.location = event.value;
|
window.location = value;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
return [typeSelect, sortSelect, library.selectable.nextPage];
|
return [typeSelect, sortChips, library.selectable.nextPage];
|
||||||
};
|
};
|
||||||
|
|
||||||
const useSelectableInputs = (library) => {
|
const useSelectableInputs = (library) => {
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ const PropTypes = require('prop-types');
|
||||||
const classnames = require('classnames');
|
const classnames = require('classnames');
|
||||||
const debounce = require('lodash.debounce');
|
const debounce = require('lodash.debounce');
|
||||||
const { useRouteFocused } = require('stremio-router');
|
const { useRouteFocused } = require('stremio-router');
|
||||||
const { Slider } = require('stremio/common');
|
const { Slider, Button, useBinaryState } = require('stremio/common');
|
||||||
const formatTime = require('./formatTime');
|
const formatTime = require('./formatTime');
|
||||||
const styles = require('./styles');
|
const styles = require('./styles');
|
||||||
|
|
||||||
|
|
@ -13,6 +13,8 @@ const SeekBar = ({ className, time, duration, buffered, onSeekRequested }) => {
|
||||||
const disabled = time === null || isNaN(time) || duration === null || isNaN(duration);
|
const disabled = time === null || isNaN(time) || duration === null || isNaN(duration);
|
||||||
const routeFocused = useRouteFocused();
|
const routeFocused = useRouteFocused();
|
||||||
const [seekTime, setSeekTime] = React.useState(null);
|
const [seekTime, setSeekTime] = React.useState(null);
|
||||||
|
|
||||||
|
const [remainingTimeMode,,, toggleRemainingTimeMode] = useBinaryState(false);
|
||||||
const resetTimeDebounced = React.useCallback(debounce(() => {
|
const resetTimeDebounced = React.useCallback(debounce(() => {
|
||||||
setSeekTime(null);
|
setSeekTime(null);
|
||||||
}, 1500), []);
|
}, 1500), []);
|
||||||
|
|
@ -56,7 +58,13 @@ const SeekBar = ({ className, time, duration, buffered, onSeekRequested }) => {
|
||||||
onSlide={onSlide}
|
onSlide={onSlide}
|
||||||
onComplete={onComplete}
|
onComplete={onComplete}
|
||||||
/>
|
/>
|
||||||
<div className={styles['label']}>{formatTime(duration)}</div>
|
<Button onClick={toggleRemainingTimeMode}>
|
||||||
|
<div className={styles['label']}>
|
||||||
|
{remainingTimeMode && duration !== null && !isNaN(duration)
|
||||||
|
? formatTime(duration - time, '-')
|
||||||
|
: formatTime(duration) }
|
||||||
|
</div>
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ const formatUnit = (value) => {
|
||||||
return ('0' + value).slice(-1 * Math.max(value.toString().length, 2));
|
return ('0' + value).slice(-1 * Math.max(value.toString().length, 2));
|
||||||
};
|
};
|
||||||
|
|
||||||
const formatTime = (time) => {
|
const formatTime = (time, prefix = '') => {
|
||||||
if (time === null || isNaN(time)) {
|
if (time === null || isNaN(time)) {
|
||||||
return '--:--:--';
|
return '--:--:--';
|
||||||
}
|
}
|
||||||
|
|
@ -12,7 +12,7 @@ const formatTime = (time) => {
|
||||||
const hours = Math.floor(time / (1000 * 60 * 60));
|
const hours = Math.floor(time / (1000 * 60 * 60));
|
||||||
const minutes = Math.floor((time / (1000 * 60)) % 60);
|
const minutes = Math.floor((time / (1000 * 60)) % 60);
|
||||||
const seconds = Math.floor((time / 1000) % 60);
|
const seconds = Math.floor((time / 1000) % 60);
|
||||||
return `${formatUnit(hours)}:${formatUnit(minutes)}:${formatUnit(seconds)}`;
|
return `${formatUnit(hours)}:${formatUnit(minutes)}:${formatUnit(seconds)}${prefix}`;
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = formatTime;
|
module.exports = formatTime;
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@
|
||||||
|
|
||||||
.label {
|
.label {
|
||||||
flex: none;
|
flex: none;
|
||||||
width: 5rem;
|
width: 6rem;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
direction: rtl;
|
direction: rtl;
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
const readdirp = require('readdirp');
|
const readdirp = require('readdirp');
|
||||||
|
|
||||||
const COPYRIGHT_HEADER = /^\/\/ Copyright \(C\) 2017-2023 Smart code 203358507.*/;
|
const COPYRIGHT_HEADER = /^\/\/ Copyright \(C\) 2017-\d{4} Smart code 203358507.*/;
|
||||||
|
|
||||||
describe('copyright', () => {
|
describe('copyright', () => {
|
||||||
test('js', async () => {
|
test('js', async () => {
|
||||||
|
|
|
||||||
|
|
@ -1,20 +1,20 @@
|
||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"lib": ["DOM", "DOM.Iterable"],
|
"lib": ["DOM", "DOM.Iterable"],
|
||||||
"jsx": "preserve",
|
"jsx": "react",
|
||||||
"rootDir": "./src",
|
"baseUrl": "src",
|
||||||
"moduleResolution": "node",
|
"moduleResolution": "node",
|
||||||
"baseUrl": ".",
|
|
||||||
"paths": {
|
"paths": {
|
||||||
"stremio/*": ["src/*"],
|
"stremio/*": ["src/*"],
|
||||||
},
|
},
|
||||||
"resolveJsonModule": true,
|
"resolveJsonModule": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
"allowJs": true,
|
"allowJs": true,
|
||||||
"checkJs": false,
|
"checkJs": false,
|
||||||
"noEmit": true,
|
"noEmit": false,
|
||||||
"strict": false
|
"strict": true,
|
||||||
},
|
},
|
||||||
"include": [
|
"include": [
|
||||||
"./src",
|
"src",
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -44,6 +44,11 @@ module.exports = (env, argv) => ({
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
test: /\.(ts|tsx)$/,
|
||||||
|
exclude: /node_modules/,
|
||||||
|
use: 'ts-loader',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
test: /\.less$/,
|
test: /\.less$/,
|
||||||
exclude: /node_modules/,
|
exclude: /node_modules/,
|
||||||
|
|
@ -142,10 +147,10 @@ module.exports = (env, argv) => ({
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
resolve: {
|
resolve: {
|
||||||
extensions: ['.js', '.json', '.less', '.wasm'],
|
extensions: ['.tsx', '.ts', '.js', '.json', '.less', '.wasm'],
|
||||||
alias: {
|
alias: {
|
||||||
'stremio': path.join(__dirname, 'src'),
|
'stremio': path.resolve(__dirname, 'src'),
|
||||||
'stremio-router': path.join(__dirname, 'src', 'router')
|
'stremio-router': path.resolve(__dirname, 'src', 'router')
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
devServer: {
|
devServer: {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue