Merge pull request #2031 from lbryio/subscription-new
Improve subscriptions page
This commit is contained in:
commit
c35b13cdc0
42 changed files with 1735 additions and 488 deletions
|
@ -10,7 +10,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||||
* Focus on search bar with {cmd,ctrl} + "l" ([#2003](https://github.com/lbryio/lbry-desktop/pull/2003))
|
* Focus on search bar with {cmd,ctrl} + "l" ([#2003](https://github.com/lbryio/lbry-desktop/pull/2003))
|
||||||
* Add support for clickable channel names on explore page headings ([#2023](https://github.com/lbryio/lbry-desktop/pull/2023))
|
* Add support for clickable channel names on explore page headings ([#2023](https://github.com/lbryio/lbry-desktop/pull/2023))
|
||||||
* Content loading placeholder styles on FileCard/FileTile ([#2022](https://github.com/lbryio/lbry-desktop/pull/2022))
|
* Content loading placeholder styles on FileCard/FileTile ([#2022](https://github.com/lbryio/lbry-desktop/pull/2022))
|
||||||
* Adds Persistence to Transaction List Filter Selection ([#2048](https://github.com/lbryio/lbry-desktop/pull/2048))
|
* Persistence to Transaction List Filter Selection ([#2048](https://github.com/lbryio/lbry-desktop/pull/2048))
|
||||||
|
* Subscription improvements ([#2031](https://github.com/lbryio/lbry-desktop/pull/2031))
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
* Make tooltip smarter ([#1979](https://github.com/lbryio/lbry-desktop/pull/1979))
|
* Make tooltip smarter ([#1979](https://github.com/lbryio/lbry-desktop/pull/1979))
|
||||||
|
|
895
flow-typed/npm/reselect_v3.x.x.js
vendored
Normal file
895
flow-typed/npm/reselect_v3.x.x.js
vendored
Normal file
|
@ -0,0 +1,895 @@
|
||||||
|
// flow-typed signature: 84ab000391e0f17dd212d57ed0b180f5
|
||||||
|
// flow-typed version: 5d8678f464/reselect_v3.x.x/flow_>=v0.47.x
|
||||||
|
|
||||||
|
type ExtractReturnType = <Return>((...rest: any[]) => Return) => Return;
|
||||||
|
|
||||||
|
declare module "reselect" {
|
||||||
|
declare type InputSelector<-TState, TProps, TResult> =
|
||||||
|
(state: TState, props: TProps, ...rest: any[]) => TResult
|
||||||
|
|
||||||
|
declare type OutputSelector<-TState, TProps, TResult> =
|
||||||
|
& InputSelector<TState, TProps, TResult>
|
||||||
|
& {
|
||||||
|
recomputations(): number,
|
||||||
|
resetRecomputations(): void,
|
||||||
|
resultFunc(state: TState, props: TProps, ...rest: Array<any>): TResult,
|
||||||
|
};
|
||||||
|
|
||||||
|
declare type SelectorCreator = {
|
||||||
|
<TState, TProps, TResult, T1>(
|
||||||
|
selector1: InputSelector<TState, TProps, T1>,
|
||||||
|
resultFunc: (arg1: T1) => TResult
|
||||||
|
): OutputSelector<TState, TProps, TResult>,
|
||||||
|
<TState, TProps, TResult, T1>(
|
||||||
|
selectors: [InputSelector<TState, TProps, T1>],
|
||||||
|
resultFunc: (arg1: T1) => TResult
|
||||||
|
): OutputSelector<TState, TProps, TResult>,
|
||||||
|
|
||||||
|
<TState, TProps, TResult, T1, T2>(
|
||||||
|
selector1: InputSelector<TState, TProps, T1>,
|
||||||
|
selector2: InputSelector<TState, TProps, T2>,
|
||||||
|
resultFunc: (arg1: T1, arg2: T2) => TResult
|
||||||
|
): OutputSelector<TState, TProps, TResult>,
|
||||||
|
<TState, TProps, TResult, T1, T2>(
|
||||||
|
selectors: [InputSelector<TState, TProps, T1>, InputSelector<TState, TProps, T2>],
|
||||||
|
resultFunc: (arg1: T1, arg2: T2) => TResult
|
||||||
|
): OutputSelector<TState, TProps, TResult>,
|
||||||
|
|
||||||
|
<TState, TProps, TResult, T1, T2, T3>(
|
||||||
|
selector1: InputSelector<TState, TProps, T1>,
|
||||||
|
selector2: InputSelector<TState, TProps, T2>,
|
||||||
|
selector3: InputSelector<TState, TProps, T3>,
|
||||||
|
resultFunc: (arg1: T1, arg2: T2, arg3: T3) => TResult
|
||||||
|
): OutputSelector<TState, TProps, TResult>,
|
||||||
|
<TState, TProps, TResult, T1, T2, T3>(
|
||||||
|
selectors: [
|
||||||
|
InputSelector<TState, TProps, T1>,
|
||||||
|
InputSelector<TState, TProps, T2>,
|
||||||
|
InputSelector<TState, TProps, T3>
|
||||||
|
],
|
||||||
|
resultFunc: (arg1: T1, arg2: T2, arg3: T3) => TResult
|
||||||
|
): OutputSelector<TState, TProps, TResult>,
|
||||||
|
|
||||||
|
<TState, TProps, TResult, T1, T2, T3, T4>(
|
||||||
|
selector1: InputSelector<TState, TProps, T1>,
|
||||||
|
selector2: InputSelector<TState, TProps, T2>,
|
||||||
|
selector3: InputSelector<TState, TProps, T3>,
|
||||||
|
selector4: InputSelector<TState, TProps, T4>,
|
||||||
|
resultFunc: (arg1: T1, arg2: T2, arg3: T3, arg4: T4) => TResult
|
||||||
|
): OutputSelector<TState, TProps, TResult>,
|
||||||
|
<TState, TProps, TResult, T1, T2, T3, T4>(
|
||||||
|
selectors: [
|
||||||
|
InputSelector<TState, TProps, T1>,
|
||||||
|
InputSelector<TState, TProps, T2>,
|
||||||
|
InputSelector<TState, TProps, T3>,
|
||||||
|
InputSelector<TState, TProps, T4>
|
||||||
|
],
|
||||||
|
resultFunc: (arg1: T1, arg2: T2, arg3: T3, arg4: T4) => TResult
|
||||||
|
): OutputSelector<TState, TProps, TResult>,
|
||||||
|
|
||||||
|
<TState, TProps, TResult, T1, T2, T3, T4, T5>(
|
||||||
|
selector1: InputSelector<TState, TProps, T1>,
|
||||||
|
selector2: InputSelector<TState, TProps, T2>,
|
||||||
|
selector3: InputSelector<TState, TProps, T3>,
|
||||||
|
selector4: InputSelector<TState, TProps, T4>,
|
||||||
|
selector5: InputSelector<TState, TProps, T5>,
|
||||||
|
resultFunc: (arg1: T1, arg2: T2, arg3: T3, arg4: T4, arg5: T5) => TResult
|
||||||
|
): OutputSelector<TState, TProps, TResult>,
|
||||||
|
<TState, TProps, TResult, T1, T2, T3, T4, T5>(
|
||||||
|
selectors: [
|
||||||
|
InputSelector<TState, TProps, T1>,
|
||||||
|
InputSelector<TState, TProps, T2>,
|
||||||
|
InputSelector<TState, TProps, T3>,
|
||||||
|
InputSelector<TState, TProps, T4>,
|
||||||
|
InputSelector<TState, TProps, T5>
|
||||||
|
],
|
||||||
|
resultFunc: (arg1: T1, arg2: T2, arg3: T3, arg4: T4, arg5: T5) => TResult
|
||||||
|
): OutputSelector<TState, TProps, TResult>,
|
||||||
|
|
||||||
|
<TState, TProps, TResult, T1, T2, T3, T4, T5, T6>(
|
||||||
|
selector1: InputSelector<TState, TProps, T1>,
|
||||||
|
selector2: InputSelector<TState, TProps, T2>,
|
||||||
|
selector3: InputSelector<TState, TProps, T3>,
|
||||||
|
selector4: InputSelector<TState, TProps, T4>,
|
||||||
|
selector5: InputSelector<TState, TProps, T5>,
|
||||||
|
selector6: InputSelector<TState, TProps, T6>,
|
||||||
|
resultFunc: (
|
||||||
|
arg1: T1,
|
||||||
|
arg2: T2,
|
||||||
|
arg3: T3,
|
||||||
|
arg4: T4,
|
||||||
|
arg5: T5,
|
||||||
|
arg6: T6
|
||||||
|
) => TResult
|
||||||
|
): OutputSelector<TState, TProps, TResult>,
|
||||||
|
<TState, TProps, TResult, T1, T2, T3, T4, T5, T6>(
|
||||||
|
selectors: [
|
||||||
|
InputSelector<TState, TProps, T1>,
|
||||||
|
InputSelector<TState, TProps, T2>,
|
||||||
|
InputSelector<TState, TProps, T3>,
|
||||||
|
InputSelector<TState, TProps, T4>,
|
||||||
|
InputSelector<TState, TProps, T5>,
|
||||||
|
InputSelector<TState, TProps, T6>
|
||||||
|
],
|
||||||
|
resultFunc: (
|
||||||
|
arg1: T1,
|
||||||
|
arg2: T2,
|
||||||
|
arg3: T3,
|
||||||
|
arg4: T4,
|
||||||
|
arg5: T5,
|
||||||
|
arg6: T6
|
||||||
|
) => TResult
|
||||||
|
): OutputSelector<TState, TProps, TResult>,
|
||||||
|
|
||||||
|
<TState, TProps, TResult, T1, T2, T3, T4, T5, T6, T7>(
|
||||||
|
selector1: InputSelector<TState, TProps, T1>,
|
||||||
|
selector2: InputSelector<TState, TProps, T2>,
|
||||||
|
selector3: InputSelector<TState, TProps, T3>,
|
||||||
|
selector4: InputSelector<TState, TProps, T4>,
|
||||||
|
selector5: InputSelector<TState, TProps, T5>,
|
||||||
|
selector6: InputSelector<TState, TProps, T6>,
|
||||||
|
selector7: InputSelector<TState, TProps, T7>,
|
||||||
|
resultFunc: (
|
||||||
|
arg1: T1,
|
||||||
|
arg2: T2,
|
||||||
|
arg3: T3,
|
||||||
|
arg4: T4,
|
||||||
|
arg5: T5,
|
||||||
|
arg6: T6,
|
||||||
|
arg7: T7
|
||||||
|
) => TResult
|
||||||
|
): OutputSelector<TState, TProps, TResult>,
|
||||||
|
<TState, TProps, TResult, T1, T2, T3, T4, T5, T6, T7>(
|
||||||
|
selectors: [
|
||||||
|
InputSelector<TState, TProps, T1>,
|
||||||
|
InputSelector<TState, TProps, T2>,
|
||||||
|
InputSelector<TState, TProps, T3>,
|
||||||
|
InputSelector<TState, TProps, T4>,
|
||||||
|
InputSelector<TState, TProps, T5>,
|
||||||
|
InputSelector<TState, TProps, T6>,
|
||||||
|
InputSelector<TState, TProps, T7>
|
||||||
|
],
|
||||||
|
resultFunc: (
|
||||||
|
arg1: T1,
|
||||||
|
arg2: T2,
|
||||||
|
arg3: T3,
|
||||||
|
arg4: T4,
|
||||||
|
arg5: T5,
|
||||||
|
arg6: T6,
|
||||||
|
arg7: T7
|
||||||
|
) => TResult
|
||||||
|
): OutputSelector<TState, TProps, TResult>,
|
||||||
|
|
||||||
|
<TState, TProps, TResult, T1, T2, T3, T4, T5, T6, T7, T8>(
|
||||||
|
selector1: InputSelector<TState, TProps, T1>,
|
||||||
|
selector2: InputSelector<TState, TProps, T2>,
|
||||||
|
selector3: InputSelector<TState, TProps, T3>,
|
||||||
|
selector4: InputSelector<TState, TProps, T4>,
|
||||||
|
selector5: InputSelector<TState, TProps, T5>,
|
||||||
|
selector6: InputSelector<TState, TProps, T6>,
|
||||||
|
selector7: InputSelector<TState, TProps, T7>,
|
||||||
|
selector8: InputSelector<TState, TProps, T8>,
|
||||||
|
resultFunc: (
|
||||||
|
arg1: T1,
|
||||||
|
arg2: T2,
|
||||||
|
arg3: T3,
|
||||||
|
arg4: T4,
|
||||||
|
arg5: T5,
|
||||||
|
arg6: T6,
|
||||||
|
arg7: T7,
|
||||||
|
arg8: T8
|
||||||
|
) => TResult
|
||||||
|
): OutputSelector<TState, TProps, TResult>,
|
||||||
|
<TState, TProps, TResult, T1, T2, T3, T4, T5, T6, T7, T8>(
|
||||||
|
selectors: [
|
||||||
|
InputSelector<TState, TProps, T1>,
|
||||||
|
InputSelector<TState, TProps, T2>,
|
||||||
|
InputSelector<TState, TProps, T3>,
|
||||||
|
InputSelector<TState, TProps, T4>,
|
||||||
|
InputSelector<TState, TProps, T5>,
|
||||||
|
InputSelector<TState, TProps, T6>,
|
||||||
|
InputSelector<TState, TProps, T7>,
|
||||||
|
InputSelector<TState, TProps, T8>
|
||||||
|
],
|
||||||
|
resultFunc: (
|
||||||
|
arg1: T1,
|
||||||
|
arg2: T2,
|
||||||
|
arg3: T3,
|
||||||
|
arg4: T4,
|
||||||
|
arg5: T5,
|
||||||
|
arg6: T6,
|
||||||
|
arg7: T7,
|
||||||
|
arg8: T8
|
||||||
|
) => TResult
|
||||||
|
): OutputSelector<TState, TProps, TResult>,
|
||||||
|
|
||||||
|
<TState, TProps, TResult, T1, T2, T3, T4, T5, T6, T7, T8, T9>(
|
||||||
|
selector1: InputSelector<TState, TProps, T1>,
|
||||||
|
selector2: InputSelector<TState, TProps, T2>,
|
||||||
|
selector3: InputSelector<TState, TProps, T3>,
|
||||||
|
selector4: InputSelector<TState, TProps, T4>,
|
||||||
|
selector5: InputSelector<TState, TProps, T5>,
|
||||||
|
selector6: InputSelector<TState, TProps, T6>,
|
||||||
|
selector7: InputSelector<TState, TProps, T7>,
|
||||||
|
selector8: InputSelector<TState, TProps, T8>,
|
||||||
|
selector9: InputSelector<TState, TProps, T9>,
|
||||||
|
resultFunc: (
|
||||||
|
arg1: T1,
|
||||||
|
arg2: T2,
|
||||||
|
arg3: T3,
|
||||||
|
arg4: T4,
|
||||||
|
arg5: T5,
|
||||||
|
arg6: T6,
|
||||||
|
arg7: T7,
|
||||||
|
arg8: T8,
|
||||||
|
arg9: T9
|
||||||
|
) => TResult
|
||||||
|
): OutputSelector<TState, TProps, TResult>,
|
||||||
|
<TState, TProps, TResult, T1, T2, T3, T4, T5, T6, T7, T8, T9>(
|
||||||
|
selectors: [
|
||||||
|
InputSelector<TState, TProps, T1>,
|
||||||
|
InputSelector<TState, TProps, T2>,
|
||||||
|
InputSelector<TState, TProps, T3>,
|
||||||
|
InputSelector<TState, TProps, T4>,
|
||||||
|
InputSelector<TState, TProps, T5>,
|
||||||
|
InputSelector<TState, TProps, T6>,
|
||||||
|
InputSelector<TState, TProps, T7>,
|
||||||
|
InputSelector<TState, TProps, T8>,
|
||||||
|
InputSelector<TState, TProps, T9>
|
||||||
|
],
|
||||||
|
resultFunc: (
|
||||||
|
arg1: T1,
|
||||||
|
arg2: T2,
|
||||||
|
arg3: T3,
|
||||||
|
arg4: T4,
|
||||||
|
arg5: T5,
|
||||||
|
arg6: T6,
|
||||||
|
arg7: T7,
|
||||||
|
arg8: T8,
|
||||||
|
arg9: T9
|
||||||
|
) => TResult
|
||||||
|
): OutputSelector<TState, TProps, TResult>,
|
||||||
|
|
||||||
|
<TState, TProps, TResult, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10>(
|
||||||
|
selector1: InputSelector<TState, TProps, T1>,
|
||||||
|
selector2: InputSelector<TState, TProps, T2>,
|
||||||
|
selector3: InputSelector<TState, TProps, T3>,
|
||||||
|
selector4: InputSelector<TState, TProps, T4>,
|
||||||
|
selector5: InputSelector<TState, TProps, T5>,
|
||||||
|
selector6: InputSelector<TState, TProps, T6>,
|
||||||
|
selector7: InputSelector<TState, TProps, T7>,
|
||||||
|
selector8: InputSelector<TState, TProps, T8>,
|
||||||
|
selector9: InputSelector<TState, TProps, T9>,
|
||||||
|
selector10: InputSelector<TState, TProps, T10>,
|
||||||
|
resultFunc: (
|
||||||
|
arg1: T1,
|
||||||
|
arg2: T2,
|
||||||
|
arg3: T3,
|
||||||
|
arg4: T4,
|
||||||
|
arg5: T5,
|
||||||
|
arg6: T6,
|
||||||
|
arg7: T7,
|
||||||
|
arg8: T8,
|
||||||
|
arg9: T9,
|
||||||
|
arg10: T10
|
||||||
|
) => TResult
|
||||||
|
): OutputSelector<TState, TProps, TResult>,
|
||||||
|
<TState, TProps, TResult, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10>(
|
||||||
|
selectors: [
|
||||||
|
InputSelector<TState, TProps, T1>,
|
||||||
|
InputSelector<TState, TProps, T2>,
|
||||||
|
InputSelector<TState, TProps, T3>,
|
||||||
|
InputSelector<TState, TProps, T4>,
|
||||||
|
InputSelector<TState, TProps, T5>,
|
||||||
|
InputSelector<TState, TProps, T6>,
|
||||||
|
InputSelector<TState, TProps, T7>,
|
||||||
|
InputSelector<TState, TProps, T8>,
|
||||||
|
InputSelector<TState, TProps, T9>,
|
||||||
|
InputSelector<TState, TProps, T10>
|
||||||
|
],
|
||||||
|
resultFunc: (
|
||||||
|
arg1: T1,
|
||||||
|
arg2: T2,
|
||||||
|
arg3: T3,
|
||||||
|
arg4: T4,
|
||||||
|
arg5: T5,
|
||||||
|
arg6: T6,
|
||||||
|
arg7: T7,
|
||||||
|
arg8: T8,
|
||||||
|
arg9: T9,
|
||||||
|
arg10: T10
|
||||||
|
) => TResult
|
||||||
|
): OutputSelector<TState, TProps, TResult>,
|
||||||
|
|
||||||
|
<TState, TProps, TResult, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11>(
|
||||||
|
selector1: InputSelector<TState, TProps, T1>,
|
||||||
|
selector2: InputSelector<TState, TProps, T2>,
|
||||||
|
selector3: InputSelector<TState, TProps, T3>,
|
||||||
|
selector4: InputSelector<TState, TProps, T4>,
|
||||||
|
selector5: InputSelector<TState, TProps, T5>,
|
||||||
|
selector6: InputSelector<TState, TProps, T6>,
|
||||||
|
selector7: InputSelector<TState, TProps, T7>,
|
||||||
|
selector8: InputSelector<TState, TProps, T8>,
|
||||||
|
selector9: InputSelector<TState, TProps, T9>,
|
||||||
|
selector10: InputSelector<TState, TProps, T10>,
|
||||||
|
selector11: InputSelector<TState, TProps, T11>,
|
||||||
|
resultFunc: (
|
||||||
|
arg1: T1,
|
||||||
|
arg2: T2,
|
||||||
|
arg3: T3,
|
||||||
|
arg4: T4,
|
||||||
|
arg5: T5,
|
||||||
|
arg6: T6,
|
||||||
|
arg7: T7,
|
||||||
|
arg8: T8,
|
||||||
|
arg9: T9,
|
||||||
|
arg10: T10,
|
||||||
|
arg11: T11
|
||||||
|
) => TResult
|
||||||
|
): OutputSelector<TState, TProps, TResult>,
|
||||||
|
<TState, TProps, TResult, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11>(
|
||||||
|
selectors: [
|
||||||
|
InputSelector<TState, TProps, T1>,
|
||||||
|
InputSelector<TState, TProps, T2>,
|
||||||
|
InputSelector<TState, TProps, T3>,
|
||||||
|
InputSelector<TState, TProps, T4>,
|
||||||
|
InputSelector<TState, TProps, T5>,
|
||||||
|
InputSelector<TState, TProps, T6>,
|
||||||
|
InputSelector<TState, TProps, T7>,
|
||||||
|
InputSelector<TState, TProps, T8>,
|
||||||
|
InputSelector<TState, TProps, T9>,
|
||||||
|
InputSelector<TState, TProps, T10>,
|
||||||
|
InputSelector<TState, TProps, T11>
|
||||||
|
],
|
||||||
|
resultFunc: (
|
||||||
|
arg1: T1,
|
||||||
|
arg2: T2,
|
||||||
|
arg3: T3,
|
||||||
|
arg4: T4,
|
||||||
|
arg5: T5,
|
||||||
|
arg6: T6,
|
||||||
|
arg7: T7,
|
||||||
|
arg8: T8,
|
||||||
|
arg9: T9,
|
||||||
|
arg10: T10,
|
||||||
|
arg11: T11
|
||||||
|
) => TResult
|
||||||
|
): OutputSelector<TState, TProps, TResult>,
|
||||||
|
|
||||||
|
<
|
||||||
|
TState,
|
||||||
|
TProps,
|
||||||
|
TResult,
|
||||||
|
T1,
|
||||||
|
T2,
|
||||||
|
T3,
|
||||||
|
T4,
|
||||||
|
T5,
|
||||||
|
T6,
|
||||||
|
T7,
|
||||||
|
T8,
|
||||||
|
T9,
|
||||||
|
T10,
|
||||||
|
T11,
|
||||||
|
T12
|
||||||
|
>(
|
||||||
|
selector1: InputSelector<TState, TProps, T1>,
|
||||||
|
selector2: InputSelector<TState, TProps, T2>,
|
||||||
|
selector3: InputSelector<TState, TProps, T3>,
|
||||||
|
selector4: InputSelector<TState, TProps, T4>,
|
||||||
|
selector5: InputSelector<TState, TProps, T5>,
|
||||||
|
selector6: InputSelector<TState, TProps, T6>,
|
||||||
|
selector7: InputSelector<TState, TProps, T7>,
|
||||||
|
selector8: InputSelector<TState, TProps, T8>,
|
||||||
|
selector9: InputSelector<TState, TProps, T9>,
|
||||||
|
selector10: InputSelector<TState, TProps, T10>,
|
||||||
|
selector11: InputSelector<TState, TProps, T11>,
|
||||||
|
selector12: InputSelector<TState, TProps, T12>,
|
||||||
|
resultFunc: (
|
||||||
|
arg1: T1,
|
||||||
|
arg2: T2,
|
||||||
|
arg3: T3,
|
||||||
|
arg4: T4,
|
||||||
|
arg5: T5,
|
||||||
|
arg6: T6,
|
||||||
|
arg7: T7,
|
||||||
|
arg8: T8,
|
||||||
|
arg9: T9,
|
||||||
|
arg10: T10,
|
||||||
|
arg11: T11,
|
||||||
|
arg12: T12
|
||||||
|
) => TResult
|
||||||
|
): OutputSelector<TState, TProps, TResult>,
|
||||||
|
<
|
||||||
|
TState,
|
||||||
|
TProps,
|
||||||
|
TResult,
|
||||||
|
T1,
|
||||||
|
T2,
|
||||||
|
T3,
|
||||||
|
T4,
|
||||||
|
T5,
|
||||||
|
T6,
|
||||||
|
T7,
|
||||||
|
T8,
|
||||||
|
T9,
|
||||||
|
T10,
|
||||||
|
T11,
|
||||||
|
T12
|
||||||
|
>(
|
||||||
|
selectors: [
|
||||||
|
InputSelector<TState, TProps, T1>,
|
||||||
|
InputSelector<TState, TProps, T2>,
|
||||||
|
InputSelector<TState, TProps, T3>,
|
||||||
|
InputSelector<TState, TProps, T4>,
|
||||||
|
InputSelector<TState, TProps, T5>,
|
||||||
|
InputSelector<TState, TProps, T6>,
|
||||||
|
InputSelector<TState, TProps, T7>,
|
||||||
|
InputSelector<TState, TProps, T8>,
|
||||||
|
InputSelector<TState, TProps, T9>,
|
||||||
|
InputSelector<TState, TProps, T10>,
|
||||||
|
InputSelector<TState, TProps, T11>,
|
||||||
|
InputSelector<TState, TProps, T12>
|
||||||
|
],
|
||||||
|
resultFunc: (
|
||||||
|
arg1: T1,
|
||||||
|
arg2: T2,
|
||||||
|
arg3: T3,
|
||||||
|
arg4: T4,
|
||||||
|
arg5: T5,
|
||||||
|
arg6: T6,
|
||||||
|
arg7: T7,
|
||||||
|
arg8: T8,
|
||||||
|
arg9: T9,
|
||||||
|
arg10: T10,
|
||||||
|
arg11: T11,
|
||||||
|
arg12: T12
|
||||||
|
) => TResult
|
||||||
|
): OutputSelector<TState, TProps, TResult>,
|
||||||
|
|
||||||
|
<
|
||||||
|
TState,
|
||||||
|
TProps,
|
||||||
|
TResult,
|
||||||
|
T1,
|
||||||
|
T2,
|
||||||
|
T3,
|
||||||
|
T4,
|
||||||
|
T5,
|
||||||
|
T6,
|
||||||
|
T7,
|
||||||
|
T8,
|
||||||
|
T9,
|
||||||
|
T10,
|
||||||
|
T11,
|
||||||
|
T12,
|
||||||
|
T13
|
||||||
|
>(
|
||||||
|
selector1: InputSelector<TState, TProps, T1>,
|
||||||
|
selector2: InputSelector<TState, TProps, T2>,
|
||||||
|
selector3: InputSelector<TState, TProps, T3>,
|
||||||
|
selector4: InputSelector<TState, TProps, T4>,
|
||||||
|
selector5: InputSelector<TState, TProps, T5>,
|
||||||
|
selector6: InputSelector<TState, TProps, T6>,
|
||||||
|
selector7: InputSelector<TState, TProps, T7>,
|
||||||
|
selector8: InputSelector<TState, TProps, T8>,
|
||||||
|
selector9: InputSelector<TState, TProps, T9>,
|
||||||
|
selector10: InputSelector<TState, TProps, T10>,
|
||||||
|
selector11: InputSelector<TState, TProps, T11>,
|
||||||
|
selector12: InputSelector<TState, TProps, T12>,
|
||||||
|
selector13: InputSelector<TState, TProps, T13>,
|
||||||
|
resultFunc: (
|
||||||
|
arg1: T1,
|
||||||
|
arg2: T2,
|
||||||
|
arg3: T3,
|
||||||
|
arg4: T4,
|
||||||
|
arg5: T5,
|
||||||
|
arg6: T6,
|
||||||
|
arg7: T7,
|
||||||
|
arg8: T8,
|
||||||
|
arg9: T9,
|
||||||
|
arg10: T10,
|
||||||
|
arg11: T11,
|
||||||
|
arg12: T12,
|
||||||
|
arg13: T13
|
||||||
|
) => TResult
|
||||||
|
): OutputSelector<TState, TProps, TResult>,
|
||||||
|
<
|
||||||
|
TState,
|
||||||
|
TProps,
|
||||||
|
TResult,
|
||||||
|
T1,
|
||||||
|
T2,
|
||||||
|
T3,
|
||||||
|
T4,
|
||||||
|
T5,
|
||||||
|
T6,
|
||||||
|
T7,
|
||||||
|
T8,
|
||||||
|
T9,
|
||||||
|
T10,
|
||||||
|
T11,
|
||||||
|
T12,
|
||||||
|
T13
|
||||||
|
>(
|
||||||
|
selectors: [
|
||||||
|
InputSelector<TState, TProps, T1>,
|
||||||
|
InputSelector<TState, TProps, T2>,
|
||||||
|
InputSelector<TState, TProps, T3>,
|
||||||
|
InputSelector<TState, TProps, T4>,
|
||||||
|
InputSelector<TState, TProps, T5>,
|
||||||
|
InputSelector<TState, TProps, T6>,
|
||||||
|
InputSelector<TState, TProps, T7>,
|
||||||
|
InputSelector<TState, TProps, T8>,
|
||||||
|
InputSelector<TState, TProps, T9>,
|
||||||
|
InputSelector<TState, TProps, T10>,
|
||||||
|
InputSelector<TState, TProps, T11>,
|
||||||
|
InputSelector<TState, TProps, T12>,
|
||||||
|
InputSelector<TState, TProps, T13>
|
||||||
|
],
|
||||||
|
resultFunc: (
|
||||||
|
arg1: T1,
|
||||||
|
arg2: T2,
|
||||||
|
arg3: T3,
|
||||||
|
arg4: T4,
|
||||||
|
arg5: T5,
|
||||||
|
arg6: T6,
|
||||||
|
arg7: T7,
|
||||||
|
arg8: T8,
|
||||||
|
arg9: T9,
|
||||||
|
arg10: T10,
|
||||||
|
arg11: T11,
|
||||||
|
arg12: T12,
|
||||||
|
arg13: T13
|
||||||
|
) => TResult
|
||||||
|
): OutputSelector<TState, TProps, TResult>,
|
||||||
|
|
||||||
|
<
|
||||||
|
TState,
|
||||||
|
TProps,
|
||||||
|
TResult,
|
||||||
|
T1,
|
||||||
|
T2,
|
||||||
|
T3,
|
||||||
|
T4,
|
||||||
|
T5,
|
||||||
|
T6,
|
||||||
|
T7,
|
||||||
|
T8,
|
||||||
|
T9,
|
||||||
|
T10,
|
||||||
|
T11,
|
||||||
|
T12,
|
||||||
|
T13,
|
||||||
|
T14
|
||||||
|
>(
|
||||||
|
selector1: InputSelector<TState, TProps, T1>,
|
||||||
|
selector2: InputSelector<TState, TProps, T2>,
|
||||||
|
selector3: InputSelector<TState, TProps, T3>,
|
||||||
|
selector4: InputSelector<TState, TProps, T4>,
|
||||||
|
selector5: InputSelector<TState, TProps, T5>,
|
||||||
|
selector6: InputSelector<TState, TProps, T6>,
|
||||||
|
selector7: InputSelector<TState, TProps, T7>,
|
||||||
|
selector8: InputSelector<TState, TProps, T8>,
|
||||||
|
selector9: InputSelector<TState, TProps, T9>,
|
||||||
|
selector10: InputSelector<TState, TProps, T10>,
|
||||||
|
selector11: InputSelector<TState, TProps, T11>,
|
||||||
|
selector12: InputSelector<TState, TProps, T12>,
|
||||||
|
selector13: InputSelector<TState, TProps, T13>,
|
||||||
|
selector14: InputSelector<TState, TProps, T14>,
|
||||||
|
resultFunc: (
|
||||||
|
arg1: T1,
|
||||||
|
arg2: T2,
|
||||||
|
arg3: T3,
|
||||||
|
arg4: T4,
|
||||||
|
arg5: T5,
|
||||||
|
arg6: T6,
|
||||||
|
arg7: T7,
|
||||||
|
arg8: T8,
|
||||||
|
arg9: T9,
|
||||||
|
arg10: T10,
|
||||||
|
arg11: T11,
|
||||||
|
arg12: T12,
|
||||||
|
arg13: T13,
|
||||||
|
arg14: T14
|
||||||
|
) => TResult
|
||||||
|
): OutputSelector<TState, TProps, TResult>,
|
||||||
|
<
|
||||||
|
TState,
|
||||||
|
TProps,
|
||||||
|
TResult,
|
||||||
|
T1,
|
||||||
|
T2,
|
||||||
|
T3,
|
||||||
|
T4,
|
||||||
|
T5,
|
||||||
|
T6,
|
||||||
|
T7,
|
||||||
|
T8,
|
||||||
|
T9,
|
||||||
|
T10,
|
||||||
|
T11,
|
||||||
|
T12,
|
||||||
|
T13,
|
||||||
|
T14
|
||||||
|
>(
|
||||||
|
selectors: [
|
||||||
|
InputSelector<TState, TProps, T1>,
|
||||||
|
InputSelector<TState, TProps, T2>,
|
||||||
|
InputSelector<TState, TProps, T3>,
|
||||||
|
InputSelector<TState, TProps, T4>,
|
||||||
|
InputSelector<TState, TProps, T5>,
|
||||||
|
InputSelector<TState, TProps, T6>,
|
||||||
|
InputSelector<TState, TProps, T7>,
|
||||||
|
InputSelector<TState, TProps, T8>,
|
||||||
|
InputSelector<TState, TProps, T9>,
|
||||||
|
InputSelector<TState, TProps, T10>,
|
||||||
|
InputSelector<TState, TProps, T11>,
|
||||||
|
InputSelector<TState, TProps, T12>,
|
||||||
|
InputSelector<TState, TProps, T13>,
|
||||||
|
InputSelector<TState, TProps, T14>
|
||||||
|
],
|
||||||
|
resultFunc: (
|
||||||
|
arg1: T1,
|
||||||
|
arg2: T2,
|
||||||
|
arg3: T3,
|
||||||
|
arg4: T4,
|
||||||
|
arg5: T5,
|
||||||
|
arg6: T6,
|
||||||
|
arg7: T7,
|
||||||
|
arg8: T8,
|
||||||
|
arg9: T9,
|
||||||
|
arg10: T10,
|
||||||
|
arg11: T11,
|
||||||
|
arg12: T12,
|
||||||
|
arg13: T13,
|
||||||
|
arg14: T14
|
||||||
|
) => TResult
|
||||||
|
): OutputSelector<TState, TProps, TResult>,
|
||||||
|
|
||||||
|
<
|
||||||
|
TState,
|
||||||
|
TProps,
|
||||||
|
TResult,
|
||||||
|
T1,
|
||||||
|
T2,
|
||||||
|
T3,
|
||||||
|
T4,
|
||||||
|
T5,
|
||||||
|
T6,
|
||||||
|
T7,
|
||||||
|
T8,
|
||||||
|
T9,
|
||||||
|
T10,
|
||||||
|
T11,
|
||||||
|
T12,
|
||||||
|
T13,
|
||||||
|
T14,
|
||||||
|
T15
|
||||||
|
>(
|
||||||
|
selector1: InputSelector<TState, TProps, T1>,
|
||||||
|
selector2: InputSelector<TState, TProps, T2>,
|
||||||
|
selector3: InputSelector<TState, TProps, T3>,
|
||||||
|
selector4: InputSelector<TState, TProps, T4>,
|
||||||
|
selector5: InputSelector<TState, TProps, T5>,
|
||||||
|
selector6: InputSelector<TState, TProps, T6>,
|
||||||
|
selector7: InputSelector<TState, TProps, T7>,
|
||||||
|
selector8: InputSelector<TState, TProps, T8>,
|
||||||
|
selector9: InputSelector<TState, TProps, T9>,
|
||||||
|
selector10: InputSelector<TState, TProps, T10>,
|
||||||
|
selector11: InputSelector<TState, TProps, T11>,
|
||||||
|
selector12: InputSelector<TState, TProps, T12>,
|
||||||
|
selector13: InputSelector<TState, TProps, T13>,
|
||||||
|
selector14: InputSelector<TState, TProps, T14>,
|
||||||
|
selector15: InputSelector<TState, TProps, T15>,
|
||||||
|
resultFunc: (
|
||||||
|
arg1: T1,
|
||||||
|
arg2: T2,
|
||||||
|
arg3: T3,
|
||||||
|
arg4: T4,
|
||||||
|
arg5: T5,
|
||||||
|
arg6: T6,
|
||||||
|
arg7: T7,
|
||||||
|
arg8: T8,
|
||||||
|
arg9: T9,
|
||||||
|
arg10: T10,
|
||||||
|
arg11: T11,
|
||||||
|
arg12: T12,
|
||||||
|
arg13: T13,
|
||||||
|
arg14: T14,
|
||||||
|
arg15: T15
|
||||||
|
) => TResult
|
||||||
|
): OutputSelector<TState, TProps, TResult>,
|
||||||
|
<
|
||||||
|
TState,
|
||||||
|
TProps,
|
||||||
|
TResult,
|
||||||
|
T1,
|
||||||
|
T2,
|
||||||
|
T3,
|
||||||
|
T4,
|
||||||
|
T5,
|
||||||
|
T6,
|
||||||
|
T7,
|
||||||
|
T8,
|
||||||
|
T9,
|
||||||
|
T10,
|
||||||
|
T11,
|
||||||
|
T12,
|
||||||
|
T13,
|
||||||
|
T14,
|
||||||
|
T15
|
||||||
|
>(
|
||||||
|
selectors: [
|
||||||
|
InputSelector<TState, TProps, T1>,
|
||||||
|
InputSelector<TState, TProps, T2>,
|
||||||
|
InputSelector<TState, TProps, T3>,
|
||||||
|
InputSelector<TState, TProps, T4>,
|
||||||
|
InputSelector<TState, TProps, T5>,
|
||||||
|
InputSelector<TState, TProps, T6>,
|
||||||
|
InputSelector<TState, TProps, T7>,
|
||||||
|
InputSelector<TState, TProps, T8>,
|
||||||
|
InputSelector<TState, TProps, T9>,
|
||||||
|
InputSelector<TState, TProps, T10>,
|
||||||
|
InputSelector<TState, TProps, T11>,
|
||||||
|
InputSelector<TState, TProps, T12>,
|
||||||
|
InputSelector<TState, TProps, T13>,
|
||||||
|
InputSelector<TState, TProps, T14>,
|
||||||
|
InputSelector<TState, TProps, T15>
|
||||||
|
],
|
||||||
|
resultFunc: (
|
||||||
|
arg1: T1,
|
||||||
|
arg2: T2,
|
||||||
|
arg3: T3,
|
||||||
|
arg4: T4,
|
||||||
|
arg5: T5,
|
||||||
|
arg6: T6,
|
||||||
|
arg7: T7,
|
||||||
|
arg8: T8,
|
||||||
|
arg9: T9,
|
||||||
|
arg10: T10,
|
||||||
|
arg11: T11,
|
||||||
|
arg12: T12,
|
||||||
|
arg13: T13,
|
||||||
|
arg14: T14,
|
||||||
|
arg15: T15
|
||||||
|
) => TResult
|
||||||
|
): OutputSelector<TState, TProps, TResult>,
|
||||||
|
|
||||||
|
<
|
||||||
|
TState,
|
||||||
|
TProps,
|
||||||
|
TResult,
|
||||||
|
T1,
|
||||||
|
T2,
|
||||||
|
T3,
|
||||||
|
T4,
|
||||||
|
T5,
|
||||||
|
T6,
|
||||||
|
T7,
|
||||||
|
T8,
|
||||||
|
T9,
|
||||||
|
T10,
|
||||||
|
T11,
|
||||||
|
T12,
|
||||||
|
T13,
|
||||||
|
T14,
|
||||||
|
T15,
|
||||||
|
T16
|
||||||
|
>(
|
||||||
|
selector1: InputSelector<TState, TProps, T1>,
|
||||||
|
selector2: InputSelector<TState, TProps, T2>,
|
||||||
|
selector3: InputSelector<TState, TProps, T3>,
|
||||||
|
selector4: InputSelector<TState, TProps, T4>,
|
||||||
|
selector5: InputSelector<TState, TProps, T5>,
|
||||||
|
selector6: InputSelector<TState, TProps, T6>,
|
||||||
|
selector7: InputSelector<TState, TProps, T7>,
|
||||||
|
selector8: InputSelector<TState, TProps, T8>,
|
||||||
|
selector9: InputSelector<TState, TProps, T9>,
|
||||||
|
selector10: InputSelector<TState, TProps, T10>,
|
||||||
|
selector11: InputSelector<TState, TProps, T11>,
|
||||||
|
selector12: InputSelector<TState, TProps, T12>,
|
||||||
|
selector13: InputSelector<TState, TProps, T13>,
|
||||||
|
selector14: InputSelector<TState, TProps, T14>,
|
||||||
|
selector15: InputSelector<TState, TProps, T15>,
|
||||||
|
selector16: InputSelector<TState, TProps, T16>,
|
||||||
|
resultFunc: (
|
||||||
|
arg1: T1,
|
||||||
|
arg2: T2,
|
||||||
|
arg3: T3,
|
||||||
|
arg4: T4,
|
||||||
|
arg5: T5,
|
||||||
|
arg6: T6,
|
||||||
|
arg7: T7,
|
||||||
|
arg8: T8,
|
||||||
|
arg9: T9,
|
||||||
|
arg10: T10,
|
||||||
|
arg11: T11,
|
||||||
|
arg12: T12,
|
||||||
|
arg13: T13,
|
||||||
|
arg14: T14,
|
||||||
|
arg15: T15,
|
||||||
|
arg16: T16
|
||||||
|
) => TResult
|
||||||
|
): OutputSelector<TState, TProps, TResult>,
|
||||||
|
<
|
||||||
|
TState,
|
||||||
|
TProps,
|
||||||
|
TResult,
|
||||||
|
T1,
|
||||||
|
T2,
|
||||||
|
T3,
|
||||||
|
T4,
|
||||||
|
T5,
|
||||||
|
T6,
|
||||||
|
T7,
|
||||||
|
T8,
|
||||||
|
T9,
|
||||||
|
T10,
|
||||||
|
T11,
|
||||||
|
T12,
|
||||||
|
T13,
|
||||||
|
T14,
|
||||||
|
T15,
|
||||||
|
T16
|
||||||
|
>(
|
||||||
|
selectors: [
|
||||||
|
InputSelector<TState, TProps, T1>,
|
||||||
|
InputSelector<TState, TProps, T2>,
|
||||||
|
InputSelector<TState, TProps, T3>,
|
||||||
|
InputSelector<TState, TProps, T4>,
|
||||||
|
InputSelector<TState, TProps, T5>,
|
||||||
|
InputSelector<TState, TProps, T6>,
|
||||||
|
InputSelector<TState, TProps, T7>,
|
||||||
|
InputSelector<TState, TProps, T8>,
|
||||||
|
InputSelector<TState, TProps, T9>,
|
||||||
|
InputSelector<TState, TProps, T10>,
|
||||||
|
InputSelector<TState, TProps, T11>,
|
||||||
|
InputSelector<TState, TProps, T12>,
|
||||||
|
InputSelector<TState, TProps, T13>,
|
||||||
|
InputSelector<TState, TProps, T14>,
|
||||||
|
InputSelector<TState, TProps, T15>,
|
||||||
|
InputSelector<TState, TProps, T16>
|
||||||
|
],
|
||||||
|
resultFunc: (
|
||||||
|
arg1: T1,
|
||||||
|
arg2: T2,
|
||||||
|
arg3: T3,
|
||||||
|
arg4: T4,
|
||||||
|
arg5: T5,
|
||||||
|
arg6: T6,
|
||||||
|
arg7: T7,
|
||||||
|
arg8: T8,
|
||||||
|
arg9: T9,
|
||||||
|
arg10: T10,
|
||||||
|
arg11: T11,
|
||||||
|
arg12: T12,
|
||||||
|
arg13: T13,
|
||||||
|
arg14: T14,
|
||||||
|
arg15: T15,
|
||||||
|
arg16: T16
|
||||||
|
) => TResult
|
||||||
|
): OutputSelector<TState, TProps, TResult>
|
||||||
|
};
|
||||||
|
|
||||||
|
declare type Reselect = {
|
||||||
|
createSelector: SelectorCreator,
|
||||||
|
|
||||||
|
defaultMemoize: <TFunc: Function>(
|
||||||
|
func: TFunc,
|
||||||
|
equalityCheck?: (a: any, b: any) => boolean
|
||||||
|
) => TFunc,
|
||||||
|
|
||||||
|
createSelectorCreator: (
|
||||||
|
memoize: Function,
|
||||||
|
...memoizeOptions: any[]
|
||||||
|
) => SelectorCreator,
|
||||||
|
|
||||||
|
createStructuredSelector: <TState, TProps, InputSelectors: {[k: string | number]: InputSelector<TState, TProps, any>}>(
|
||||||
|
inputSelectors: InputSelectors,
|
||||||
|
selectorCreator?: SelectorCreator
|
||||||
|
) => OutputSelector<TState, TProps, $ObjMap<InputSelectors, ExtractReturnType>>
|
||||||
|
};
|
||||||
|
|
||||||
|
declare module.exports: Reselect;
|
||||||
|
}
|
|
@ -49,8 +49,8 @@
|
||||||
"formik": "^0.10.4",
|
"formik": "^0.10.4",
|
||||||
"hast-util-sanitize": "^1.1.2",
|
"hast-util-sanitize": "^1.1.2",
|
||||||
"keytar": "^4.2.1",
|
"keytar": "^4.2.1",
|
||||||
"lbry-redux": "lbryio/lbry-redux#67cae46983d9fea90dd1e4c5bd121dd5077a3f0e",
|
"lbry-redux": "lbryio/lbry-redux#957d221c1830ecbb7a9e74fad78e711fb14539f4",
|
||||||
"lbryinc": "lbryio/lbryinc#de7ff055605b02a24821f0f9bab1d206eb7f235d",
|
"lbryinc": "lbryio/lbryinc#3f34af546ee73ff2ee7d8ad05e540b3b0aa658fb",
|
||||||
"localforage": "^1.7.1",
|
"localforage": "^1.7.1",
|
||||||
"mammoth": "^1.4.6",
|
"mammoth": "^1.4.6",
|
||||||
"mime": "^2.3.1",
|
"mime": "^2.3.1",
|
||||||
|
@ -91,7 +91,7 @@
|
||||||
"axios": "^0.18.0",
|
"axios": "^0.18.0",
|
||||||
"babel-eslint": "^8.2.2",
|
"babel-eslint": "^8.2.2",
|
||||||
"babel-plugin-module-resolver": "^3.1.1",
|
"babel-plugin-module-resolver": "^3.1.1",
|
||||||
"babel-polyfill": "^6.20.0",
|
"babel-polyfill": "^6.26.0",
|
||||||
"babel-preset-env": "^1.6.1",
|
"babel-preset-env": "^1.6.1",
|
||||||
"babel-preset-react": "^6.24.1",
|
"babel-preset-react": "^6.24.1",
|
||||||
"babel-preset-stage-2": "^6.18.0",
|
"babel-preset-stage-2": "^6.18.0",
|
||||||
|
|
|
@ -52,7 +52,12 @@ const analytics: Analytics = {
|
||||||
onSuccessCb: ?() => void
|
onSuccessCb: ?() => void
|
||||||
): void => {
|
): void => {
|
||||||
if (analyticsEnabled) {
|
if (analyticsEnabled) {
|
||||||
const params = {
|
const params: {
|
||||||
|
uri: string,
|
||||||
|
outpoint: string,
|
||||||
|
claim_id: string,
|
||||||
|
time_to_start?: number,
|
||||||
|
} = {
|
||||||
uri,
|
uri,
|
||||||
outpoint,
|
outpoint,
|
||||||
claim_id: claimId,
|
claim_id: claimId,
|
||||||
|
|
|
@ -14,7 +14,7 @@ type Props = {
|
||||||
showLBC?: boolean,
|
showLBC?: boolean,
|
||||||
fee?: boolean,
|
fee?: boolean,
|
||||||
inheritStyle?: boolean,
|
inheritStyle?: boolean,
|
||||||
filePage?: boolean,
|
badge?: boolean,
|
||||||
};
|
};
|
||||||
|
|
||||||
class CreditAmount extends React.PureComponent<Props> {
|
class CreditAmount extends React.PureComponent<Props> {
|
||||||
|
@ -38,7 +38,7 @@ class CreditAmount extends React.PureComponent<Props> {
|
||||||
fee,
|
fee,
|
||||||
showLBC,
|
showLBC,
|
||||||
inheritStyle,
|
inheritStyle,
|
||||||
filePage,
|
badge,
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
const minimumRenderableAmount = 10 ** (-1 * precision);
|
const minimumRenderableAmount = 10 ** (-1 * precision);
|
||||||
|
@ -78,11 +78,13 @@ class CreditAmount extends React.PureComponent<Props> {
|
||||||
<span
|
<span
|
||||||
title={fullPrice}
|
title={fullPrice}
|
||||||
className={classnames('credit-amount', {
|
className={classnames('credit-amount', {
|
||||||
'credit-amount--free': !large && isFree,
|
|
||||||
'credit-amount--cost': !large && !isFree,
|
|
||||||
'credit-amount--large': large,
|
'credit-amount--large': large,
|
||||||
|
// TODO: remove inheritStyle prop
|
||||||
|
// It just complicates things
|
||||||
'credit-amount--inherit': inheritStyle,
|
'credit-amount--inherit': inheritStyle,
|
||||||
'credit-amount--file-page': filePage,
|
badge: badge,
|
||||||
|
'badge--cost': badge && !isFree,
|
||||||
|
'badge--free': badge && isFree,
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
{amountText}
|
{amountText}
|
||||||
|
|
|
@ -27,9 +27,15 @@ type Props = {
|
||||||
isResolvingUri: boolean,
|
isResolvingUri: boolean,
|
||||||
/* eslint-enable react/no-unused-prop-types */
|
/* eslint-enable react/no-unused-prop-types */
|
||||||
isSubscribed: boolean,
|
isSubscribed: boolean,
|
||||||
|
showSubscribedLogo: boolean,
|
||||||
|
isNew: boolean,
|
||||||
};
|
};
|
||||||
|
|
||||||
class FileCard extends React.PureComponent<Props> {
|
class FileCard extends React.PureComponent<Props> {
|
||||||
|
static defaultProps = {
|
||||||
|
showSubscribedLogo: false,
|
||||||
|
};
|
||||||
|
|
||||||
componentWillMount() {
|
componentWillMount() {
|
||||||
this.resolve(this.props);
|
this.resolve(this.props);
|
||||||
}
|
}
|
||||||
|
@ -57,6 +63,8 @@ class FileCard extends React.PureComponent<Props> {
|
||||||
claimIsMine,
|
claimIsMine,
|
||||||
pending,
|
pending,
|
||||||
isSubscribed,
|
isSubscribed,
|
||||||
|
isNew,
|
||||||
|
showSubscribedLogo,
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
if (!claim && !pending) {
|
if (!claim && !pending) {
|
||||||
|
@ -112,10 +120,15 @@ class FileCard extends React.PureComponent<Props> {
|
||||||
<div className="card__file-properties">
|
<div className="card__file-properties">
|
||||||
<FilePrice hideFree uri={uri} />
|
<FilePrice hideFree uri={uri} />
|
||||||
{isRewardContent && <Icon iconColor="red" icon={icons.FEATURED} />}
|
{isRewardContent && <Icon iconColor="red" icon={icons.FEATURED} />}
|
||||||
{isSubscribed && <Icon icon={icons.HEART} />}
|
{showSubscribedLogo && isSubscribed && <Icon icon={icons.HEART} />}
|
||||||
{fileInfo && <Icon icon={icons.LOCAL} />}
|
{fileInfo && <Icon icon={icons.LOCAL} />}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{isNew && (
|
||||||
|
<div className="card__subtitle">
|
||||||
|
<span className="badge badge--alert">{__('NEW')}</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
/* eslint-enable jsx-a11y/click-events-have-key-events */
|
/* eslint-enable jsx-a11y/click-events-have-key-events */
|
||||||
|
|
|
@ -147,6 +147,7 @@ class FileList extends React.PureComponent<Props, State> {
|
||||||
claim_id: claimId,
|
claim_id: claimId,
|
||||||
txid,
|
txid,
|
||||||
nout,
|
nout,
|
||||||
|
isNew,
|
||||||
} = fileInfo;
|
} = fileInfo;
|
||||||
const uriParams = {};
|
const uriParams = {};
|
||||||
|
|
||||||
|
@ -159,13 +160,13 @@ class FileList extends React.PureComponent<Props, State> {
|
||||||
const outpoint = `${txid}:${nout}`;
|
const outpoint = `${txid}:${nout}`;
|
||||||
|
|
||||||
// See https://github.com/lbryio/lbry-desktop/issues/1327 for discussion around using outpoint as the key
|
// See https://github.com/lbryio/lbry-desktop/issues/1327 for discussion around using outpoint as the key
|
||||||
content.push(<FileCard key={outpoint} uri={uri} checkPending={checkPending} />);
|
content.push(<FileCard key={outpoint} uri={uri} checkPending={checkPending} isNew={isNew} />);
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section>
|
<section>
|
||||||
<div className="file-list__sort">
|
|
||||||
{!hideFilter && (
|
{!hideFilter && (
|
||||||
|
<div className="file-list__sort">
|
||||||
<FormField
|
<FormField
|
||||||
prefix={__('Sort by')}
|
prefix={__('Sort by')}
|
||||||
affixClass="form-field--align-center"
|
affixClass="form-field--align-center"
|
||||||
|
@ -177,9 +178,9 @@ class FileList extends React.PureComponent<Props, State> {
|
||||||
<option value="dateOld">{__('Oldest First')}</option>
|
<option value="dateOld">{__('Oldest First')}</option>
|
||||||
<option value="title">{__('Title')}</option>
|
<option value="title">{__('Title')}</option>
|
||||||
</FormField>
|
</FormField>
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
<div className="card__list">{content}</div>
|
)}
|
||||||
|
<div className="card__list card__content">{content}</div>
|
||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,7 @@ type Props = {
|
||||||
fetching: boolean,
|
fetching: boolean,
|
||||||
claim: ?{},
|
claim: ?{},
|
||||||
// below props are just passed to <CreditAmount />
|
// below props are just passed to <CreditAmount />
|
||||||
filePage?: boolean,
|
badge?: boolean,
|
||||||
inheritStyle?: boolean,
|
inheritStyle?: boolean,
|
||||||
showLBC?: boolean,
|
showLBC?: boolean,
|
||||||
hideFree?: boolean, // hide the file price if it's free
|
hideFree?: boolean, // hide the file price if it's free
|
||||||
|
@ -38,7 +38,7 @@ class FilePrice extends React.PureComponent<Props> {
|
||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { costInfo, showFullPrice, filePage, inheritStyle, showLBC, hideFree } = this.props;
|
const { costInfo, showFullPrice, badge, inheritStyle, showLBC, hideFree } = this.props;
|
||||||
|
|
||||||
if (costInfo && !costInfo.cost && hideFree) {
|
if (costInfo && !costInfo.cost && hideFree) {
|
||||||
return null;
|
return null;
|
||||||
|
@ -47,7 +47,7 @@ class FilePrice extends React.PureComponent<Props> {
|
||||||
return costInfo ? (
|
return costInfo ? (
|
||||||
<CreditAmount
|
<CreditAmount
|
||||||
showFree
|
showFree
|
||||||
filePage={filePage}
|
badge={badge}
|
||||||
inheritStyle={inheritStyle}
|
inheritStyle={inheritStyle}
|
||||||
showLBC={showLBC}
|
showLBC={showLBC}
|
||||||
amount={costInfo.cost}
|
amount={costInfo.cost}
|
||||||
|
|
|
@ -524,7 +524,7 @@ class PublishForm extends React.PureComponent<Props> {
|
||||||
step="any"
|
step="any"
|
||||||
label={__('Deposit')}
|
label={__('Deposit')}
|
||||||
postfix="LBC"
|
postfix="LBC"
|
||||||
value={bid || ''}
|
value={bid}
|
||||||
error={bidError}
|
error={bidError}
|
||||||
min="0"
|
min="0"
|
||||||
disabled={!name}
|
disabled={!name}
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { selectNavLinks } from 'redux/selectors/app';
|
import { selectNavLinks } from 'redux/selectors/app';
|
||||||
import { selectNotifications } from 'redux/selectors/subscriptions';
|
import { selectUnreadAmount } from 'redux/selectors/subscriptions';
|
||||||
import SideBar from './view';
|
import SideBar from './view';
|
||||||
|
|
||||||
const select = state => ({
|
const select = state => ({
|
||||||
navLinks: selectNavLinks(state),
|
navLinks: selectNavLinks(state),
|
||||||
notifications: selectNotifications(state),
|
unreadSubscriptionTotal: selectUnreadAmount(state),
|
||||||
});
|
});
|
||||||
|
|
||||||
const perform = () => ({});
|
const perform = () => ({});
|
||||||
|
|
|
@ -2,7 +2,6 @@
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import Button from 'component/button';
|
import Button from 'component/button';
|
||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
import * as NOTIFICATION_TYPES from 'constants/notification_types';
|
|
||||||
|
|
||||||
type SideBarLink = {
|
type SideBarLink = {
|
||||||
label: string,
|
label: string,
|
||||||
|
@ -17,15 +16,11 @@ type Props = {
|
||||||
primary: Array<SideBarLink>,
|
primary: Array<SideBarLink>,
|
||||||
secondary: Array<SideBarLink>,
|
secondary: Array<SideBarLink>,
|
||||||
},
|
},
|
||||||
notifications: {
|
unreadSubscriptionTotal: number,
|
||||||
type: string,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const SideBar = (props: Props) => {
|
const SideBar = (props: Props) => {
|
||||||
const { navLinks, notifications } = props;
|
const { navLinks, unreadSubscriptionTotal } = props;
|
||||||
|
|
||||||
const badges = Object.keys(notifications).length;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<nav className="nav">
|
<nav className="nav">
|
||||||
|
@ -40,7 +35,11 @@ const SideBar = (props: Props) => {
|
||||||
>
|
>
|
||||||
<Button
|
<Button
|
||||||
navigate={path}
|
navigate={path}
|
||||||
label={path === '/subscriptions' && badges ? `${label} (${badges})` : label}
|
label={
|
||||||
|
path === '/subscriptions' && unreadSubscriptionTotal
|
||||||
|
? `${label} (${unreadSubscriptionTotal})`
|
||||||
|
: label
|
||||||
|
}
|
||||||
icon={icon}
|
icon={icon}
|
||||||
/>
|
/>
|
||||||
</li>
|
</li>
|
||||||
|
|
|
@ -1,15 +1,19 @@
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { doChannelSubscribe, doChannelUnsubscribe } from 'redux/actions/subscriptions';
|
import { doChannelSubscribe, doChannelUnsubscribe } from 'redux/actions/subscriptions';
|
||||||
import { doNotify } from 'lbry-redux';
|
import { doNotify } from 'lbry-redux';
|
||||||
import { selectSubscriptions } from 'redux/selectors/subscriptions';
|
import { selectSubscriptions, makeSelectIsSubscribed } from 'redux/selectors/subscriptions';
|
||||||
import SubscribeButton from './view';
|
import SubscribeButton from './view';
|
||||||
|
|
||||||
const select = (state, props) => ({
|
const select = (state, props) => ({
|
||||||
subscriptions: selectSubscriptions(state),
|
subscriptions: selectSubscriptions(state),
|
||||||
|
isSubscribed: makeSelectIsSubscribed(props.uri, true)(state),
|
||||||
});
|
});
|
||||||
|
|
||||||
export default connect(select, {
|
export default connect(
|
||||||
|
select,
|
||||||
|
{
|
||||||
doChannelSubscribe,
|
doChannelSubscribe,
|
||||||
doChannelUnsubscribe,
|
doChannelUnsubscribe,
|
||||||
doNotify,
|
doNotify,
|
||||||
})(SubscribeButton);
|
}
|
||||||
|
)(SubscribeButton);
|
||||||
|
|
|
@ -3,7 +3,6 @@ import React from 'react';
|
||||||
import { MODALS } from 'lbry-redux';
|
import { MODALS } from 'lbry-redux';
|
||||||
import * as icons from 'constants/icons';
|
import * as icons from 'constants/icons';
|
||||||
import Button from 'component/button';
|
import Button from 'component/button';
|
||||||
import type { Subscription } from 'types/subscription';
|
|
||||||
|
|
||||||
type SubscribtionArgs = {
|
type SubscribtionArgs = {
|
||||||
channelName: string,
|
channelName: string,
|
||||||
|
@ -13,7 +12,8 @@ type SubscribtionArgs = {
|
||||||
type Props = {
|
type Props = {
|
||||||
channelName: ?string,
|
channelName: ?string,
|
||||||
uri: ?string,
|
uri: ?string,
|
||||||
subscriptions: Array<Subscription>,
|
isSubscribed: boolean,
|
||||||
|
subscriptions: Array<string>,
|
||||||
doChannelSubscribe: ({ channelName: string, uri: string }) => void,
|
doChannelSubscribe: ({ channelName: string, uri: string }) => void,
|
||||||
doChannelUnsubscribe: SubscribtionArgs => void,
|
doChannelUnsubscribe: SubscribtionArgs => void,
|
||||||
doNotify: ({ id: string }) => void,
|
doNotify: ({ id: string }) => void,
|
||||||
|
@ -23,15 +23,13 @@ export default (props: Props) => {
|
||||||
const {
|
const {
|
||||||
channelName,
|
channelName,
|
||||||
uri,
|
uri,
|
||||||
subscriptions,
|
|
||||||
doChannelSubscribe,
|
doChannelSubscribe,
|
||||||
doChannelUnsubscribe,
|
doChannelUnsubscribe,
|
||||||
doNotify,
|
doNotify,
|
||||||
|
subscriptions,
|
||||||
|
isSubscribed,
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
const isSubscribed =
|
|
||||||
subscriptions.map(subscription => subscription.channelName).indexOf(channelName) !== -1;
|
|
||||||
|
|
||||||
const subscriptionHandler = isSubscribed ? doChannelUnsubscribe : doChannelSubscribe;
|
const subscriptionHandler = isSubscribed ? doChannelUnsubscribe : doChannelSubscribe;
|
||||||
const subscriptionLabel = isSubscribed ? __('Unsubscribe') : __('Subscribe');
|
const subscriptionLabel = isSubscribed ? __('Unsubscribe') : __('Subscribe');
|
||||||
|
|
||||||
|
|
|
@ -161,7 +161,9 @@ class UserHistoryPage extends React.PureComponent<Props, State> {
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
) : (
|
) : (
|
||||||
<div className="page__empty">
|
<div className="page__empty">
|
||||||
|
<h3 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!")}
|
||||||
|
</h3>
|
||||||
<div className="card__actions card__actions--center">
|
<div className="card__actions card__actions--center">
|
||||||
<Button button="primary" navigate="/discover" label={__('Explore new content')} />
|
<Button button="primary" navigate="/discover" label={__('Explore new content')} />
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -176,14 +176,15 @@ export const CHANNEL_SUBSCRIBE = 'CHANNEL_SUBSCRIBE';
|
||||||
export const CHANNEL_UNSUBSCRIBE = 'CHANNEL_UNSUBSCRIBE';
|
export const CHANNEL_UNSUBSCRIBE = 'CHANNEL_UNSUBSCRIBE';
|
||||||
export const HAS_FETCHED_SUBSCRIPTIONS = 'HAS_FETCHED_SUBSCRIPTIONS';
|
export const HAS_FETCHED_SUBSCRIPTIONS = 'HAS_FETCHED_SUBSCRIPTIONS';
|
||||||
export const SET_SUBSCRIPTION_LATEST = 'SET_SUBSCRIPTION_LATEST';
|
export const SET_SUBSCRIPTION_LATEST = 'SET_SUBSCRIPTION_LATEST';
|
||||||
export const SET_SUBSCRIPTION_NOTIFICATION = 'SET_SUBSCRIPTION_NOTIFICATION';
|
export const UPDATE_SUBSCRIPTION_UNREADS = 'UPDATE_SUBSCRIPTION_UNREADS';
|
||||||
export const SET_SUBSCRIPTION_NOTIFICATIONS = 'SET_SUBSCRIPTION_NOTIFICATIONS';
|
export const REMOVE_SUBSCRIPTION_UNREADS = 'REMOVE_SUBSCRIPTION_UNREADS';
|
||||||
export const CHECK_SUBSCRIPTION_STARTED = 'CHECK_SUBSCRIPTION_STARTED';
|
export const CHECK_SUBSCRIPTION_STARTED = 'CHECK_SUBSCRIPTION_STARTED';
|
||||||
export const CHECK_SUBSCRIPTION_COMPLETED = 'CHECK_SUBSCRIPTION_COMPLETED';
|
export const CHECK_SUBSCRIPTION_COMPLETED = 'CHECK_SUBSCRIPTION_COMPLETED';
|
||||||
export const CHECK_SUBSCRIPTIONS_SUBSCRIBE = 'CHECK_SUBSCRIPTIONS_SUBSCRIBE';
|
export const CHECK_SUBSCRIPTIONS_SUBSCRIBE = 'CHECK_SUBSCRIPTIONS_SUBSCRIBE';
|
||||||
export const FETCH_SUBSCRIPTIONS_START = 'FETCH_SUBSCRIPTIONS_START';
|
export const FETCH_SUBSCRIPTIONS_START = 'FETCH_SUBSCRIPTIONS_START';
|
||||||
export const FETCH_SUBSCRIPTIONS_FAIL = 'FETCH_SUBSCRIPTIONS_FAIL';
|
export const FETCH_SUBSCRIPTIONS_FAIL = 'FETCH_SUBSCRIPTIONS_FAIL';
|
||||||
export const FETCH_SUBSCRIPTIONS_SUCCESS = 'FETCH_SUBSCRIPTIONS_SUCCESS';
|
export const FETCH_SUBSCRIPTIONS_SUCCESS = 'FETCH_SUBSCRIPTIONS_SUCCESS';
|
||||||
|
export const SET_VIEW_MODE = 'SET_VIEW_MODE';
|
||||||
|
|
||||||
// Publishing
|
// Publishing
|
||||||
export const CLEAR_PUBLISH = 'CLEAR_PUBLISH';
|
export const CLEAR_PUBLISH = 'CLEAR_PUBLISH';
|
||||||
|
|
|
@ -32,3 +32,5 @@ export const EYE = 'Eye';
|
||||||
export const PLAY = 'Play';
|
export const PLAY = 'Play';
|
||||||
export const FACEBOOK = 'Facebook';
|
export const FACEBOOK = 'Facebook';
|
||||||
export const TWITTER = 'Twitter';
|
export const TWITTER = 'Twitter';
|
||||||
|
export const CREDIT_CARD = 'CreditCard';
|
||||||
|
export const SETTINGS = 'Settings';
|
||||||
|
|
|
@ -1,3 +1,7 @@
|
||||||
|
export const VIEW_ALL = 'view_all';
|
||||||
|
export const VIEW_LATEST_FIRST = 'view_latest_first';
|
||||||
|
|
||||||
|
// Types for unreads
|
||||||
export const DOWNLOADING = 'DOWNLOADING';
|
export const DOWNLOADING = 'DOWNLOADING';
|
||||||
export const DOWNLOADED = 'DOWNLOADED';
|
export const DOWNLOADED = 'DOWNLOADED';
|
||||||
export const NOTIFY_ONLY = 'NOTIFY_ONLY;';
|
export const NOTIFY_ONLY = 'NOTIFY_ONLY;';
|
|
@ -206,4 +206,5 @@ const init = () => {
|
||||||
|
|
||||||
init();
|
init();
|
||||||
|
|
||||||
|
/* eslint-enable react/jsx-filename-extension */
|
||||||
/* eslint-enable no-console */
|
/* eslint-enable no-console */
|
||||||
|
|
|
@ -31,7 +31,7 @@ class ModalWalletDecrypt extends React.PureComponent<Props> {
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { closeModalgaa } = this.props;
|
const { closeModal } = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
|
|
|
@ -2,7 +2,7 @@ import { connect } from 'react-redux';
|
||||||
import * as settings from 'constants/settings';
|
import * as settings from 'constants/settings';
|
||||||
import { doNavigate } from 'redux/actions/navigation';
|
import { doNavigate } from 'redux/actions/navigation';
|
||||||
import { selectRewardContentClaimIds, selectPlayingUri } from 'redux/selectors/content';
|
import { selectRewardContentClaimIds, selectPlayingUri } from 'redux/selectors/content';
|
||||||
import { doCheckSubscription } from 'redux/actions/subscriptions';
|
import { doRemoveUnreadSubscription } from 'redux/actions/subscriptions';
|
||||||
import { doSetClientSetting } from 'redux/actions/settings';
|
import { doSetClientSetting } from 'redux/actions/settings';
|
||||||
import { doSetContentHistoryItem } from 'redux/actions/content';
|
import { doSetContentHistoryItem } from 'redux/actions/content';
|
||||||
import {
|
import {
|
||||||
|
@ -15,9 +15,10 @@ import {
|
||||||
makeSelectContentTypeForUri,
|
makeSelectContentTypeForUri,
|
||||||
makeSelectMetadataForUri,
|
makeSelectMetadataForUri,
|
||||||
doNotify,
|
doNotify,
|
||||||
|
makeSelectChannelForClaimUri,
|
||||||
} from 'lbry-redux';
|
} from 'lbry-redux';
|
||||||
import { selectShowNsfw, makeSelectClientSetting } from 'redux/selectors/settings';
|
import { selectShowNsfw, makeSelectClientSetting } from 'redux/selectors/settings';
|
||||||
import { selectSubscriptions } from 'redux/selectors/subscriptions';
|
import { makeSelectIsSubscribed } from 'redux/selectors/subscriptions';
|
||||||
import { doPrepareEdit } from 'redux/actions/publish';
|
import { doPrepareEdit } from 'redux/actions/publish';
|
||||||
import FilePage from './view';
|
import FilePage from './view';
|
||||||
|
|
||||||
|
@ -29,21 +30,22 @@ const select = (state, props) => ({
|
||||||
obscureNsfw: !selectShowNsfw(state),
|
obscureNsfw: !selectShowNsfw(state),
|
||||||
fileInfo: makeSelectFileInfoForUri(props.uri)(state),
|
fileInfo: makeSelectFileInfoForUri(props.uri)(state),
|
||||||
rewardedContentClaimIds: selectRewardContentClaimIds(state, props),
|
rewardedContentClaimIds: selectRewardContentClaimIds(state, props),
|
||||||
subscriptions: selectSubscriptions(state),
|
|
||||||
playingUri: selectPlayingUri(state),
|
playingUri: selectPlayingUri(state),
|
||||||
claimIsMine: makeSelectClaimIsMine(props.uri)(state),
|
claimIsMine: makeSelectClaimIsMine(props.uri)(state),
|
||||||
autoplay: makeSelectClientSetting(settings.AUTOPLAY)(state),
|
autoplay: makeSelectClientSetting(settings.AUTOPLAY)(state),
|
||||||
|
isSubscribed: makeSelectIsSubscribed(props.uri)(state),
|
||||||
|
channelUri: makeSelectChannelForClaimUri(props.uri, true)(state),
|
||||||
});
|
});
|
||||||
|
|
||||||
const perform = dispatch => ({
|
const perform = dispatch => ({
|
||||||
navigate: (path, params) => dispatch(doNavigate(path, params)),
|
navigate: (path, params) => dispatch(doNavigate(path, params)),
|
||||||
fetchFileInfo: uri => dispatch(doFetchFileInfo(uri)),
|
fetchFileInfo: uri => dispatch(doFetchFileInfo(uri)),
|
||||||
fetchCostInfo: uri => dispatch(doFetchCostInfoForUri(uri)),
|
fetchCostInfo: uri => dispatch(doFetchCostInfoForUri(uri)),
|
||||||
checkSubscription: uri => dispatch(doCheckSubscription(uri)),
|
|
||||||
openModal: (modal, props) => dispatch(doNotify(modal, props)),
|
openModal: (modal, props) => dispatch(doNotify(modal, props)),
|
||||||
prepareEdit: (publishData, uri) => dispatch(doPrepareEdit(publishData, uri)),
|
prepareEdit: (publishData, uri) => dispatch(doPrepareEdit(publishData, uri)),
|
||||||
setClientSetting: (key, value) => dispatch(doSetClientSetting(key, value)),
|
setClientSetting: (key, value) => dispatch(doSetClientSetting(key, value)),
|
||||||
setViewed: uri => dispatch(doSetContentHistoryItem(uri)),
|
setViewed: uri => dispatch(doSetContentHistoryItem(uri)),
|
||||||
|
markSubscriptionRead: (channel, uri) => dispatch(doRemoveUnreadSubscription(channel, uri)),
|
||||||
});
|
});
|
||||||
|
|
||||||
export default connect(
|
export default connect(
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
// @flow
|
// @flow
|
||||||
|
import type { Claim, Metadata } from 'types/claim';
|
||||||
|
import type { FileInfo } from 'types/file_info';
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import * as settings from 'constants/settings';
|
import * as settings from 'constants/settings';
|
||||||
import { buildURI, normalizeURI, MODALS } from 'lbry-redux';
|
import { buildURI, normalizeURI, MODALS } from 'lbry-redux';
|
||||||
|
@ -14,8 +16,6 @@ import * as icons from 'constants/icons';
|
||||||
import Button from 'component/button';
|
import Button from 'component/button';
|
||||||
import SubscribeButton from 'component/subscribeButton';
|
import SubscribeButton from 'component/subscribeButton';
|
||||||
import Page from 'component/page';
|
import Page from 'component/page';
|
||||||
import type { Claim } from 'types/claim';
|
|
||||||
import type { Subscription } from 'types/subscription';
|
|
||||||
import FileDownloadLink from 'component/fileDownloadLink';
|
import FileDownloadLink from 'component/fileDownloadLink';
|
||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
import getMediaType from 'util/getMediaType';
|
import getMediaType from 'util/getMediaType';
|
||||||
|
@ -25,31 +25,26 @@ import ToolTip from 'component/common/tooltip';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
claim: Claim,
|
claim: Claim,
|
||||||
fileInfo: {},
|
fileInfo: FileInfo,
|
||||||
metadata: {
|
metadata: Metadata,
|
||||||
title: string,
|
|
||||||
thumbnail: string,
|
|
||||||
file_name: string,
|
|
||||||
nsfw: boolean,
|
|
||||||
},
|
|
||||||
contentType: string,
|
contentType: string,
|
||||||
uri: string,
|
uri: string,
|
||||||
rewardedContentClaimIds: Array<string>,
|
rewardedContentClaimIds: Array<string>,
|
||||||
obscureNsfw: boolean,
|
obscureNsfw: boolean,
|
||||||
claimIsMine: boolean,
|
claimIsMine: boolean,
|
||||||
costInfo: ?{},
|
costInfo: ?{ cost: number },
|
||||||
navigate: (string, ?{}) => void,
|
|
||||||
openModal: ({ id: string }, { uri: string }) => void,
|
|
||||||
fetchFileInfo: string => void,
|
fetchFileInfo: string => void,
|
||||||
fetchCostInfo: string => void,
|
fetchCostInfo: string => void,
|
||||||
prepareEdit: ({}, string) => void,
|
|
||||||
setViewed: string => void,
|
setViewed: string => void,
|
||||||
autoplay: boolean,
|
autoplay: boolean,
|
||||||
|
isSubscribed: ?string,
|
||||||
|
isSubscribed: boolean,
|
||||||
|
channelUri: string,
|
||||||
|
prepareEdit: ({}, string) => void,
|
||||||
|
navigate: (string, ?{}) => void,
|
||||||
|
openModal: ({ id: string }, { uri: string }) => void,
|
||||||
setClientSetting: (string, string | boolean | number) => void,
|
setClientSetting: (string, string | boolean | number) => void,
|
||||||
/* eslint-disable react/no-unused-prop-types */
|
markSubscriptionRead: (string, string) => void,
|
||||||
checkSubscription: (uri: string) => void,
|
|
||||||
subscriptions: Array<Subscription>,
|
|
||||||
/* eslint-enable react/no-unused-prop-types */
|
|
||||||
};
|
};
|
||||||
|
|
||||||
class FilePage extends React.Component<Props> {
|
class FilePage extends React.Component<Props> {
|
||||||
|
@ -73,7 +68,11 @@ class FilePage extends React.Component<Props> {
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
const { uri, fileInfo, fetchFileInfo, fetchCostInfo, setViewed } = this.props;
|
const { uri, fileInfo, fetchFileInfo, fetchCostInfo, setViewed, isSubscribed } = this.props;
|
||||||
|
|
||||||
|
if (isSubscribed) {
|
||||||
|
this.removeFromSubscriptionNotifications();
|
||||||
|
}
|
||||||
|
|
||||||
if (fileInfo === undefined) {
|
if (fileInfo === undefined) {
|
||||||
fetchFileInfo(uri);
|
fetchFileInfo(uri);
|
||||||
|
@ -81,9 +80,6 @@ class FilePage extends React.Component<Props> {
|
||||||
|
|
||||||
// See https://github.com/lbryio/lbry-desktop/pull/1563 for discussion
|
// See https://github.com/lbryio/lbry-desktop/pull/1563 for discussion
|
||||||
fetchCostInfo(uri);
|
fetchCostInfo(uri);
|
||||||
|
|
||||||
this.checkSubscription(this.props);
|
|
||||||
|
|
||||||
setViewed(uri);
|
setViewed(uri);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -98,23 +94,22 @@ class FilePage extends React.Component<Props> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
componentDidUpdate(prevProps: Props) {
|
||||||
|
if (!prevProps.isSubscribed && this.props.isSubscribed) {
|
||||||
|
this.removeFromSubscriptionNotifications();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
onAutoplayChange(event: SyntheticInputEvent<*>) {
|
onAutoplayChange(event: SyntheticInputEvent<*>) {
|
||||||
this.props.setClientSetting(settings.AUTOPLAY, event.target.checked);
|
this.props.setClientSetting(settings.AUTOPLAY, event.target.checked);
|
||||||
}
|
}
|
||||||
|
|
||||||
checkSubscription = (props: Props) => {
|
removeFromSubscriptionNotifications() {
|
||||||
if (props.subscriptions.find(sub => sub.channelName === props.claim.channel_name)) {
|
// Always try to remove
|
||||||
props.checkSubscription(
|
// If it doesn't exist, nothing will happen
|
||||||
buildURI(
|
const { markSubscriptionRead, uri, channelUri } = this.props;
|
||||||
{
|
markSubscriptionRead(channelUri, uri);
|
||||||
contentName: props.claim.channel_name,
|
|
||||||
claimId: props.claim.value.publisherSignature.certificateId,
|
|
||||||
},
|
|
||||||
false
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
|
@ -131,11 +126,12 @@ class FilePage extends React.Component<Props> {
|
||||||
costInfo,
|
costInfo,
|
||||||
fileInfo,
|
fileInfo,
|
||||||
autoplay,
|
autoplay,
|
||||||
|
channelUri,
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
// File info
|
// File info
|
||||||
const { title, thumbnail } = metadata;
|
const { title, thumbnail } = metadata;
|
||||||
const { height, channel_name: channelName, value } = claim;
|
const { height, channel_name: channelName } = claim;
|
||||||
const { PLAYABLE_MEDIA_TYPES, PREVIEW_MEDIA_TYPES } = FilePage;
|
const { PLAYABLE_MEDIA_TYPES, PREVIEW_MEDIA_TYPES } = FilePage;
|
||||||
const isRewardContent = (rewardedContentClaimIds || []).includes(claim.claim_id);
|
const isRewardContent = (rewardedContentClaimIds || []).includes(claim.claim_id);
|
||||||
const shouldObscureThumbnail = obscureNsfw && metadata.nsfw;
|
const shouldObscureThumbnail = obscureNsfw && metadata.nsfw;
|
||||||
|
@ -143,12 +139,6 @@ class FilePage extends React.Component<Props> {
|
||||||
const mediaType = getMediaType(contentType, fileName);
|
const mediaType = getMediaType(contentType, fileName);
|
||||||
const showFile =
|
const showFile =
|
||||||
PLAYABLE_MEDIA_TYPES.includes(mediaType) || PREVIEW_MEDIA_TYPES.includes(mediaType);
|
PLAYABLE_MEDIA_TYPES.includes(mediaType) || PREVIEW_MEDIA_TYPES.includes(mediaType);
|
||||||
const channelClaimId =
|
|
||||||
value && value.publisherSignature && value.publisherSignature.certificateId;
|
|
||||||
let subscriptionUri;
|
|
||||||
if (channelName && channelClaimId) {
|
|
||||||
subscriptionUri = buildURI({ channelName, claimId: channelClaimId }, false);
|
|
||||||
}
|
|
||||||
const speechShareable =
|
const speechShareable =
|
||||||
costInfo &&
|
costInfo &&
|
||||||
costInfo.cost === 0 &&
|
costInfo.cost === 0 &&
|
||||||
|
@ -159,7 +149,10 @@ class FilePage extends React.Component<Props> {
|
||||||
// We will select the claim id before they publish
|
// We will select the claim id before they publish
|
||||||
let editUri;
|
let editUri;
|
||||||
if (claimIsMine) {
|
if (claimIsMine) {
|
||||||
const uriObject = { contentName: claim.name, claimId: claim.claim_id };
|
const uriObject: { contentName: string, claimId: string, channelName: ?string } = {
|
||||||
|
contentName: claim.name,
|
||||||
|
claimId: claim.claim_id,
|
||||||
|
};
|
||||||
if (channelName) {
|
if (channelName) {
|
||||||
uriObject.channelName = channelName;
|
uriObject.channelName = channelName;
|
||||||
}
|
}
|
||||||
|
@ -193,7 +186,7 @@ class FilePage extends React.Component<Props> {
|
||||||
{isRewardContent && (
|
{isRewardContent && (
|
||||||
<Icon size={20} iconColor="red" tooltip="bottom" icon={icons.FEATURED} />
|
<Icon size={20} iconColor="red" tooltip="bottom" icon={icons.FEATURED} />
|
||||||
)}
|
)}
|
||||||
<FilePrice filePage uri={normalizeURI(uri)} />
|
<FilePrice badge uri={normalizeURI(uri)} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<span className="card__subtitle">
|
<span className="card__subtitle">
|
||||||
|
@ -214,7 +207,7 @@ class FilePage extends React.Component<Props> {
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<SubscribeButton uri={subscriptionUri} channelName={channelName} />
|
<SubscribeButton uri={channelUri} channelName={channelName} />
|
||||||
)}
|
)}
|
||||||
{!claimIsMine && (
|
{!claimIsMine && (
|
||||||
<Button
|
<Button
|
||||||
|
|
|
@ -21,7 +21,7 @@ class FileListDownloaded extends React.PureComponent<Props> {
|
||||||
<FileList fileInfos={fileInfos} />
|
<FileList fileInfos={fileInfos} />
|
||||||
) : (
|
) : (
|
||||||
<div className="page__empty">
|
<div className="page__empty">
|
||||||
{__("You haven't downloaded anything from LBRY yet.")}
|
<h3 className="card__title">{__("You haven't downloaded anything from LBRY yet.")}</h3>
|
||||||
<div className="card__actions card__actions--center">
|
<div className="card__actions card__actions--center">
|
||||||
<Button
|
<Button
|
||||||
button="primary"
|
button="primary"
|
||||||
|
|
|
@ -29,7 +29,9 @@ class FileListPublished extends React.PureComponent<Props> {
|
||||||
<FileList checkPending fileInfos={claims} sortByHeight />
|
<FileList checkPending fileInfos={claims} sortByHeight />
|
||||||
) : (
|
) : (
|
||||||
<div className="page__empty">
|
<div className="page__empty">
|
||||||
|
<h3 className="card__title">
|
||||||
{__("It looks like you haven't published anything to LBRY yet.")}
|
{__("It looks like you haven't published anything to LBRY yet.")}
|
||||||
|
</h3>
|
||||||
<div className="card__actions card__actions--center">
|
<div className="card__actions card__actions--center">
|
||||||
<Button
|
<Button
|
||||||
button="primary"
|
button="primary"
|
||||||
|
|
|
@ -184,11 +184,13 @@ class SettingsPage extends React.PureComponent<Props, State> {
|
||||||
<section className="card card--section">
|
<section className="card card--section">
|
||||||
<div className="card__title">{__('Download Directory')}</div>
|
<div className="card__title">{__('Download Directory')}</div>
|
||||||
<span className="card__subtitle">{__('LBRY downloads will be saved here.')}</span>
|
<span className="card__subtitle">{__('LBRY downloads will be saved here.')}</span>
|
||||||
|
<div className="card__content">
|
||||||
<FileSelector
|
<FileSelector
|
||||||
type="openDirectory"
|
type="openDirectory"
|
||||||
currentPath={daemonSettings.download_directory}
|
currentPath={daemonSettings.download_directory}
|
||||||
onFileChosen={this.onDownloadDirChange}
|
onFileChosen={this.onDownloadDirChange}
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
</section>
|
</section>
|
||||||
<section className="card card--section">
|
<section className="card card--section">
|
||||||
<div className="card__title">{__('Max Purchase Price')}</div>
|
<div className="card__title">{__('Max Purchase Price')}</div>
|
||||||
|
|
|
@ -5,9 +5,14 @@ import {
|
||||||
selectSubscriptions,
|
selectSubscriptions,
|
||||||
selectSubscriptionsBeingFetched,
|
selectSubscriptionsBeingFetched,
|
||||||
selectIsFetchingSubscriptions,
|
selectIsFetchingSubscriptions,
|
||||||
selectNotifications,
|
selectUnreadSubscriptions,
|
||||||
|
selectViewMode,
|
||||||
} from 'redux/selectors/subscriptions';
|
} from 'redux/selectors/subscriptions';
|
||||||
import { setSubscriptionNotifications, doFetchMySubscriptions } from 'redux/actions/subscriptions';
|
import {
|
||||||
|
doUpdateUnreadSubscriptions,
|
||||||
|
doFetchMySubscriptions,
|
||||||
|
doSetViewMode,
|
||||||
|
} from 'redux/actions/subscriptions';
|
||||||
import { doSetClientSetting } from 'redux/actions/settings';
|
import { doSetClientSetting } from 'redux/actions/settings';
|
||||||
import { makeSelectClientSetting } from 'redux/selectors/settings';
|
import { makeSelectClientSetting } from 'redux/selectors/settings';
|
||||||
import SubscriptionsPage from './view';
|
import SubscriptionsPage from './view';
|
||||||
|
@ -16,17 +21,19 @@ const select = state => ({
|
||||||
loading:
|
loading:
|
||||||
selectIsFetchingSubscriptions(state) ||
|
selectIsFetchingSubscriptions(state) ||
|
||||||
Boolean(Object.keys(selectSubscriptionsBeingFetched(state)).length),
|
Boolean(Object.keys(selectSubscriptionsBeingFetched(state)).length),
|
||||||
subscriptions: selectSubscriptions(state),
|
subscribedChannels: selectSubscriptions(state),
|
||||||
subscriptionClaims: selectSubscriptionClaims(state),
|
|
||||||
notifications: selectNotifications(state),
|
|
||||||
autoDownload: makeSelectClientSetting(settings.AUTO_DOWNLOAD)(state),
|
autoDownload: makeSelectClientSetting(settings.AUTO_DOWNLOAD)(state),
|
||||||
|
allSubscriptions: selectSubscriptionClaims(state),
|
||||||
|
unreadSubscriptions: selectUnreadSubscriptions(state),
|
||||||
|
viewMode: selectViewMode(state),
|
||||||
});
|
});
|
||||||
|
|
||||||
export default connect(
|
export default connect(
|
||||||
select,
|
select,
|
||||||
{
|
{
|
||||||
setSubscriptionNotifications,
|
doUpdateUnreadSubscriptions,
|
||||||
doFetchMySubscriptions,
|
doFetchMySubscriptions,
|
||||||
doSetClientSetting,
|
doSetClientSetting,
|
||||||
|
doSetViewMode,
|
||||||
}
|
}
|
||||||
)(SubscriptionsPage);
|
)(SubscriptionsPage);
|
||||||
|
|
|
@ -1,83 +1,153 @@
|
||||||
// @flow
|
// @flow
|
||||||
import React from 'react';
|
import type { ViewMode } from 'types/subscription';
|
||||||
import Page from 'component/page';
|
import type { Claim } from 'types/claim';
|
||||||
|
import { VIEW_ALL, VIEW_LATEST_FIRST } from 'constants/subscriptions';
|
||||||
import * as settings from 'constants/settings';
|
import * as settings from 'constants/settings';
|
||||||
import type { Subscription } from 'types/subscription';
|
import * as React from 'react';
|
||||||
import * as NOTIFICATION_TYPES from 'constants/notification_types';
|
import Page from 'component/page';
|
||||||
import Button from 'component/button';
|
import Button from 'component/button';
|
||||||
import FileList from 'component/fileList';
|
import FileList from 'component/fileList';
|
||||||
import type { Claim } from 'types/claim';
|
|
||||||
import HiddenNsfwClaims from 'component/hiddenNsfwClaims';
|
import HiddenNsfwClaims from 'component/hiddenNsfwClaims';
|
||||||
import { FormField, FormRow } from 'component/common/form';
|
import { FormField } from 'component/common/form';
|
||||||
|
import FileCard from 'component/fileCard';
|
||||||
|
import { parseURI } from 'lbry-redux';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
doFetchMySubscriptions: () => void,
|
subscribedChannels: Array<string>, // The channels a user is subscribed to
|
||||||
setSubscriptionNotifications: ({}) => void,
|
unreadSubscriptions: Array<{
|
||||||
subscriptions: Array<Subscription>,
|
channel: string,
|
||||||
subscriptionClaims: Array<{ uri: string, claims: Array<Claim> }>,
|
uris: Array<string>,
|
||||||
notifications: {},
|
}>,
|
||||||
|
allSubscriptions: Array<{ uri: string, ...Claim }>,
|
||||||
loading: boolean,
|
loading: boolean,
|
||||||
autoDownload: boolean,
|
autoDownload: boolean,
|
||||||
|
viewMode: ViewMode,
|
||||||
|
doSetViewMode: ViewMode => void,
|
||||||
|
doFetchMySubscriptions: () => void,
|
||||||
doSetClientSetting: (string, boolean) => void,
|
doSetClientSetting: (string, boolean) => void,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default class extends React.PureComponent<Props> {
|
export default class extends React.PureComponent<Props> {
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
(this: any).onAutoDownloadChange = this.onAutoDownloadChange.bind(this);
|
(this: any).onAutoDownloadChange = this.onAutoDownloadChange.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
const { notifications, setSubscriptionNotifications, doFetchMySubscriptions } = this.props;
|
const { doFetchMySubscriptions } = this.props;
|
||||||
doFetchMySubscriptions();
|
doFetchMySubscriptions();
|
||||||
|
|
||||||
// @sean will change this behavior when implementing new content labeling
|
|
||||||
// notifications should be cleared individually
|
|
||||||
// do we want a way to clear individual claims without viewing?
|
|
||||||
const newNotifications = {};
|
|
||||||
Object.keys(notifications).forEach(cur => {
|
|
||||||
if (notifications[cur].type === NOTIFICATION_TYPES.DOWNLOADING) {
|
|
||||||
newNotifications[cur] = { ...notifications[cur] };
|
|
||||||
}
|
|
||||||
});
|
|
||||||
setSubscriptionNotifications(newNotifications);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onAutoDownloadChange(event: SyntheticInputEvent<*>) {
|
onAutoDownloadChange(event: SyntheticInputEvent<*>) {
|
||||||
this.props.doSetClientSetting(settings.AUTO_DOWNLOAD, event.target.checked);
|
this.props.doSetClientSetting(settings.AUTO_DOWNLOAD, event.target.checked);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
renderSubscriptions() {
|
||||||
|
const { viewMode, unreadSubscriptions, allSubscriptions } = this.props;
|
||||||
|
|
||||||
|
if (viewMode === VIEW_ALL) {
|
||||||
|
return (
|
||||||
|
<React.Fragment>
|
||||||
|
<div className="card__title">{__('Your subscriptions')}</div>
|
||||||
|
<FileList hideFilter sortByHeight fileInfos={allSubscriptions} />
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<React.Fragment>
|
||||||
|
{unreadSubscriptions.length ? (
|
||||||
|
unreadSubscriptions.map(({ channel, uris }) => {
|
||||||
|
const { claimName } = parseURI(channel);
|
||||||
|
return (
|
||||||
|
<section key={channel}>
|
||||||
|
<div className="card__title">
|
||||||
|
<Button
|
||||||
|
button="link"
|
||||||
|
navigate="/show"
|
||||||
|
navigateParams={{ uri: channel }}
|
||||||
|
label={claimName}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="card__list card__content">
|
||||||
|
{uris.map(uri => <FileCard isNew key={uri} uri={uri} />)}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
})
|
||||||
|
) : (
|
||||||
|
<div className="page__empty">
|
||||||
|
<h3 className="card__title">{__('You are all caught up!')}</h3>
|
||||||
|
<div className="card__actions">
|
||||||
|
<Button button="primary" navigate="/discover" label={__('Explore new content')} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { subscriptions, subscriptionClaims, loading, autoDownload } = this.props;
|
const {
|
||||||
|
subscribedChannels,
|
||||||
let claimList = [];
|
allSubscriptions,
|
||||||
subscriptionClaims.forEach(claimData => {
|
loading,
|
||||||
claimList = claimList.concat(claimData.claims);
|
autoDownload,
|
||||||
});
|
viewMode,
|
||||||
|
doSetViewMode,
|
||||||
const subscriptionUris = claimList.map(claim => `lbry://${claim.name}#${claim.claim_id}`);
|
} = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Page notContained loading={loading}>
|
// Only pass in the loading prop if there are no subscriptions
|
||||||
<HiddenNsfwClaims uris={subscriptionUris} />
|
// If there are any, let the page update in the background
|
||||||
<FormRow alignRight>
|
// The loading prop removes children and shows a loading spinner
|
||||||
|
<Page notContained loading={loading && !subscribedChannels}>
|
||||||
|
<HiddenNsfwClaims
|
||||||
|
uris={allSubscriptions.reduce((arr, { name, claim_id: claimId }) => {
|
||||||
|
if (name && claimId) {
|
||||||
|
arr.push(`lbry://${name}#${claimId}`);
|
||||||
|
}
|
||||||
|
return arr;
|
||||||
|
}, [])}
|
||||||
|
/>
|
||||||
|
{!!subscribedChannels.length && (
|
||||||
|
<div className="card--space-between">
|
||||||
|
<div className="card__actions card__actions--no-margin">
|
||||||
|
<Button
|
||||||
|
disabled={viewMode === VIEW_ALL}
|
||||||
|
button="link"
|
||||||
|
label="All Subscriptions"
|
||||||
|
onClick={() => doSetViewMode(VIEW_ALL)}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
button="link"
|
||||||
|
disabled={viewMode === VIEW_LATEST_FIRST}
|
||||||
|
label={__('Latest Only')}
|
||||||
|
onClick={() => doSetViewMode(VIEW_LATEST_FIRST)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
<FormField
|
<FormField
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
name="auto_download"
|
name="auto_download"
|
||||||
onChange={this.onAutoDownloadChange}
|
onChange={this.onAutoDownloadChange}
|
||||||
checked={autoDownload}
|
checked={autoDownload}
|
||||||
prefix={__('Automatically download new content from your subscriptions')}
|
prefix={__('Auto download')}
|
||||||
/>
|
/>
|
||||||
</FormRow>
|
</div>
|
||||||
{!subscriptions.length && (
|
)}
|
||||||
|
{!subscribedChannels.length && (
|
||||||
<div className="page__empty">
|
<div className="page__empty">
|
||||||
|
<h3 className="card__title">
|
||||||
{__("It looks like you aren't subscribed to any channels yet.")}
|
{__("It looks like you aren't subscribed to any channels yet.")}
|
||||||
<div className="card__actions card__actions--center">
|
</h3>
|
||||||
|
<div className="card__actions">
|
||||||
<Button button="primary" navigate="/discover" label={__('Explore new content')} />
|
<Button button="primary" navigate="/discover" label={__('Explore new content')} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{!!claimList.length && <FileList hideFilter sortByHeight fileInfos={claimList} />}
|
{!!subscribedChannels.length && (
|
||||||
|
<div className="card__content">{this.renderSubscriptions()}</div>
|
||||||
|
)}
|
||||||
</Page>
|
</Page>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
// @flow
|
// @flow
|
||||||
import * as NOTIFICATION_TYPES from 'constants/notification_types';
|
import * as NOTIFICATION_TYPES from 'constants/subscriptions';
|
||||||
import { ipcRenderer } from 'electron';
|
import { ipcRenderer } from 'electron';
|
||||||
import { doAlertError } from 'redux/actions/app';
|
import { doAlertError } from 'redux/actions/app';
|
||||||
import { doNavigate } from 'redux/actions/navigation';
|
import { doNavigate } from 'redux/actions/navigation';
|
||||||
import { setSubscriptionLatest, setSubscriptionNotification } from 'redux/actions/subscriptions';
|
import { setSubscriptionLatest, doUpdateUnreadSubscriptions } from 'redux/actions/subscriptions';
|
||||||
import { selectNotifications } from 'redux/selectors/subscriptions';
|
import { makeSelectUnreadByChannel } from 'redux/selectors/subscriptions';
|
||||||
import { selectBadgeNumber } from 'redux/selectors/app';
|
import { selectBadgeNumber } from 'redux/selectors/app';
|
||||||
import {
|
import {
|
||||||
ACTIONS,
|
ACTIONS,
|
||||||
|
@ -21,6 +21,8 @@ import {
|
||||||
selectBalance,
|
selectBalance,
|
||||||
MODALS,
|
MODALS,
|
||||||
doNotify,
|
doNotify,
|
||||||
|
makeSelectChannelForClaimUri,
|
||||||
|
parseURI,
|
||||||
} from 'lbry-redux';
|
} from 'lbry-redux';
|
||||||
import { makeSelectClientSetting, selectosNotificationsEnabled } from 'redux/selectors/settings';
|
import { makeSelectClientSetting, selectosNotificationsEnabled } from 'redux/selectors/settings';
|
||||||
import setBadge from 'util/setBadge';
|
import setBadge from 'util/setBadge';
|
||||||
|
@ -66,19 +68,15 @@ export function doUpdateLoadStatus(uri: string, outpoint: string) {
|
||||||
const totalProgress = selectTotalDownloadProgress(state);
|
const totalProgress = selectTotalDownloadProgress(state);
|
||||||
setProgressBar(totalProgress);
|
setProgressBar(totalProgress);
|
||||||
|
|
||||||
const notifications = selectNotifications(state);
|
const channelUri = makeSelectChannelForClaimUri(uri, true)(state);
|
||||||
if (notifications[uri] && notifications[uri].type === NOTIFICATION_TYPES.DOWNLOADING) {
|
const { claimName: channelName } = parseURI(channelUri);
|
||||||
const count = Object.keys(notifications).reduce(
|
|
||||||
(acc, cur) =>
|
const unreadForChannel = makeSelectUnreadByChannel(channelUri)(state);
|
||||||
notifications[cur].subscription.channelName ===
|
if (unreadForChannel.type === NOTIFICATION_TYPES.DOWNLOADING) {
|
||||||
notifications[uri].subscription.channelName
|
const count = unreadForChannel.uris.length;
|
||||||
? acc + 1
|
|
||||||
: acc,
|
|
||||||
0
|
|
||||||
);
|
|
||||||
|
|
||||||
if (selectosNotificationsEnabled(state)) {
|
if (selectosNotificationsEnabled(state)) {
|
||||||
const notif = new window.Notification(notifications[uri].subscription.channelName, {
|
const notif = new window.Notification(channelName, {
|
||||||
body: `Posted ${fileInfo.metadata.title}${
|
body: `Posted ${fileInfo.metadata.title}${
|
||||||
count > 1 && count < 10 ? ` and ${count - 1} other new items` : ''
|
count > 1 && count < 10 ? ` and ${count - 1} other new items` : ''
|
||||||
}${count > 9 ? ' and 9+ other new items' : ''}`,
|
}${count > 9 ? ' and 9+ other new items' : ''}`,
|
||||||
|
@ -92,18 +90,12 @@ export function doUpdateLoadStatus(uri: string, outpoint: string) {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
if (state.navigation.currentPath !== '/subscriptions') {
|
|
||||||
dispatch(
|
dispatch(doUpdateUnreadSubscriptions(channelUri, null, NOTIFICATION_TYPES.DOWNLOADED));
|
||||||
setSubscriptionNotification(
|
|
||||||
notifications[uri].subscription,
|
|
||||||
uri,
|
|
||||||
NOTIFICATION_TYPES.DOWNLOADED
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
// If notifications are disabled(false) just return
|
// If notifications are disabled(false) just return
|
||||||
if (!selectosNotificationsEnabled(getState())) return;
|
if (!selectosNotificationsEnabled(getState())) return;
|
||||||
|
|
||||||
const notif = new window.Notification('LBRY Download Complete', {
|
const notif = new window.Notification('LBRY Download Complete', {
|
||||||
body: fileInfo.metadata.title,
|
body: fileInfo.metadata.title,
|
||||||
silent: false,
|
silent: false,
|
||||||
|
@ -293,14 +285,14 @@ export function doPurchaseUri(uri, specificCostInfo, shouldRecordViewEvent) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function doFetchClaimsByChannel(uri, page) {
|
export function doFetchClaimsByChannel(uri, page, pageSize) {
|
||||||
return dispatch => {
|
return dispatch => {
|
||||||
dispatch({
|
dispatch({
|
||||||
type: ACTIONS.FETCH_CHANNEL_CLAIMS_STARTED,
|
type: ACTIONS.FETCH_CHANNEL_CLAIMS_STARTED,
|
||||||
data: { uri, page },
|
data: { uri, page },
|
||||||
});
|
});
|
||||||
|
|
||||||
Lbry.claim_list_by_channel({ uri, page: page || 1, page_size: 48 }).then(result => {
|
Lbry.claim_list_by_channel({ uri, page: page || 1, page_size: pageSize || 48 }).then(result => {
|
||||||
const claimResult = result[uri] || {};
|
const claimResult = result[uri] || {};
|
||||||
const { claims_in_channel: claimsInChannel, returned_page: returnedPage } = claimResult;
|
const { claims_in_channel: claimsInChannel, returned_page: returnedPage } = claimResult;
|
||||||
|
|
||||||
|
@ -321,18 +313,6 @@ export function doFetchClaimsByChannel(uri, page) {
|
||||||
buildURI({ contentName: latest.name, claimId: latest.claim_id }, false)
|
buildURI({ contentName: latest.name, claimId: latest.claim_id }, false)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
// commented out as a note for @sean, notification will be clared individually
|
|
||||||
// const notifications = selectNotifications(getState());
|
|
||||||
// const newNotifications = {};
|
|
||||||
// Object.keys(notifications).forEach(cur => {
|
|
||||||
// if (
|
|
||||||
// notifications[cur].subscription.channelName !== latest.channel_name ||
|
|
||||||
// notifications[cur].type === NOTIFICATION_TYPES.DOWNLOADING
|
|
||||||
// ) {
|
|
||||||
// newNotifications[cur] = { ...notifications[cur] };
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
// dispatch(setSubscriptionNotifications(newNotifications));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
dispatch({
|
dispatch({
|
||||||
|
|
|
@ -1,26 +1,36 @@
|
||||||
// @flow
|
// @flow
|
||||||
|
import type { GetState } from 'types/redux';
|
||||||
|
import type {
|
||||||
|
Dispatch as ReduxDispatch,
|
||||||
|
SubscriptionState,
|
||||||
|
Subscription,
|
||||||
|
SubscriptionNotificationType,
|
||||||
|
ViewMode,
|
||||||
|
UnreadSubscription,
|
||||||
|
} from 'types/subscription';
|
||||||
import * as ACTIONS from 'constants/action_types';
|
import * as ACTIONS from 'constants/action_types';
|
||||||
import * as NOTIFICATION_TYPES from 'constants/notification_types';
|
|
||||||
import * as SETTINGS from 'constants/settings';
|
import * as SETTINGS from 'constants/settings';
|
||||||
|
import * as NOTIFICATION_TYPES from 'constants/subscriptions';
|
||||||
import { Lbryio, rewards, doClaimRewardType } from 'lbryinc';
|
import { Lbryio, rewards, doClaimRewardType } from 'lbryinc';
|
||||||
import type { Dispatch, SubscriptionNotifications } from 'redux/reducers/subscriptions';
|
import { selectSubscriptions, selectUnreadByChannel } from 'redux/selectors/subscriptions';
|
||||||
import type { Subscription } from 'types/subscription';
|
|
||||||
import { selectSubscriptions } from 'redux/selectors/subscriptions';
|
|
||||||
import { makeSelectClientSetting } from 'redux/selectors/settings';
|
import { makeSelectClientSetting } from 'redux/selectors/settings';
|
||||||
import { Lbry, buildURI, parseURI, selectCurrentPage } from 'lbry-redux';
|
import { Lbry, buildURI, parseURI } from 'lbry-redux';
|
||||||
import { doPurchaseUri, doFetchClaimsByChannel } from 'redux/actions/content';
|
import { doPurchaseUri, doFetchClaimsByChannel } from 'redux/actions/content';
|
||||||
import Promise from 'bluebird';
|
import Promise from 'bluebird';
|
||||||
|
|
||||||
const CHECK_SUBSCRIPTIONS_INTERVAL = 15 * 60 * 1000;
|
const CHECK_SUBSCRIPTIONS_INTERVAL = 15 * 60 * 1000;
|
||||||
const SUBSCRIPTION_DOWNLOAD_LIMIT = 1;
|
const SUBSCRIPTION_DOWNLOAD_LIMIT = 1;
|
||||||
|
|
||||||
export const doFetchMySubscriptions = () => (dispatch: Dispatch, getState: () => any) => {
|
export const doSetViewMode = (viewMode: ViewMode) => (dispatch: ReduxDispatch) =>
|
||||||
const {
|
dispatch({
|
||||||
subscriptions: subscriptionState,
|
type: ACTIONS.SET_VIEW_MODE,
|
||||||
settings: { daemonSettings },
|
data: viewMode,
|
||||||
} = getState();
|
});
|
||||||
const { subscriptions: reduxSubscriptions } = subscriptionState;
|
|
||||||
const { share_usage_data: isSharingData } = daemonSettings;
|
export const doFetchMySubscriptions = () => (dispatch: ReduxDispatch, getState: GetState) => {
|
||||||
|
const state: { subscriptions: SubscriptionState, settings: any } = getState();
|
||||||
|
const { subscriptions: reduxSubscriptions } = state.subscriptions;
|
||||||
|
const { share_usage_data: isSharingData } = state.settings.daemonSettings;
|
||||||
|
|
||||||
if (!isSharingData && isSharingData !== undefined) {
|
if (!isSharingData && isSharingData !== undefined) {
|
||||||
// They aren't sharing their data, subscriptions will be handled by persisted redux state
|
// They aren't sharing their data, subscriptions will be handled by persisted redux state
|
||||||
|
@ -84,13 +94,13 @@ export const doFetchMySubscriptions = () => (dispatch: Dispatch, getState: () =>
|
||||||
// DB is already synced, just return the subscriptions in redux
|
// DB is already synced, just return the subscriptions in redux
|
||||||
return reduxSubscriptions;
|
return reduxSubscriptions;
|
||||||
})
|
})
|
||||||
.then(subscriptions => {
|
.then((subscriptions: Array<Subscription>) => {
|
||||||
dispatch({
|
dispatch({
|
||||||
type: ACTIONS.FETCH_SUBSCRIPTIONS_SUCCESS,
|
type: ACTIONS.FETCH_SUBSCRIPTIONS_SUCCESS,
|
||||||
data: subscriptions,
|
data: subscriptions,
|
||||||
});
|
});
|
||||||
|
|
||||||
subscriptions.forEach(({ uri }) => dispatch(doFetchClaimsByChannel(uri)));
|
subscriptions.forEach(({ uri }) => dispatch(doFetchClaimsByChannel(uri, 1, 20)));
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
dispatch({
|
dispatch({
|
||||||
|
@ -100,7 +110,7 @@ export const doFetchMySubscriptions = () => (dispatch: Dispatch, getState: () =>
|
||||||
};
|
};
|
||||||
|
|
||||||
export const setSubscriptionLatest = (subscription: Subscription, uri: string) => (
|
export const setSubscriptionLatest = (subscription: Subscription, uri: string) => (
|
||||||
dispatch: Dispatch
|
dispatch: ReduxDispatch
|
||||||
) =>
|
) =>
|
||||||
dispatch({
|
dispatch({
|
||||||
type: ACTIONS.SET_SUBSCRIPTION_LATEST,
|
type: ACTIONS.SET_SUBSCRIPTION_LATEST,
|
||||||
|
@ -110,75 +120,160 @@ export const setSubscriptionLatest = (subscription: Subscription, uri: string) =
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export const setSubscriptionNotification = (
|
// Populate a channels unread subscriptions or update the type
|
||||||
subscription: Subscription,
|
export const doUpdateUnreadSubscriptions = (
|
||||||
uri: string,
|
channelUri: string,
|
||||||
notificationType: string
|
uris: ?Array<string>,
|
||||||
) => (dispatch: Dispatch) =>
|
type: ?SubscriptionNotificationType
|
||||||
|
) => (dispatch: ReduxDispatch, getState: GetState) => {
|
||||||
|
const state = getState();
|
||||||
|
const unreadByChannel = selectUnreadByChannel(state);
|
||||||
|
const currentUnreadForChannel: UnreadSubscription = unreadByChannel[channelUri];
|
||||||
|
|
||||||
|
let newUris;
|
||||||
|
let newType;
|
||||||
|
|
||||||
|
if (!currentUnreadForChannel) {
|
||||||
|
newUris = uris;
|
||||||
|
newType = type;
|
||||||
|
} else {
|
||||||
|
if (uris) {
|
||||||
|
// If a channel currently has no unread uris, just add them all
|
||||||
|
if (!currentUnreadForChannel.uris || !currentUnreadForChannel.uris.length) {
|
||||||
|
newUris = uris;
|
||||||
|
} else {
|
||||||
|
// They already have unreads and now there are new ones
|
||||||
|
// Add the new ones to the beginning of the list
|
||||||
|
// Make sure there are no duplicates
|
||||||
|
const currentUnreadUris = currentUnreadForChannel.uris;
|
||||||
|
newUris = uris.filter(uri => !currentUnreadUris.includes(uri)).concat(currentUnreadUris);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
newUris = currentUnreadForChannel.uris;
|
||||||
|
}
|
||||||
|
|
||||||
|
newType = type || currentUnreadForChannel.type;
|
||||||
|
}
|
||||||
|
|
||||||
dispatch({
|
dispatch({
|
||||||
type: ACTIONS.SET_SUBSCRIPTION_NOTIFICATION,
|
type: ACTIONS.UPDATE_SUBSCRIPTION_UNREADS,
|
||||||
data: {
|
data: {
|
||||||
subscription,
|
channel: channelUri,
|
||||||
uri,
|
uris: newUris,
|
||||||
type: notificationType,
|
type: newType,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
};
|
||||||
|
|
||||||
export const doCheckSubscription = (subscriptionUri: string, notify?: boolean) => (
|
// Remove multiple files (or all) from a channels unread subscriptions
|
||||||
dispatch: Dispatch,
|
export const doRemoveUnreadSubscriptions = (channelUri: string, readUris: Array<string>) => (
|
||||||
getState: () => {}
|
dispatch: ReduxDispatch,
|
||||||
|
getState: GetState
|
||||||
|
) => {
|
||||||
|
const state = getState();
|
||||||
|
const unreadByChannel = selectUnreadByChannel(state);
|
||||||
|
const currentChannelUnread = unreadByChannel[channelUri];
|
||||||
|
if (!currentChannelUnread || !currentChannelUnread.uris) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// For each uri passed in, remove it from the list of unread uris
|
||||||
|
const urisToRemoveMap = readUris.reduce(
|
||||||
|
(acc, val) => ({
|
||||||
|
...acc,
|
||||||
|
[val]: true,
|
||||||
|
}),
|
||||||
|
{}
|
||||||
|
);
|
||||||
|
|
||||||
|
const filteredUris = currentChannelUnread.uris.filter(uri => !urisToRemoveMap[uri]);
|
||||||
|
const newUris = filteredUris.length ? filteredUris : null;
|
||||||
|
|
||||||
|
dispatch({
|
||||||
|
type: ACTIONS.REMOVE_SUBSCRIPTION_UNREADS,
|
||||||
|
data: {
|
||||||
|
channel: channelUri,
|
||||||
|
uris: newUris,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// Remove a single file from a channels unread subscriptions
|
||||||
|
export const doRemoveUnreadSubscription = (channelUri: string, readUri: string) => (
|
||||||
|
dispatch: ReduxDispatch
|
||||||
|
) => {
|
||||||
|
dispatch(doRemoveUnreadSubscriptions(channelUri, [readUri]));
|
||||||
|
};
|
||||||
|
|
||||||
|
export const doCheckSubscription = (subscriptionUri: string, shouldNotify?: boolean) => async (
|
||||||
|
dispatch: ReduxDispatch,
|
||||||
|
getState: GetState
|
||||||
) => {
|
) => {
|
||||||
// no dispatching FETCH_CHANNEL_CLAIMS_STARTED; causes loading issues on <SubscriptionsPage>
|
// no dispatching FETCH_CHANNEL_CLAIMS_STARTED; causes loading issues on <SubscriptionsPage>
|
||||||
|
|
||||||
const state = getState();
|
const state = getState();
|
||||||
const currentPage = selectCurrentPage(state);
|
const shouldAutoDownload = makeSelectClientSetting(SETTINGS.AUTO_DOWNLOAD)(state);
|
||||||
const savedSubscription = state.subscriptions.subscriptions.find(
|
const savedSubscription = state.subscriptions.subscriptions.find(
|
||||||
sub => sub.uri === subscriptionUri
|
sub => sub.uri === subscriptionUri
|
||||||
);
|
);
|
||||||
|
|
||||||
Lbry.claim_list_by_channel({ uri: subscriptionUri, page: 1 }).then(result => {
|
if (!savedSubscription) {
|
||||||
const claimResult = result[subscriptionUri] || {};
|
throw Error(
|
||||||
|
`Trying to find new content for ${subscriptionUri} but it doesn't exist in your subscriptions`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const claimListByChannel = await Lbry.claim_list_by_channel({ uri: subscriptionUri, page: 1 });
|
||||||
|
const claimResult = claimListByChannel[subscriptionUri] || {};
|
||||||
const { claims_in_channel: claimsInChannel } = claimResult;
|
const { claims_in_channel: claimsInChannel } = claimResult;
|
||||||
|
|
||||||
// may happen if subscribed to an abandoned channel or an empty channel
|
// may happen if subscribed to an abandoned channel or an empty channel
|
||||||
if (!claimsInChannel) {
|
if (!claimsInChannel || !claimsInChannel.length) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Determine if the latest subscription currently saved is actually the latest subscription
|
||||||
const latestIndex = claimsInChannel.findIndex(
|
const latestIndex = claimsInChannel.findIndex(
|
||||||
claim => `${claim.name}#${claim.claim_id}` === savedSubscription.latest
|
claim => `${claim.name}#${claim.claim_id}` === savedSubscription.latest
|
||||||
);
|
);
|
||||||
|
|
||||||
// if latest is 0, nothing has changed
|
// If latest is -1, it is a newly subscribed channel or there have been 10+ claims published since last viewed
|
||||||
// when there is no subscription latest, it is either a newly subscriubed channel or
|
const latestIndexToNotify = latestIndex === -1 ? 10 : latestIndex;
|
||||||
// the user has cleared their cache. Either way, do not download or notify about new content
|
|
||||||
// as that would download/notify 10 claims per channel
|
// If latest is 0, nothing has changed
|
||||||
if (claimsInChannel.length && latestIndex !== 0 && savedSubscription.latest) {
|
// Do not download/notify about new content, it would download/notify 10 claims per channel
|
||||||
|
if (latestIndex !== 0 && savedSubscription.latest) {
|
||||||
let downloadCount = 0;
|
let downloadCount = 0;
|
||||||
claimsInChannel.slice(0, latestIndex === -1 ? 10 : latestIndex).forEach(claim => {
|
|
||||||
const uri = buildURI({ contentName: claim.name, claimId: claim.claim_id }, false);
|
const newUnread = [];
|
||||||
const shouldDownload = Boolean(
|
claimsInChannel.slice(0, latestIndexToNotify).forEach(claim => {
|
||||||
downloadCount < SUBSCRIPTION_DOWNLOAD_LIMIT &&
|
const uri = buildURI({ contentName: claim.name, claimId: claim.claim_id }, true);
|
||||||
!claim.value.stream.metadata.fee &&
|
const shouldDownload =
|
||||||
makeSelectClientSetting(SETTINGS.AUTO_DOWNLOAD)(state)
|
shouldAutoDownload &&
|
||||||
);
|
Boolean(downloadCount < SUBSCRIPTION_DOWNLOAD_LIMIT && !claim.value.stream.metadata.fee);
|
||||||
if (notify && currentPage !== 'subscriptions') {
|
|
||||||
dispatch(
|
// Add the new content to the list of "un-read" subscriptions
|
||||||
setSubscriptionNotification(
|
if (shouldNotify) {
|
||||||
savedSubscription,
|
newUnread.push(uri);
|
||||||
uri,
|
|
||||||
shouldDownload ? NOTIFICATION_TYPES.DOWNLOADING : NOTIFICATION_TYPES.NOTIFY_ONLY
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (shouldDownload) {
|
if (shouldDownload) {
|
||||||
downloadCount += 1;
|
downloadCount += 1;
|
||||||
dispatch(doPurchaseUri(uri, { cost: 0 }, true));
|
dispatch(doPurchaseUri(uri, { cost: 0 }, true));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
dispatch(
|
||||||
|
doUpdateUnreadSubscriptions(
|
||||||
|
subscriptionUri,
|
||||||
|
newUnread,
|
||||||
|
downloadCount > 0 ? NOTIFICATION_TYPES.DOWNLOADING : NOTIFICATION_TYPES.NOTIFY_ONLY
|
||||||
|
)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// always setLatest; important for newly subscribed channels
|
// Set the latest piece of content for a channel
|
||||||
|
// This allows the app to know if there has been new content since it was last set
|
||||||
dispatch(
|
dispatch(
|
||||||
setSubscriptionLatest(
|
setSubscriptionLatest(
|
||||||
{
|
{
|
||||||
|
@ -208,22 +303,11 @@ export const doCheckSubscription = (subscriptionUri: string, notify?: boolean) =
|
||||||
page: 1,
|
page: 1,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const setSubscriptionNotifications = (notifications: SubscriptionNotifications) => (
|
|
||||||
dispatch: Dispatch
|
|
||||||
) =>
|
|
||||||
dispatch({
|
|
||||||
type: ACTIONS.SET_SUBSCRIPTION_NOTIFICATIONS,
|
|
||||||
data: {
|
|
||||||
notifications,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
export const doChannelSubscribe = (subscription: Subscription) => (
|
export const doChannelSubscribe = (subscription: Subscription) => (
|
||||||
dispatch: Dispatch,
|
dispatch: ReduxDispatch,
|
||||||
getState: () => any
|
getState: GetState
|
||||||
) => {
|
) => {
|
||||||
const {
|
const {
|
||||||
settings: { daemonSettings },
|
settings: { daemonSettings },
|
||||||
|
@ -251,8 +335,8 @@ export const doChannelSubscribe = (subscription: Subscription) => (
|
||||||
};
|
};
|
||||||
|
|
||||||
export const doChannelUnsubscribe = (subscription: Subscription) => (
|
export const doChannelUnsubscribe = (subscription: Subscription) => (
|
||||||
dispatch: Dispatch,
|
dispatch: ReduxDispatch,
|
||||||
getState: () => any
|
getState: GetState
|
||||||
) => {
|
) => {
|
||||||
const {
|
const {
|
||||||
settings: { daemonSettings },
|
settings: { daemonSettings },
|
||||||
|
@ -272,15 +356,16 @@ export const doChannelUnsubscribe = (subscription: Subscription) => (
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const doCheckSubscriptions = () => (dispatch: Dispatch, getState: () => any) => {
|
export const doCheckSubscriptions = () => (dispatch: ReduxDispatch, getState: GetState) => {
|
||||||
const state = getState();
|
const state = getState();
|
||||||
const subscriptions = selectSubscriptions(state);
|
const subscriptions = selectSubscriptions(state);
|
||||||
|
|
||||||
subscriptions.forEach((sub: Subscription) => {
|
subscriptions.forEach((sub: Subscription) => {
|
||||||
dispatch(doCheckSubscription(sub.uri, true));
|
dispatch(doCheckSubscription(sub.uri, true));
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export const doCheckSubscriptionsInit = () => (dispatch: Dispatch) => {
|
export const doCheckSubscriptionsInit = () => (dispatch: ReduxDispatch) => {
|
||||||
// doCheckSubscriptionsInit is called by doDaemonReady
|
// doCheckSubscriptionsInit is called by doDaemonReady
|
||||||
// setTimeout below is a hack to ensure redux is hydrated when subscriptions are checked
|
// setTimeout below is a hack to ensure redux is hydrated when subscriptions are checked
|
||||||
// this will be replaced with <PersistGate> which reqiures a package upgrade
|
// this will be replaced with <PersistGate> which reqiures a package upgrade
|
||||||
|
|
|
@ -1,98 +1,31 @@
|
||||||
// @flow
|
// @flow
|
||||||
import * as ACTIONS from 'constants/action_types';
|
import * as ACTIONS from 'constants/action_types';
|
||||||
import * as NOTIFICATION_TYPES from 'constants/notification_types';
|
import { VIEW_ALL } from 'constants/subscriptions';
|
||||||
import { handleActions } from 'util/redux-utils';
|
import { handleActions } from 'util/redux-utils';
|
||||||
import type { Subscription } from 'types/subscription';
|
import type {
|
||||||
import type { Dispatch as ReduxDispatch } from 'types/redux';
|
SubscriptionState,
|
||||||
|
Subscription,
|
||||||
|
DoChannelSubscribe,
|
||||||
|
DoChannelUnsubscribe,
|
||||||
|
SetSubscriptionLatest,
|
||||||
|
DoUpdateSubscriptionUnreads,
|
||||||
|
DoRemoveSubscriptionUnreads,
|
||||||
|
FetchedSubscriptionsSucess,
|
||||||
|
SetViewMode,
|
||||||
|
} from 'types/subscription';
|
||||||
|
|
||||||
export type NotificationType =
|
const defaultState: SubscriptionState = {
|
||||||
| NOTIFICATION_TYPES.DOWNLOADING
|
|
||||||
| NOTIFICATION_TYPES.DOWNLOADED
|
|
||||||
| NOTIFICATION_TYPES.NOTIFY_ONLY;
|
|
||||||
|
|
||||||
export type SubscriptionNotifications = {
|
|
||||||
[string]: {
|
|
||||||
subscription: Subscription,
|
|
||||||
type: NotificationType,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
// Subscription redux types
|
|
||||||
export type SubscriptionState = {
|
|
||||||
subscriptions: Array<Subscription>,
|
|
||||||
notifications: SubscriptionNotifications,
|
|
||||||
loading: boolean,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Subscription action types
|
|
||||||
type doChannelSubscribe = {
|
|
||||||
type: ACTIONS.CHANNEL_SUBSCRIBE,
|
|
||||||
data: Subscription,
|
|
||||||
};
|
|
||||||
|
|
||||||
type doChannelUnsubscribe = {
|
|
||||||
type: ACTIONS.CHANNEL_UNSUBSCRIBE,
|
|
||||||
data: Subscription,
|
|
||||||
};
|
|
||||||
|
|
||||||
type setSubscriptionLatest = {
|
|
||||||
type: ACTIONS.SET_SUBSCRIPTION_LATEST,
|
|
||||||
data: {
|
|
||||||
subscription: Subscription,
|
|
||||||
uri: string,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
type setSubscriptionNotification = {
|
|
||||||
type: ACTIONS.SET_SUBSCRIPTION_NOTIFICATION,
|
|
||||||
data: {
|
|
||||||
subscription: Subscription,
|
|
||||||
uri: string,
|
|
||||||
type: NotificationType,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
type setSubscriptionNotifications = {
|
|
||||||
type: ACTIONS.SET_SUBSCRIPTION_NOTIFICATIONS,
|
|
||||||
data: {
|
|
||||||
notifications: SubscriptionNotifications,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
type CheckSubscriptionStarted = {
|
|
||||||
type: ACTIONS.CHECK_SUBSCRIPTION_STARTED,
|
|
||||||
};
|
|
||||||
|
|
||||||
type CheckSubscriptionCompleted = {
|
|
||||||
type: ACTIONS.CHECK_SUBSCRIPTION_COMPLETED,
|
|
||||||
};
|
|
||||||
|
|
||||||
type fetchedSubscriptionsSucess = {
|
|
||||||
type: ACTIONS.FETCH_SUBSCRIPTIONS_SUCCESS,
|
|
||||||
data: Array<Subscription>,
|
|
||||||
};
|
|
||||||
|
|
||||||
export type Action =
|
|
||||||
| doChannelSubscribe
|
|
||||||
| doChannelUnsubscribe
|
|
||||||
| setSubscriptionLatest
|
|
||||||
| setSubscriptionNotification
|
|
||||||
| CheckSubscriptionStarted
|
|
||||||
| CheckSubscriptionCompleted
|
|
||||||
| Function;
|
|
||||||
export type Dispatch = ReduxDispatch<Action>;
|
|
||||||
|
|
||||||
const defaultState = {
|
|
||||||
subscriptions: [],
|
subscriptions: [],
|
||||||
notifications: {},
|
unread: {},
|
||||||
loading: false,
|
loading: false,
|
||||||
|
viewMode: VIEW_ALL,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default handleActions(
|
export default handleActions(
|
||||||
{
|
{
|
||||||
[ACTIONS.CHANNEL_SUBSCRIBE]: (
|
[ACTIONS.CHANNEL_SUBSCRIBE]: (
|
||||||
state: SubscriptionState,
|
state: SubscriptionState,
|
||||||
action: doChannelSubscribe
|
action: DoChannelSubscribe
|
||||||
): SubscriptionState => {
|
): SubscriptionState => {
|
||||||
const newSubscription: Subscription = action.data;
|
const newSubscription: Subscription = action.data;
|
||||||
const newSubscriptions: Array<Subscription> = state.subscriptions.slice();
|
const newSubscriptions: Array<Subscription> = state.subscriptions.slice();
|
||||||
|
@ -105,7 +38,7 @@ export default handleActions(
|
||||||
},
|
},
|
||||||
[ACTIONS.CHANNEL_UNSUBSCRIBE]: (
|
[ACTIONS.CHANNEL_UNSUBSCRIBE]: (
|
||||||
state: SubscriptionState,
|
state: SubscriptionState,
|
||||||
action: doChannelUnsubscribe
|
action: DoChannelUnsubscribe
|
||||||
): SubscriptionState => {
|
): SubscriptionState => {
|
||||||
const subscriptionToRemove: Subscription = action.data;
|
const subscriptionToRemove: Subscription = action.data;
|
||||||
|
|
||||||
|
@ -120,7 +53,7 @@ export default handleActions(
|
||||||
},
|
},
|
||||||
[ACTIONS.SET_SUBSCRIPTION_LATEST]: (
|
[ACTIONS.SET_SUBSCRIPTION_LATEST]: (
|
||||||
state: SubscriptionState,
|
state: SubscriptionState,
|
||||||
action: setSubscriptionLatest
|
action: SetSubscriptionLatest
|
||||||
): SubscriptionState => ({
|
): SubscriptionState => ({
|
||||||
...state,
|
...state,
|
||||||
subscriptions: state.subscriptions.map(
|
subscriptions: state.subscriptions.map(
|
||||||
|
@ -130,23 +63,43 @@ export default handleActions(
|
||||||
: subscription
|
: subscription
|
||||||
),
|
),
|
||||||
}),
|
}),
|
||||||
[ACTIONS.SET_SUBSCRIPTION_NOTIFICATION]: (
|
[ACTIONS.UPDATE_SUBSCRIPTION_UNREADS]: (
|
||||||
state: SubscriptionState,
|
state: SubscriptionState,
|
||||||
action: setSubscriptionNotification
|
action: DoUpdateSubscriptionUnreads
|
||||||
): SubscriptionState => ({
|
): SubscriptionState => {
|
||||||
|
const { channel, uris, type } = action.data;
|
||||||
|
|
||||||
|
return {
|
||||||
...state,
|
...state,
|
||||||
notifications: {
|
unread: {
|
||||||
...state.notifications,
|
...state.unread,
|
||||||
[action.data.uri]: { subscription: action.data.subscription, type: action.data.type },
|
[channel]: {
|
||||||
|
uris,
|
||||||
|
type,
|
||||||
},
|
},
|
||||||
}),
|
},
|
||||||
[ACTIONS.SET_SUBSCRIPTION_NOTIFICATIONS]: (
|
};
|
||||||
|
},
|
||||||
|
[ACTIONS.REMOVE_SUBSCRIPTION_UNREADS]: (
|
||||||
state: SubscriptionState,
|
state: SubscriptionState,
|
||||||
action: setSubscriptionNotifications
|
action: DoRemoveSubscriptionUnreads
|
||||||
): SubscriptionState => ({
|
): SubscriptionState => {
|
||||||
|
const { channel, uris } = action.data;
|
||||||
|
const newUnread = { ...state.unread };
|
||||||
|
|
||||||
|
if (!uris) {
|
||||||
|
delete newUnread[channel];
|
||||||
|
} else {
|
||||||
|
newUnread[channel].uris = uris;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
...state,
|
...state,
|
||||||
notifications: action.data.notifications,
|
unread: {
|
||||||
}),
|
...newUnread,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
[ACTIONS.FETCH_SUBSCRIPTIONS_START]: (state: SubscriptionState): SubscriptionState => ({
|
[ACTIONS.FETCH_SUBSCRIPTIONS_START]: (state: SubscriptionState): SubscriptionState => ({
|
||||||
...state,
|
...state,
|
||||||
loading: true,
|
loading: true,
|
||||||
|
@ -157,12 +110,19 @@ export default handleActions(
|
||||||
}),
|
}),
|
||||||
[ACTIONS.FETCH_SUBSCRIPTIONS_SUCCESS]: (
|
[ACTIONS.FETCH_SUBSCRIPTIONS_SUCCESS]: (
|
||||||
state: SubscriptionState,
|
state: SubscriptionState,
|
||||||
action: fetchedSubscriptionsSucess
|
action: FetchedSubscriptionsSucess
|
||||||
): SubscriptionState => ({
|
): SubscriptionState => ({
|
||||||
...state,
|
...state,
|
||||||
loading: false,
|
loading: false,
|
||||||
subscriptions: action.data,
|
subscriptions: action.data,
|
||||||
}),
|
}),
|
||||||
|
[ACTIONS.SET_VIEW_MODE]: (
|
||||||
|
state: SubscriptionState,
|
||||||
|
action: SetViewMode
|
||||||
|
): SubscriptionState => ({
|
||||||
|
...state,
|
||||||
|
viewMode: action.data,
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
defaultState
|
defaultState
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import { createSelector } from 'reselect';
|
import { createSelector } from 'reselect';
|
||||||
import { selectCurrentPage, selectHistoryStack } from 'lbry-redux';
|
import { selectCurrentPage, selectHistoryStack } from 'lbry-redux';
|
||||||
|
import * as icons from 'constants/icons';
|
||||||
|
|
||||||
export const selectState = state => state.app || {};
|
export const selectState = state => state.app || {};
|
||||||
|
|
||||||
|
@ -198,46 +199,46 @@ export const selectNavLinks = createSelector(
|
||||||
label: 'Explore',
|
label: 'Explore',
|
||||||
path: '/discover',
|
path: '/discover',
|
||||||
active: currentPage === 'discover',
|
active: currentPage === 'discover',
|
||||||
icon: 'Compass',
|
icon: icons.COMPASS,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Subscriptions',
|
label: 'Subscriptions',
|
||||||
path: '/subscriptions',
|
path: '/subscriptions',
|
||||||
active: currentPage === 'subscriptions',
|
active: currentPage === 'subscriptions',
|
||||||
icon: 'AtSign',
|
icon: icons.HEART,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
secondary: [
|
secondary: [
|
||||||
{
|
{
|
||||||
label: 'Wallet',
|
label: 'Wallet',
|
||||||
icon: 'CreditCard',
|
icon: icons.CREDIT_CARD,
|
||||||
subLinks: walletSubLinks,
|
subLinks: walletSubLinks,
|
||||||
path: isCurrentlyWalletPage ? '/wallet' : getActiveSublink('wallet'),
|
path: isCurrentlyWalletPage ? '/wallet' : getActiveSublink('wallet'),
|
||||||
active: isWalletPage(currentPage),
|
active: isWalletPage(currentPage),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'My LBRY',
|
label: 'My LBRY',
|
||||||
icon: 'Folder',
|
icon: icons.LOCAL,
|
||||||
subLinks: myLbrySubLinks,
|
subLinks: myLbrySubLinks,
|
||||||
path: isCurrentlyMyLbryPage ? '/downloaded' : getActiveSublink('myLbry'),
|
path: isCurrentlyMyLbryPage ? '/downloaded' : getActiveSublink('myLbry'),
|
||||||
active: isMyLbryPage(currentPage),
|
active: isMyLbryPage(currentPage),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Publish',
|
label: 'Publish',
|
||||||
icon: 'UploadCloud',
|
icon: icons.UPLOAD,
|
||||||
path: '/publish',
|
path: '/publish',
|
||||||
active: currentPage === 'publish',
|
active: currentPage === 'publish',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Settings',
|
label: 'Settings',
|
||||||
icon: 'Settings',
|
icon: icons.SETTINGS,
|
||||||
path: '/settings',
|
path: '/settings',
|
||||||
active: currentPage === 'settings',
|
active: currentPage === 'settings',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Help',
|
label: 'Help',
|
||||||
path: '/help',
|
path: '/help',
|
||||||
icon: 'HelpCircle',
|
icon: icons.HELP,
|
||||||
active: currentPage === 'help',
|
active: currentPage === 'help',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|
|
@ -3,24 +3,127 @@ import {
|
||||||
selectAllClaimsByChannel,
|
selectAllClaimsByChannel,
|
||||||
selectClaimsById,
|
selectClaimsById,
|
||||||
selectAllFetchingChannelClaims,
|
selectAllFetchingChannelClaims,
|
||||||
makeSelectClaimForUri,
|
makeSelectChannelForClaimUri,
|
||||||
|
selectClaimsByUri,
|
||||||
|
parseURI,
|
||||||
} from 'lbry-redux';
|
} from 'lbry-redux';
|
||||||
|
|
||||||
// get the entire subscriptions state
|
// Returns the entire subscriptions state
|
||||||
const selectState = state => state.subscriptions || {};
|
const selectState = state => state.subscriptions || {};
|
||||||
|
|
||||||
export const selectIsFetchingSubscriptions = createSelector(selectState, state => state.loading);
|
// Returns the list of channel uris a user is subscribed to
|
||||||
|
|
||||||
export const selectNotifications = createSelector(selectState, state => state.notifications);
|
|
||||||
|
|
||||||
// list of saved channel names and uris
|
|
||||||
export const selectSubscriptions = createSelector(selectState, state => state.subscriptions);
|
export const selectSubscriptions = createSelector(selectState, state => state.subscriptions);
|
||||||
|
|
||||||
|
// Fetching list of users subscriptions
|
||||||
|
export const selectIsFetchingSubscriptions = createSelector(selectState, state => state.loading);
|
||||||
|
|
||||||
|
// The current view mode on the subscriptions page
|
||||||
|
export const selectViewMode = createSelector(selectState, state => state.viewMode);
|
||||||
|
|
||||||
|
// Fetching any claims that are a part of a users subscriptions
|
||||||
|
export const selectSubscriptionsBeingFetched = createSelector(
|
||||||
|
selectSubscriptions,
|
||||||
|
selectAllFetchingChannelClaims,
|
||||||
|
(subscriptions, fetchingChannelClaims) => {
|
||||||
|
const fetchingSubscriptionMap = {};
|
||||||
|
subscriptions.forEach(sub => {
|
||||||
|
const isFetching = fetchingChannelClaims && fetchingChannelClaims[sub.uri];
|
||||||
|
if (isFetching) {
|
||||||
|
fetchingSubscriptionMap[sub.uri] = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return fetchingSubscriptionMap;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export const selectUnreadByChannel = createSelector(selectState, state => state.unread);
|
||||||
|
|
||||||
|
// Returns the current total of unread subscriptions
|
||||||
|
export const selectUnreadAmount = createSelector(selectUnreadByChannel, unreadByChannel => {
|
||||||
|
const unreadChannels = Object.keys(unreadByChannel);
|
||||||
|
let badges = 0;
|
||||||
|
|
||||||
|
if (!unreadChannels.length) {
|
||||||
|
return badges;
|
||||||
|
}
|
||||||
|
|
||||||
|
unreadChannels.forEach(channel => {
|
||||||
|
badges += unreadByChannel[channel].uris.length;
|
||||||
|
});
|
||||||
|
|
||||||
|
return badges;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Returns the uris with channels as an array with the channel with the newest content first
|
||||||
|
// If you just want the `unread` state, use selectUnread
|
||||||
|
export const selectUnreadSubscriptions = createSelector(
|
||||||
|
selectUnreadAmount,
|
||||||
|
selectUnreadByChannel,
|
||||||
|
selectClaimsByUri,
|
||||||
|
(unreadAmount, unreadByChannel, claimsByUri) => {
|
||||||
|
// determine which channel has the newest content
|
||||||
|
const unreadList = [];
|
||||||
|
if (!unreadAmount) {
|
||||||
|
return unreadList;
|
||||||
|
}
|
||||||
|
|
||||||
|
const channelUriList = Object.keys(unreadByChannel);
|
||||||
|
|
||||||
|
// There is only one channel with unread notifications
|
||||||
|
if (unreadAmount === 1) {
|
||||||
|
channelUriList.forEach(channel => {
|
||||||
|
const unreadChannel = {
|
||||||
|
channel,
|
||||||
|
uris: unreadByChannel[channel].uris,
|
||||||
|
};
|
||||||
|
unreadList.push(unreadChannel);
|
||||||
|
});
|
||||||
|
|
||||||
|
return unreadList;
|
||||||
|
}
|
||||||
|
|
||||||
|
channelUriList
|
||||||
|
.sort((channel1, channel2) => {
|
||||||
|
const latestUriFromChannel1 = unreadByChannel[channel1].uris[0];
|
||||||
|
const latestClaimFromChannel1 = claimsByUri[latestUriFromChannel1] || {};
|
||||||
|
const latestUriFromChannel2 = unreadByChannel[channel2].uris[0];
|
||||||
|
const latestClaimFromChannel2 = claimsByUri[latestUriFromChannel2] || {};
|
||||||
|
|
||||||
|
const latestHeightFromChannel1 = latestClaimFromChannel1.height || 0;
|
||||||
|
const latestHeightFromChannel2 = latestClaimFromChannel2.height || 0;
|
||||||
|
|
||||||
|
if (latestHeightFromChannel1 !== latestHeightFromChannel2) {
|
||||||
|
return latestHeightFromChannel2 - latestHeightFromChannel1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
})
|
||||||
|
.forEach(channel => {
|
||||||
|
const unreadSubscription = unreadByChannel[channel];
|
||||||
|
const unreadChannel = {
|
||||||
|
channel,
|
||||||
|
uris: unreadSubscription.uris,
|
||||||
|
};
|
||||||
|
|
||||||
|
unreadList.push(unreadChannel);
|
||||||
|
});
|
||||||
|
|
||||||
|
return unreadList;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// Returns all unread subscriptions for a uri passed in
|
||||||
|
export const makeSelectUnreadByChannel = uri =>
|
||||||
|
createSelector(selectUnreadByChannel, unread => unread[uri]);
|
||||||
|
|
||||||
|
// Returns the first page of claims for every channel a user is subscribed to
|
||||||
export const selectSubscriptionClaims = createSelector(
|
export const selectSubscriptionClaims = createSelector(
|
||||||
selectAllClaimsByChannel,
|
selectAllClaimsByChannel,
|
||||||
selectClaimsById,
|
selectClaimsById,
|
||||||
selectSubscriptions,
|
selectSubscriptions,
|
||||||
(channelIds, allClaims, savedSubscriptions) => {
|
selectUnreadByChannel,
|
||||||
|
(channelIds, allClaims, savedSubscriptions, unreadByChannel) => {
|
||||||
// no claims loaded yet
|
// no claims loaded yet
|
||||||
if (!Object.keys(channelIds).length) {
|
if (!Object.keys(channelIds).length) {
|
||||||
return [];
|
return [];
|
||||||
|
@ -34,51 +137,51 @@ export const selectSubscriptionClaims = createSelector(
|
||||||
// if subscribed channel has content
|
// if subscribed channel has content
|
||||||
if (channelIds[subscription.uri] && channelIds[subscription.uri]['1']) {
|
if (channelIds[subscription.uri] && channelIds[subscription.uri]['1']) {
|
||||||
// This will need to be more robust, we will want to be able to load more than the first page
|
// This will need to be more robust, we will want to be able to load more than the first page
|
||||||
|
|
||||||
|
// Strip out any ids that will be shown as notifications
|
||||||
const pageOneChannelIds = channelIds[subscription.uri]['1'];
|
const pageOneChannelIds = channelIds[subscription.uri]['1'];
|
||||||
|
|
||||||
// we have the channel ids and the corresponding claims
|
// we have the channel ids and the corresponding claims
|
||||||
// loop over the list of ids and grab the claim
|
// loop over the list of ids and grab the claim
|
||||||
pageOneChannelIds.forEach(id => {
|
pageOneChannelIds.forEach(id => {
|
||||||
const grabbedClaim = allClaims[id];
|
const grabbedClaim = allClaims[id];
|
||||||
|
|
||||||
|
if (
|
||||||
|
unreadByChannel[subscription.uri] &&
|
||||||
|
unreadByChannel[subscription.uri].uris.some(uri => uri.includes(id))
|
||||||
|
) {
|
||||||
|
grabbedClaim.isNew = true;
|
||||||
|
}
|
||||||
|
|
||||||
channelClaims = channelClaims.concat([grabbedClaim]);
|
channelClaims = channelClaims.concat([grabbedClaim]);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fetchedSubscriptions = fetchedSubscriptions.concat([
|
fetchedSubscriptions = fetchedSubscriptions.concat(channelClaims);
|
||||||
{
|
|
||||||
claims: [...channelClaims],
|
|
||||||
channelName: subscription.channelName,
|
|
||||||
uri: subscription.uri,
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return [...fetchedSubscriptions];
|
return fetchedSubscriptions;
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
export const selectSubscriptionsBeingFetched = createSelector(
|
|
||||||
selectSubscriptions,
|
|
||||||
selectAllFetchingChannelClaims,
|
|
||||||
(subscriptions, fetchingChannelClaims) => {
|
|
||||||
const fetchingSubscriptionMap = {};
|
|
||||||
subscriptions.forEach(sub => {
|
|
||||||
const isFetching = fetchingChannelClaims && fetchingChannelClaims[sub.uri];
|
|
||||||
if (isFetching) {
|
|
||||||
fetchingSubscriptionMap[sub.uri] = 1;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return fetchingSubscriptionMap;
|
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Returns true if a user is subscribed to the channel associated with the uri passed in
|
||||||
|
// Accepts content or channel uris
|
||||||
export const makeSelectIsSubscribed = uri =>
|
export const makeSelectIsSubscribed = uri =>
|
||||||
createSelector(selectSubscriptions, makeSelectClaimForUri(uri), (subscriptions, claim) => {
|
createSelector(
|
||||||
if (!claim || !claim.channel_name) {
|
selectSubscriptions,
|
||||||
|
makeSelectChannelForClaimUri(uri, true),
|
||||||
|
(subscriptions, channelUri) => {
|
||||||
|
if (channelUri) {
|
||||||
|
return subscriptions.some(sub => sub.uri === channelUri);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we couldn't get a channel uri from the claim uri, the uri passed in might be a channel already
|
||||||
|
const { isChannel } = parseURI(uri);
|
||||||
|
if (isChannel) {
|
||||||
|
const uriWithPrefix = uri.startsWith('lbry://') ? uri : `lbry://${uri}`;
|
||||||
|
return subscriptions.some(sub => sub.uri === uriWithPrefix);
|
||||||
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
);
|
||||||
const channelUri = `${claim.channel_name}#${claim.value.publisherSignature.certificateId}`;
|
|
||||||
return subscriptions.some(sub => sub.uri === channelUri);
|
|
||||||
});
|
|
||||||
|
|
|
@ -233,32 +233,8 @@ p:not(:first-of-type) {
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
.credit-amount--file-page {
|
|
||||||
border-radius: 5px;
|
|
||||||
font-weight: 700;
|
|
||||||
padding: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.credit-amount--free {
|
|
||||||
&:not(.credit-amount--file-page) {
|
|
||||||
color: $lbry-blue-5;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.credit-amount--file-page {
|
|
||||||
background-color: $lbry-blue-2;
|
|
||||||
color: $lbry-blue-5;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.credit-amount--cost {
|
.credit-amount--cost {
|
||||||
&:not(.credit-amount--file-page) {
|
|
||||||
color: $lbry-gray-5;
|
color: $lbry-gray-5;
|
||||||
}
|
|
||||||
|
|
||||||
&.credit-amount--file-page {
|
|
||||||
background-color: $lbry-yellow-3;
|
|
||||||
color: $lbry-black;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.credit-amount--inherit {
|
.credit-amount--inherit {
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
@charset "utf-8";
|
|
||||||
|
|
||||||
@import '~@lbry/color/lbry-color', 'reset', 'type', 'vars', 'gui', 'component/syntax-highlighter',
|
@import '~@lbry/color/lbry-color', 'reset', 'type', 'vars', 'gui', 'component/syntax-highlighter',
|
||||||
'component/table', 'component/button', 'component/card', 'component/file-download',
|
'component/table', 'component/button', 'component/card', 'component/file-download',
|
||||||
'component/form-field', 'component/header', 'component/menu', 'component/tooltip',
|
'component/form-field', 'component/header', 'component/menu', 'component/tooltip',
|
||||||
|
@ -8,4 +6,4 @@
|
||||||
'component/markdown-editor', 'component/scrollbar', 'component/spinner', 'component/nav',
|
'component/markdown-editor', 'component/scrollbar', 'component/spinner', 'component/nav',
|
||||||
'component/file-list', 'component/file-render', 'component/search', 'component/toggle',
|
'component/file-list', 'component/file-render', 'component/search', 'component/toggle',
|
||||||
'component/search', 'component/dat-gui', 'component/item-list', 'component/time', 'component/icon',
|
'component/search', 'component/dat-gui', 'component/item-list', 'component/time', 'component/icon',
|
||||||
'component/placeholder', 'themes/dark';
|
'component/placeholder', 'component/badge', 'themes/dark';
|
||||||
|
|
21
src/renderer/scss/component/_badge.scss
Normal file
21
src/renderer/scss/component/_badge.scss
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
.badge {
|
||||||
|
border-radius: 5px;
|
||||||
|
padding: 5px;
|
||||||
|
font-weight: 800;
|
||||||
|
font-size: 0.8em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge--alert {
|
||||||
|
background-color: #e45454;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge--free {
|
||||||
|
background-color: $lbry-blue-2;
|
||||||
|
color: $lbry-blue-5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge--cost {
|
||||||
|
background-color: $lbry-yellow-3;
|
||||||
|
color: $lbry-black;
|
||||||
|
}
|
|
@ -57,6 +57,23 @@
|
||||||
background-color: $lbry-red-3;
|
background-color: $lbry-red-3;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.btn--link {
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
background-color: inherit;
|
||||||
|
color: $lbry-teal-4;
|
||||||
|
font-size: 1em;
|
||||||
|
border-radius: 0;
|
||||||
|
display: inline;
|
||||||
|
min-width: 0;
|
||||||
|
box-shadow: none;
|
||||||
|
text-align: left;
|
||||||
|
|
||||||
|
&:disabled {
|
||||||
|
color: $lbry-gray-5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
&.btn--disabled:disabled {
|
&.btn--disabled:disabled {
|
||||||
// wtf?
|
// wtf?
|
||||||
cursor: default;
|
cursor: default;
|
||||||
|
|
|
@ -5,6 +5,11 @@
|
||||||
margin-top: $spacing-vertical * 2/3;
|
margin-top: $spacing-vertical * 2/3;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.file-list__sort {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
.file-list__header {
|
.file-list__header {
|
||||||
font-size: 24px;
|
font-size: 24px;
|
||||||
padding-top: $spacing-vertical * 4/3;
|
padding-top: $spacing-vertical * 4/3;
|
||||||
|
@ -14,12 +19,6 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.file-list__sort {
|
|
||||||
display: flex;
|
|
||||||
justify-content: flex-end;
|
|
||||||
padding-bottom: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.file-tile {
|
.file-tile {
|
||||||
display: flex;
|
display: flex;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
|
|
|
@ -101,7 +101,7 @@ const compressor = createCompressor();
|
||||||
// We were caching so much data the app was locking up
|
// We were caching so much data the app was locking up
|
||||||
// We can't add this back until we can perform this in a non-blocking way
|
// We can't add this back until we can perform this in a non-blocking way
|
||||||
// const saveClaimsFilter = createFilter('claims', ['byId', 'claimsByUri']);
|
// const saveClaimsFilter = createFilter('claims', ['byId', 'claimsByUri']);
|
||||||
const subscriptionsFilter = createFilter('subscriptions', ['subscriptions']);
|
const subscriptionsFilter = createFilter('subscriptions', ['subscriptions', 'unread', 'viewMode']);
|
||||||
const contentFilter = createFilter('content', ['positions', 'history']);
|
const contentFilter = createFilter('content', ['positions', 'history']);
|
||||||
|
|
||||||
// We only need to persist the receiveAddress for the wallet
|
// We only need to persist the receiveAddress for the wallet
|
||||||
|
|
|
@ -6,6 +6,7 @@ export type FileInfo = {
|
||||||
channelName: ?string,
|
channelName: ?string,
|
||||||
pending?: boolean,
|
pending?: boolean,
|
||||||
channel_claim_id: string,
|
channel_claim_id: string,
|
||||||
|
file_name: string,
|
||||||
value?: {
|
value?: {
|
||||||
publisherSignature: {
|
publisherSignature: {
|
||||||
certificateId: string,
|
certificateId: string,
|
||||||
|
|
|
@ -2,5 +2,5 @@
|
||||||
|
|
||||||
// eslint-disable-next-line no-use-before-define
|
// eslint-disable-next-line no-use-before-define
|
||||||
export type Dispatch<T> = (action: T | Promise<T> | Array<T> | ThunkAction<T>) => any; // Need to refer to ThunkAction
|
export type Dispatch<T> = (action: T | Promise<T> | Array<T> | ThunkAction<T>) => any; // Need to refer to ThunkAction
|
||||||
export type GetState = () => {};
|
export type GetState = () => any;
|
||||||
export type ThunkAction<T> = (dispatch: Dispatch<T>, getState: GetState) => any;
|
export type ThunkAction<T> = (dispatch: Dispatch<T>, getState: GetState) => any;
|
||||||
|
|
|
@ -1,7 +1,108 @@
|
||||||
// @flow
|
// @flow
|
||||||
|
import type { Dispatch as ReduxDispatch } from 'types/redux';
|
||||||
|
import * as ACTIONS from 'constants/action_types';
|
||||||
|
import {
|
||||||
|
DOWNLOADED,
|
||||||
|
DOWNLOADING,
|
||||||
|
NOTIFY_ONLY,
|
||||||
|
VIEW_ALL,
|
||||||
|
VIEW_LATEST_FIRST,
|
||||||
|
} from 'constants/subscriptions';
|
||||||
|
|
||||||
export type Subscription = {
|
export type Subscription = {
|
||||||
channelName: string, // @CryptoCandor,
|
channelName: string, // @CryptoCandor,
|
||||||
uri: string, // lbry://@CryptoCandor#9152f3b054f692076a6882d1b58a30e8781cc8e6
|
uri: string, // lbry://@CryptoCandor#9152f3b054f692076a6882d1b58a30e8781cc8e6
|
||||||
latest: string, // substratum#b0ab143243020e7831fd070d9f871e1fda948620
|
latest?: string, // substratum#b0ab143243020e7831fd070d9f871e1fda948620
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Tracking for new content
|
||||||
|
// i.e. If a subscription has a DOWNLOADING type, we will trigger an OS notification
|
||||||
|
// to tell users there is new content from their subscriptions
|
||||||
|
export type SubscriptionNotificationType = DOWNLOADED | DOWNLOADING | NOTIFY_ONLY;
|
||||||
|
|
||||||
|
export type UnreadSubscription = {
|
||||||
|
type: SubscriptionNotificationType,
|
||||||
|
uris: Array<string>,
|
||||||
|
};
|
||||||
|
|
||||||
|
export type UnreadSubscriptions = {
|
||||||
|
[string]: UnreadSubscription,
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ViewMode = VIEW_LATEST_FIRST | VIEW_ALL;
|
||||||
|
|
||||||
|
export type SubscriptionState = {
|
||||||
|
subscriptions: Array<Subscription>,
|
||||||
|
unread: UnreadSubscriptions,
|
||||||
|
loading: boolean,
|
||||||
|
viewMode: ViewMode,
|
||||||
|
};
|
||||||
|
|
||||||
|
//
|
||||||
|
// Action types
|
||||||
|
//
|
||||||
|
export type DoChannelSubscribe = {
|
||||||
|
type: ACTIONS.CHANNEL_SUBSCRIBE,
|
||||||
|
data: Subscription,
|
||||||
|
};
|
||||||
|
|
||||||
|
export type DoChannelUnsubscribe = {
|
||||||
|
type: ACTIONS.CHANNEL_UNSUBSCRIBE,
|
||||||
|
data: Subscription,
|
||||||
|
};
|
||||||
|
|
||||||
|
export type DoUpdateSubscriptionUnreads = {
|
||||||
|
type: ACTIONS.UPDATE_SUBSCRIPTION_UNREADS,
|
||||||
|
data: {
|
||||||
|
channel: string,
|
||||||
|
uris: Array<string>,
|
||||||
|
type?: SubscriptionNotificationType,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export type DoRemoveSubscriptionUnreads = {
|
||||||
|
type: ACTIONS.REMOVE_SUBSCRIPTION_UNREADS,
|
||||||
|
data: {
|
||||||
|
channel: string,
|
||||||
|
uris: Array<string>,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export type SetSubscriptionLatest = {
|
||||||
|
type: ACTIONS.SET_SUBSCRIPTION_LATEST,
|
||||||
|
data: {
|
||||||
|
subscription: Subscription,
|
||||||
|
uri: string,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export type CheckSubscriptionStarted = {
|
||||||
|
type: ACTIONS.CHECK_SUBSCRIPTION_STARTED,
|
||||||
|
};
|
||||||
|
|
||||||
|
export type CheckSubscriptionCompleted = {
|
||||||
|
type: ACTIONS.CHECK_SUBSCRIPTION_COMPLETED,
|
||||||
|
};
|
||||||
|
|
||||||
|
export type FetchedSubscriptionsSucess = {
|
||||||
|
type: ACTIONS.FETCH_SUBSCRIPTIONS_SUCCESS,
|
||||||
|
data: Array<Subscription>,
|
||||||
|
};
|
||||||
|
|
||||||
|
export type SetViewMode = {
|
||||||
|
type: ACTIONS.SET_VIEW_MODE,
|
||||||
|
data: ViewMode,
|
||||||
|
};
|
||||||
|
|
||||||
|
export type Action =
|
||||||
|
| DoChannelSubscribe
|
||||||
|
| DoChannelUnsubscribe
|
||||||
|
| DoUpdateSubscriptionUnreads
|
||||||
|
| DoRemoveSubscriptionUnreads
|
||||||
|
| SetSubscriptionLatest
|
||||||
|
| CheckSubscriptionStarted
|
||||||
|
| CheckSubscriptionCompleted
|
||||||
|
| SetViewMode
|
||||||
|
| Function;
|
||||||
|
|
||||||
|
export type Dispatch = ReduxDispatch<Action>;
|
||||||
|
|
|
@ -13,6 +13,7 @@ const isDev = PROCESS_ARGV && PROCESS_ARGV.original &&
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
// This rule is temporarily necessary until https://github.com/electron-userland/electron-webpack/issues/60 is fixed.
|
// This rule is temporarily necessary until https://github.com/electron-userland/electron-webpack/issues/60 is fixed.
|
||||||
|
entry: ['babel-polyfill', `${ELECTRON_RENDERER_PROCESS_ROOT}/index.js`],
|
||||||
module: {
|
module: {
|
||||||
rules: [
|
rules: [
|
||||||
{
|
{
|
||||||
|
|
10
yarn.lock
10
yarn.lock
|
@ -1051,7 +1051,7 @@ babel-plugin-transform-strict-mode@^6.24.1:
|
||||||
babel-runtime "^6.22.0"
|
babel-runtime "^6.22.0"
|
||||||
babel-types "^6.24.1"
|
babel-types "^6.24.1"
|
||||||
|
|
||||||
babel-polyfill@^6.20.0, babel-polyfill@^6.26.0:
|
babel-polyfill@^6.26.0:
|
||||||
version "6.26.0"
|
version "6.26.0"
|
||||||
resolved "https://registry.yarnpkg.com/babel-polyfill/-/babel-polyfill-6.26.0.tgz#379937abc67d7895970adc621f284cd966cf2153"
|
resolved "https://registry.yarnpkg.com/babel-polyfill/-/babel-polyfill-6.26.0.tgz#379937abc67d7895970adc621f284cd966cf2153"
|
||||||
dependencies:
|
dependencies:
|
||||||
|
@ -5670,16 +5670,16 @@ lbry-redux@lbryio/lbry-redux:
|
||||||
proxy-polyfill "0.1.6"
|
proxy-polyfill "0.1.6"
|
||||||
reselect "^3.0.0"
|
reselect "^3.0.0"
|
||||||
|
|
||||||
lbry-redux@lbryio/lbry-redux#67cae46983d9fea90dd1e4c5bd121dd5077a3f0e:
|
lbry-redux@lbryio/lbry-redux#957d221c1830ecbb7a9e74fad78e711fb14539f4:
|
||||||
version "0.0.1"
|
version "0.0.1"
|
||||||
resolved "https://codeload.github.com/lbryio/lbry-redux/tar.gz/67cae46983d9fea90dd1e4c5bd121dd5077a3f0e"
|
resolved "https://codeload.github.com/lbryio/lbry-redux/tar.gz/957d221c1830ecbb7a9e74fad78e711fb14539f4"
|
||||||
dependencies:
|
dependencies:
|
||||||
proxy-polyfill "0.1.6"
|
proxy-polyfill "0.1.6"
|
||||||
reselect "^3.0.0"
|
reselect "^3.0.0"
|
||||||
|
|
||||||
lbryinc@lbryio/lbryinc#de7ff055605b02a24821f0f9bab1d206eb7f235d:
|
lbryinc@lbryio/lbryinc#3f34af546ee73ff2ee7d8ad05e540b3b0aa658fb:
|
||||||
version "0.0.1"
|
version "0.0.1"
|
||||||
resolved "https://codeload.github.com/lbryio/lbryinc/tar.gz/de7ff055605b02a24821f0f9bab1d206eb7f235d"
|
resolved "https://codeload.github.com/lbryio/lbryinc/tar.gz/3f34af546ee73ff2ee7d8ad05e540b3b0aa658fb"
|
||||||
dependencies:
|
dependencies:
|
||||||
lbry-redux lbryio/lbry-redux
|
lbry-redux lbryio/lbry-redux
|
||||||
reselect "^3.0.0"
|
reselect "^3.0.0"
|
||||||
|
|
Loading…
Reference in a new issue