redesign channel page #2462
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"parser": "babel-eslint",
|
"parser": "babel-eslint",
|
||||||
"extends": ["standard", "standard-jsx", "plugin:flowtype/recommended"],
|
"extends": ["standard", "standard-jsx", "plugin:react/recommended", "plugin:flowtype/recommended"],
|
||||||
"plugins": ["flowtype", "import"],
|
"plugins": ["flowtype", "import", "react-hooks"],
|
||||||
"settings": {
|
"settings": {
|
||||||
"import/resolver": {
|
"import/resolver": {
|
||||||
"webpack": {
|
"webpack": {
|
||||||
|
@ -22,20 +22,21 @@
|
||||||
"IS_WEB": true
|
"IS_WEB": true
|
||||||
},
|
},
|
||||||
"rules": {
|
"rules": {
|
||||||
"no-multi-spaces": 0,
|
"comma-dangle": ["error", "always-multiline"],
|
||||||
"new-cap": 0,
|
|
||||||
"prefer-promise-reject-errors": 0,
|
|
||||||
"no-unused-vars": 0,
|
|
||||||
"standard/object-curly-even-spacing": 0,
|
|
||||||
"handle-callback-err": 0,
|
"handle-callback-err": 0,
|
||||||
"one-var": 0,
|
"jsx-quotes": ["error", "prefer-double"],
|
||||||
"object-curly-spacing": 0,
|
"new-cap": 0,
|
||||||
|
"no-multi-spaces": 0,
|
||||||
"no-redeclare": 0,
|
"no-redeclare": 0,
|
||||||
"no-return-await": 0,
|
"no-return-await": 0,
|
||||||
"standard/no-callback-literal": 0,
|
"object-curly-spacing": 0,
|
||||||
"comma-dangle": ["error", "always-multiline"],
|
"one-var": 0,
|
||||||
|
"prefer-promise-reject-errors": 0,
|
||||||
|
"react-hooks/exhaustive-deps": "warn",
|
||||||
|
"react-hooks/rules-of-hooks": "error",
|
||||||
"space-before-function-paren": ["error", "never"],
|
"space-before-function-paren": ["error", "never"],
|
||||||
"jsx-quotes": ["error", "prefer-double"],
|
"standard/object-curly-even-spacing": 0,
|
||||||
|
"standard/no-callback-literal": 0,
|
||||||
"semi": [
|
"semi": [
|
||||||
"error",
|
"error",
|
||||||
"always",
|
"always",
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"linters": {
|
"linters": {
|
||||||
"src/**/*.{js,jsx,scss,json}": ["prettier --write", "git add"],
|
"src/**/*.{js,jsx,scss,json}": ["prettier --write", "git add"],
|
||||||
"src/**/*.{js,jsx}": ["eslint --fix", "flow focus-check --color always", "git add"]
|
"src/**/*.{js,jsx}": ["eslint", "flow focus-check --color always", "git add"]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"trailingComma": "es5",
|
"trailingComma": "es5",
|
||||||
"printWidth": 100,
|
"printWidth": 120,
|
||||||
"singleQuote": true
|
"singleQuote": true
|
||||||
}
|
}
|
||||||
|
|
|
@ -40,6 +40,8 @@
|
||||||
"postinstall": "electron-builder install-app-deps && node build/downloadDaemon.js"
|
"postinstall": "electron-builder install-app-deps && node build/downloadDaemon.js"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@reach/rect": "^0.2.1",
|
||||||
|
"@reach/tabs": "^0.1.5",
|
||||||
"electron-dl": "^1.11.0",
|
"electron-dl": "^1.11.0",
|
||||||
"electron-log": "^2.2.12",
|
"electron-log": "^2.2.12",
|
||||||
"electron-updater": "^4.0.6",
|
"electron-updater": "^4.0.6",
|
||||||
|
@ -104,6 +106,7 @@
|
||||||
"eslint-plugin-prettier": "^2.6.0",
|
"eslint-plugin-prettier": "^2.6.0",
|
||||||
"eslint-plugin-promise": "^4.0.1",
|
"eslint-plugin-promise": "^4.0.1",
|
||||||
"eslint-plugin-react": "^7.7.0",
|
"eslint-plugin-react": "^7.7.0",
|
||||||
|
"eslint-plugin-react-hooks": "^1.6.0",
|
||||||
"eslint-plugin-standard": "^4.0.0",
|
"eslint-plugin-standard": "^4.0.0",
|
||||||
"flow-bin": "^0.97.0",
|
"flow-bin": "^0.97.0",
|
||||||
"flow-typed": "^2.3.0",
|
"flow-typed": "^2.3.0",
|
||||||
|
@ -114,7 +117,7 @@
|
||||||
"jsmediatags": "^3.8.1",
|
"jsmediatags": "^3.8.1",
|
||||||
"json-loader": "^0.5.4",
|
"json-loader": "^0.5.4",
|
||||||
"lbry-format": "https://github.com/lbryio/lbry-format.git",
|
"lbry-format": "https://github.com/lbryio/lbry-format.git",
|
||||||
"lbry-redux": "lbryio/lbry-redux#cc42856676541120b088e4228c04246ba8ff3274",
|
"lbry-redux": "lbryio/lbry-redux#459bea2257d61003e591daf169fefe9624522680",
|
||||||
"lbryinc": "lbryio/lbryinc#9665f2d1c818f1a86b2e5daab642f6879746f25f",
|
"lbryinc": "lbryio/lbryinc#9665f2d1c818f1a86b2e5daab642f6879746f25f",
|
||||||
"lint-staged": "^7.0.2",
|
"lint-staged": "^7.0.2",
|
||||||
"localforage": "^1.7.1",
|
"localforage": "^1.7.1",
|
||||||
|
@ -185,7 +188,7 @@
|
||||||
"yarn": "^1.3"
|
"yarn": "^1.3"
|
||||||
},
|
},
|
||||||
"lbrySettings": {
|
"lbrySettings": {
|
||||||
"lbrynetDaemonVersion": "0.37.0rc3",
|
"lbrynetDaemonVersion": "0.37.0rc4",
|
||||||
"lbrynetDaemonUrlTemplate": "https://github.com/lbryio/lbry/releases/download/vDAEMONVER/lbrynet-OSNAME.zip",
|
"lbrynetDaemonUrlTemplate": "https://github.com/lbryio/lbry/releases/download/vDAEMONVER/lbrynet-OSNAME.zip",
|
||||||
"lbrynetDaemonDir": "static/daemon",
|
"lbrynetDaemonDir": "static/daemon",
|
||||||
"lbrynetDaemonFileName": "lbrynet"
|
"lbrynetDaemonFileName": "lbrynet"
|
||||||
|
|
|
@ -10,7 +10,7 @@ module.exports = ({ file, options, env }) => {
|
||||||
parser: file.extname === '.sss' ? 'sugarss' : false,
|
parser: file.extname === '.sss' ? 'sugarss' : false,
|
||||||
plugins: {
|
plugins: {
|
||||||
'postcss-import': { root: file.dirname },
|
'postcss-import': { root: file.dirname },
|
||||||
'cssnano': env === 'production' ? options.cssnano : false
|
cssnano: env === 'production' ? options.cssnano : false,
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
14
src/ui/component/channelAbout/index.js
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import { makeSelectMetadataItemForUri } from 'lbry-redux';
|
||||||
|
import ChannelAbout from './view';
|
||||||
|
|
||||||
|
const select = (state, props) => ({
|
||||||
|
description: makeSelectMetadataItemForUri(props.uri, 'description')(state),
|
||||||
|
website: makeSelectMetadataItemForUri(props.uri, 'website_url')(state),
|
||||||
|
email: makeSelectMetadataItemForUri(props.uri, 'email')(state),
|
||||||
|
});
|
||||||
|
|
||||||
|
export default connect(
|
||||||
|
select,
|
||||||
|
null
|
||||||
|
)(ChannelAbout);
|
38
src/ui/component/channelAbout/view.jsx
Normal file
|
@ -0,0 +1,38 @@
|
||||||
Yeah just made it the same as the other "items". Will probably change it a little once I see what more youtuber's descriptions are. Yeah just made it the same as the other "items". Will probably change it a little once I see what more youtuber's descriptions are.
Do any existing styles fit for this? If not, is there a more generic and/or resuable term for this style? Do any existing styles fit for this? If not, is there a more generic and/or resuable term for this style?
Yeah just made it the same as the other "items". Will probably change it a little once I see what more youtuber's descriptions are. Yeah just made it the same as the other "items". Will probably change it a little once I see what more youtuber's descriptions are.
|
|||||||
|
// @flow
|
||||||
Do any existing styles fit for this? If not, is there a more generic and/or resuable term for this style? Do any existing styles fit for this? If not, is there a more generic and/or resuable term for this style?
Yeah just made it the same as the other "items". Will probably change it a little once I see what more youtuber's descriptions are. Yeah just made it the same as the other "items". Will probably change it a little once I see what more youtuber's descriptions are.
|
|||||||
|
import React, { Fragment } from 'react';
|
||||||
Do any existing styles fit for this? If not, is there a more generic and/or resuable term for this style? Do any existing styles fit for this? If not, is there a more generic and/or resuable term for this style?
Yeah just made it the same as the other "items". Will probably change it a little once I see what more youtuber's descriptions are. Yeah just made it the same as the other "items". Will probably change it a little once I see what more youtuber's descriptions are.
|
|||||||
|
|
||||||
Do any existing styles fit for this? If not, is there a more generic and/or resuable term for this style? Do any existing styles fit for this? If not, is there a more generic and/or resuable term for this style?
Yeah just made it the same as the other "items". Will probably change it a little once I see what more youtuber's descriptions are. Yeah just made it the same as the other "items". Will probably change it a little once I see what more youtuber's descriptions are.
|
|||||||
|
type Props = {
|
||||||
Do any existing styles fit for this? If not, is there a more generic and/or resuable term for this style? Do any existing styles fit for this? If not, is there a more generic and/or resuable term for this style?
Yeah just made it the same as the other "items". Will probably change it a little once I see what more youtuber's descriptions are. Yeah just made it the same as the other "items". Will probably change it a little once I see what more youtuber's descriptions are.
|
|||||||
|
description: ?string,
|
||||||
Do any existing styles fit for this? If not, is there a more generic and/or resuable term for this style? Do any existing styles fit for this? If not, is there a more generic and/or resuable term for this style?
Yeah just made it the same as the other "items". Will probably change it a little once I see what more youtuber's descriptions are. Yeah just made it the same as the other "items". Will probably change it a little once I see what more youtuber's descriptions are.
|
|||||||
|
email: ?string,
|
||||||
Do any existing styles fit for this? If not, is there a more generic and/or resuable term for this style? Do any existing styles fit for this? If not, is there a more generic and/or resuable term for this style?
Yeah just made it the same as the other "items". Will probably change it a little once I see what more youtuber's descriptions are. Yeah just made it the same as the other "items". Will probably change it a little once I see what more youtuber's descriptions are.
|
|||||||
|
website: ?string,
|
||||||
Do any existing styles fit for this? If not, is there a more generic and/or resuable term for this style? Do any existing styles fit for this? If not, is there a more generic and/or resuable term for this style?
Yeah just made it the same as the other "items". Will probably change it a little once I see what more youtuber's descriptions are. Yeah just made it the same as the other "items". Will probably change it a little once I see what more youtuber's descriptions are.
|
|||||||
|
};
|
||||||
Do any existing styles fit for this? If not, is there a more generic and/or resuable term for this style? Do any existing styles fit for this? If not, is there a more generic and/or resuable term for this style?
Yeah just made it the same as the other "items". Will probably change it a little once I see what more youtuber's descriptions are. Yeah just made it the same as the other "items". Will probably change it a little once I see what more youtuber's descriptions are.
|
|||||||
|
|
||||||
Do any existing styles fit for this? If not, is there a more generic and/or resuable term for this style? Do any existing styles fit for this? If not, is there a more generic and/or resuable term for this style?
Yeah just made it the same as the other "items". Will probably change it a little once I see what more youtuber's descriptions are. Yeah just made it the same as the other "items". Will probably change it a little once I see what more youtuber's descriptions are.
|
|||||||
|
function ChannelContent(props: Props) {
|
||||||
Do any existing styles fit for this? If not, is there a more generic and/or resuable term for this style? Do any existing styles fit for this? If not, is there a more generic and/or resuable term for this style?
Yeah just made it the same as the other "items". Will probably change it a little once I see what more youtuber's descriptions are. Yeah just made it the same as the other "items". Will probably change it a little once I see what more youtuber's descriptions are.
|
|||||||
|
const { description, email, website } = props;
|
||||||
Do any existing styles fit for this? If not, is there a more generic and/or resuable term for this style? Do any existing styles fit for this? If not, is there a more generic and/or resuable term for this style?
Yeah just made it the same as the other "items". Will probably change it a little once I see what more youtuber's descriptions are. Yeah just made it the same as the other "items". Will probably change it a little once I see what more youtuber's descriptions are.
|
|||||||
|
const showAbout = description || email || website;
|
||||||
Do any existing styles fit for this? If not, is there a more generic and/or resuable term for this style? Do any existing styles fit for this? If not, is there a more generic and/or resuable term for this style?
Yeah just made it the same as the other "items". Will probably change it a little once I see what more youtuber's descriptions are. Yeah just made it the same as the other "items". Will probably change it a little once I see what more youtuber's descriptions are.
|
|||||||
|
|
||||||
Do any existing styles fit for this? If not, is there a more generic and/or resuable term for this style? Do any existing styles fit for this? If not, is there a more generic and/or resuable term for this style?
Yeah just made it the same as the other "items". Will probably change it a little once I see what more youtuber's descriptions are. Yeah just made it the same as the other "items". Will probably change it a little once I see what more youtuber's descriptions are.
|
|||||||
|
return (
|
||||||
Do any existing styles fit for this? If not, is there a more generic and/or resuable term for this style? Do any existing styles fit for this? If not, is there a more generic and/or resuable term for this style?
Yeah just made it the same as the other "items". Will probably change it a little once I see what more youtuber's descriptions are. Yeah just made it the same as the other "items". Will probably change it a little once I see what more youtuber's descriptions are.
|
|||||||
|
<section>
|
||||||
Do any existing styles fit for this? If not, is there a more generic and/or resuable term for this style? Do any existing styles fit for this? If not, is there a more generic and/or resuable term for this style?
Yeah just made it the same as the other "items". Will probably change it a little once I see what more youtuber's descriptions are. Yeah just made it the same as the other "items". Will probably change it a little once I see what more youtuber's descriptions are.
|
|||||||
|
{!showAbout && <h2 className="empty">{__('Nothing here yet')}</h2>}
|
||||||
Do any existing styles fit for this? If not, is there a more generic and/or resuable term for this style? Do any existing styles fit for this? If not, is there a more generic and/or resuable term for this style?
Yeah just made it the same as the other "items". Will probably change it a little once I see what more youtuber's descriptions are. Yeah just made it the same as the other "items". Will probably change it a little once I see what more youtuber's descriptions are.
|
|||||||
|
{showAbout && (
|
||||||
Do any existing styles fit for this? If not, is there a more generic and/or resuable term for this style? Do any existing styles fit for this? If not, is there a more generic and/or resuable term for this style?
Yeah just made it the same as the other "items". Will probably change it a little once I see what more youtuber's descriptions are. Yeah just made it the same as the other "items". Will probably change it a little once I see what more youtuber's descriptions are.
|
|||||||
|
<Fragment>
|
||||||
Do any existing styles fit for this? If not, is there a more generic and/or resuable term for this style? Do any existing styles fit for this? If not, is there a more generic and/or resuable term for this style?
Yeah just made it the same as the other "items". Will probably change it a little once I see what more youtuber's descriptions are. Yeah just made it the same as the other "items". Will probably change it a little once I see what more youtuber's descriptions are.
|
|||||||
|
{description && <div className="media__info-text">{description}</div>}
|
||||||
Do any existing styles fit for this? If not, is there a more generic and/or resuable term for this style? Do any existing styles fit for this? If not, is there a more generic and/or resuable term for this style?
Yeah just made it the same as the other "items". Will probably change it a little once I see what more youtuber's descriptions are. Yeah just made it the same as the other "items". Will probably change it a little once I see what more youtuber's descriptions are.
|
|||||||
|
{email && (
|
||||||
Do any existing styles fit for this? If not, is there a more generic and/or resuable term for this style? Do any existing styles fit for this? If not, is there a more generic and/or resuable term for this style?
Yeah just made it the same as the other "items". Will probably change it a little once I see what more youtuber's descriptions are. Yeah just made it the same as the other "items". Will probably change it a little once I see what more youtuber's descriptions are.
|
|||||||
|
<Fragment>
|
||||||
Do any existing styles fit for this? If not, is there a more generic and/or resuable term for this style? Do any existing styles fit for this? If not, is there a more generic and/or resuable term for this style?
Yeah just made it the same as the other "items". Will probably change it a little once I see what more youtuber's descriptions are. Yeah just made it the same as the other "items". Will probably change it a little once I see what more youtuber's descriptions are.
|
|||||||
|
<div className="media__info-title">{__('Contact')}</div>
|
||||||
Do any existing styles fit for this? If not, is there a more generic and/or resuable term for this style? Do any existing styles fit for this? If not, is there a more generic and/or resuable term for this style?
Yeah just made it the same as the other "items". Will probably change it a little once I see what more youtuber's descriptions are. Yeah just made it the same as the other "items". Will probably change it a little once I see what more youtuber's descriptions are.
|
|||||||
|
<div className="media__info-text">{email}</div>
|
||||||
Do any existing styles fit for this? If not, is there a more generic and/or resuable term for this style? Do any existing styles fit for this? If not, is there a more generic and/or resuable term for this style?
Yeah just made it the same as the other "items". Will probably change it a little once I see what more youtuber's descriptions are. Yeah just made it the same as the other "items". Will probably change it a little once I see what more youtuber's descriptions are.
|
|||||||
|
</Fragment>
|
||||||
Do any existing styles fit for this? If not, is there a more generic and/or resuable term for this style? Do any existing styles fit for this? If not, is there a more generic and/or resuable term for this style?
Yeah just made it the same as the other "items". Will probably change it a little once I see what more youtuber's descriptions are. Yeah just made it the same as the other "items". Will probably change it a little once I see what more youtuber's descriptions are.
|
|||||||
|
)}
|
||||||
Do any existing styles fit for this? If not, is there a more generic and/or resuable term for this style? Do any existing styles fit for this? If not, is there a more generic and/or resuable term for this style?
Yeah just made it the same as the other "items". Will probably change it a little once I see what more youtuber's descriptions are. Yeah just made it the same as the other "items". Will probably change it a little once I see what more youtuber's descriptions are.
|
|||||||
|
{website && (
|
||||||
Do any existing styles fit for this? If not, is there a more generic and/or resuable term for this style? Do any existing styles fit for this? If not, is there a more generic and/or resuable term for this style?
Yeah just made it the same as the other "items". Will probably change it a little once I see what more youtuber's descriptions are. Yeah just made it the same as the other "items". Will probably change it a little once I see what more youtuber's descriptions are.
|
|||||||
|
<Fragment>
|
||||||
Do any existing styles fit for this? If not, is there a more generic and/or resuable term for this style? Do any existing styles fit for this? If not, is there a more generic and/or resuable term for this style?
Yeah just made it the same as the other "items". Will probably change it a little once I see what more youtuber's descriptions are. Yeah just made it the same as the other "items". Will probably change it a little once I see what more youtuber's descriptions are.
|
|||||||
|
<div className="media__info-title">{__('Site')}</div>
|
||||||
Do any existing styles fit for this? If not, is there a more generic and/or resuable term for this style? Do any existing styles fit for this? If not, is there a more generic and/or resuable term for this style?
Yeah just made it the same as the other "items". Will probably change it a little once I see what more youtuber's descriptions are. Yeah just made it the same as the other "items". Will probably change it a little once I see what more youtuber's descriptions are.
|
|||||||
|
<div className="media__info-text">{website}</div>
|
||||||
Do any existing styles fit for this? If not, is there a more generic and/or resuable term for this style? Do any existing styles fit for this? If not, is there a more generic and/or resuable term for this style?
Yeah just made it the same as the other "items". Will probably change it a little once I see what more youtuber's descriptions are. Yeah just made it the same as the other "items". Will probably change it a little once I see what more youtuber's descriptions are.
|
|||||||
|
</Fragment>
|
||||||
Do any existing styles fit for this? If not, is there a more generic and/or resuable term for this style? Do any existing styles fit for this? If not, is there a more generic and/or resuable term for this style?
Yeah just made it the same as the other "items". Will probably change it a little once I see what more youtuber's descriptions are. Yeah just made it the same as the other "items". Will probably change it a little once I see what more youtuber's descriptions are.
|
|||||||
|
)}
|
||||||
Do any existing styles fit for this? If not, is there a more generic and/or resuable term for this style? Do any existing styles fit for this? If not, is there a more generic and/or resuable term for this style?
Yeah just made it the same as the other "items". Will probably change it a little once I see what more youtuber's descriptions are. Yeah just made it the same as the other "items". Will probably change it a little once I see what more youtuber's descriptions are.
|
|||||||
|
</Fragment>
|
||||||
Do any existing styles fit for this? If not, is there a more generic and/or resuable term for this style? Do any existing styles fit for this? If not, is there a more generic and/or resuable term for this style?
Yeah just made it the same as the other "items". Will probably change it a little once I see what more youtuber's descriptions are. Yeah just made it the same as the other "items". Will probably change it a little once I see what more youtuber's descriptions are.
|
|||||||
|
)}
|
||||||
Do any existing styles fit for this? If not, is there a more generic and/or resuable term for this style? Do any existing styles fit for this? If not, is there a more generic and/or resuable term for this style?
Yeah just made it the same as the other "items". Will probably change it a little once I see what more youtuber's descriptions are. Yeah just made it the same as the other "items". Will probably change it a little once I see what more youtuber's descriptions are.
|
|||||||
|
</section>
|
||||||
Do any existing styles fit for this? If not, is there a more generic and/or resuable term for this style? Do any existing styles fit for this? If not, is there a more generic and/or resuable term for this style?
Yeah just made it the same as the other "items". Will probably change it a little once I see what more youtuber's descriptions are. Yeah just made it the same as the other "items". Will probably change it a little once I see what more youtuber's descriptions are.
|
|||||||
|
);
|
||||||
Do any existing styles fit for this? If not, is there a more generic and/or resuable term for this style? Do any existing styles fit for this? If not, is there a more generic and/or resuable term for this style?
Yeah just made it the same as the other "items". Will probably change it a little once I see what more youtuber's descriptions are. Yeah just made it the same as the other "items". Will probably change it a little once I see what more youtuber's descriptions are.
|
|||||||
|
}
|
||||||
Do any existing styles fit for this? If not, is there a more generic and/or resuable term for this style? Do any existing styles fit for this? If not, is there a more generic and/or resuable term for this style?
Yeah just made it the same as the other "items". Will probably change it a little once I see what more youtuber's descriptions are. Yeah just made it the same as the other "items". Will probably change it a little once I see what more youtuber's descriptions are.
|
|||||||
|
|
||||||
Do any existing styles fit for this? If not, is there a more generic and/or resuable term for this style? Do any existing styles fit for this? If not, is there a more generic and/or resuable term for this style?
Yeah just made it the same as the other "items". Will probably change it a little once I see what more youtuber's descriptions are. Yeah just made it the same as the other "items". Will probably change it a little once I see what more youtuber's descriptions are.
|
|||||||
|
export default ChannelContent;
|
||||||
Do any existing styles fit for this? If not, is there a more generic and/or resuable term for this style? Do any existing styles fit for this? If not, is there a more generic and/or resuable term for this style?
Yeah just made it the same as the other "items". Will probably change it a little once I see what more youtuber's descriptions are. Yeah just made it the same as the other "items". Will probably change it a little once I see what more youtuber's descriptions are.
|
26
src/ui/component/channelContent/index.js
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import { doFetchClaimsByChannel } from 'redux/actions/content';
|
||||||
|
import { PAGE_SIZE } from 'constants/claim';
|
||||||
|
import {
|
||||||
|
makeSelectClaimsInChannelForCurrentPageState,
|
||||||
|
makeSelectFetchingChannelClaims,
|
||||||
|
makeSelectClaimIsMine,
|
||||||
|
makeSelectTotalPagesForChannel,
|
||||||
|
} from 'lbry-redux';
|
||||||
|
import ChannelPage from './view';
|
||||||
|
|
||||||
|
const select = (state, props) => ({
|
||||||
|
claimsInChannel: makeSelectClaimsInChannelForCurrentPageState(props.uri)(state),
|
||||||
|
fetching: makeSelectFetchingChannelClaims(props.uri)(state),
|
||||||
|
totalPages: makeSelectTotalPagesForChannel(props.uri, PAGE_SIZE)(state),
|
||||||
|
channelIsMine: makeSelectClaimIsMine(props.uri)(state),
|
||||||
|
});
|
||||||
|
|
||||||
|
const perform = dispatch => ({
|
||||||
|
fetchClaims: (uri, page) => dispatch(doFetchClaimsByChannel(uri, page)),
|
||||||
|
});
|
||||||
|
|
||||||
|
export default connect(
|
||||||
|
select,
|
||||||
|
perform
|
||||||
|
)(ChannelPage);
|
47
src/ui/component/channelContent/view.jsx
Normal file
|
@ -0,0 +1,47 @@
|
||||||
Unused? Unused?
Unused? Unused?
|
|||||||
|
// @flow
|
||||||
Unused? Unused?
|
|||||||
|
import React, { Fragment } from 'react';
|
||||||
Unused? Unused?
|
|||||||
|
import FileList from 'component/fileList';
|
||||||
Unused? Unused?
|
|||||||
|
import HiddenNsfwClaims from 'component/hiddenNsfwClaims';
|
||||||
Unused? Unused?
|
|||||||
|
import { withRouter } from 'react-router-dom';
|
||||||
Unused? Unused?
|
|||||||
|
import Paginate from 'component/common/paginate';
|
||||||
Unused? Unused?
|
|||||||
|
import Spinner from 'component/spinner';
|
||||||
Unused? Unused?
|
|||||||
|
|
||||||
Unused? Unused?
|
|||||||
|
type Props = {
|
||||||
Unused? Unused?
|
|||||||
|
uri: string,
|
||||||
Unused? Unused?
|
|||||||
|
totalPages: number,
|
||||||
Unused? Unused?
|
|||||||
|
fetching: boolean,
|
||||||
Unused? Unused?
|
|||||||
|
params: { page: number },
|
||||||
Unused? Unused?
|
|||||||
|
claimsInChannel: Array<StreamClaim>,
|
||||||
Unused? Unused?
|
|||||||
|
channelIsMine: boolean,
|
||||||
Unused? Unused?
|
|||||||
|
fetchClaims: (string, number) => void,
|
||||||
Unused? Unused?
|
|||||||
|
location: UrlLocation,
|
||||||
Unused? Unused?
|
|||||||
|
};
|
||||||
Unused? Unused?
|
|||||||
|
|
||||||
Unused? Unused?
|
|||||||
|
function ChannelContent(props: Props) {
|
||||||
Unused? Unused?
|
|||||||
|
const { uri, fetching, claimsInChannel, totalPages, channelIsMine, fetchClaims } = props;
|
||||||
Unused? Unused?
|
|||||||
|
const hasContent = Boolean(claimsInChannel && claimsInChannel.length);
|
||||||
Unused? Unused?
|
|||||||
|
|
||||||
Unused? Unused?
|
|||||||
|
return (
|
||||||
Unused? Unused?
|
|||||||
|
<Fragment>
|
||||||
Unused? Unused?
|
|||||||
|
{fetching && !hasContent && (
|
||||||
Unused? Unused?
|
|||||||
|
<section className="main--empty">
|
||||||
Unused? Unused?
|
|||||||
|
<Spinner delayed />
|
||||||
Unused? Unused?
|
|||||||
|
</section>
|
||||||
Unused? Unused?
|
|||||||
|
)}
|
||||||
Unused? Unused?
|
|||||||
|
|
||||||
Unused? Unused?
|
|||||||
|
{!fetching && !hasContent && <h2 className="empty">{__("This channel hasn't uploaded anything.")}</h2>}
|
||||||
Unused? Unused?
|
|||||||
|
|
||||||
Unused? Unused?
|
|||||||
|
{!channelIsMine && <HiddenNsfwClaims className="card__content help" uri={uri} />}
|
||||||
Unused? Unused?
|
|||||||
|
|
||||||
Unused? Unused?
|
|||||||
|
{hasContent && <FileList sortByHeight hideFilter fileInfos={claimsInChannel} />}
|
||||||
Unused? Unused?
|
|||||||
|
|
||||||
Unused? Unused?
|
|||||||
|
<Paginate
|
||||||
Unused? Unused?
|
|||||||
|
onPageChange={page => fetchClaims(uri, page)}
|
||||||
Unused? Unused?
|
|||||||
|
totalPages={totalPages}
|
||||||
Unused? Unused?
|
|||||||
|
loading={fetching && !hasContent}
|
||||||
Unused? Unused?
|
|||||||
|
/>
|
||||||
Unused? Unused?
|
|||||||
|
</Fragment>
|
||||||
Unused? Unused?
|
|||||||
|
);
|
||||||
Unused? Unused?
|
|||||||
|
}
|
||||||
Unused? Unused?
|
|||||||
|
|
||||||
Unused? Unused?
|
|||||||
|
export default withRouter(ChannelContent);
|
||||||
Unused? Unused?
|
12
src/ui/component/channelThumbnail/index.js
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import { makeSelectThumbnailForUri } from 'lbry-redux';
|
||||||
|
import ChannelThumbnail from './view';
|
||||||
|
|
||||||
|
const select = (state, props) => ({
|
||||||
|
thumbnail: makeSelectThumbnailForUri(props.uri)(state),
|
||||||
|
});
|
||||||
|
|
||||||
|
export default connect(
|
||||||
|
select,
|
||||||
|
null
|
||||||
|
)(ChannelThumbnail);
|
17
src/ui/component/channelThumbnail/view.jsx
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
// @flow
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
thumbnail: ?string,
|
||||||
|
};
|
||||||
|
|
||||||
|
function ChannelThumbnail(props: Props) {
|
||||||
|
const { thumbnail } = props;
|
||||||
|
return (
|
||||||
|
<div className="channel__thumbnail">
|
||||||
|
{thumbnail && <img className="channel__thumbnail--custom" src={thumbnail} />}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ChannelThumbnail;
|
81
src/ui/component/common/paginate.jsx
Normal file
|
@ -0,0 +1,81 @@
|
||||||
|
// @flow
|
||||||
|
import React from 'react';
|
||||||
|
import { withRouter } from 'react-router';
|
||||||
|
import { Form, FormField } from 'component/common/form';
|
||||||
|
import ReactPaginate from 'react-paginate';
|
||||||
|
|
||||||
|
const PAGINATE_PARAM = 'page';
|
||||||
|
const ENTER_KEY_CODE = 13;
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
loading: boolean,
|
||||||
|
totalPages: number,
|
||||||
|
location: { search: string },
|
||||||
|
history: { push: string => void },
|
||||||
|
onPageChange?: number => void,
|
||||||
|
};
|
||||||
|
|
||||||
|
function Paginate(props: Props) {
|
||||||
|
const { totalPages, loading, location, history, onPageChange } = props;
|
||||||
|
const { search } = location;
|
||||||
|
const urlParams = new URLSearchParams(search);
|
||||||
|
const currentPage = Number(urlParams.get(PAGINATE_PARAM)) || 1;
|
||||||
|
|
||||||
|
function handleChangePage(newPageNumber: number) {
|
||||||
|
if (onPageChange) {
|
||||||
|
onPageChange(newPageNumber);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentPage !== newPageNumber) {
|
||||||
|
history.push(`?${PAGINATE_PARAM}=${newPageNumber}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handlePaginateKeyUp(e: SyntheticKeyboardEvent<*>) {
|
||||||
|
const newPage = Number(e.currentTarget.value);
|
||||||
|
const isEnterKey = e.keyCode === ENTER_KEY_CODE;
|
||||||
|
|
||||||
|
if (newPage && isEnterKey && newPage > 0 && newPage <= totalPages) {
|
||||||
|
handleChangePage(newPage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (totalPages <= 1 || loading) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Form>
|
||||||
|
<fieldset-group class="fieldset-group--smushed fieldgroup--paginate">
|
||||||
|
<fieldset-section>
|
||||||
|
<ReactPaginate
|
||||||
|
pageCount={totalPages}
|
||||||
|
pageRangeDisplayed={2}
|
||||||
|
previousLabel="‹"
|
||||||
|
nextLabel="›"
|
||||||
|
activeClassName="pagination__item--selected"
|
||||||
|
pageClassName="pagination__item"
|
||||||
|
previousClassName="pagination__item pagination__item--previous"
|
||||||
|
nextClassName="pagination__item pagination__item--next"
|
||||||
|
breakClassName="pagination__item pagination__item--break"
|
||||||
|
marginPagesDisplayed={2}
|
||||||
|
onPageChange={e => handleChangePage(e.selected + 1)}
|
||||||
|
forcePage={currentPage - 1}
|
||||||
|
initialPage={currentPage - 1}
|
||||||
|
containerClassName="pagination"
|
||||||
|
/>
|
||||||
|
</fieldset-section>
|
||||||
|
|
||||||
|
<FormField
|
||||||
|
className="paginate-channel"
|
||||||
|
onKeyUp={handlePaginateKeyUp}
|
||||||
|
label={__('Go to page:')}
|
||||||
|
type="text"
|
||||||
|
name="paginate-file"
|
||||||
|
/>
|
||||||
|
</fieldset-group>
|
||||||
|
</Form>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default withRouter(Paginate);
|
130
src/ui/component/common/tabs.jsx
Normal file
|
@ -0,0 +1,130 @@
|
||||||
|
// @flow
|
||||||
|
import React, { Fragment, useState, useRef, useContext, useLayoutEffect, createContext } from 'react';
|
||||||
|
import {
|
||||||
|
Tabs as ReachTabs,
|
||||||
|
Tab as ReachTab,
|
||||||
|
TabList as ReachTabList,
|
||||||
|
TabPanels as ReachTabPanels,
|
||||||
|
TabPanel as ReachTabPanel,
|
||||||
|
} from '@reach/tabs';
|
||||||
|
import classnames from 'classnames';
|
||||||
|
import { useRect } from '@reach/rect';
|
||||||
|
|
||||||
|
// Tabs are a compound component
|
||||||
|
// The components are used individually, but they will still interact and share state
|
||||||
|
// When using, at a minimum you must arrange the components in this pattern
|
||||||
|
// When the <Tab> at index 0 is active, the TabPanel at index 0 will be displayed
|
||||||
|
//
|
||||||
|
// <Tabs onChange={...} index={...}>
|
||||||
|
// <TabList>
|
||||||
|
// <Tab>Tab label 1</Tab>
|
||||||
|
// <Tab>Tab label 2</Tab>
|
||||||
|
// ...
|
||||||
|
// </TabList>
|
||||||
|
// <TabPanels>
|
||||||
|
// <TabPanel>Content for Tab 1</TabPanel>
|
||||||
|
// <TabPanel>Content for Tab 2</TabPanel>
|
||||||
|
// ...
|
||||||
|
// </TabPanels>
|
||||||
|
// </Tabs>
|
||||||
|
//
|
||||||
|
// the base @reach/tabs components handle all the focus/accessibilty labels
|
||||||
|
// We're just adding some styling
|
||||||
|
|
||||||
|
type TabsProps = {
|
||||||
|
index?: number,
|
||||||
|
onChange?: number => void,
|
||||||
|
children: Array<React$Node>,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Use context so child TabPanels can set the active tab, which is kept in Tabs' state
|
||||||
|
const AnimatedContext = createContext<any>();
|
||||||
|
|
||||||
|
function Tabs(props: TabsProps) {
|
||||||
|
// Store the position of the selected Tab so we can animate the "active" bar to its position
|
||||||
|
const [selectedRect, setSelectedRect] = useState(null);
|
||||||
|
|
||||||
|
// Create a ref of the parent element so we can measure the relative "left" for the bar for the child Tab's
|
||||||
|
const tabsRef = useRef();
|
||||||
|
const tabsRect = useRect(tabsRef);
|
||||||
|
|
||||||
|
const tabLabels = props.children[0];
|
||||||
|
const tabContent = props.children[1];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AnimatedContext.Provider value={setSelectedRect}>
|
||||||
|
<ReachTabs className="tabs" {...props} ref={tabsRef}>
|
||||||
|
{tabLabels}
|
||||||
|
|
||||||
|
<div
|
||||||
|
className="tab__divider"
|
||||||
|
style={{
|
||||||
|
left: selectedRect && selectedRect.left - tabsRect.left,
|
||||||
|
width: selectedRect && selectedRect.width,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{tabContent}
|
||||||
|
</ReachTabs>
|
||||||
|
</AnimatedContext.Provider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// The wrapper for the list of tab labels that users can click
|
||||||
|
type TabListProps = {
|
||||||
|
className?: string,
|
||||||
|
};
|
||||||
|
function TabList(props: TabListProps) {
|
||||||
|
const { className, ...rest } = props;
|
||||||
|
return <ReachTabList className={classnames('tabs__list', className)} {...rest} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// The links that users click
|
||||||
|
// Accesses `setSelectedRect` from context to set itself as active if needed
|
||||||
|
// Flow doesn't understand we don't have to pass it in ourselves
|
||||||
|
type TabProps = {
|
||||||
|
isSelected?: Boolean,
|
||||||
|
};
|
||||||
|
function Tab(props: TabProps) {
|
||||||
|
// @reach/tabs provides an `isSelected` prop
|
||||||
|
// We could also useContext to read it manually
|
||||||
|
const { isSelected } = props;
|
||||||
|
|
||||||
|
// Each tab measures itself
|
||||||
|
const ref = useRef();
|
||||||
|
const rect = useRect(ref, isSelected);
|
||||||
|
|
||||||
|
// and calls up to the parent when it becomes selected
|
||||||
|
// we useLayoutEffect to avoid flicker
|
||||||
|
const setSelectedRect = useContext(AnimatedContext);
|
||||||
|
useLayoutEffect(() => {
|
||||||
|
if (isSelected) setSelectedRect(rect);
|
||||||
|
}, [isSelected, rect, setSelectedRect]);
|
||||||
|
|
||||||
|
return <ReachTab ref={ref} {...props} className="tab" />;
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// The wrapper for TabPanel
|
||||||
|
type TabPanelsProps = {
|
||||||
|
header?: React$Node,
|
||||||
|
};
|
||||||
|
function TabPanels(props: TabPanelsProps) {
|
||||||
|
const { header, ...rest } = props;
|
||||||
|
return (
|
||||||
|
<Fragment>
|
||||||
|
{header}
|
||||||
|
<ReachTabPanels {...rest} />
|
||||||
|
</Fragment>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// The wrapper for content when it's associated Tab is selected
|
||||||
|
function TabPanel(props: any) {
|
||||||
|
return <ReachTabPanel className="tab__panel" {...props} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
export { Tabs, TabList, Tab, TabPanels, TabPanel };
|
|
@ -10,12 +10,12 @@ type Props = {
|
||||||
|
|
||||||
export default (props: Props) => {
|
export default (props: Props) => {
|
||||||
const { numberOfNsfwClaims, obscureNsfw, className } = props;
|
const { numberOfNsfwClaims, obscureNsfw, className } = props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
obscureNsfw &&
|
obscureNsfw &&
|
||||||
Boolean(numberOfNsfwClaims) && (
|
Boolean(numberOfNsfwClaims) && (
|
||||||
<div className={className || 'help'}>
|
<div className={className || 'help'}>
|
||||||
{numberOfNsfwClaims} {numberOfNsfwClaims > 1 ? __('files') : __('file')}{' '}
|
{numberOfNsfwClaims} {numberOfNsfwClaims > 1 ? __('files') : __('file')} {__('hidden due to your')}{' '}
|
||||||
{__('hidden due to your')}{' '}
|
|
||||||
<Button button="link" navigate="/$/settings" label={__('content viewing preferences')} />.
|
<Button button="link" navigate="/$/settings" label={__('content viewing preferences')} />.
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,10 +1,8 @@
|
||||||
// @flow
|
// @flow
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import Button from 'component/button';
|
import Button from 'component/button';
|
||||||
import { Form, FormField } from 'component/common/form';
|
|
||||||
import ReactPaginate from 'react-paginate';
|
|
||||||
import NavigationHistoryItem from 'component/navigationHistoryItem';
|
import NavigationHistoryItem from 'component/navigationHistoryItem';
|
||||||
import { withRouter } from 'react-router-dom';
|
import Paginate from 'component/common/paginate';
|
||||||
|
|
||||||
type HistoryItem = {
|
type HistoryItem = {
|
||||||
uri: string,
|
uri: string,
|
||||||
|
@ -13,11 +11,8 @@ type HistoryItem = {
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
historyItems: Array<HistoryItem>,
|
historyItems: Array<HistoryItem>,
|
||||||
page: number,
|
|
||||||
pageCount: number,
|
pageCount: number,
|
||||||
clearHistoryUri: string => void,
|
clearHistoryUri: string => void,
|
||||||
params: { page: number },
|
|
||||||
history: { push: string => void },
|
|
||||||
};
|
};
|
||||||
|
|
||||||
type State = {
|
type State = {
|
||||||
|
@ -52,24 +47,6 @@ class UserHistoryPage extends React.PureComponent<Props, State> {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
changePage(pageNumber: number) {
|
|
||||||
const { history } = this.props;
|
|
||||||
history.push(`?page=${pageNumber}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
paginate(e: SyntheticKeyboardEvent<*>) {
|
|
||||||
const pageFromInput = Number(e.currentTarget.value);
|
|
||||||
if (
|
|
||||||
pageFromInput &&
|
|
||||||
e.keyCode === 13 &&
|
|
||||||
!Number.isNaN(pageFromInput) &&
|
|
||||||
pageFromInput > 0 &&
|
|
||||||
pageFromInput <= this.props.pageCount
|
|
||||||
) {
|
|
||||||
this.changePage(pageFromInput);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
selectAll() {
|
selectAll() {
|
||||||
const { historyItems } = this.props;
|
const { historyItems } = this.props;
|
||||||
const newSelectedState = {};
|
const newSelectedState = {};
|
||||||
|
@ -94,26 +71,20 @@ class UserHistoryPage extends React.PureComponent<Props, State> {
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { historyItems = [], page, pageCount } = this.props;
|
const { historyItems = [], pageCount } = this.props;
|
||||||
const { itemsSelected } = this.state;
|
const { itemsSelected } = this.state;
|
||||||
const allSelected = Object.keys(itemsSelected).length === history.length;
|
const allSelected = Object.keys(itemsSelected).length === historyItems.length;
|
||||||
const selectHandler = allSelected ? this.unselectAll : this.selectAll;
|
const selectHandler = allSelected ? this.unselectAll : this.selectAll;
|
||||||
|
|
||||||
return history.length ? (
|
return historyItems.length ? (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
<div className="card__actions card__actions--between">
|
<div className="card__actions card__actions--between">
|
||||||
{Object.keys(itemsSelected).length ? (
|
{Object.keys(itemsSelected).length ? (
|
||||||
<Button button="link" label={__('Delete')} onClick={this.removeSelected} />
|
<Button button="link" label={__('Delete')} onClick={this.removeSelected} />
|
||||||
) : (
|
) : (
|
||||||
<span>
|
<span>{/* Using an empty span so spacing stays the same if the button isn't rendered */}</span>
|
||||||
{/* Using an empty span so spacing stays the same if the button isn't rendered */}
|
|
||||||
</span>
|
|
||||||
)}
|
)}
|
||||||
<Button
|
<Button button="link" label={allSelected ? __('Cancel') : __('Select All')} onClick={selectHandler} />
|
||||||
button="link"
|
|
||||||
label={allSelected ? __('Cancel') : __('Select All')}
|
|
||||||
onClick={selectHandler}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
{!!historyItems.length && (
|
{!!historyItems.length && (
|
||||||
<section className="card__content item-list">
|
<section className="card__content item-list">
|
||||||
|
@ -130,47 +101,14 @@ class UserHistoryPage extends React.PureComponent<Props, State> {
|
||||||
))}
|
))}
|
||||||
</section>
|
</section>
|
||||||
)}
|
)}
|
||||||
{pageCount > 1 && (
|
<Paginate totalPages={pageCount} />
|
||||||
<Form>
|
|
||||||
<fieldset-group class="fieldset-group--smushed fieldgroup--paginate">
|
|
||||||
<fieldset-section>
|
|
||||||
<ReactPaginate
|
|
||||||
pageCount={pageCount}
|
|
||||||
pageRangeDisplayed={2}
|
|
||||||
previousLabel="‹"
|
|
||||||
nextLabel="›"
|
|
||||||
activeClassName="pagination__item--selected"
|
|
||||||
pageClassName="pagination__item"
|
|
||||||
previousClassName="pagination__item pagination__item--previous"
|
|
||||||
nextClassName="pagination__item pagination__item--next"
|
|
||||||
breakClassName="pagination__item pagination__item--break"
|
|
||||||
marginPagesDisplayed={2}
|
|
||||||
onPageChange={e => this.changePage(e.selected)}
|
|
||||||
forcePage={page}
|
|
||||||
initialPage={page}
|
|
||||||
disableInitialCallback
|
|
||||||
containerClassName="pagination"
|
|
||||||
/>
|
|
||||||
</fieldset-section>
|
|
||||||
<FormField
|
|
||||||
type="text"
|
|
||||||
name="paginate-input"
|
|
||||||
label={__('Go to page:')}
|
|
||||||
className="paginate-channel"
|
|
||||||
onKeyUp={e => this.paginate(e)}
|
|
||||||
/>
|
|
||||||
</fieldset-group>
|
|
||||||
</Form>
|
|
||||||
)}
|
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
) : (
|
) : (
|
||||||
<div className="main--empty">
|
<div className="main--empty">
|
||||||
<section className="card card--section">
|
<section className="card card--section">
|
||||||
<header className="card__header">
|
<header className="card__header">
|
||||||
<h2 className="card__title">
|
<h2 className="card__title">
|
||||||
{__(
|
{__("You don't have anything saved in history yet, go check out some content on LBRY!")}
|
||||||
"You don't have anything saved in history yet, go check out some content on LBRY!"
|
|
||||||
)}
|
|
||||||
</h2>
|
</h2>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
|
@ -184,4 +122,4 @@ class UserHistoryPage extends React.PureComponent<Props, State> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
export default withRouter(UserHistoryPage);
|
export default UserHistoryPage;
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
// @flow
|
// @flow
|
||||||
import React, { Fragment } from 'react';
|
import React from 'react';
|
||||||
import Button from 'component/button';
|
import Button from 'component/button';
|
||||||
import { Form, FormField } from 'component/common/form';
|
|
||||||
import ReactPaginate from 'react-paginate';
|
|
||||||
import NavigationHistoryItem from 'component/navigationHistoryItem';
|
import NavigationHistoryItem from 'component/navigationHistoryItem';
|
||||||
|
|
||||||
type HistoryItem = {
|
type HistoryItem = {
|
||||||
|
@ -12,14 +10,10 @@ type HistoryItem = {
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
history: Array<HistoryItem>,
|
history: Array<HistoryItem>,
|
||||||
page: number,
|
|
||||||
pageCount: number,
|
|
||||||
clearHistoryUri: string => void,
|
|
||||||
params: { page: number },
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function UserHistoryRecent(props: Props) {
|
export default function NavigationHistoryRecent(props: Props) {
|
||||||
const { history = [], page, pageCount } = props;
|
const { history = [] } = props;
|
||||||
|
|
||||||
return history.length ? (
|
return history.length ? (
|
||||||
<div className="item-list">
|
<div className="item-list">
|
||||||
|
|
|
@ -9,8 +9,6 @@ const LOADER_TIMEOUT = 1000;
|
||||||
type Props = {
|
type Props = {
|
||||||
children: React.Node | Array<React.Node>,
|
children: React.Node | Array<React.Node>,
|
||||||
pageTitle: ?string,
|
pageTitle: ?string,
|
||||||
noPadding: ?boolean,
|
|
||||||
extraPadding: ?boolean,
|
|
||||||
notContained: ?boolean, // No max-width, but keep the padding
|
notContained: ?boolean, // No max-width, but keep the padding
|
||||||
loading: ?boolean,
|
loading: ?boolean,
|
||||||
className: ?string,
|
className: ?string,
|
||||||
|
@ -46,7 +44,6 @@ class Page extends React.PureComponent<Props, State> {
|
||||||
this.beginLoadingTimeout();
|
this.beginLoadingTimeout();
|
||||||
} else if (!loading && this.loaderTimeout) {
|
} else if (!loading && this.loaderTimeout) {
|
||||||
clearTimeout(this.loaderTimeout);
|
clearTimeout(this.loaderTimeout);
|
||||||
|
|
||||||
if (showLoader) {
|
if (showLoader) {
|
||||||
this.removeLoader();
|
this.removeLoader();
|
||||||
}
|
}
|
||||||
|
@ -72,23 +69,14 @@ class Page extends React.PureComponent<Props, State> {
|
||||||
loaderTimeout: ?TimeoutID;
|
loaderTimeout: ?TimeoutID;
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {
|
const { children, notContained, loading, className } = this.props;
|
||||||
pageTitle,
|
|
||||||
children,
|
|
||||||
noPadding,
|
|
||||||
extraPadding,
|
|
||||||
notContained,
|
|
||||||
loading,
|
|
||||||
className,
|
|
||||||
} = this.props;
|
|
||||||
const { showLoader } = this.state;
|
const { showLoader } = this.state;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<main
|
<main
|
||||||
className={classnames('main', className, {
|
className={classnames('main', className, {
|
||||||
'main--contained': !notContained && !noPadding && !extraPadding,
|
'main--contained': !notContained,
|
||||||
'main--no-padding': noPadding,
|
'main--not-contained': notContained,
|
||||||
'main--extra-padding': extraPadding,
|
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
{!loading && children}
|
{!loading && children}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import * as PAGES from 'constants/pages';
|
import * as PAGES from 'constants/pages';
|
||||||
import React, { useEffect } from 'react';
|
import React, { useEffect } from 'react';
|
||||||
import { Route, Switch, withRouter } from 'react-router-dom';
|
import { Route, Redirect, Switch, withRouter } from 'react-router-dom';
|
||||||
import SettingsPage from 'page/settings';
|
import SettingsPage from 'page/settings';
|
||||||
import HelpPage from 'page/help';
|
import HelpPage from 'page/help';
|
||||||
import ReportPage from 'page/report';
|
import ReportPage from 'page/report';
|
||||||
|
@ -58,8 +58,11 @@ export default function AppRouter() {
|
||||||
<Route path={`/$/${PAGES.HISTORY}/all`} exact component={NavigationHistory} />
|
<Route path={`/$/${PAGES.HISTORY}/all`} exact component={NavigationHistory} />
|
||||||
|
|
||||||
{/* Below need to go at the end to make sure we don't match any of our pages first */}
|
{/* Below need to go at the end to make sure we don't match any of our pages first */}
|
||||||
<Route path="/:claimName/:claimId" component={ShowPage} />
|
<Route path="/:claimName" exact component={ShowPage} />
|
||||||
<Route path="/:claimName" component={ShowPage} />
|
<Route path="/:claimName/:claimId" exact component={ShowPage} />
|
||||||
|
|
||||||
|
{/* Route not found. Mostly for people typing crazy urls into the url */}
|
||||||
|
<Route render={() => <Redirect to="/" />} />
|
||||||
</Switch>
|
</Switch>
|
||||||
</Scroll>
|
</Scroll>
|
||||||
);
|
);
|
||||||
|
|
17
src/ui/component/shareButton/index.js
Normal file
|
@ -0,0 +1,17 @@
|
||||||
misnamed misnamed
misnamed misnamed
|
|||||||
|
import { connect } from 'react-redux';
|
||||||
misnamed misnamed
|
|||||||
|
import { doChannelSubscribe, doChannelUnsubscribe } from 'redux/actions/subscriptions';
|
||||||
misnamed misnamed
|
|||||||
|
import { doOpenModal } from 'redux/actions/app';
|
||||||
misnamed misnamed
|
|||||||
|
import { doToast } from 'lbry-redux';
|
||||||
misnamed misnamed
|
|||||||
|
import ShareButton from './view';
|
||||||
misnamed misnamed
|
|||||||
|
|
||||||
misnamed misnamed
|
|||||||
|
const select = (state, props) => ({});
|
||||||
misnamed misnamed
|
|||||||
|
|
||||||
misnamed misnamed
|
|||||||
|
export default connect(
|
||||||
misnamed misnamed
|
|||||||
|
select,
|
||||||
misnamed misnamed
|
|||||||
|
{
|
||||||
misnamed misnamed
|
|||||||
|
doChannelSubscribe,
|
||||||
misnamed misnamed
|
|||||||
|
doChannelUnsubscribe,
|
||||||
misnamed misnamed
|
|||||||
|
doOpenModal,
|
||||||
misnamed misnamed
|
|||||||
|
doToast,
|
||||||
misnamed misnamed
|
|||||||
|
}
|
||||||
misnamed misnamed
|
|||||||
|
)(ShareButton);
|
||||||
misnamed misnamed
|
23
src/ui/component/shareButton/view.jsx
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
// @flow
|
||||||
|
import * as MODALS from 'constants/modal_types';
|
||||||
|
import * as ICONS from 'constants/icons';
|
||||||
|
import React from 'react';
|
||||||
|
import Button from 'component/button';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
uri: string,
|
||||||
|
doOpenModal: (id: string, {}) => void,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function ShareButton(props: Props) {
|
||||||
|
const { uri, doOpenModal } = props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
button="alt"
|
||||||
|
icon={ICONS.SHARE}
|
||||||
|
label={__('Share Channel')}
|
||||||
|
onClick={() => doOpenModal(MODALS.SOCIAL_SHARE, { uri, speechShareable: true, isChannel: true })}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
|
@ -26,7 +26,7 @@ class SuggestedSubscriptions extends Component<Props> {
|
||||||
}
|
}
|
||||||
|
|
||||||
return suggested ? (
|
return suggested ? (
|
||||||
<div className="card__content subscriptions__suggested">
|
<div className="card__content subscriptions__suggested main__item--extend-outside">
|
||||||
{suggested.map(({ uri, label }) => (
|
{suggested.map(({ uri, label }) => (
|
||||||
<CategoryList key={uri} category={label} categoryLink={uri} />
|
<CategoryList key={uri} category={label} categoryLink={uri} />
|
||||||
))}
|
))}
|
||||||
|
|
|
@ -11,20 +11,13 @@ import * as MODALS from 'constants/modal_types';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import ReactDOM from 'react-dom';
|
import ReactDOM from 'react-dom';
|
||||||
import { Provider } from 'react-redux';
|
import { Provider } from 'react-redux';
|
||||||
import {
|
import { doConditionalAuthNavigate, doDaemonReady, doAutoUpdate, doOpenModal, doHideModal } from 'redux/actions/app';
|
||||||
doConditionalAuthNavigate,
|
|
||||||
doDaemonReady,
|
|
||||||
doAutoUpdate,
|
|
||||||
doOpenModal,
|
|
||||||
doHideModal,
|
|
||||||
} from 'redux/actions/app';
|
|
||||||
import { Lbry, doToast, isURIValid, setSearchApi } from 'lbry-redux';
|
import { Lbry, doToast, isURIValid, setSearchApi } from 'lbry-redux';
|
||||||
import { doDownloadLanguages, doUpdateIsNightAsync } from 'redux/actions/settings';
|
import { doDownloadLanguages, doUpdateIsNightAsync } from 'redux/actions/settings';
|
||||||
import { doAuthenticate, Lbryio, rewards, doBlackListedOutpointsSubscribe } from 'lbryinc';
|
import { doAuthenticate, Lbryio, rewards, doBlackListedOutpointsSubscribe } from 'lbryinc';
|
||||||
import { store, history } from 'store';
|
import { store, history } from 'store';
|
||||||
import pjson from 'package.json';
|
import pjson from 'package.json';
|
||||||
import app from './app';
|
import app from './app';
|
||||||
import analytics from './analytics';
|
|
||||||
import doLogWarningConsoleMessage from './logWarningConsoleMessage';
|
import doLogWarningConsoleMessage from './logWarningConsoleMessage';
|
||||||
import { ConnectedRouter, push } from 'connected-react-router';
|
import { ConnectedRouter, push } from 'connected-react-router';
|
||||||
import cookie from 'cookie';
|
import cookie from 'cookie';
|
||||||
|
|
|
@ -1,30 +1,20 @@
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { doFetchClaimsByChannel } from 'redux/actions/content';
|
|
||||||
import { PAGE_SIZE } from 'constants/claim';
|
|
||||||
import {
|
import {
|
||||||
makeSelectClaimForUri,
|
|
||||||
makeSelectClaimsInChannelForCurrentPageState,
|
|
||||||
makeSelectFetchingChannelClaims,
|
|
||||||
makeSelectClaimIsMine,
|
makeSelectClaimIsMine,
|
||||||
makeSelectTotalPagesForChannel,
|
makeSelectTitleForUri,
|
||||||
|
makeSelectThumbnailForUri,
|
||||||
|
makeSelectCoverForUri,
|
||||||
} from 'lbry-redux';
|
} from 'lbry-redux';
|
||||||
import { doOpenModal } from 'redux/actions/app';
|
|
||||||
import ChannelPage from './view';
|
import ChannelPage from './view';
|
||||||
|
|
||||||
const select = (state, props) => ({
|
const select = (state, props) => ({
|
||||||
claim: makeSelectClaimForUri(props.uri)(state),
|
title: makeSelectTitleForUri(props.uri)(state),
|
||||||
claimsInChannel: makeSelectClaimsInChannelForCurrentPageState(props.uri)(state),
|
thumbnail: makeSelectThumbnailForUri(props.uri)(state),
|
||||||
fetching: makeSelectFetchingChannelClaims(props.uri)(state),
|
cover: makeSelectCoverForUri(props.uri)(state),
|
||||||
totalPages: makeSelectTotalPagesForChannel(props.uri, PAGE_SIZE)(state),
|
|
||||||
channelIsMine: makeSelectClaimIsMine(props.uri)(state),
|
channelIsMine: makeSelectClaimIsMine(props.uri)(state),
|
||||||
});
|
});
|
||||||
|
|
||||||
const perform = dispatch => ({
|
|
||||||
fetchClaims: (uri, page) => dispatch(doFetchClaimsByChannel(uri, page)),
|
|
||||||
openModal: (modal, props) => dispatch(doOpenModal(modal, props)),
|
|
||||||
});
|
|
||||||
|
|
||||||
export default connect(
|
export default connect(
|
||||||
select,
|
select,
|
||||||
perform
|
null
|
||||||
)(ChannelPage);
|
)(ChannelPage);
|
||||||
|
|
|
@ -1,143 +1,86 @@
|
||||||
// @flow
|
// @flow
|
||||||
import * as icons from 'constants/icons';
|
import React from 'react';
|
||||||
import * as MODALS from 'constants/modal_types';
|
import { parseURI } from 'lbry-redux';
|
||||||
import React, { useEffect } from 'react';
|
|
||||||
import BusyIndicator from 'component/common/busy-indicator';
|
|
||||||
import { FormField, Form } from 'component/common/form';
|
|
||||||
import ReactPaginate from 'react-paginate';
|
|
||||||
import SubscribeButton from 'component/subscribeButton';
|
|
||||||
import Page from 'component/page';
|
import Page from 'component/page';
|
||||||
import FileList from 'component/fileList';
|
import SubscribeButton from 'component/subscribeButton';
|
||||||
import HiddenNsfwClaims from 'component/hiddenNsfwClaims';
|
import ShareButton from 'component/shareButton';
|
||||||
import Button from 'component/button';
|
import { Tabs, TabList, Tab, TabPanels, TabPanel } from 'component/common/tabs';
|
||||||
import { withRouter } from 'react-router-dom';
|
import { withRouter } from 'react-router';
|
||||||
|
import { formatLbryUriForWeb } from 'util/uri';
|
||||||
|
import ChannelContent from 'component/channelContent';
|
||||||
|
import ChannelAbout from 'component/channelAbout';
|
||||||
|
import ChannelThumbnail from 'component/channelThumbnail';
|
||||||
|
|
||||||
|
const PAGE_VIEW_QUERY = `view`;
|
||||||
|
const ABOUT_PAGE = `about`;
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
uri: string,
|
uri: string,
|
||||||
totalPages: number,
|
title: ?string,
|
||||||
fetching: boolean,
|
cover: ?string,
|
||||||
params: { page: number },
|
thumbnail: ?string,
|
||||||
claim: ChannelClaim,
|
location: { search: string },
|
||||||
claimsInChannel: Array<StreamClaim>,
|
|
||||||
channelIsMine: boolean,
|
|
||||||
fetchClaims: (string, number) => void,
|
|
||||||
history: { push: string => void },
|
history: { push: string => void },
|
||||||
openModal: (id: string, { uri: string }) => void,
|
match: { params: { attribute: ?string } },
|
||||||
location: UrlLocation,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
function ChannelPage(props: Props) {
|
function ChannelPage(props: Props) {
|
||||||
const {
|
const { uri, title, cover, history, location } = props;
|
||||||
uri,
|
const { channelName, claimName, claimId } = parseURI(uri);
|
||||||
fetching,
|
|
||||||
claimsInChannel,
|
|
||||||
claim,
|
|
||||||
totalPages,
|
|
||||||
channelIsMine,
|
|
||||||
openModal,
|
|
||||||
fetchClaims,
|
|
||||||
location,
|
|
||||||
history,
|
|
||||||
} = props;
|
|
||||||
|
|
||||||
const { name, permanent_url: permanentUrl } = claim;
|
|
||||||
const { search } = location;
|
const { search } = location;
|
||||||
const urlParams = new URLSearchParams(search);
|
const urlParams = new URLSearchParams(search);
|
||||||
const page = Number(urlParams.get('page')) || 1;
|
const currentView = urlParams.get(PAGE_VIEW_QUERY) || undefined;
|
||||||
|
|
||||||
useEffect(() => {
|
// If a user changes tabs, update the url so it stays on the same page if they refresh.
|
||||||
// Fetch new claims if the channel or page number changes
|
// We don't want to use links here because we can't animate the tab change and using links
|
||||||
fetchClaims(uri, page);
|
// would alter the Tab label's role attribute, which should stay role="tab" to work with keyboards/screen readers.
|
||||||
}, [uri, page]);
|
const tabIndex = currentView === ABOUT_PAGE ? 1 : 0;
|
||||||
|
const onTabChange = newTabIndex => {
|
||||||
const changePage = (pageNumber: number) => {
|
let url = formatLbryUriForWeb(uri);
|
||||||
if (!page && pageNumber === 1) {
|
if (newTabIndex !== 0) {
|
||||||
return;
|
url += `?${PAGE_VIEW_QUERY}=${ABOUT_PAGE}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
history.push(`?page=${pageNumber}`);
|
history.push(url);
|
||||||
};
|
|
||||||
|
|
||||||
const paginate = (e: SyntheticKeyboardEvent<*>) => {
|
|
||||||
// Change page if enter was pressed, and the given page is between the first and the last page
|
|
||||||
const pageFromInput = Number(e.currentTarget.value);
|
|
||||||
|
|
||||||
if (
|
|
||||||
pageFromInput &&
|
|
||||||
e.keyCode === 13 &&
|
|
||||||
!Number.isNaN(pageFromInput) &&
|
|
||||||
pageFromInput > 0 &&
|
|
||||||
pageFromInput <= totalPages
|
|
||||||
) {
|
|
||||||
changePage(pageFromInput);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Page notContained>
|
<Page notContained className="main--no-padding-top">
|
||||||
<header className="channel-info">
|
<header className="channel__cover main__item--extend-outside">
|
||||||
<h1 className="media__title media__title--large">
|
{cover && <img className="channel__cover--custom" src={cover} />}
|
||||||
{name}
|
|
||||||
{fetching && <BusyIndicator />}
|
|
||||||
</h1>
|
|
||||||
<span>{permanentUrl}</span>
|
|
||||||
|
|
||||||
<div className="channel-info__actions__group">
|
<div className="channel__primary-info">
|
||||||
<SubscribeButton uri={permanentUrl} channelName={name} />
|
<ChannelThumbnail uri={uri} />
|
||||||
<Button
|
|
||||||
button="alt"
|
<div>
|
||||||
icon={icons.SHARE}
|
<h1 className="channel__title">{title || channelName}</h1>
|
||||||
label={__('Share Channel')}
|
<h2 className="channel__url">
|
||||||
onClick={() =>
|
{claimName}
|
||||||
openModal(MODALS.SOCIAL_SHARE, { uri, speechShareable: true, isChannel: true })
|
{claimId && `#${claimId}`}
|
||||||
}
|
</h2>
|
||||||
/>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<section className="media-group--list">
|
<Tabs onChange={onTabChange} index={tabIndex}>
|
||||||
{claimsInChannel && claimsInChannel.length ? (
|
<TabList className="main__item--extend-outside tabs__list--channel-page">
|
||||||
<FileList sortByHeight hideFilter fileInfos={claimsInChannel} />
|
<Tab>{__('Content')}</Tab>
|
||||||
) : (
|
<Tab>{__('About')}</Tab>
|
||||||
!fetching && <span className="empty">{__('No content found.')}</span>
|
<div className="card__actions">
|
||||||
)}
|
<ShareButton uri={uri} />
|
||||||
</section>
|
<SubscribeButton uri={uri} />
|
||||||
|
</div>
|
||||||
|
</TabList>
|
||||||
|
|
||||||
{(!fetching || (claimsInChannel && claimsInChannel.length)) && totalPages > 1 && (
|
<TabPanels>
|
||||||
<Form>
|
<TabPanel>
|
||||||
<fieldset-group class="fieldset-group--smushed fieldgroup--paginate">
|
<ChannelContent uri={uri} />
|
||||||
<fieldset-section>
|
</TabPanel>
|
||||||
<ReactPaginate
|
<TabPanel>
|
||||||
pageCount={totalPages}
|
<ChannelAbout uri={uri} />
|
||||||
pageRangeDisplayed={2}
|
</TabPanel>
|
||||||
previousLabel="‹"
|
</TabPanels>
|
||||||
nextLabel="›"
|
</Tabs>
|
||||||
activeClassName="pagination__item--selected"
|
|
||||||
pageClassName="pagination__item"
|
|
||||||
previousClassName="pagination__item pagination__item--previous"
|
|
||||||
nextClassName="pagination__item pagination__item--next"
|
|
||||||
breakClassName="pagination__item pagination__item--break"
|
|
||||||
marginPagesDisplayed={2}
|
|
||||||
onPageChange={e => changePage(e.selected + 1)}
|
|
||||||
forcePage={page - 1}
|
|
||||||
initialPage={page - 1}
|
|
||||||
disableInitialCallback
|
|
||||||
containerClassName="pagination"
|
|
||||||
/>
|
|
||||||
</fieldset-section>
|
|
||||||
|
|
||||||
<FormField
|
|
||||||
className="paginate-channel"
|
|
||||||
onKeyUp={e => paginate(e)}
|
|
||||||
label={__('Go to page:')}
|
|
||||||
type="text"
|
|
||||||
name="paginate-file"
|
|
||||||
/>
|
|
||||||
</fieldset-group>
|
|
||||||
</Form>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{!channelIsMine && <HiddenNsfwClaims className="card__content help" uri={uri} />}
|
|
||||||
</Page>
|
</Page>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -61,7 +61,7 @@ class DiscoverPage extends React.PureComponent<Props> {
|
||||||
const failedToLoad = !fetchingFeaturedUris && !hasContent;
|
const failedToLoad = !fetchingFeaturedUris && !hasContent;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Page noPadding isLoading={!hasContent && fetchingFeaturedUris}>
|
<Page notContained isLoading={!hasContent && fetchingFeaturedUris} className="main--no-padding">
|
||||||
<FirstRun />
|
<FirstRun />
|
||||||
{hasContent &&
|
{hasContent &&
|
||||||
Object.keys(featuredUris).map(category => (
|
Object.keys(featuredUris).map(category => (
|
||||||
|
|
|
@ -23,29 +23,17 @@ class FileListPublished extends React.PureComponent<Props> {
|
||||||
return (
|
return (
|
||||||
<Page notContained loading={fetching}>
|
<Page notContained loading={fetching}>
|
||||||
{claims && claims.length ? (
|
{claims && claims.length ? (
|
||||||
<FileList
|
<FileList checkPending fileInfos={claims} sortByHeight sortBy={sortBy} page={PAGES.PUBLISHED} />
|
||||||
checkPending
|
|
||||||
fileInfos={claims}
|
|
||||||
sortByHeight
|
|
||||||
sortBy={sortBy}
|
|
||||||
page={PAGES.PUBLISHED}
|
|
||||||
/>
|
|
||||||
) : (
|
) : (
|
||||||
<div className="main--empty">
|
<div className="main--empty">
|
||||||
<section className="card card--section">
|
<section className="card card--section">
|
||||||
<header className="card__header">
|
<header className="card__header">
|
||||||
<h2 className="card__title">
|
<h2 className="card__title">{__("It looks like you haven't published anything to LBRY yet.")}</h2>
|
||||||
{__("It looks like you haven't published anything to LBRY yet.")}
|
|
||||||
</h2>
|
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<div className="card__content">
|
<div className="card__content">
|
||||||
<div className="card__actions card__actions--center">
|
<div className="card__actions card__actions--center">
|
||||||
<Button
|
<Button button="primary" navigate="/$/publish" label={__('Publish something new')} />
|
||||||
button="primary"
|
|
||||||
navigate="/$/publish"
|
|
||||||
label={__('Publish something new')}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
|
@ -1,13 +1,10 @@
|
||||||
// @flow
|
// @flow
|
||||||
import * as ICONS from 'constants/icons';
|
|
||||||
import React, { useEffect, Fragment } from 'react';
|
import React, { useEffect, Fragment } from 'react';
|
||||||
import { isURIValid, normalizeURI, parseURI } from 'lbry-redux';
|
import { isURIValid, normalizeURI, parseURI } from 'lbry-redux';
|
||||||
import FileTile from 'component/fileTile';
|
import FileTile from 'component/fileTile';
|
||||||
import ChannelTile from 'component/channelTile';
|
import ChannelTile from 'component/channelTile';
|
||||||
import FileListSearch from 'component/fileListSearch';
|
import FileListSearch from 'component/fileListSearch';
|
||||||
import Page from 'component/page';
|
import Page from 'component/page';
|
||||||
import ToolTip from 'component/common/tooltip';
|
|
||||||
import Icon from 'component/common/icon';
|
|
||||||
import SearchOptions from 'component/searchOptions';
|
import SearchOptions from 'component/searchOptions';
|
||||||
import Button from 'component/button';
|
import Button from 'component/button';
|
||||||
|
|
||||||
|
@ -24,7 +21,6 @@ export default function SearchPage(props: Props) {
|
||||||
|
|
||||||
let uri;
|
let uri;
|
||||||
let isChannel;
|
let isChannel;
|
||||||
let label;
|
|
||||||
if (isValid) {
|
if (isValid) {
|
||||||
uri = normalizeURI(urlQuery);
|
uri = normalizeURI(urlQuery);
|
||||||
({ isChannel } = parseURI(uri));
|
({ isChannel } = parseURI(uri));
|
||||||
|
@ -34,10 +30,10 @@ export default function SearchPage(props: Props) {
|
||||||
if (urlQuery) {
|
if (urlQuery) {
|
||||||
doSearch(urlQuery);
|
doSearch(urlQuery);
|
||||||
}
|
}
|
||||||
}, [urlQuery]);
|
}, [doSearch, urlQuery]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Page noPadding>
|
<Page>
|
||||||
<section className="search">
|
<section className="search">
|
||||||
{urlQuery && (
|
{urlQuery && (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
|
@ -58,9 +54,7 @@ export default function SearchPage(props: Props) {
|
||||||
<SearchOptions />
|
<SearchOptions />
|
||||||
|
|
||||||
<FileListSearch query={urlQuery} />
|
<FileListSearch query={urlQuery} />
|
||||||
<div className="card__content help">
|
<div className="card__content help">{__('These search results are provided by LBRY, Inc.')}</div>
|
||||||
{__('These search results are provided by LBRY, Inc.')}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</Fragment>
|
</Fragment>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
// @flow
|
// @flow
|
||||||
import React, { Fragment } from 'react';
|
import React from 'react';
|
||||||
import Button from 'component/button';
|
import Button from 'component/button';
|
||||||
import SuggestedSubscriptions from 'component/subscribeSuggested';
|
import SuggestedSubscriptions from 'component/subscribeSuggested';
|
||||||
import Yrbl from 'component/yrbl';
|
import Yrbl from 'component/yrbl';
|
||||||
|
@ -12,17 +12,11 @@ type Props = {
|
||||||
doShowSuggestedSubs: () => void,
|
doShowSuggestedSubs: () => void,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default (props: Props) => {
|
export default function SubscriptionsFirstRun(props: Props) {
|
||||||
const {
|
const { showSuggested, loadingSuggested, numberOfSubscriptions, doShowSuggestedSubs, onFinish } = props;
|
||||||
showSuggested,
|
|
||||||
loadingSuggested,
|
|
||||||
numberOfSubscriptions,
|
|
||||||
doShowSuggestedSubs,
|
|
||||||
onFinish,
|
|
||||||
} = props;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Fragment>
|
<div>
|
||||||
<Yrbl
|
<Yrbl
|
||||||
title={numberOfSubscriptions > 0 ? __('Woohoo!') : __('No subscriptions... yet.')}
|
title={numberOfSubscriptions > 0 ? __('Woohoo!') : __('No subscriptions... yet.')}
|
||||||
subtitle={
|
subtitle={
|
||||||
|
@ -52,6 +46,6 @@ export default (props: Props) => {
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
{showSuggested && !loadingSuggested && <SuggestedSubscriptions />}
|
{showSuggested && !loadingSuggested && <SuggestedSubscriptions />}
|
||||||
</Fragment>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
|
@ -11,7 +11,7 @@ import SuggestedSubscriptions from 'component/subscribeSuggested';
|
||||||
import MarkAsRead from 'component/subscribeMarkAsRead';
|
import MarkAsRead from 'component/subscribeMarkAsRead';
|
||||||
import Tooltip from 'component/common/tooltip';
|
import Tooltip from 'component/common/tooltip';
|
||||||
import Yrbl from 'component/yrbl';
|
import Yrbl from 'component/yrbl';
|
||||||
import { formatLbryUriForWeb } from 'util/uri';
|
import { Tabs, TabList, Tab, TabPanels, TabPanel } from 'component/common/tabs';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
viewMode: ViewMode,
|
viewMode: ViewMode,
|
||||||
|
@ -33,27 +33,18 @@ export default (props: Props) => {
|
||||||
onChangeAutoDownload,
|
onChangeAutoDownload,
|
||||||
unreadSubscriptions,
|
unreadSubscriptions,
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
|
const index = viewMode === VIEW_ALL ? 0 : 1;
|
||||||
|
const onTabChange = index => (index === 0 ? doSetViewMode(VIEW_ALL) : doSetViewMode(VIEW_LATEST_FIRST));
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
{hasSubscriptions && (
|
{hasSubscriptions && (
|
||||||
<section className="card card--section">
|
<Tabs onChange={onTabChange} index={index}>
|
||||||
<div className="card__content card--space-between">
|
<TabList className="main__item--extend-outside">
|
||||||
<div className="card__actions card__actions--no-margin">
|
<Tab>{__('All Subscriptions')}</Tab>
|
||||||
<Button
|
<Tab>{__('Latest Only')}</Tab>
|
||||||
disabled={viewMode === VIEW_ALL}
|
|
||||||
className={viewMode === VIEW_ALL && 'button--subscription-view-selected'}
|
|
||||||
button="link"
|
|
||||||
label="All Subscriptions"
|
|
||||||
onClick={() => doSetViewMode(VIEW_ALL)}
|
|
||||||
/>
|
|
||||||
<Button
|
|
||||||
button="link"
|
|
||||||
disabled={viewMode === VIEW_LATEST_FIRST}
|
|
||||||
className={viewMode === VIEW_LATEST_FIRST && 'button--subscription-view-selected'}
|
|
||||||
label={__('Latest Only')}
|
|
||||||
onClick={() => doSetViewMode(VIEW_LATEST_FIRST)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<Tooltip onComponent body={__('Automatically download new subscriptions.')}>
|
<Tooltip onComponent body={__('Automatically download new subscriptions.')}>
|
||||||
<FormField
|
<FormField
|
||||||
type="setting"
|
type="setting"
|
||||||
|
@ -64,44 +55,29 @@ export default (props: Props) => {
|
||||||
labelOnLeft
|
labelOnLeft
|
||||||
/>
|
/>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</div>
|
</TabList>
|
||||||
</section>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<HiddenNsfwClaims
|
<TabPanels
|
||||||
uris={subscriptions.reduce((arr, { name, claim_id: claimId }) => {
|
header={
|
||||||
if (name && claimId) {
|
<HiddenNsfwClaims
|
||||||
arr.push(`lbry://${name}#${claimId}`);
|
uris={subscriptions.reduce((arr, { name, claim_id: claimId }) => {
|
||||||
}
|
if (name && claimId) {
|
||||||
return arr;
|
arr.push(`lbry://${name}#${claimId}`);
|
||||||
}, [])}
|
}
|
||||||
/>
|
return arr;
|
||||||
|
}, [])}
|
||||||
{!hasSubscriptions && (
|
/>
|
||||||
<Fragment>
|
}
|
||||||
<Yrbl
|
>
|
||||||
type="sad"
|
<TabPanel>
|
||||||
title={__('Oh no! What happened to your subscriptions?')}
|
|
||||||
subtitle={__('These channels look pretty cool.')}
|
|
||||||
/>
|
|
||||||
<SuggestedSubscriptions />
|
|
||||||
</Fragment>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{hasSubscriptions && (
|
|
||||||
<div className="card__content">
|
|
||||||
{viewMode === VIEW_ALL && (
|
|
||||||
<Fragment>
|
|
||||||
<div className="card__title card__title--flex">
|
<div className="card__title card__title--flex">
|
||||||
<span>{__('Your subscriptions')}</span>
|
<span>{__('Your subscriptions')}</span>
|
||||||
{unreadSubscriptions.length > 0 && <MarkAsRead />}
|
{unreadSubscriptions.length > 0 && <MarkAsRead />}
|
||||||
</div>
|
</div>
|
||||||
<FileList hideFilter sortByHeight fileInfos={subscriptions} />
|
<FileList hideFilter sortByHeight fileInfos={subscriptions} />
|
||||||
</Fragment>
|
</TabPanel>
|
||||||
)}
|
|
||||||
|
|
||||||
{viewMode === VIEW_LATEST_FIRST && (
|
<TabPanel>
|
||||||
<Fragment>
|
|
||||||
{unreadSubscriptions.length ? (
|
{unreadSubscriptions.length ? (
|
||||||
unreadSubscriptions.map(({ channel, uris }) => {
|
unreadSubscriptions.map(({ channel, uris }) => {
|
||||||
const { claimName } = parseURI(channel);
|
const { claimName } = parseURI(channel);
|
||||||
|
@ -124,16 +100,24 @@ export default (props: Props) => {
|
||||||
})
|
})
|
||||||
) : (
|
) : (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<Yrbl
|
<Yrbl title={__('All caught up!')} subtitle={__('You might like the channels below.')} />
|
||||||
title={__('All caught up!')}
|
|
||||||
subtitle={__('You might like the channels below.')}
|
|
||||||
/>
|
|
||||||
<SuggestedSubscriptions />
|
<SuggestedSubscriptions />
|
||||||
</Fragment>
|
</Fragment>
|
||||||
)}
|
)}
|
||||||
</Fragment>
|
</TabPanel>
|
||||||
)}
|
</TabPanels>
|
||||||
</div>
|
</Tabs>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{!hasSubscriptions && (
|
||||||
|
<Fragment>
|
||||||
|
<Yrbl
|
||||||
|
type="sad"
|
||||||
|
title={__('Oh no! What happened to your subscriptions?')}
|
||||||
|
subtitle={__('These channels look pretty cool.')}
|
||||||
|
/>
|
||||||
|
<SuggestedSubscriptions />
|
||||||
|
</Fragment>
|
||||||
)}
|
)}
|
||||||
</Fragment>
|
</Fragment>
|
||||||
);
|
);
|
||||||
|
|
|
@ -26,7 +26,7 @@ type Props = {
|
||||||
showSuggestedSubs: boolean,
|
showSuggestedSubs: boolean,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default class extends PureComponent<Props> {
|
export default class SubscriptionsPage extends PureComponent<Props> {
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
|
@ -78,7 +78,7 @@ export default class extends PureComponent<Props> {
|
||||||
// Only pass in the loading prop if there are no subscriptions
|
// Only pass in the loading prop if there are no subscriptions
|
||||||
// If there are any, let the page update in the background
|
// If there are any, let the page update in the background
|
||||||
// The loading prop removes children and shows a loading spinner
|
// The loading prop removes children and shows a loading spinner
|
||||||
<Page notContained loading={loading && !subscribedChannels}>
|
<Page notContained loading={loading && !subscribedChannels} className="main--no-padding-top">
|
||||||
{firstRunCompleted ? (
|
{firstRunCompleted ? (
|
||||||
<UserSubscriptions
|
<UserSubscriptions
|
||||||
viewMode={viewMode}
|
viewMode={viewMode}
|
||||||
|
|
|
@ -10,10 +10,7 @@ function getLocalStorageSetting(setting, fallback) {
|
||||||
const reducers = {};
|
const reducers = {};
|
||||||
const defaultState = {
|
const defaultState = {
|
||||||
clientSettings: {
|
clientSettings: {
|
||||||
[SETTINGS.INSTANT_PURCHASE_ENABLED]: getLocalStorageSetting(
|
[SETTINGS.INSTANT_PURCHASE_ENABLED]: getLocalStorageSetting(SETTINGS.INSTANT_PURCHASE_ENABLED, false),
|
||||||
SETTINGS.INSTANT_PURCHASE_ENABLED,
|
|
||||||
false
|
|
||||||
),
|
|
||||||
[SETTINGS.INSTANT_PURCHASE_MAX]: getLocalStorageSetting(SETTINGS.INSTANT_PURCHASE_MAX, {
|
[SETTINGS.INSTANT_PURCHASE_MAX]: getLocalStorageSetting(SETTINGS.INSTANT_PURCHASE_MAX, {
|
||||||
currency: 'LBC',
|
currency: 'LBC',
|
||||||
amount: 0.1,
|
amount: 0.1,
|
||||||
|
@ -21,26 +18,18 @@ const defaultState = {
|
||||||
[SETTINGS.SHOW_NSFW]: getLocalStorageSetting(SETTINGS.SHOW_NSFW, false),
|
[SETTINGS.SHOW_NSFW]: getLocalStorageSetting(SETTINGS.SHOW_NSFW, false),
|
||||||
[SETTINGS.SHOW_UNAVAILABLE]: getLocalStorageSetting(SETTINGS.SHOW_UNAVAILABLE, true),
|
[SETTINGS.SHOW_UNAVAILABLE]: getLocalStorageSetting(SETTINGS.SHOW_UNAVAILABLE, true),
|
||||||
[SETTINGS.NEW_USER_ACKNOWLEDGED]: getLocalStorageSetting(SETTINGS.NEW_USER_ACKNOWLEDGED, false),
|
[SETTINGS.NEW_USER_ACKNOWLEDGED]: getLocalStorageSetting(SETTINGS.NEW_USER_ACKNOWLEDGED, false),
|
||||||
[SETTINGS.EMAIL_COLLECTION_ACKNOWLEDGED]: getLocalStorageSetting(
|
[SETTINGS.EMAIL_COLLECTION_ACKNOWLEDGED]: getLocalStorageSetting(SETTINGS.EMAIL_COLLECTION_ACKNOWLEDGED, false),
|
||||||
SETTINGS.EMAIL_COLLECTION_ACKNOWLEDGED,
|
|
||||||
false
|
|
||||||
),
|
|
||||||
[SETTINGS.INVITE_ACKNOWLEDGED]: getLocalStorageSetting(SETTINGS.INVITE_ACKNOWLEDGED, false),
|
[SETTINGS.INVITE_ACKNOWLEDGED]: getLocalStorageSetting(SETTINGS.INVITE_ACKNOWLEDGED, false),
|
||||||
[SETTINGS.FIRST_RUN_COMPLETED]: getLocalStorageSetting(SETTINGS.FIRST_RUN_COMPLETED, false),
|
[SETTINGS.FIRST_RUN_COMPLETED]: getLocalStorageSetting(SETTINGS.FIRST_RUN_COMPLETED, false),
|
||||||
[SETTINGS.CREDIT_REQUIRED_ACKNOWLEDGED]: false, // this needs to be re-acknowledged every run
|
[SETTINGS.CREDIT_REQUIRED_ACKNOWLEDGED]: false, // this needs to be re-acknowledged every run
|
||||||
[SETTINGS.LANGUAGE]: getLocalStorageSetting(SETTINGS.LANGUAGE, 'en'),
|
[SETTINGS.LANGUAGE]: getLocalStorageSetting(SETTINGS.LANGUAGE, 'en'),
|
||||||
[SETTINGS.THEME]: getLocalStorageSetting(SETTINGS.THEME, 'dark'),
|
[SETTINGS.THEME]: getLocalStorageSetting(SETTINGS.THEME, 'dark'),
|
||||||
[SETTINGS.THEMES]: getLocalStorageSetting(SETTINGS.THEMES, []),
|
[SETTINGS.THEMES]: getLocalStorageSetting(SETTINGS.THEMES, []),
|
||||||
[SETTINGS.AUTOMATIC_DARK_MODE_ENABLED]: getLocalStorageSetting(
|
[SETTINGS.AUTOMATIC_DARK_MODE_ENABLED]: getLocalStorageSetting(SETTINGS.AUTOMATIC_DARK_MODE_ENABLED, false),
|
||||||
SETTINGS.AUTOMATIC_DARK_MODE_ENABLED,
|
|
||||||
false
|
|
||||||
),
|
|
||||||
[SETTINGS.AUTOPLAY]: getLocalStorageSetting(SETTINGS.AUTOPLAY, false),
|
[SETTINGS.AUTOPLAY]: getLocalStorageSetting(SETTINGS.AUTOPLAY, false),
|
||||||
[SETTINGS.RESULT_COUNT]: Number(getLocalStorageSetting(SETTINGS.RESULT_COUNT, 50)),
|
[SETTINGS.RESULT_COUNT]: Number(getLocalStorageSetting(SETTINGS.RESULT_COUNT, 50)),
|
||||||
[SETTINGS.AUTO_DOWNLOAD]: getLocalStorageSetting(SETTINGS.AUTO_DOWNLOAD, true),
|
[SETTINGS.AUTO_DOWNLOAD]: getLocalStorageSetting(SETTINGS.AUTO_DOWNLOAD, true),
|
||||||
[SETTINGS.OS_NOTIFICATIONS_ENABLED]: Boolean(
|
[SETTINGS.OS_NOTIFICATIONS_ENABLED]: Boolean(getLocalStorageSetting(SETTINGS.OS_NOTIFICATIONS_ENABLED, true)),
|
||||||
getLocalStorageSetting(SETTINGS.OS_NOTIFICATIONS_ENABLED, true)
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
isNight: false,
|
isNight: false,
|
||||||
languages: {},
|
languages: {},
|
||||||
|
|
|
@ -41,6 +41,7 @@
|
||||||
@import 'component/subscriptions';
|
@import 'component/subscriptions';
|
||||||
@import 'component/syntax-highlighter';
|
@import 'component/syntax-highlighter';
|
||||||
@import 'component/table';
|
@import 'component/table';
|
||||||
|
@import 'component/tabs';
|
||||||
@import 'component/time';
|
@import 'component/time';
|
||||||
@import 'component/toggle';
|
@import 'component/toggle';
|
||||||
@import 'component/tooltip';
|
@import 'component/tooltip';
|
||||||
|
|
|
@ -3,10 +3,13 @@
|
||||||
border: 1px solid $lbry-gray-1;
|
border: 1px solid $lbry-gray-1;
|
||||||
margin-bottom: var(--spacing-vertical-xlarge);
|
margin-bottom: var(--spacing-vertical-xlarge);
|
||||||
position: relative;
|
position: relative;
|
||||||
|
border-radius: var(--card-radius);
|
||||||
|
box-shadow: var(--box-shadow) $lbry-gray-1;
|
||||||
|
|
||||||
html[data-mode='dark'] & {
|
html[data-mode='dark'] & {
|
||||||
background-color: rgba($lbry-white, 0.1);
|
background-color: rgba($lbry-white, 0.1);
|
||||||
border-color: rgba($lbry-white, 0.1);
|
border-color: rgba($lbry-white, 0.1);
|
||||||
|
box-shadow: var(--box-shadow) darken($lbry-gray-1, 80%);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -46,7 +49,7 @@
|
||||||
font-size: 1.15rem;
|
font-size: 1.15rem;
|
||||||
|
|
||||||
> *:not(:last-child) {
|
> *:not(:last-child) {
|
||||||
margin-right: var(--spacing-vertical-large);
|
margin-right: var(--spacing-vertical-medium);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -118,6 +121,37 @@
|
||||||
.card__list {
|
.card__list {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-gap: var(--spacing-vertical-medium);
|
grid-gap: var(--spacing-vertical-medium);
|
||||||
|
|
||||||
|
// Depending on screen width, the amount of items in
|
||||||
|
// each row change and are auto-sized
|
||||||
|
|
||||||
|
@media (min-width: 2001px) {
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(calc(100% / 10), 1fr));
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 1801px) and (max-width: 2000px) {
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(calc(100% / 8), 1fr));
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 1551px) and (max-width: 1800px) {
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(calc(100% / 7), 1fr));
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 1051px) and (max-width: 1550px) {
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(calc(100% / 6), 1fr));
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 901px) and (max-width: 1050px) {
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(calc(100% / 5), 1fr));
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 751px) and (max-width: 900px) {
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(calc(100% / 4), 1fr));
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 750px) {
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(calc(100% / 3), 1fr));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.card__list--rewards {
|
.card__list--rewards {
|
||||||
|
@ -137,8 +171,8 @@
|
||||||
|
|
||||||
.card__message {
|
.card__message {
|
||||||
border-left: 0.5rem solid;
|
border-left: 0.5rem solid;
|
||||||
padding: var(--spacing-vertical-medium) var(--spacing-vertical-medium)
|
padding: var(--spacing-vertical-medium) var(--spacing-vertical-medium) var(--spacing-vertical-medium)
|
||||||
var(--spacing-vertical-medium) var(--spacing-vertical-large);
|
var(--spacing-vertical-large);
|
||||||
|
|
||||||
&:not(&--error):not(&--failure):not(&--success) {
|
&:not(&--error):not(&--failure):not(&--success) {
|
||||||
background-color: rgba($lbry-teal-1, 0.1);
|
background-color: rgba($lbry-teal-1, 0.1);
|
||||||
|
@ -184,6 +218,7 @@
|
||||||
.card__title {
|
.card__title {
|
||||||
font-size: 2rem;
|
font-size: 2rem;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
|
padding-bottom: var(--spacing-vertical-medium);
|
||||||
|
|
||||||
+ .card__content {
|
+ .card__content {
|
||||||
margin-top: var(--spacing-vertical-medium);
|
margin-top: var(--spacing-vertical-medium);
|
||||||
|
|
|
@ -1,27 +1,71 @@
|
||||||
.channel-info {
|
$cover-z-index: 0;
|
||||||
.media__title {
|
$metadata-z-index: 1;
|
||||||
display: block;
|
|
||||||
user-select: text;
|
|
||||||
margin-bottom: var(--spacing-vertical-medium);
|
|
||||||
}
|
|
||||||
|
|
||||||
.channel-info__actions__group {
|
.channel__cover {
|
||||||
margin-bottom: var(--spacing-vertical-large);
|
background-image: linear-gradient(to right, $lbry-indigo-4, $lbry-cyan-5 80%);
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.channel-info__actions {
|
|
||||||
display: flex;
|
display: flex;
|
||||||
|
align-items: flex-end;
|
||||||
|
box-sizing: content-box;
|
||||||
|
color: $lbry-white;
|
||||||
}
|
}
|
||||||
|
|
||||||
.channel-info__actions__group {
|
.channel__cover--custom {
|
||||||
@extend .media__action-group;
|
z-index: $cover-z-index;
|
||||||
@extend .media__action-group--large;
|
align-self: flex-start;
|
||||||
|
position: absolute;
|
||||||
|
object-fit: cover;
|
||||||
|
filter: brightness(40%);
|
||||||
}
|
}
|
||||||
|
|
||||||
.channel-name {
|
.channel__cover,
|
||||||
overflow: hidden;
|
.channel__cover--custom {
|
||||||
text-align: left;
|
height: var(--cover-photo-height);
|
||||||
text-overflow: ellipsis;
|
width: 100%;
|
||||||
white-space: nowrap;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.channel__thumbnail {
|
||||||
|
position: absolute;
|
||||||
|
left: var(--spacing-main-padding);
|
||||||
|
height: var(--channel-thumbnail-size);
|
||||||
|
width: var(--channel-thumbnail-size);
|
||||||
|
background-color: $lbry-gray-3;
|
||||||
|
background-image: linear-gradient(to right, $lbry-white, $lbry-gray-3 80%);
|
||||||
|
background-size: cover;
|
||||||
|
box-shadow: 0px 8px 40px -3px $lbry-black;
|
||||||
|
}
|
||||||
|
|
||||||
|
.channel__thumbnail--custom {
|
||||||
|
width: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
.channel__thumbnail,
|
||||||
|
.channel__thumbnail--custom {
|
||||||
|
border-radius: var(--card-radius);
|
||||||
|
}
|
||||||
|
|
||||||
|
.channel__primary-info {
|
||||||
|
// Ensure the profile pic/title sit ontop of the default cover background
|
||||||
|
z-index: $metadata-z-index;
|
||||||
|
// Jump over the thumbnail photo because it is absolutely positioned
|
||||||
|
// Then add normal page spacing, _then_ add the actual padding
|
||||||
|
margin-left: calc(var(--channel-thumbnail-size) + var(--spacing-main-padding));
|
||||||
|
padding-left: var(--spacing-vertical-large);
|
||||||
|
padding-bottom: var(--spacing-vertical-medium);
|
||||||
|
}
|
||||||
|
|
||||||
|
.channel__title {
|
||||||
|
font-size: 3rem;
|
||||||
|
font-weight: 800;
|
||||||
|
}
|
||||||
|
|
||||||
|
.channel__url {
|
||||||
|
font-size: 1.2rem;
|
||||||
|
user-select: all;
|
||||||
|
margin-top: -0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
// .channel__description {
|
||||||
|
// font-size: 1.3rem;
|
||||||
|
// margin: var(--spacing-vertical-large) 0;
|
||||||
|
// }
|
||||||
|
|
|
@ -87,7 +87,7 @@ fieldset-group {
|
||||||
}
|
}
|
||||||
|
|
||||||
&.fieldgroup--paginate {
|
&.fieldgroup--paginate {
|
||||||
margin-top: var(--spacing-vertical-medium);
|
margin: var(--spacing-vertical-large) 0;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
|
||||||
|
|
|
@ -1,14 +1,18 @@
|
||||||
.main-wrapper {
|
.main-wrapper {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: var(--header-height);
|
top: var(--header-height);
|
||||||
left: var(--side-nav-width);
|
|
||||||
min-height: calc(100% - var(--header-height));
|
min-height: calc(100% - var(--header-height));
|
||||||
width: calc(100% - var(--side-nav-width));
|
width: 100%;
|
||||||
background-color: mix($lbry-white, $lbry-gray-1, 70%);
|
background-color: mix($lbry-white, $lbry-gray-1, 70%);
|
||||||
|
|
||||||
html[data-mode='dark'] & {
|
html[data-mode='dark'] & {
|
||||||
background-color: $lbry-black;
|
background-color: $lbry-black;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media (min-width: 600px) {
|
||||||
|
left: var(--side-nav-width);
|
||||||
|
width: calc(100% - var(--side-nav-width));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.main {
|
.main {
|
||||||
|
@ -19,13 +23,23 @@
|
||||||
|
|
||||||
.main--contained {
|
.main--contained {
|
||||||
max-width: 900px;
|
max-width: 900px;
|
||||||
|
padding: var(--spacing-main-padding);
|
||||||
}
|
}
|
||||||
|
|
||||||
.main:not(.main--no-padding) {
|
.main--not-contained {
|
||||||
padding: var(--spacing-vertical-large);
|
padding: var(--spacing-main-padding);
|
||||||
|
}
|
||||||
|
|
||||||
|
.main--no-padding {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main--no-padding-top {
|
||||||
|
padding-top: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.main--file-page {
|
.main--file-page {
|
||||||
|
padding: var(--spacing-main-padding);
|
||||||
max-width: var(--file-page-max-width);
|
max-width: var(--file-page-max-width);
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-gap: var(--spacing-vertical-large);
|
grid-gap: var(--spacing-vertical-large);
|
||||||
|
@ -35,7 +49,7 @@
|
||||||
'content content'
|
'content content'
|
||||||
'info related';
|
'info related';
|
||||||
|
|
||||||
@media (min-width: 1470px) {
|
@media (min-width: 1300px) {
|
||||||
grid-template-areas:
|
grid-template-areas:
|
||||||
'content related'
|
'content related'
|
||||||
'info related';
|
'info related';
|
||||||
|
@ -60,3 +74,16 @@
|
||||||
margin-bottom: 100px;
|
margin-bottom: 100px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// On pages that are not contained, they still might want to have items inside the page
|
||||||
|
// that extend the full width ex: cover photo
|
||||||
|
// But the components inside of those pages should be still have "page" padding
|
||||||
|
.main__item--extend-outside {
|
||||||
|
$main-width: calc(100vw - var(--side-nav-width));
|
||||||
|
width: $main-width;
|
||||||
|
position: relative;
|
||||||
|
left: 50%;
|
||||||
|
right: 50%;
|
||||||
|
margin-left: calc(-50vw + (var(--side-nav-width) * 0.5));
|
||||||
|
margin-right: calc(-50vw + (var(--side-nav-width) * 0.5));
|
||||||
|
}
|
||||||
|
|
|
@ -345,42 +345,6 @@
|
||||||
// G R O U P
|
// G R O U P
|
||||||
|
|
||||||
.media-group--list {
|
.media-group--list {
|
||||||
.card__list {
|
|
||||||
padding-top: var(--spacing-vertical-large);
|
|
||||||
padding-bottom: var(--spacing-vertical-large);
|
|
||||||
|
|
||||||
// Depending on screen width, the amount of items in
|
|
||||||
// each row change and are auto-sized
|
|
||||||
|
|
||||||
@media (min-width: 2001px) {
|
|
||||||
grid-template-columns: repeat(auto-fill, minmax(calc(100% / 10), 1fr));
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (min-width: 1801px) and (max-width: 2000px) {
|
|
||||||
grid-template-columns: repeat(auto-fill, minmax(calc(100% / 8), 1fr));
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (min-width: 1551px) and (max-width: 1800px) {
|
|
||||||
grid-template-columns: repeat(auto-fill, minmax(calc(100% / 7), 1fr));
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (min-width: 1051px) and (max-width: 1550px) {
|
|
||||||
grid-template-columns: repeat(auto-fill, minmax(calc(100% / 6), 1fr));
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (min-width: 901px) and (max-width: 1050px) {
|
|
||||||
grid-template-columns: repeat(auto-fill, minmax(calc(100% / 5), 1fr));
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (min-width: 751px) and (max-width: 900px) {
|
|
||||||
grid-template-columns: repeat(auto-fill, minmax(calc(100% / 4), 1fr));
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 750px) {
|
|
||||||
grid-template-columns: repeat(auto-fill, minmax(calc(100% / 3), 1fr));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.media-card {
|
.media-card {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
margin-bottom: var(--spacing-vertical-large);
|
margin-bottom: var(--spacing-vertical-large);
|
||||||
|
@ -473,11 +437,7 @@
|
||||||
background-image: linear-gradient(to right, $lbry-white 80%, transparent 100%);
|
background-image: linear-gradient(to right, $lbry-white 80%, transparent 100%);
|
||||||
|
|
||||||
html[data-mode='dark'] & {
|
html[data-mode='dark'] & {
|
||||||
background-image: linear-gradient(
|
background-image: linear-gradient(to right, mix($lbry-white, $lbry-gray-3, 50%) 80%, transparent 100%);
|
||||||
to right,
|
|
||||||
mix($lbry-white, $lbry-gray-3, 50%) 80%,
|
|
||||||
transparent 100%
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -35,11 +35,7 @@
|
||||||
right: 0;
|
right: 0;
|
||||||
|
|
||||||
// TODO: Make this gradient "to bottom" on mobile view
|
// TODO: Make this gradient "to bottom" on mobile view
|
||||||
background-image: linear-gradient(
|
background-image: linear-gradient(to right, transparent, rgba(mix($lbry-blue-3, $lbry-gray-4, 15%), 0.2) 100%);
|
||||||
to right,
|
|
||||||
transparent,
|
|
||||||
rgba(mix($lbry-blue-3, $lbry-gray-4, 15%), 0.2) 100%
|
|
||||||
);
|
|
||||||
content: '';
|
content: '';
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
|
||||||
|
@ -93,7 +89,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
&::before {
|
&::before {
|
||||||
width: 0.5rem;
|
width: var(--tab-indicator-size);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,25 +1,13 @@
|
||||||
.search__header {
|
.search__header {
|
||||||
background-color: $lbry-black;
|
|
||||||
color: $lbry-white;
|
|
||||||
padding: var(--spacing-vertical-large);
|
padding: var(--spacing-vertical-large);
|
||||||
|
|
||||||
.placeholder {
|
.placeholder {
|
||||||
background-color: rgba($lbry-white, 0.1);
|
background-color: rgba($lbry-white, 0.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
.media__subtext {
|
|
||||||
color: rgba($lbry-white, 0.6);
|
|
||||||
}
|
|
||||||
|
|
||||||
.media__subtitle {
|
.media__subtitle {
|
||||||
color: rgba($lbry-white, 0.9);
|
|
||||||
font-size: 0.7em;
|
font-size: 0.7em;
|
||||||
}
|
}
|
||||||
|
|
||||||
html[data-mode='dark'] & {
|
|
||||||
background-color: transparent;
|
|
||||||
border-bottom: 1px solid rgba($lbry-white, 0.1);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.search__title {
|
.search__title {
|
||||||
|
@ -37,7 +25,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.search__results-wrapper {
|
.search__results-wrapper {
|
||||||
padding: var(--spacing-vertical-large);
|
margin: var(--spacing-vertical-large);
|
||||||
}
|
}
|
||||||
|
|
||||||
.search__results-section {
|
.search__results-section {
|
||||||
|
@ -53,7 +41,7 @@
|
||||||
|
|
||||||
.search__options-wrapper {
|
.search__options-wrapper {
|
||||||
font-size: 1.25em;
|
font-size: 1.25em;
|
||||||
padding-bottom: var(--spacing-vertical-large);
|
margin-bottom: var(--spacing-vertical-large);
|
||||||
}
|
}
|
||||||
|
|
||||||
.search__options {
|
.search__options {
|
||||||
|
|
|
@ -1,8 +1,4 @@
|
||||||
// The gerbil is tied to subscriptions currently, but this style should move to it's own file once
|
|
||||||
// the gerbil is added in more places with different layouts
|
|
||||||
.subscriptions__suggested {
|
.subscriptions__suggested {
|
||||||
animation: expand 0.2s;
|
animation: expand 0.2s;
|
||||||
left: -2rem;
|
|
||||||
position: relative;
|
position: relative;
|
||||||
width: calc(100% + 4rem);
|
|
||||||
}
|
}
|
||||||
|
|
62
src/ui/scss/component/tabs.scss
Normal file
|
@ -0,0 +1,62 @@
|
||||||
What is special about channel tabs? What is special about channel tabs?
Channel tabs are slid to the right because they have to deal with the channel thumbnail photo overlap from above. Channel tabs are slid to the right because they have to deal with the channel thumbnail photo overlap from above.
👍 :+1:
What is special about channel tabs? What is special about channel tabs?
Channel tabs are slid to the right because they have to deal with the channel thumbnail photo overlap from above. Channel tabs are slid to the right because they have to deal with the channel thumbnail photo overlap from above.
👍 :+1:
|
|||||||
|
.tabs {
|
||||||
What is special about channel tabs? What is special about channel tabs?
Channel tabs are slid to the right because they have to deal with the channel thumbnail photo overlap from above. Channel tabs are slid to the right because they have to deal with the channel thumbnail photo overlap from above.
👍 :+1:
|
|||||||
|
position: relative;
|
||||||
What is special about channel tabs? What is special about channel tabs?
Channel tabs are slid to the right because they have to deal with the channel thumbnail photo overlap from above. Channel tabs are slid to the right because they have to deal with the channel thumbnail photo overlap from above.
👍 :+1:
|
|||||||
|
}
|
||||||
What is special about channel tabs? What is special about channel tabs?
Channel tabs are slid to the right because they have to deal with the channel thumbnail photo overlap from above. Channel tabs are slid to the right because they have to deal with the channel thumbnail photo overlap from above.
👍 :+1:
|
|||||||
|
|
||||||
What is special about channel tabs? What is special about channel tabs?
Channel tabs are slid to the right because they have to deal with the channel thumbnail photo overlap from above. Channel tabs are slid to the right because they have to deal with the channel thumbnail photo overlap from above.
👍 :+1:
|
|||||||
|
.tabs__list {
|
||||||
What is special about channel tabs? What is special about channel tabs?
Channel tabs are slid to the right because they have to deal with the channel thumbnail photo overlap from above. Channel tabs are slid to the right because they have to deal with the channel thumbnail photo overlap from above.
👍 :+1:
|
|||||||
|
display: flex;
|
||||||
What is special about channel tabs? What is special about channel tabs?
Channel tabs are slid to the right because they have to deal with the channel thumbnail photo overlap from above. Channel tabs are slid to the right because they have to deal with the channel thumbnail photo overlap from above.
👍 :+1:
|
|||||||
|
align-items: center;
|
||||||
What is special about channel tabs? What is special about channel tabs?
Channel tabs are slid to the right because they have to deal with the channel thumbnail photo overlap from above. Channel tabs are slid to the right because they have to deal with the channel thumbnail photo overlap from above.
👍 :+1:
|
|||||||
|
background-color: $lbry-black;
|
||||||
What is special about channel tabs? What is special about channel tabs?
Channel tabs are slid to the right because they have to deal with the channel thumbnail photo overlap from above. Channel tabs are slid to the right because they have to deal with the channel thumbnail photo overlap from above.
👍 :+1:
|
|||||||
|
color: $lbry-white;
|
||||||
What is special about channel tabs? What is special about channel tabs?
Channel tabs are slid to the right because they have to deal with the channel thumbnail photo overlap from above. Channel tabs are slid to the right because they have to deal with the channel thumbnail photo overlap from above.
👍 :+1:
|
|||||||
|
padding: var(--spacing-vertical-medium) var(--spacing-main-padding);
|
||||||
What is special about channel tabs? What is special about channel tabs?
Channel tabs are slid to the right because they have to deal with the channel thumbnail photo overlap from above. Channel tabs are slid to the right because they have to deal with the channel thumbnail photo overlap from above.
👍 :+1:
|
|||||||
|
|
||||||
What is special about channel tabs? What is special about channel tabs?
Channel tabs are slid to the right because they have to deal with the channel thumbnail photo overlap from above. Channel tabs are slid to the right because they have to deal with the channel thumbnail photo overlap from above.
👍 :+1:
|
|||||||
|
& > *:not(.tab) {
|
||||||
What is special about channel tabs? What is special about channel tabs?
Channel tabs are slid to the right because they have to deal with the channel thumbnail photo overlap from above. Channel tabs are slid to the right because they have to deal with the channel thumbnail photo overlap from above.
👍 :+1:
|
|||||||
|
// If there is anything after the tabs, render it on the opposite side of the page
|
||||||
What is special about channel tabs? What is special about channel tabs?
Channel tabs are slid to the right because they have to deal with the channel thumbnail photo overlap from above. Channel tabs are slid to the right because they have to deal with the channel thumbnail photo overlap from above.
👍 :+1:
|
|||||||
|
margin-left: auto;
|
||||||
What is special about channel tabs? What is special about channel tabs?
Channel tabs are slid to the right because they have to deal with the channel thumbnail photo overlap from above. Channel tabs are slid to the right because they have to deal with the channel thumbnail photo overlap from above.
👍 :+1:
|
|||||||
|
}
|
||||||
What is special about channel tabs? What is special about channel tabs?
Channel tabs are slid to the right because they have to deal with the channel thumbnail photo overlap from above. Channel tabs are slid to the right because they have to deal with the channel thumbnail photo overlap from above.
👍 :+1:
|
|||||||
|
|
||||||
What is special about channel tabs? What is special about channel tabs?
Channel tabs are slid to the right because they have to deal with the channel thumbnail photo overlap from above. Channel tabs are slid to the right because they have to deal with the channel thumbnail photo overlap from above.
👍 :+1:
|
|||||||
|
[data-mode='dark'] & {
|
||||||
What is special about channel tabs? What is special about channel tabs?
Channel tabs are slid to the right because they have to deal with the channel thumbnail photo overlap from above. Channel tabs are slid to the right because they have to deal with the channel thumbnail photo overlap from above.
👍 :+1:
|
|||||||
|
background-color: darken($lbry-black, 5%);
|
||||||
What is special about channel tabs? What is special about channel tabs?
Channel tabs are slid to the right because they have to deal with the channel thumbnail photo overlap from above. Channel tabs are slid to the right because they have to deal with the channel thumbnail photo overlap from above.
👍 :+1:
|
|||||||
|
}
|
||||||
What is special about channel tabs? What is special about channel tabs?
Channel tabs are slid to the right because they have to deal with the channel thumbnail photo overlap from above. Channel tabs are slid to the right because they have to deal with the channel thumbnail photo overlap from above.
👍 :+1:
|
|||||||
|
}
|
||||||
What is special about channel tabs? What is special about channel tabs?
Channel tabs are slid to the right because they have to deal with the channel thumbnail photo overlap from above. Channel tabs are slid to the right because they have to deal with the channel thumbnail photo overlap from above.
👍 :+1:
|
|||||||
|
|
||||||
What is special about channel tabs? What is special about channel tabs?
Channel tabs are slid to the right because they have to deal with the channel thumbnail photo overlap from above. Channel tabs are slid to the right because they have to deal with the channel thumbnail photo overlap from above.
👍 :+1:
|
|||||||
|
.tabs__list--channel-page {
|
||||||
What is special about channel tabs? What is special about channel tabs?
Channel tabs are slid to the right because they have to deal with the channel thumbnail photo overlap from above. Channel tabs are slid to the right because they have to deal with the channel thumbnail photo overlap from above.
👍 :+1:
|
|||||||
|
padding-left: calc(var(--channel-thumbnail-size) + var(--spacing-main-padding) + var(--spacing-vertical-large));
|
||||||
What is special about channel tabs? What is special about channel tabs?
Channel tabs are slid to the right because they have to deal with the channel thumbnail photo overlap from above. Channel tabs are slid to the right because they have to deal with the channel thumbnail photo overlap from above.
👍 :+1:
|
|||||||
|
}
|
||||||
What is special about channel tabs? What is special about channel tabs?
Channel tabs are slid to the right because they have to deal with the channel thumbnail photo overlap from above. Channel tabs are slid to the right because they have to deal with the channel thumbnail photo overlap from above.
👍 :+1:
|
|||||||
|
|
||||||
What is special about channel tabs? What is special about channel tabs?
Channel tabs are slid to the right because they have to deal with the channel thumbnail photo overlap from above. Channel tabs are slid to the right because they have to deal with the channel thumbnail photo overlap from above.
👍 :+1:
|
|||||||
|
.tab {
|
||||||
What is special about channel tabs? What is special about channel tabs?
Channel tabs are slid to the right because they have to deal with the channel thumbnail photo overlap from above. Channel tabs are slid to the right because they have to deal with the channel thumbnail photo overlap from above.
👍 :+1:
|
|||||||
|
margin-right: var(--spacing-vertical-large);
|
||||||
What is special about channel tabs? What is special about channel tabs?
Channel tabs are slid to the right because they have to deal with the channel thumbnail photo overlap from above. Channel tabs are slid to the right because they have to deal with the channel thumbnail photo overlap from above.
👍 :+1:
|
|||||||
|
padding: 5px 0;
|
||||||
What is special about channel tabs? What is special about channel tabs?
Channel tabs are slid to the right because they have to deal with the channel thumbnail photo overlap from above. Channel tabs are slid to the right because they have to deal with the channel thumbnail photo overlap from above.
👍 :+1:
|
|||||||
|
font-weight: 700;
|
||||||
What is special about channel tabs? What is special about channel tabs?
Channel tabs are slid to the right because they have to deal with the channel thumbnail photo overlap from above. Channel tabs are slid to the right because they have to deal with the channel thumbnail photo overlap from above.
👍 :+1:
|
|||||||
|
font-size: var(--tab-header-size);
|
||||||
What is special about channel tabs? What is special about channel tabs?
Channel tabs are slid to the right because they have to deal with the channel thumbnail photo overlap from above. Channel tabs are slid to the right because they have to deal with the channel thumbnail photo overlap from above.
👍 :+1:
|
|||||||
|
color: $lbry-white;
|
||||||
What is special about channel tabs? What is special about channel tabs?
Channel tabs are slid to the right because they have to deal with the channel thumbnail photo overlap from above. Channel tabs are slid to the right because they have to deal with the channel thumbnail photo overlap from above.
👍 :+1:
|
|||||||
|
position: relative;
|
||||||
What is special about channel tabs? What is special about channel tabs?
Channel tabs are slid to the right because they have to deal with the channel thumbnail photo overlap from above. Channel tabs are slid to the right because they have to deal with the channel thumbnail photo overlap from above.
👍 :+1:
|
|||||||
|
|
||||||
What is special about channel tabs? What is special about channel tabs?
Channel tabs are slid to the right because they have to deal with the channel thumbnail photo overlap from above. Channel tabs are slid to the right because they have to deal with the channel thumbnail photo overlap from above.
👍 :+1:
|
|||||||
|
&::after {
|
||||||
What is special about channel tabs? What is special about channel tabs?
Channel tabs are slid to the right because they have to deal with the channel thumbnail photo overlap from above. Channel tabs are slid to the right because they have to deal with the channel thumbnail photo overlap from above.
👍 :+1:
|
|||||||
|
position: absolute;
|
||||||
What is special about channel tabs? What is special about channel tabs?
Channel tabs are slid to the right because they have to deal with the channel thumbnail photo overlap from above. Channel tabs are slid to the right because they have to deal with the channel thumbnail photo overlap from above.
👍 :+1:
|
|||||||
|
bottom: calc(var(--tab-indicator-size) * -2);
|
||||||
What is special about channel tabs? What is special about channel tabs?
Channel tabs are slid to the right because they have to deal with the channel thumbnail photo overlap from above. Channel tabs are slid to the right because they have to deal with the channel thumbnail photo overlap from above.
👍 :+1:
|
|||||||
|
height: 0;
|
||||||
What is special about channel tabs? What is special about channel tabs?
Channel tabs are slid to the right because they have to deal with the channel thumbnail photo overlap from above. Channel tabs are slid to the right because they have to deal with the channel thumbnail photo overlap from above.
👍 :+1:
|
|||||||
|
width: 100%;
|
||||||
What is special about channel tabs? What is special about channel tabs?
Channel tabs are slid to the right because they have to deal with the channel thumbnail photo overlap from above. Channel tabs are slid to the right because they have to deal with the channel thumbnail photo overlap from above.
👍 :+1:
|
|||||||
|
content: '';
|
||||||
What is special about channel tabs? What is special about channel tabs?
Channel tabs are slid to the right because they have to deal with the channel thumbnail photo overlap from above. Channel tabs are slid to the right because they have to deal with the channel thumbnail photo overlap from above.
👍 :+1:
|
|||||||
|
}
|
||||||
What is special about channel tabs? What is special about channel tabs?
Channel tabs are slid to the right because they have to deal with the channel thumbnail photo overlap from above. Channel tabs are slid to the right because they have to deal with the channel thumbnail photo overlap from above.
👍 :+1:
|
|||||||
|
}
|
||||||
What is special about channel tabs? What is special about channel tabs?
Channel tabs are slid to the right because they have to deal with the channel thumbnail photo overlap from above. Channel tabs are slid to the right because they have to deal with the channel thumbnail photo overlap from above.
👍 :+1:
|
|||||||
|
|
||||||
What is special about channel tabs? What is special about channel tabs?
Channel tabs are slid to the right because they have to deal with the channel thumbnail photo overlap from above. Channel tabs are slid to the right because they have to deal with the channel thumbnail photo overlap from above.
👍 :+1:
|
|||||||
|
.tab__divider {
|
||||||
What is special about channel tabs? What is special about channel tabs?
Channel tabs are slid to the right because they have to deal with the channel thumbnail photo overlap from above. Channel tabs are slid to the right because they have to deal with the channel thumbnail photo overlap from above.
👍 :+1:
|
|||||||
|
position: absolute;
|
||||||
What is special about channel tabs? What is special about channel tabs?
Channel tabs are slid to the right because they have to deal with the channel thumbnail photo overlap from above. Channel tabs are slid to the right because they have to deal with the channel thumbnail photo overlap from above.
👍 :+1:
|
|||||||
|
margin-top: calc(var(--tab-indicator-size) * -1);
|
||||||
What is special about channel tabs? What is special about channel tabs?
Channel tabs are slid to the right because they have to deal with the channel thumbnail photo overlap from above. Channel tabs are slid to the right because they have to deal with the channel thumbnail photo overlap from above.
👍 :+1:
|
|||||||
|
}
|
||||||
What is special about channel tabs? What is special about channel tabs?
Channel tabs are slid to the right because they have to deal with the channel thumbnail photo overlap from above. Channel tabs are slid to the right because they have to deal with the channel thumbnail photo overlap from above.
👍 :+1:
|
|||||||
|
|
||||||
What is special about channel tabs? What is special about channel tabs?
Channel tabs are slid to the right because they have to deal with the channel thumbnail photo overlap from above. Channel tabs are slid to the right because they have to deal with the channel thumbnail photo overlap from above.
👍 :+1:
|
|||||||
|
.tab::after,
|
||||||
What is special about channel tabs? What is special about channel tabs?
Channel tabs are slid to the right because they have to deal with the channel thumbnail photo overlap from above. Channel tabs are slid to the right because they have to deal with the channel thumbnail photo overlap from above.
👍 :+1:
|
|||||||
|
.tab__divider {
|
||||||
What is special about channel tabs? What is special about channel tabs?
Channel tabs are slid to the right because they have to deal with the channel thumbnail photo overlap from above. Channel tabs are slid to the right because they have to deal with the channel thumbnail photo overlap from above.
👍 :+1:
|
|||||||
|
display: block;
|
||||||
What is special about channel tabs? What is special about channel tabs?
Channel tabs are slid to the right because they have to deal with the channel thumbnail photo overlap from above. Channel tabs are slid to the right because they have to deal with the channel thumbnail photo overlap from above.
👍 :+1:
|
|||||||
|
transition: all var(--animation-duration) var(--animation-style);
|
||||||
What is special about channel tabs? What is special about channel tabs?
Channel tabs are slid to the right because they have to deal with the channel thumbnail photo overlap from above. Channel tabs are slid to the right because they have to deal with the channel thumbnail photo overlap from above.
👍 :+1:
|
|||||||
|
}
|
||||||
What is special about channel tabs? What is special about channel tabs?
Channel tabs are slid to the right because they have to deal with the channel thumbnail photo overlap from above. Channel tabs are slid to the right because they have to deal with the channel thumbnail photo overlap from above.
👍 :+1:
|
|||||||
|
|
||||||
What is special about channel tabs? What is special about channel tabs?
Channel tabs are slid to the right because they have to deal with the channel thumbnail photo overlap from above. Channel tabs are slid to the right because they have to deal with the channel thumbnail photo overlap from above.
👍 :+1:
|
|||||||
|
.tab:hover::after,
|
||||||
What is special about channel tabs? What is special about channel tabs?
Channel tabs are slid to the right because they have to deal with the channel thumbnail photo overlap from above. Channel tabs are slid to the right because they have to deal with the channel thumbnail photo overlap from above.
👍 :+1:
|
|||||||
|
.tab__divider {
|
||||||
What is special about channel tabs? What is special about channel tabs?
Channel tabs are slid to the right because they have to deal with the channel thumbnail photo overlap from above. Channel tabs are slid to the right because they have to deal with the channel thumbnail photo overlap from above.
👍 :+1:
|
|||||||
|
height: var(--tab-indicator-size);
|
||||||
What is special about channel tabs? What is special about channel tabs?
Channel tabs are slid to the right because they have to deal with the channel thumbnail photo overlap from above. Channel tabs are slid to the right because they have to deal with the channel thumbnail photo overlap from above.
👍 :+1:
|
|||||||
|
background-color: $lbry-teal-3;
|
||||||
What is special about channel tabs? What is special about channel tabs?
Channel tabs are slid to the right because they have to deal with the channel thumbnail photo overlap from above. Channel tabs are slid to the right because they have to deal with the channel thumbnail photo overlap from above.
👍 :+1:
|
|||||||
|
}
|
||||||
What is special about channel tabs? What is special about channel tabs?
Channel tabs are slid to the right because they have to deal with the channel thumbnail photo overlap from above. Channel tabs are slid to the right because they have to deal with the channel thumbnail photo overlap from above.
👍 :+1:
|
|||||||
|
|
||||||
What is special about channel tabs? What is special about channel tabs?
Channel tabs are slid to the right because they have to deal with the channel thumbnail photo overlap from above. Channel tabs are slid to the right because they have to deal with the channel thumbnail photo overlap from above.
👍 :+1:
|
|||||||
|
.tab__panel {
|
||||||
What is special about channel tabs? What is special about channel tabs?
Channel tabs are slid to the right because they have to deal with the channel thumbnail photo overlap from above. Channel tabs are slid to the right because they have to deal with the channel thumbnail photo overlap from above.
👍 :+1:
|
|||||||
|
margin-top: var(--spacing-vertical-large);
|
||||||
What is special about channel tabs? What is special about channel tabs?
Channel tabs are slid to the right because they have to deal with the channel thumbnail photo overlap from above. Channel tabs are slid to the right because they have to deal with the channel thumbnail photo overlap from above.
👍 :+1:
|
|||||||
|
}
|
||||||
What is special about channel tabs? What is special about channel tabs?
Channel tabs are slid to the right because they have to deal with the channel thumbnail photo overlap from above. Channel tabs are slid to the right because they have to deal with the channel thumbnail photo overlap from above.
👍 :+1:
|
|
@ -134,6 +134,7 @@ code {
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
margin-top: var(--spacing-vertical-large);
|
margin-top: var(--spacing-vertical-large);
|
||||||
margin-bottom: var(--spacing-vertical-large);
|
margin-bottom: var(--spacing-vertical-large);
|
||||||
|
border-radius: 5px;
|
||||||
|
|
||||||
html[data-mode='dark'] & {
|
html[data-mode='dark'] & {
|
||||||
color: inherit;
|
color: inherit;
|
||||||
|
|
|
@ -79,16 +79,6 @@ dl {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
dt {
|
|
||||||
float: left;
|
|
||||||
width: 20%;
|
|
||||||
}
|
|
||||||
|
|
||||||
dd {
|
|
||||||
float: left;
|
|
||||||
width: 80%;
|
|
||||||
}
|
|
||||||
|
|
||||||
textarea {
|
textarea {
|
||||||
border: 1px solid $lbry-gray-2;
|
border: 1px solid $lbry-gray-2;
|
||||||
padding: $spacing-vertical * 1/3;
|
padding: $spacing-vertical * 1/3;
|
||||||
|
|
|
@ -16,6 +16,7 @@ $large-breakpoint: 1921px;
|
||||||
--spacing-vertical-medium: calc(2rem / 2);
|
--spacing-vertical-medium: calc(2rem / 2);
|
||||||
--spacing-vertical-large: 2rem;
|
--spacing-vertical-large: 2rem;
|
||||||
--spacing-vertical-xlarge: 3rem;
|
--spacing-vertical-xlarge: 3rem;
|
||||||
|
--spacing-main-padding: var(--spacing-vertical-xlarge);
|
||||||
|
|
||||||
--file-page-max-width: 1787px;
|
--file-page-max-width: 1787px;
|
||||||
--file-max-height: 788px;
|
--file-max-height: 788px;
|
||||||
|
@ -23,10 +24,16 @@ $large-breakpoint: 1921px;
|
||||||
|
|
||||||
--video-aspect-ratio: 56.25%; // 9 x 16
|
--video-aspect-ratio: 56.25%; // 9 x 16
|
||||||
|
|
||||||
|
--channel-thumbnail-size: 10rem;
|
||||||
|
|
||||||
// Text
|
// Text
|
||||||
--text-max-width: 660px;
|
--text-max-width: 660px;
|
||||||
--text-link-padding: 4px;
|
--text-link-padding: 4px;
|
||||||
|
|
||||||
|
// Tabs
|
||||||
|
--tab-indicator-size: 0.5rem;
|
||||||
|
--tab-header-size: 1.5rem; // Needs to be static so the animated styling always works
|
||||||
|
|
||||||
// Input
|
// Input
|
||||||
--input-border-size: 1px;
|
--input-border-size: 1px;
|
||||||
|
|
||||||
|
@ -44,8 +51,9 @@ $large-breakpoint: 1921px;
|
||||||
--search-modal-input-height: 70px;
|
--search-modal-input-height: 70px;
|
||||||
|
|
||||||
// Card
|
// Card
|
||||||
--card-radius: 2px;
|
--card-radius: 5px;
|
||||||
--card-max-width: 1000px;
|
--card-max-width: 1000px;
|
||||||
|
--card-box-shadow: 0px 8px 20px;
|
||||||
|
|
||||||
// File
|
// File
|
||||||
--file-tile-media-height: 125px;
|
--file-tile-media-height: 125px;
|
||||||
|
@ -62,10 +70,11 @@ $large-breakpoint: 1921px;
|
||||||
--modal-width: 440px;
|
--modal-width: 440px;
|
||||||
|
|
||||||
// Animation :)
|
// Animation :)
|
||||||
--animation-duration: 0.3s;
|
--animation-duration: 0.2s;
|
||||||
--animation-style: cubic-bezier(0.55, 0, 0.1, 1);
|
--animation-style: ease-in-out;
|
||||||
|
|
||||||
// Image
|
// Image
|
||||||
--thumbnail-preview-height: 100px;
|
--thumbnail-preview-height: 100px;
|
||||||
--thumbnail-preview-width: 177px;
|
--thumbnail-preview-width: 177px;
|
||||||
|
--cover-photo-height: 250px;
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,6 +18,9 @@ const webConfig = {
|
||||||
path: __dirname + '/dist/web',
|
path: __dirname + '/dist/web',
|
||||||
publicPath: '/',
|
publicPath: '/',
|
||||||
},
|
},
|
||||||
|
devServer: {
|
||||||
|
historyApiFallback: true,
|
||||||
|
},
|
||||||
module: {
|
module: {
|
||||||
rules: [
|
rules: [
|
||||||
{
|
{
|
||||||
|
|
53
yarn.lock
|
@ -885,6 +885,43 @@
|
||||||
resolved "https://registry.yarnpkg.com/@posthtml/esm/-/esm-1.0.0.tgz#09bcb28a02438dcee22ad1970ca1d85a000ae0cf"
|
resolved "https://registry.yarnpkg.com/@posthtml/esm/-/esm-1.0.0.tgz#09bcb28a02438dcee22ad1970ca1d85a000ae0cf"
|
||||||
integrity sha512-dEVG+ITnvqKGa4v040tP+n8LOKOqr94qjLva7bE5pnfm2KHJwsKz69J4KMxgWLznbpBJzy8vQfCayEk3vLZnZQ==
|
integrity sha512-dEVG+ITnvqKGa4v040tP+n8LOKOqr94qjLva7bE5pnfm2KHJwsKz69J4KMxgWLznbpBJzy8vQfCayEk3vLZnZQ==
|
||||||
|
|
||||||
|
"@reach/auto-id@^0.2.0":
|
||||||
|
version "0.2.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@reach/auto-id/-/auto-id-0.2.0.tgz#97f9e48fe736aa5c6f4f32cf73c1f19d005f8550"
|
||||||
|
integrity sha512-lVK/svL2HuQdp7jgvlrLkFsUx50Az9chAhxpiPwBqcS83I2pVWvXp98FOcSCCJCV++l115QmzHhFd+ycw1zLBg==
|
||||||
|
|
||||||
|
"@reach/component-component@^0.1.3":
|
||||||
|
version "0.1.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/@reach/component-component/-/component-component-0.1.3.tgz#5d156319572dc38995b246f81878bc2577c517e5"
|
||||||
|
integrity sha512-a1USH7L3bEfDdPN4iNZGvMEFuBfkdG+QNybeyDv8RloVFgZYRoM+KGXyy2KOfEnTUM8QWDRSROwaL3+ts5Angg==
|
||||||
|
|
||||||
|
"@reach/observe-rect@^1.0.3":
|
||||||
|
version "1.0.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/@reach/observe-rect/-/observe-rect-1.0.3.tgz#2ea3dcc369ab22bd9f050a92ea319321356a61e8"
|
||||||
|
integrity sha1-LqPcw2mrIr2fBQqS6jGTITVqYeg=
|
||||||
|
|
||||||
|
"@reach/rect@^0.2.1":
|
||||||
|
version "0.2.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/@reach/rect/-/rect-0.2.1.tgz#7343020174c90e2290b844d17c03fd9c78e6b601"
|
||||||
|
integrity sha512-aZ9RsNHDMQ3zETonikqu9/85iXxj+LPqZ9Gr9UAncj3AufYmGeWG3XG6b37B+7ORH+mkhVpLU2ZlIWxmOe9Cqg==
|
||||||
|
dependencies:
|
||||||
|
"@reach/component-component" "^0.1.3"
|
||||||
|
"@reach/observe-rect" "^1.0.3"
|
||||||
|
|
||||||
|
"@reach/tabs@^0.1.5":
|
||||||
|
version "0.1.5"
|
||||||
|
resolved "https://registry.yarnpkg.com/@reach/tabs/-/tabs-0.1.5.tgz#0a3a8c863cc50ac661b3a66afea0f9315c8d8b2b"
|
||||||
|
integrity sha512-thQKlbN7kN/YoFfBjTVxAlRlYor0dFg7QnZwUN9v1OYFLHMoPpmwaQkae8mAEibRb4BPGgjnoSpdfco2lzP37A==
|
||||||
|
dependencies:
|
||||||
|
"@reach/auto-id" "^0.2.0"
|
||||||
|
"@reach/utils" "^0.2.2"
|
||||||
|
warning "^4.0.2"
|
||||||
|
|
||||||
|
"@reach/utils@^0.2.2":
|
||||||
|
version "0.2.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/@reach/utils/-/utils-0.2.2.tgz#c3a05ae9fd1f921988ae8a89b5a0d28d1a2b92df"
|
||||||
|
integrity sha512-jYeIi46AA5jh2gfdXD/nInUYfeLp3girRafiajP7AVHF6B4hpYAzUSx/ZH4xmPyf5alut5rml2DHxrv+X+Xu+A==
|
||||||
|
|
||||||
"@samverschueren/stream-to-observable@^0.3.0":
|
"@samverschueren/stream-to-observable@^0.3.0":
|
||||||
version "0.3.0"
|
version "0.3.0"
|
||||||
resolved "https://registry.yarnpkg.com/@samverschueren/stream-to-observable/-/stream-to-observable-0.3.0.tgz#ecdf48d532c58ea477acfcab80348424f8d0662f"
|
resolved "https://registry.yarnpkg.com/@samverschueren/stream-to-observable/-/stream-to-observable-0.3.0.tgz#ecdf48d532c58ea477acfcab80348424f8d0662f"
|
||||||
|
@ -4092,6 +4129,11 @@ eslint-plugin-promise@^4.0.1:
|
||||||
resolved "https://registry.yarnpkg.com/eslint-plugin-promise/-/eslint-plugin-promise-4.1.1.tgz#1e08cb68b5b2cd8839f8d5864c796f56d82746db"
|
resolved "https://registry.yarnpkg.com/eslint-plugin-promise/-/eslint-plugin-promise-4.1.1.tgz#1e08cb68b5b2cd8839f8d5864c796f56d82746db"
|
||||||
integrity sha512-faAHw7uzlNPy7b45J1guyjazw28M+7gJokKUjC5JSFoYfUEyy6Gw/i7YQvmv2Yk00sUjWcmzXQLpU1Ki/C2IZQ==
|
integrity sha512-faAHw7uzlNPy7b45J1guyjazw28M+7gJokKUjC5JSFoYfUEyy6Gw/i7YQvmv2Yk00sUjWcmzXQLpU1Ki/C2IZQ==
|
||||||
|
|
||||||
|
eslint-plugin-react-hooks@^1.6.0:
|
||||||
|
version "1.6.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-1.6.0.tgz#348efcda8fb426399ac7b8609607c7b4025a6f5f"
|
||||||
|
integrity sha512-lHBVRIaz5ibnIgNG07JNiAuBUeKhEf8l4etNx5vfAEwqQ5tcuK3jV9yjmopPgQDagQb7HwIuQVsE3IVcGrRnag==
|
||||||
|
|
||||||
eslint-plugin-react@^7.7.0:
|
eslint-plugin-react@^7.7.0:
|
||||||
version "7.12.4"
|
version "7.12.4"
|
||||||
resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.12.4.tgz#b1ecf26479d61aee650da612e425c53a99f48c8c"
|
resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.12.4.tgz#b1ecf26479d61aee650da612e425c53a99f48c8c"
|
||||||
|
@ -6456,9 +6498,9 @@ lazy-val@^1.0.3, lazy-val@^1.0.4:
|
||||||
yargs "^13.2.2"
|
yargs "^13.2.2"
|
||||||
zstd-codec "^0.1.1"
|
zstd-codec "^0.1.1"
|
||||||
|
|
||||||
lbry-redux@lbryio/lbry-redux#cc42856676541120b088e4228c04246ba8ff3274:
|
lbry-redux@lbryio/lbry-redux#459bea2257d61003e591daf169fefe9624522680:
|
||||||
version "0.0.1"
|
version "0.0.1"
|
||||||
resolved "https://codeload.github.com/lbryio/lbry-redux/tar.gz/cc42856676541120b088e4228c04246ba8ff3274"
|
resolved "https://codeload.github.com/lbryio/lbry-redux/tar.gz/459bea2257d61003e591daf169fefe9624522680"
|
||||||
dependencies:
|
dependencies:
|
||||||
proxy-polyfill "0.1.6"
|
proxy-polyfill "0.1.6"
|
||||||
reselect "^3.0.0"
|
reselect "^3.0.0"
|
||||||
|
@ -11671,6 +11713,13 @@ warning@^3.0.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
loose-envify "^1.0.0"
|
loose-envify "^1.0.0"
|
||||||
|
|
||||||
|
warning@^4.0.2:
|
||||||
|
version "4.0.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/warning/-/warning-4.0.3.tgz#16e9e077eb8a86d6af7d64aa1e05fd85b4678ca3"
|
||||||
|
integrity sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==
|
||||||
|
dependencies:
|
||||||
|
loose-envify "^1.0.0"
|
||||||
|
|
||||||
watchpack@^1.5.0:
|
watchpack@^1.5.0:
|
||||||
version "1.6.0"
|
version "1.6.0"
|
||||||
resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-1.6.0.tgz#4bc12c2ebe8aa277a71f1d3f14d685c7b446cd00"
|
resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-1.6.0.tgz#4bc12c2ebe8aa277a71f1d3f14d685c7b446cd00"
|
||||||
|
|
Do any existing styles fit for this? If not, is there a more generic and/or resuable term for this style?