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))
|
||||
* 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))
|
||||
* 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
|
||||
* 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",
|
||||
"hast-util-sanitize": "^1.1.2",
|
||||
"keytar": "^4.2.1",
|
||||
"lbry-redux": "lbryio/lbry-redux#67cae46983d9fea90dd1e4c5bd121dd5077a3f0e",
|
||||
"lbryinc": "lbryio/lbryinc#de7ff055605b02a24821f0f9bab1d206eb7f235d",
|
||||
"lbry-redux": "lbryio/lbry-redux#957d221c1830ecbb7a9e74fad78e711fb14539f4",
|
||||
"lbryinc": "lbryio/lbryinc#3f34af546ee73ff2ee7d8ad05e540b3b0aa658fb",
|
||||
"localforage": "^1.7.1",
|
||||
"mammoth": "^1.4.6",
|
||||
"mime": "^2.3.1",
|
||||
|
@ -91,7 +91,7 @@
|
|||
"axios": "^0.18.0",
|
||||
"babel-eslint": "^8.2.2",
|
||||
"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-react": "^6.24.1",
|
||||
"babel-preset-stage-2": "^6.18.0",
|
||||
|
|
|
@ -52,7 +52,12 @@ const analytics: Analytics = {
|
|||
onSuccessCb: ?() => void
|
||||
): void => {
|
||||
if (analyticsEnabled) {
|
||||
const params = {
|
||||
const params: {
|
||||
uri: string,
|
||||
outpoint: string,
|
||||
claim_id: string,
|
||||
time_to_start?: number,
|
||||
} = {
|
||||
uri,
|
||||
outpoint,
|
||||
claim_id: claimId,
|
||||
|
|
|
@ -14,7 +14,7 @@ type Props = {
|
|||
showLBC?: boolean,
|
||||
fee?: boolean,
|
||||
inheritStyle?: boolean,
|
||||
filePage?: boolean,
|
||||
badge?: boolean,
|
||||
};
|
||||
|
||||
class CreditAmount extends React.PureComponent<Props> {
|
||||
|
@ -38,7 +38,7 @@ class CreditAmount extends React.PureComponent<Props> {
|
|||
fee,
|
||||
showLBC,
|
||||
inheritStyle,
|
||||
filePage,
|
||||
badge,
|
||||
} = this.props;
|
||||
|
||||
const minimumRenderableAmount = 10 ** (-1 * precision);
|
||||
|
@ -78,11 +78,13 @@ class CreditAmount extends React.PureComponent<Props> {
|
|||
<span
|
||||
title={fullPrice}
|
||||
className={classnames('credit-amount', {
|
||||
'credit-amount--free': !large && isFree,
|
||||
'credit-amount--cost': !large && !isFree,
|
||||
'credit-amount--large': large,
|
||||
// TODO: remove inheritStyle prop
|
||||
// It just complicates things
|
||||
'credit-amount--inherit': inheritStyle,
|
||||
'credit-amount--file-page': filePage,
|
||||
badge: badge,
|
||||
'badge--cost': badge && !isFree,
|
||||
'badge--free': badge && isFree,
|
||||
})}
|
||||
>
|
||||
{amountText}
|
||||
|
|
|
@ -27,9 +27,15 @@ type Props = {
|
|||
isResolvingUri: boolean,
|
||||
/* eslint-enable react/no-unused-prop-types */
|
||||
isSubscribed: boolean,
|
||||
showSubscribedLogo: boolean,
|
||||
isNew: boolean,
|
||||
};
|
||||
|
||||
class FileCard extends React.PureComponent<Props> {
|
||||
static defaultProps = {
|
||||
showSubscribedLogo: false,
|
||||
};
|
||||
|
||||
componentWillMount() {
|
||||
this.resolve(this.props);
|
||||
}
|
||||
|
@ -57,6 +63,8 @@ class FileCard extends React.PureComponent<Props> {
|
|||
claimIsMine,
|
||||
pending,
|
||||
isSubscribed,
|
||||
isNew,
|
||||
showSubscribedLogo,
|
||||
} = this.props;
|
||||
|
||||
if (!claim && !pending) {
|
||||
|
@ -112,10 +120,15 @@ class FileCard extends React.PureComponent<Props> {
|
|||
<div className="card__file-properties">
|
||||
<FilePrice hideFree uri={uri} />
|
||||
{isRewardContent && <Icon iconColor="red" icon={icons.FEATURED} />}
|
||||
{isSubscribed && <Icon icon={icons.HEART} />}
|
||||
{showSubscribedLogo && isSubscribed && <Icon icon={icons.HEART} />}
|
||||
{fileInfo && <Icon icon={icons.LOCAL} />}
|
||||
</div>
|
||||
</div>
|
||||
{isNew && (
|
||||
<div className="card__subtitle">
|
||||
<span className="badge badge--alert">{__('NEW')}</span>
|
||||
</div>
|
||||
)}
|
||||
</section>
|
||||
);
|
||||
/* eslint-enable jsx-a11y/click-events-have-key-events */
|
||||
|
|
|
@ -147,6 +147,7 @@ class FileList extends React.PureComponent<Props, State> {
|
|||
claim_id: claimId,
|
||||
txid,
|
||||
nout,
|
||||
isNew,
|
||||
} = fileInfo;
|
||||
const uriParams = {};
|
||||
|
||||
|
@ -159,13 +160,13 @@ class FileList extends React.PureComponent<Props, State> {
|
|||
const outpoint = `${txid}:${nout}`;
|
||||
|
||||
// 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 (
|
||||
<section>
|
||||
<div className="file-list__sort">
|
||||
{!hideFilter && (
|
||||
<div className="file-list__sort">
|
||||
<FormField
|
||||
prefix={__('Sort by')}
|
||||
affixClass="form-field--align-center"
|
||||
|
@ -177,9 +178,9 @@ class FileList extends React.PureComponent<Props, State> {
|
|||
<option value="dateOld">{__('Oldest First')}</option>
|
||||
<option value="title">{__('Title')}</option>
|
||||
</FormField>
|
||||
)}
|
||||
</div>
|
||||
<div className="card__list">{content}</div>
|
||||
)}
|
||||
<div className="card__list card__content">{content}</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -10,7 +10,7 @@ type Props = {
|
|||
fetching: boolean,
|
||||
claim: ?{},
|
||||
// below props are just passed to <CreditAmount />
|
||||
filePage?: boolean,
|
||||
badge?: boolean,
|
||||
inheritStyle?: boolean,
|
||||
showLBC?: boolean,
|
||||
hideFree?: boolean, // hide the file price if it's free
|
||||
|
@ -38,7 +38,7 @@ class FilePrice extends React.PureComponent<Props> {
|
|||
};
|
||||
|
||||
render() {
|
||||
const { costInfo, showFullPrice, filePage, inheritStyle, showLBC, hideFree } = this.props;
|
||||
const { costInfo, showFullPrice, badge, inheritStyle, showLBC, hideFree } = this.props;
|
||||
|
||||
if (costInfo && !costInfo.cost && hideFree) {
|
||||
return null;
|
||||
|
@ -47,7 +47,7 @@ class FilePrice extends React.PureComponent<Props> {
|
|||
return costInfo ? (
|
||||
<CreditAmount
|
||||
showFree
|
||||
filePage={filePage}
|
||||
badge={badge}
|
||||
inheritStyle={inheritStyle}
|
||||
showLBC={showLBC}
|
||||
amount={costInfo.cost}
|
||||
|
|
|
@ -524,7 +524,7 @@ class PublishForm extends React.PureComponent<Props> {
|
|||
step="any"
|
||||
label={__('Deposit')}
|
||||
postfix="LBC"
|
||||
value={bid || ''}
|
||||
value={bid}
|
||||
error={bidError}
|
||||
min="0"
|
||||
disabled={!name}
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
import { connect } from 'react-redux';
|
||||
import { selectNavLinks } from 'redux/selectors/app';
|
||||
import { selectNotifications } from 'redux/selectors/subscriptions';
|
||||
import { selectUnreadAmount } from 'redux/selectors/subscriptions';
|
||||
import SideBar from './view';
|
||||
|
||||
const select = state => ({
|
||||
navLinks: selectNavLinks(state),
|
||||
notifications: selectNotifications(state),
|
||||
unreadSubscriptionTotal: selectUnreadAmount(state),
|
||||
});
|
||||
|
||||
const perform = () => ({});
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
import * as React from 'react';
|
||||
import Button from 'component/button';
|
||||
import classnames from 'classnames';
|
||||
import * as NOTIFICATION_TYPES from 'constants/notification_types';
|
||||
|
||||
type SideBarLink = {
|
||||
label: string,
|
||||
|
@ -17,15 +16,11 @@ type Props = {
|
|||
primary: Array<SideBarLink>,
|
||||
secondary: Array<SideBarLink>,
|
||||
},
|
||||
notifications: {
|
||||
type: string,
|
||||
},
|
||||
unreadSubscriptionTotal: number,
|
||||
};
|
||||
|
||||
const SideBar = (props: Props) => {
|
||||
const { navLinks, notifications } = props;
|
||||
|
||||
const badges = Object.keys(notifications).length;
|
||||
const { navLinks, unreadSubscriptionTotal } = props;
|
||||
|
||||
return (
|
||||
<nav className="nav">
|
||||
|
@ -40,7 +35,11 @@ const SideBar = (props: Props) => {
|
|||
>
|
||||
<Button
|
||||
navigate={path}
|
||||
label={path === '/subscriptions' && badges ? `${label} (${badges})` : label}
|
||||
label={
|
||||
path === '/subscriptions' && unreadSubscriptionTotal
|
||||
? `${label} (${unreadSubscriptionTotal})`
|
||||
: label
|
||||
}
|
||||
icon={icon}
|
||||
/>
|
||||
</li>
|
||||
|
|
|
@ -1,15 +1,19 @@
|
|||
import { connect } from 'react-redux';
|
||||
import { doChannelSubscribe, doChannelUnsubscribe } from 'redux/actions/subscriptions';
|
||||
import { doNotify } from 'lbry-redux';
|
||||
import { selectSubscriptions } from 'redux/selectors/subscriptions';
|
||||
import { selectSubscriptions, makeSelectIsSubscribed } from 'redux/selectors/subscriptions';
|
||||
import SubscribeButton from './view';
|
||||
|
||||
const select = (state, props) => ({
|
||||
subscriptions: selectSubscriptions(state),
|
||||
isSubscribed: makeSelectIsSubscribed(props.uri, true)(state),
|
||||
});
|
||||
|
||||
export default connect(select, {
|
||||
export default connect(
|
||||
select,
|
||||
{
|
||||
doChannelSubscribe,
|
||||
doChannelUnsubscribe,
|
||||
doNotify,
|
||||
})(SubscribeButton);
|
||||
}
|
||||
)(SubscribeButton);
|
||||
|
|
|
@ -3,7 +3,6 @@ import React from 'react';
|
|||
import { MODALS } from 'lbry-redux';
|
||||
import * as icons from 'constants/icons';
|
||||
import Button from 'component/button';
|
||||
import type { Subscription } from 'types/subscription';
|
||||
|
||||
type SubscribtionArgs = {
|
||||
channelName: string,
|
||||
|
@ -13,7 +12,8 @@ type SubscribtionArgs = {
|
|||
type Props = {
|
||||
channelName: ?string,
|
||||
uri: ?string,
|
||||
subscriptions: Array<Subscription>,
|
||||
isSubscribed: boolean,
|
||||
subscriptions: Array<string>,
|
||||
doChannelSubscribe: ({ channelName: string, uri: string }) => void,
|
||||
doChannelUnsubscribe: SubscribtionArgs => void,
|
||||
doNotify: ({ id: string }) => void,
|
||||
|
@ -23,15 +23,13 @@ export default (props: Props) => {
|
|||
const {
|
||||
channelName,
|
||||
uri,
|
||||
subscriptions,
|
||||
doChannelSubscribe,
|
||||
doChannelUnsubscribe,
|
||||
doNotify,
|
||||
subscriptions,
|
||||
isSubscribed,
|
||||
} = props;
|
||||
|
||||
const isSubscribed =
|
||||
subscriptions.map(subscription => subscription.channelName).indexOf(channelName) !== -1;
|
||||
|
||||
const subscriptionHandler = isSubscribed ? doChannelUnsubscribe : doChannelSubscribe;
|
||||
const subscriptionLabel = isSubscribed ? __('Unsubscribe') : __('Subscribe');
|
||||
|
||||
|
|
|
@ -161,7 +161,9 @@ class UserHistoryPage extends React.PureComponent<Props, State> {
|
|||
</React.Fragment>
|
||||
) : (
|
||||
<div className="page__empty">
|
||||
<h3 className="card__title">
|
||||
{__("You don't have anything saved in history yet, go check out some content on LBRY!")}
|
||||
</h3>
|
||||
<div className="card__actions card__actions--center">
|
||||
<Button button="primary" navigate="/discover" label={__('Explore new content')} />
|
||||
</div>
|
||||
|
|
|
@ -176,14 +176,15 @@ export const CHANNEL_SUBSCRIBE = 'CHANNEL_SUBSCRIBE';
|
|||
export const CHANNEL_UNSUBSCRIBE = 'CHANNEL_UNSUBSCRIBE';
|
||||
export const HAS_FETCHED_SUBSCRIPTIONS = 'HAS_FETCHED_SUBSCRIPTIONS';
|
||||
export const SET_SUBSCRIPTION_LATEST = 'SET_SUBSCRIPTION_LATEST';
|
||||
export const SET_SUBSCRIPTION_NOTIFICATION = 'SET_SUBSCRIPTION_NOTIFICATION';
|
||||
export const SET_SUBSCRIPTION_NOTIFICATIONS = 'SET_SUBSCRIPTION_NOTIFICATIONS';
|
||||
export const UPDATE_SUBSCRIPTION_UNREADS = 'UPDATE_SUBSCRIPTION_UNREADS';
|
||||
export const REMOVE_SUBSCRIPTION_UNREADS = 'REMOVE_SUBSCRIPTION_UNREADS';
|
||||
export const CHECK_SUBSCRIPTION_STARTED = 'CHECK_SUBSCRIPTION_STARTED';
|
||||
export const CHECK_SUBSCRIPTION_COMPLETED = 'CHECK_SUBSCRIPTION_COMPLETED';
|
||||
export const CHECK_SUBSCRIPTIONS_SUBSCRIBE = 'CHECK_SUBSCRIPTIONS_SUBSCRIBE';
|
||||
export const FETCH_SUBSCRIPTIONS_START = 'FETCH_SUBSCRIPTIONS_START';
|
||||
export const FETCH_SUBSCRIPTIONS_FAIL = 'FETCH_SUBSCRIPTIONS_FAIL';
|
||||
export const FETCH_SUBSCRIPTIONS_SUCCESS = 'FETCH_SUBSCRIPTIONS_SUCCESS';
|
||||
export const SET_VIEW_MODE = 'SET_VIEW_MODE';
|
||||
|
||||
// Publishing
|
||||
export const CLEAR_PUBLISH = 'CLEAR_PUBLISH';
|
||||
|
|
|
@ -32,3 +32,5 @@ export const EYE = 'Eye';
|
|||
export const PLAY = 'Play';
|
||||
export const FACEBOOK = 'Facebook';
|
||||
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 DOWNLOADED = 'DOWNLOADED';
|
||||
export const NOTIFY_ONLY = 'NOTIFY_ONLY;';
|
|
@ -206,4 +206,5 @@ const init = () => {
|
|||
|
||||
init();
|
||||
|
||||
/* eslint-enable react/jsx-filename-extension */
|
||||
/* eslint-enable no-console */
|
||||
|
|
|
@ -31,7 +31,7 @@ class ModalWalletDecrypt extends React.PureComponent<Props> {
|
|||
}
|
||||
|
||||
render() {
|
||||
const { closeModalgaa } = this.props;
|
||||
const { closeModal } = this.props;
|
||||
|
||||
return (
|
||||
<Modal
|
||||
|
|
|
@ -2,7 +2,7 @@ import { connect } from 'react-redux';
|
|||
import * as settings from 'constants/settings';
|
||||
import { doNavigate } from 'redux/actions/navigation';
|
||||
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 { doSetContentHistoryItem } from 'redux/actions/content';
|
||||
import {
|
||||
|
@ -15,9 +15,10 @@ import {
|
|||
makeSelectContentTypeForUri,
|
||||
makeSelectMetadataForUri,
|
||||
doNotify,
|
||||
makeSelectChannelForClaimUri,
|
||||
} from 'lbry-redux';
|
||||
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 FilePage from './view';
|
||||
|
||||
|
@ -29,21 +30,22 @@ const select = (state, props) => ({
|
|||
obscureNsfw: !selectShowNsfw(state),
|
||||
fileInfo: makeSelectFileInfoForUri(props.uri)(state),
|
||||
rewardedContentClaimIds: selectRewardContentClaimIds(state, props),
|
||||
subscriptions: selectSubscriptions(state),
|
||||
playingUri: selectPlayingUri(state),
|
||||
claimIsMine: makeSelectClaimIsMine(props.uri)(state),
|
||||
autoplay: makeSelectClientSetting(settings.AUTOPLAY)(state),
|
||||
isSubscribed: makeSelectIsSubscribed(props.uri)(state),
|
||||
channelUri: makeSelectChannelForClaimUri(props.uri, true)(state),
|
||||
});
|
||||
|
||||
const perform = dispatch => ({
|
||||
navigate: (path, params) => dispatch(doNavigate(path, params)),
|
||||
fetchFileInfo: uri => dispatch(doFetchFileInfo(uri)),
|
||||
fetchCostInfo: uri => dispatch(doFetchCostInfoForUri(uri)),
|
||||
checkSubscription: uri => dispatch(doCheckSubscription(uri)),
|
||||
openModal: (modal, props) => dispatch(doNotify(modal, props)),
|
||||
prepareEdit: (publishData, uri) => dispatch(doPrepareEdit(publishData, uri)),
|
||||
setClientSetting: (key, value) => dispatch(doSetClientSetting(key, value)),
|
||||
setViewed: uri => dispatch(doSetContentHistoryItem(uri)),
|
||||
markSubscriptionRead: (channel, uri) => dispatch(doRemoveUnreadSubscription(channel, uri)),
|
||||
});
|
||||
|
||||
export default connect(
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
// @flow
|
||||
import type { Claim, Metadata } from 'types/claim';
|
||||
import type { FileInfo } from 'types/file_info';
|
||||
import * as React from 'react';
|
||||
import * as settings from 'constants/settings';
|
||||
import { buildURI, normalizeURI, MODALS } from 'lbry-redux';
|
||||
|
@ -14,8 +16,6 @@ import * as icons from 'constants/icons';
|
|||
import Button from 'component/button';
|
||||
import SubscribeButton from 'component/subscribeButton';
|
||||
import Page from 'component/page';
|
||||
import type { Claim } from 'types/claim';
|
||||
import type { Subscription } from 'types/subscription';
|
||||
import FileDownloadLink from 'component/fileDownloadLink';
|
||||
import classnames from 'classnames';
|
||||
import getMediaType from 'util/getMediaType';
|
||||
|
@ -25,31 +25,26 @@ import ToolTip from 'component/common/tooltip';
|
|||
|
||||
type Props = {
|
||||
claim: Claim,
|
||||
fileInfo: {},
|
||||
metadata: {
|
||||
title: string,
|
||||
thumbnail: string,
|
||||
file_name: string,
|
||||
nsfw: boolean,
|
||||
},
|
||||
fileInfo: FileInfo,
|
||||
metadata: Metadata,
|
||||
contentType: string,
|
||||
uri: string,
|
||||
rewardedContentClaimIds: Array<string>,
|
||||
obscureNsfw: boolean,
|
||||
claimIsMine: boolean,
|
||||
costInfo: ?{},
|
||||
navigate: (string, ?{}) => void,
|
||||
openModal: ({ id: string }, { uri: string }) => void,
|
||||
costInfo: ?{ cost: number },
|
||||
fetchFileInfo: string => void,
|
||||
fetchCostInfo: string => void,
|
||||
prepareEdit: ({}, string) => void,
|
||||
setViewed: string => void,
|
||||
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,
|
||||
/* eslint-disable react/no-unused-prop-types */
|
||||
checkSubscription: (uri: string) => void,
|
||||
subscriptions: Array<Subscription>,
|
||||
/* eslint-enable react/no-unused-prop-types */
|
||||
markSubscriptionRead: (string, string) => void,
|
||||
};
|
||||
|
||||
class FilePage extends React.Component<Props> {
|
||||
|
@ -73,7 +68,11 @@ class FilePage extends React.Component<Props> {
|
|||
}
|
||||
|
||||
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) {
|
||||
fetchFileInfo(uri);
|
||||
|
@ -81,9 +80,6 @@ class FilePage extends React.Component<Props> {
|
|||
|
||||
// See https://github.com/lbryio/lbry-desktop/pull/1563 for discussion
|
||||
fetchCostInfo(uri);
|
||||
|
||||
this.checkSubscription(this.props);
|
||||
|
||||
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<*>) {
|
||||
this.props.setClientSetting(settings.AUTOPLAY, event.target.checked);
|
||||
}
|
||||
|
||||
checkSubscription = (props: Props) => {
|
||||
if (props.subscriptions.find(sub => sub.channelName === props.claim.channel_name)) {
|
||||
props.checkSubscription(
|
||||
buildURI(
|
||||
{
|
||||
contentName: props.claim.channel_name,
|
||||
claimId: props.claim.value.publisherSignature.certificateId,
|
||||
},
|
||||
false
|
||||
)
|
||||
);
|
||||
removeFromSubscriptionNotifications() {
|
||||
// Always try to remove
|
||||
// If it doesn't exist, nothing will happen
|
||||
const { markSubscriptionRead, uri, channelUri } = this.props;
|
||||
markSubscriptionRead(channelUri, uri);
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const {
|
||||
|
@ -131,11 +126,12 @@ class FilePage extends React.Component<Props> {
|
|||
costInfo,
|
||||
fileInfo,
|
||||
autoplay,
|
||||
channelUri,
|
||||
} = this.props;
|
||||
|
||||
// File info
|
||||
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 isRewardContent = (rewardedContentClaimIds || []).includes(claim.claim_id);
|
||||
const shouldObscureThumbnail = obscureNsfw && metadata.nsfw;
|
||||
|
@ -143,12 +139,6 @@ class FilePage extends React.Component<Props> {
|
|||
const mediaType = getMediaType(contentType, fileName);
|
||||
const showFile =
|
||||
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 =
|
||||
costInfo &&
|
||||
costInfo.cost === 0 &&
|
||||
|
@ -159,7 +149,10 @@ class FilePage extends React.Component<Props> {
|
|||
// We will select the claim id before they publish
|
||||
let editUri;
|
||||
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) {
|
||||
uriObject.channelName = channelName;
|
||||
}
|
||||
|
@ -193,7 +186,7 @@ class FilePage extends React.Component<Props> {
|
|||
{isRewardContent && (
|
||||
<Icon size={20} iconColor="red" tooltip="bottom" icon={icons.FEATURED} />
|
||||
)}
|
||||
<FilePrice filePage uri={normalizeURI(uri)} />
|
||||
<FilePrice badge uri={normalizeURI(uri)} />
|
||||
</div>
|
||||
</div>
|
||||
<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 && (
|
||||
<Button
|
||||
|
|
|
@ -21,7 +21,7 @@ class FileListDownloaded extends React.PureComponent<Props> {
|
|||
<FileList fileInfos={fileInfos} />
|
||||
) : (
|
||||
<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">
|
||||
<Button
|
||||
button="primary"
|
||||
|
|
|
@ -29,7 +29,9 @@ class FileListPublished extends React.PureComponent<Props> {
|
|||
<FileList checkPending fileInfos={claims} sortByHeight />
|
||||
) : (
|
||||
<div className="page__empty">
|
||||
<h3 className="card__title">
|
||||
{__("It looks like you haven't published anything to LBRY yet.")}
|
||||
</h3>
|
||||
<div className="card__actions card__actions--center">
|
||||
<Button
|
||||
button="primary"
|
||||
|
|
|
@ -184,11 +184,13 @@ class SettingsPage extends React.PureComponent<Props, State> {
|
|||
<section className="card card--section">
|
||||
<div className="card__title">{__('Download Directory')}</div>
|
||||
<span className="card__subtitle">{__('LBRY downloads will be saved here.')}</span>
|
||||
<div className="card__content">
|
||||
<FileSelector
|
||||
type="openDirectory"
|
||||
currentPath={daemonSettings.download_directory}
|
||||
onFileChosen={this.onDownloadDirChange}
|
||||
/>
|
||||
</div>
|
||||
</section>
|
||||
<section className="card card--section">
|
||||
<div className="card__title">{__('Max Purchase Price')}</div>
|
||||
|
|
|
@ -5,9 +5,14 @@ import {
|
|||
selectSubscriptions,
|
||||
selectSubscriptionsBeingFetched,
|
||||
selectIsFetchingSubscriptions,
|
||||
selectNotifications,
|
||||
selectUnreadSubscriptions,
|
||||
selectViewMode,
|
||||
} 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 { makeSelectClientSetting } from 'redux/selectors/settings';
|
||||
import SubscriptionsPage from './view';
|
||||
|
@ -16,17 +21,19 @@ const select = state => ({
|
|||
loading:
|
||||
selectIsFetchingSubscriptions(state) ||
|
||||
Boolean(Object.keys(selectSubscriptionsBeingFetched(state)).length),
|
||||
subscriptions: selectSubscriptions(state),
|
||||
subscriptionClaims: selectSubscriptionClaims(state),
|
||||
notifications: selectNotifications(state),
|
||||
subscribedChannels: selectSubscriptions(state),
|
||||
autoDownload: makeSelectClientSetting(settings.AUTO_DOWNLOAD)(state),
|
||||
allSubscriptions: selectSubscriptionClaims(state),
|
||||
unreadSubscriptions: selectUnreadSubscriptions(state),
|
||||
viewMode: selectViewMode(state),
|
||||
});
|
||||
|
||||
export default connect(
|
||||
select,
|
||||
{
|
||||
setSubscriptionNotifications,
|
||||
doUpdateUnreadSubscriptions,
|
||||
doFetchMySubscriptions,
|
||||
doSetClientSetting,
|
||||
doSetViewMode,
|
||||
}
|
||||
)(SubscriptionsPage);
|
||||
|
|
|
@ -1,83 +1,153 @@
|
|||
// @flow
|
||||
import React from 'react';
|
||||
import Page from 'component/page';
|
||||
import type { ViewMode } from 'types/subscription';
|
||||
import type { Claim } from 'types/claim';
|
||||
import { VIEW_ALL, VIEW_LATEST_FIRST } from 'constants/subscriptions';
|
||||
import * as settings from 'constants/settings';
|
||||
import type { Subscription } from 'types/subscription';
|
||||
import * as NOTIFICATION_TYPES from 'constants/notification_types';
|
||||
import * as React from 'react';
|
||||
import Page from 'component/page';
|
||||
import Button from 'component/button';
|
||||
import FileList from 'component/fileList';
|
||||
import type { Claim } from 'types/claim';
|
||||
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 = {
|
||||
doFetchMySubscriptions: () => void,
|
||||
setSubscriptionNotifications: ({}) => void,
|
||||
subscriptions: Array<Subscription>,
|
||||
subscriptionClaims: Array<{ uri: string, claims: Array<Claim> }>,
|
||||
notifications: {},
|
||||
subscribedChannels: Array<string>, // The channels a user is subscribed to
|
||||
unreadSubscriptions: Array<{
|
||||
channel: string,
|
||||
uris: Array<string>,
|
||||
}>,
|
||||
allSubscriptions: Array<{ uri: string, ...Claim }>,
|
||||
loading: boolean,
|
||||
autoDownload: boolean,
|
||||
viewMode: ViewMode,
|
||||
doSetViewMode: ViewMode => void,
|
||||
doFetchMySubscriptions: () => void,
|
||||
doSetClientSetting: (string, boolean) => void,
|
||||
};
|
||||
|
||||
export default class extends React.PureComponent<Props> {
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
(this: any).onAutoDownloadChange = this.onAutoDownloadChange.bind(this);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const { notifications, setSubscriptionNotifications, doFetchMySubscriptions } = this.props;
|
||||
const { doFetchMySubscriptions } = this.props;
|
||||
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<*>) {
|
||||
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() {
|
||||
const { subscriptions, subscriptionClaims, loading, autoDownload } = this.props;
|
||||
|
||||
let claimList = [];
|
||||
subscriptionClaims.forEach(claimData => {
|
||||
claimList = claimList.concat(claimData.claims);
|
||||
});
|
||||
|
||||
const subscriptionUris = claimList.map(claim => `lbry://${claim.name}#${claim.claim_id}`);
|
||||
const {
|
||||
subscribedChannels,
|
||||
allSubscriptions,
|
||||
loading,
|
||||
autoDownload,
|
||||
viewMode,
|
||||
doSetViewMode,
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<Page notContained loading={loading}>
|
||||
<HiddenNsfwClaims uris={subscriptionUris} />
|
||||
<FormRow alignRight>
|
||||
// Only pass in the loading prop if there are no subscriptions
|
||||
// If there are any, let the page update in the background
|
||||
// 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
|
||||
type="checkbox"
|
||||
name="auto_download"
|
||||
onChange={this.onAutoDownloadChange}
|
||||
checked={autoDownload}
|
||||
prefix={__('Automatically download new content from your subscriptions')}
|
||||
prefix={__('Auto download')}
|
||||
/>
|
||||
</FormRow>
|
||||
{!subscriptions.length && (
|
||||
</div>
|
||||
)}
|
||||
{!subscribedChannels.length && (
|
||||
<div className="page__empty">
|
||||
<h3 className="card__title">
|
||||
{__("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')} />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{!!claimList.length && <FileList hideFilter sortByHeight fileInfos={claimList} />}
|
||||
{!!subscribedChannels.length && (
|
||||
<div className="card__content">{this.renderSubscriptions()}</div>
|
||||
)}
|
||||
</Page>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
// @flow
|
||||
import * as NOTIFICATION_TYPES from 'constants/notification_types';
|
||||
import * as NOTIFICATION_TYPES from 'constants/subscriptions';
|
||||
import { ipcRenderer } from 'electron';
|
||||
import { doAlertError } from 'redux/actions/app';
|
||||
import { doNavigate } from 'redux/actions/navigation';
|
||||
import { setSubscriptionLatest, setSubscriptionNotification } from 'redux/actions/subscriptions';
|
||||
import { selectNotifications } from 'redux/selectors/subscriptions';
|
||||
import { setSubscriptionLatest, doUpdateUnreadSubscriptions } from 'redux/actions/subscriptions';
|
||||
import { makeSelectUnreadByChannel } from 'redux/selectors/subscriptions';
|
||||
import { selectBadgeNumber } from 'redux/selectors/app';
|
||||
import {
|
||||
ACTIONS,
|
||||
|
@ -21,6 +21,8 @@ import {
|
|||
selectBalance,
|
||||
MODALS,
|
||||
doNotify,
|
||||
makeSelectChannelForClaimUri,
|
||||
parseURI,
|
||||
} from 'lbry-redux';
|
||||
import { makeSelectClientSetting, selectosNotificationsEnabled } from 'redux/selectors/settings';
|
||||
import setBadge from 'util/setBadge';
|
||||
|
@ -66,19 +68,15 @@ export function doUpdateLoadStatus(uri: string, outpoint: string) {
|
|||
const totalProgress = selectTotalDownloadProgress(state);
|
||||
setProgressBar(totalProgress);
|
||||
|
||||
const notifications = selectNotifications(state);
|
||||
if (notifications[uri] && notifications[uri].type === NOTIFICATION_TYPES.DOWNLOADING) {
|
||||
const count = Object.keys(notifications).reduce(
|
||||
(acc, cur) =>
|
||||
notifications[cur].subscription.channelName ===
|
||||
notifications[uri].subscription.channelName
|
||||
? acc + 1
|
||||
: acc,
|
||||
0
|
||||
);
|
||||
const channelUri = makeSelectChannelForClaimUri(uri, true)(state);
|
||||
const { claimName: channelName } = parseURI(channelUri);
|
||||
|
||||
const unreadForChannel = makeSelectUnreadByChannel(channelUri)(state);
|
||||
if (unreadForChannel.type === NOTIFICATION_TYPES.DOWNLOADING) {
|
||||
const count = unreadForChannel.uris.length;
|
||||
|
||||
if (selectosNotificationsEnabled(state)) {
|
||||
const notif = new window.Notification(notifications[uri].subscription.channelName, {
|
||||
const notif = new window.Notification(channelName, {
|
||||
body: `Posted ${fileInfo.metadata.title}${
|
||||
count > 1 && count < 10 ? ` and ${count - 1} 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(
|
||||
setSubscriptionNotification(
|
||||
notifications[uri].subscription,
|
||||
uri,
|
||||
NOTIFICATION_TYPES.DOWNLOADED
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
dispatch(doUpdateUnreadSubscriptions(channelUri, null, NOTIFICATION_TYPES.DOWNLOADED));
|
||||
} else {
|
||||
// If notifications are disabled(false) just return
|
||||
if (!selectosNotificationsEnabled(getState())) return;
|
||||
|
||||
const notif = new window.Notification('LBRY Download Complete', {
|
||||
body: fileInfo.metadata.title,
|
||||
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 => {
|
||||
dispatch({
|
||||
type: ACTIONS.FETCH_CHANNEL_CLAIMS_STARTED,
|
||||
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 { 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)
|
||||
)
|
||||
);
|
||||
// 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({
|
||||
|
|
|
@ -1,26 +1,36 @@
|
|||
// @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 NOTIFICATION_TYPES from 'constants/notification_types';
|
||||
import * as SETTINGS from 'constants/settings';
|
||||
import * as NOTIFICATION_TYPES from 'constants/subscriptions';
|
||||
import { Lbryio, rewards, doClaimRewardType } from 'lbryinc';
|
||||
import type { Dispatch, SubscriptionNotifications } from 'redux/reducers/subscriptions';
|
||||
import type { Subscription } from 'types/subscription';
|
||||
import { selectSubscriptions } from 'redux/selectors/subscriptions';
|
||||
import { selectSubscriptions, selectUnreadByChannel } from 'redux/selectors/subscriptions';
|
||||
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 Promise from 'bluebird';
|
||||
|
||||
const CHECK_SUBSCRIPTIONS_INTERVAL = 15 * 60 * 1000;
|
||||
const SUBSCRIPTION_DOWNLOAD_LIMIT = 1;
|
||||
|
||||
export const doFetchMySubscriptions = () => (dispatch: Dispatch, getState: () => any) => {
|
||||
const {
|
||||
subscriptions: subscriptionState,
|
||||
settings: { daemonSettings },
|
||||
} = getState();
|
||||
const { subscriptions: reduxSubscriptions } = subscriptionState;
|
||||
const { share_usage_data: isSharingData } = daemonSettings;
|
||||
export const doSetViewMode = (viewMode: ViewMode) => (dispatch: ReduxDispatch) =>
|
||||
dispatch({
|
||||
type: ACTIONS.SET_VIEW_MODE,
|
||||
data: viewMode,
|
||||
});
|
||||
|
||||
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) {
|
||||
// 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
|
||||
return reduxSubscriptions;
|
||||
})
|
||||
.then(subscriptions => {
|
||||
.then((subscriptions: Array<Subscription>) => {
|
||||
dispatch({
|
||||
type: ACTIONS.FETCH_SUBSCRIPTIONS_SUCCESS,
|
||||
data: subscriptions,
|
||||
});
|
||||
|
||||
subscriptions.forEach(({ uri }) => dispatch(doFetchClaimsByChannel(uri)));
|
||||
subscriptions.forEach(({ uri }) => dispatch(doFetchClaimsByChannel(uri, 1, 20)));
|
||||
})
|
||||
.catch(() => {
|
||||
dispatch({
|
||||
|
@ -100,7 +110,7 @@ export const doFetchMySubscriptions = () => (dispatch: Dispatch, getState: () =>
|
|||
};
|
||||
|
||||
export const setSubscriptionLatest = (subscription: Subscription, uri: string) => (
|
||||
dispatch: Dispatch
|
||||
dispatch: ReduxDispatch
|
||||
) =>
|
||||
dispatch({
|
||||
type: ACTIONS.SET_SUBSCRIPTION_LATEST,
|
||||
|
@ -110,75 +120,160 @@ export const setSubscriptionLatest = (subscription: Subscription, uri: string) =
|
|||
},
|
||||
});
|
||||
|
||||
export const setSubscriptionNotification = (
|
||||
subscription: Subscription,
|
||||
uri: string,
|
||||
notificationType: string
|
||||
) => (dispatch: Dispatch) =>
|
||||
// Populate a channels unread subscriptions or update the type
|
||||
export const doUpdateUnreadSubscriptions = (
|
||||
channelUri: string,
|
||||
uris: ?Array<string>,
|
||||
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({
|
||||
type: ACTIONS.SET_SUBSCRIPTION_NOTIFICATION,
|
||||
type: ACTIONS.UPDATE_SUBSCRIPTION_UNREADS,
|
||||
data: {
|
||||
subscription,
|
||||
uri,
|
||||
type: notificationType,
|
||||
channel: channelUri,
|
||||
uris: newUris,
|
||||
type: newType,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const doCheckSubscription = (subscriptionUri: string, notify?: boolean) => (
|
||||
dispatch: Dispatch,
|
||||
getState: () => {}
|
||||
// Remove multiple files (or all) from a channels unread subscriptions
|
||||
export const doRemoveUnreadSubscriptions = (channelUri: string, readUris: Array<string>) => (
|
||||
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>
|
||||
|
||||
const state = getState();
|
||||
const currentPage = selectCurrentPage(state);
|
||||
const shouldAutoDownload = makeSelectClientSetting(SETTINGS.AUTO_DOWNLOAD)(state);
|
||||
const savedSubscription = state.subscriptions.subscriptions.find(
|
||||
sub => sub.uri === subscriptionUri
|
||||
);
|
||||
|
||||
Lbry.claim_list_by_channel({ uri: subscriptionUri, page: 1 }).then(result => {
|
||||
const claimResult = result[subscriptionUri] || {};
|
||||
if (!savedSubscription) {
|
||||
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;
|
||||
|
||||
// may happen if subscribed to an abandoned channel or an empty channel
|
||||
if (!claimsInChannel) {
|
||||
if (!claimsInChannel || !claimsInChannel.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Determine if the latest subscription currently saved is actually the latest subscription
|
||||
const latestIndex = claimsInChannel.findIndex(
|
||||
claim => `${claim.name}#${claim.claim_id}` === savedSubscription.latest
|
||||
);
|
||||
|
||||
// if latest is 0, nothing has changed
|
||||
// when there is no subscription latest, it is either a newly subscriubed channel or
|
||||
// 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 (claimsInChannel.length && latestIndex !== 0 && savedSubscription.latest) {
|
||||
// If latest is -1, it is a newly subscribed channel or there have been 10+ claims published since last viewed
|
||||
const latestIndexToNotify = latestIndex === -1 ? 10 : latestIndex;
|
||||
|
||||
// If latest is 0, nothing has changed
|
||||
// Do not download/notify about new content, it would download/notify 10 claims per channel
|
||||
if (latestIndex !== 0 && savedSubscription.latest) {
|
||||
let downloadCount = 0;
|
||||
claimsInChannel.slice(0, latestIndex === -1 ? 10 : latestIndex).forEach(claim => {
|
||||
const uri = buildURI({ contentName: claim.name, claimId: claim.claim_id }, false);
|
||||
const shouldDownload = Boolean(
|
||||
downloadCount < SUBSCRIPTION_DOWNLOAD_LIMIT &&
|
||||
!claim.value.stream.metadata.fee &&
|
||||
makeSelectClientSetting(SETTINGS.AUTO_DOWNLOAD)(state)
|
||||
);
|
||||
if (notify && currentPage !== 'subscriptions') {
|
||||
dispatch(
|
||||
setSubscriptionNotification(
|
||||
savedSubscription,
|
||||
uri,
|
||||
shouldDownload ? NOTIFICATION_TYPES.DOWNLOADING : NOTIFICATION_TYPES.NOTIFY_ONLY
|
||||
)
|
||||
);
|
||||
|
||||
const newUnread = [];
|
||||
claimsInChannel.slice(0, latestIndexToNotify).forEach(claim => {
|
||||
const uri = buildURI({ contentName: claim.name, claimId: claim.claim_id }, true);
|
||||
const shouldDownload =
|
||||
shouldAutoDownload &&
|
||||
Boolean(downloadCount < SUBSCRIPTION_DOWNLOAD_LIMIT && !claim.value.stream.metadata.fee);
|
||||
|
||||
// Add the new content to the list of "un-read" subscriptions
|
||||
if (shouldNotify) {
|
||||
newUnread.push(uri);
|
||||
}
|
||||
|
||||
if (shouldDownload) {
|
||||
downloadCount += 1;
|
||||
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(
|
||||
setSubscriptionLatest(
|
||||
{
|
||||
|
@ -208,22 +303,11 @@ export const doCheckSubscription = (subscriptionUri: string, notify?: boolean) =
|
|||
page: 1,
|
||||
},
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
export const setSubscriptionNotifications = (notifications: SubscriptionNotifications) => (
|
||||
dispatch: Dispatch
|
||||
) =>
|
||||
dispatch({
|
||||
type: ACTIONS.SET_SUBSCRIPTION_NOTIFICATIONS,
|
||||
data: {
|
||||
notifications,
|
||||
},
|
||||
});
|
||||
|
||||
export const doChannelSubscribe = (subscription: Subscription) => (
|
||||
dispatch: Dispatch,
|
||||
getState: () => any
|
||||
dispatch: ReduxDispatch,
|
||||
getState: GetState
|
||||
) => {
|
||||
const {
|
||||
settings: { daemonSettings },
|
||||
|
@ -251,8 +335,8 @@ export const doChannelSubscribe = (subscription: Subscription) => (
|
|||
};
|
||||
|
||||
export const doChannelUnsubscribe = (subscription: Subscription) => (
|
||||
dispatch: Dispatch,
|
||||
getState: () => any
|
||||
dispatch: ReduxDispatch,
|
||||
getState: GetState
|
||||
) => {
|
||||
const {
|
||||
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 subscriptions = selectSubscriptions(state);
|
||||
|
||||
subscriptions.forEach((sub: Subscription) => {
|
||||
dispatch(doCheckSubscription(sub.uri, true));
|
||||
});
|
||||
};
|
||||
|
||||
export const doCheckSubscriptionsInit = () => (dispatch: Dispatch) => {
|
||||
export const doCheckSubscriptionsInit = () => (dispatch: ReduxDispatch) => {
|
||||
// doCheckSubscriptionsInit is called by doDaemonReady
|
||||
// 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
|
||||
|
|
|
@ -1,98 +1,31 @@
|
|||
// @flow
|
||||
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 type { Subscription } from 'types/subscription';
|
||||
import type { Dispatch as ReduxDispatch } from 'types/redux';
|
||||
import type {
|
||||
SubscriptionState,
|
||||
Subscription,
|
||||
DoChannelSubscribe,
|
||||
DoChannelUnsubscribe,
|
||||
SetSubscriptionLatest,
|
||||
DoUpdateSubscriptionUnreads,
|
||||
DoRemoveSubscriptionUnreads,
|
||||
FetchedSubscriptionsSucess,
|
||||
SetViewMode,
|
||||
} from 'types/subscription';
|
||||
|
||||
export type NotificationType =
|
||||
| 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 = {
|
||||
const defaultState: SubscriptionState = {
|
||||
subscriptions: [],
|
||||
notifications: {},
|
||||
unread: {},
|
||||
loading: false,
|
||||
viewMode: VIEW_ALL,
|
||||
};
|
||||
|
||||
export default handleActions(
|
||||
{
|
||||
[ACTIONS.CHANNEL_SUBSCRIBE]: (
|
||||
state: SubscriptionState,
|
||||
action: doChannelSubscribe
|
||||
action: DoChannelSubscribe
|
||||
): SubscriptionState => {
|
||||
const newSubscription: Subscription = action.data;
|
||||
const newSubscriptions: Array<Subscription> = state.subscriptions.slice();
|
||||
|
@ -105,7 +38,7 @@ export default handleActions(
|
|||
},
|
||||
[ACTIONS.CHANNEL_UNSUBSCRIBE]: (
|
||||
state: SubscriptionState,
|
||||
action: doChannelUnsubscribe
|
||||
action: DoChannelUnsubscribe
|
||||
): SubscriptionState => {
|
||||
const subscriptionToRemove: Subscription = action.data;
|
||||
|
||||
|
@ -120,7 +53,7 @@ export default handleActions(
|
|||
},
|
||||
[ACTIONS.SET_SUBSCRIPTION_LATEST]: (
|
||||
state: SubscriptionState,
|
||||
action: setSubscriptionLatest
|
||||
action: SetSubscriptionLatest
|
||||
): SubscriptionState => ({
|
||||
...state,
|
||||
subscriptions: state.subscriptions.map(
|
||||
|
@ -130,23 +63,43 @@ export default handleActions(
|
|||
: subscription
|
||||
),
|
||||
}),
|
||||
[ACTIONS.SET_SUBSCRIPTION_NOTIFICATION]: (
|
||||
[ACTIONS.UPDATE_SUBSCRIPTION_UNREADS]: (
|
||||
state: SubscriptionState,
|
||||
action: setSubscriptionNotification
|
||||
): SubscriptionState => ({
|
||||
action: DoUpdateSubscriptionUnreads
|
||||
): SubscriptionState => {
|
||||
const { channel, uris, type } = action.data;
|
||||
|
||||
return {
|
||||
...state,
|
||||
notifications: {
|
||||
...state.notifications,
|
||||
[action.data.uri]: { subscription: action.data.subscription, type: action.data.type },
|
||||
unread: {
|
||||
...state.unread,
|
||||
[channel]: {
|
||||
uris,
|
||||
type,
|
||||
},
|
||||
}),
|
||||
[ACTIONS.SET_SUBSCRIPTION_NOTIFICATIONS]: (
|
||||
},
|
||||
};
|
||||
},
|
||||
[ACTIONS.REMOVE_SUBSCRIPTION_UNREADS]: (
|
||||
state: SubscriptionState,
|
||||
action: setSubscriptionNotifications
|
||||
): SubscriptionState => ({
|
||||
action: DoRemoveSubscriptionUnreads
|
||||
): SubscriptionState => {
|
||||
const { channel, uris } = action.data;
|
||||
const newUnread = { ...state.unread };
|
||||
|
||||
if (!uris) {
|
||||
delete newUnread[channel];
|
||||
} else {
|
||||
newUnread[channel].uris = uris;
|
||||
}
|
||||
|
||||
return {
|
||||
...state,
|
||||
notifications: action.data.notifications,
|
||||
}),
|
||||
unread: {
|
||||
...newUnread,
|
||||
},
|
||||
};
|
||||
},
|
||||
[ACTIONS.FETCH_SUBSCRIPTIONS_START]: (state: SubscriptionState): SubscriptionState => ({
|
||||
...state,
|
||||
loading: true,
|
||||
|
@ -157,12 +110,19 @@ export default handleActions(
|
|||
}),
|
||||
[ACTIONS.FETCH_SUBSCRIPTIONS_SUCCESS]: (
|
||||
state: SubscriptionState,
|
||||
action: fetchedSubscriptionsSucess
|
||||
action: FetchedSubscriptionsSucess
|
||||
): SubscriptionState => ({
|
||||
...state,
|
||||
loading: false,
|
||||
subscriptions: action.data,
|
||||
}),
|
||||
[ACTIONS.SET_VIEW_MODE]: (
|
||||
state: SubscriptionState,
|
||||
action: SetViewMode
|
||||
): SubscriptionState => ({
|
||||
...state,
|
||||
viewMode: action.data,
|
||||
}),
|
||||
},
|
||||
defaultState
|
||||
);
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { createSelector } from 'reselect';
|
||||
import { selectCurrentPage, selectHistoryStack } from 'lbry-redux';
|
||||
import * as icons from 'constants/icons';
|
||||
|
||||
export const selectState = state => state.app || {};
|
||||
|
||||
|
@ -198,46 +199,46 @@ export const selectNavLinks = createSelector(
|
|||
label: 'Explore',
|
||||
path: '/discover',
|
||||
active: currentPage === 'discover',
|
||||
icon: 'Compass',
|
||||
icon: icons.COMPASS,
|
||||
},
|
||||
{
|
||||
label: 'Subscriptions',
|
||||
path: '/subscriptions',
|
||||
active: currentPage === 'subscriptions',
|
||||
icon: 'AtSign',
|
||||
icon: icons.HEART,
|
||||
},
|
||||
],
|
||||
secondary: [
|
||||
{
|
||||
label: 'Wallet',
|
||||
icon: 'CreditCard',
|
||||
icon: icons.CREDIT_CARD,
|
||||
subLinks: walletSubLinks,
|
||||
path: isCurrentlyWalletPage ? '/wallet' : getActiveSublink('wallet'),
|
||||
active: isWalletPage(currentPage),
|
||||
},
|
||||
{
|
||||
label: 'My LBRY',
|
||||
icon: 'Folder',
|
||||
icon: icons.LOCAL,
|
||||
subLinks: myLbrySubLinks,
|
||||
path: isCurrentlyMyLbryPage ? '/downloaded' : getActiveSublink('myLbry'),
|
||||
active: isMyLbryPage(currentPage),
|
||||
},
|
||||
{
|
||||
label: 'Publish',
|
||||
icon: 'UploadCloud',
|
||||
icon: icons.UPLOAD,
|
||||
path: '/publish',
|
||||
active: currentPage === 'publish',
|
||||
},
|
||||
{
|
||||
label: 'Settings',
|
||||
icon: 'Settings',
|
||||
icon: icons.SETTINGS,
|
||||
path: '/settings',
|
||||
active: currentPage === 'settings',
|
||||
},
|
||||
{
|
||||
label: 'Help',
|
||||
path: '/help',
|
||||
icon: 'HelpCircle',
|
||||
icon: icons.HELP,
|
||||
active: currentPage === 'help',
|
||||
},
|
||||
],
|
||||
|
|
|
@ -3,24 +3,127 @@ import {
|
|||
selectAllClaimsByChannel,
|
||||
selectClaimsById,
|
||||
selectAllFetchingChannelClaims,
|
||||
makeSelectClaimForUri,
|
||||
makeSelectChannelForClaimUri,
|
||||
selectClaimsByUri,
|
||||
parseURI,
|
||||
} from 'lbry-redux';
|
||||
|
||||
// get the entire subscriptions state
|
||||
// Returns the entire subscriptions state
|
||||
const selectState = state => state.subscriptions || {};
|
||||
|
||||
export const selectIsFetchingSubscriptions = createSelector(selectState, state => state.loading);
|
||||
|
||||
export const selectNotifications = createSelector(selectState, state => state.notifications);
|
||||
|
||||
// list of saved channel names and uris
|
||||
// Returns the list of channel uris a user is subscribed to
|
||||
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(
|
||||
selectAllClaimsByChannel,
|
||||
selectClaimsById,
|
||||
selectSubscriptions,
|
||||
(channelIds, allClaims, savedSubscriptions) => {
|
||||
selectUnreadByChannel,
|
||||
(channelIds, allClaims, savedSubscriptions, unreadByChannel) => {
|
||||
// no claims loaded yet
|
||||
if (!Object.keys(channelIds).length) {
|
||||
return [];
|
||||
|
@ -34,51 +137,51 @@ export const selectSubscriptionClaims = createSelector(
|
|||
// if subscribed channel has content
|
||||
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
|
||||
|
||||
// Strip out any ids that will be shown as notifications
|
||||
const pageOneChannelIds = channelIds[subscription.uri]['1'];
|
||||
|
||||
// we have the channel ids and the corresponding claims
|
||||
// loop over the list of ids and grab the claim
|
||||
pageOneChannelIds.forEach(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]);
|
||||
});
|
||||
}
|
||||
|
||||
fetchedSubscriptions = fetchedSubscriptions.concat([
|
||||
{
|
||||
claims: [...channelClaims],
|
||||
channelName: subscription.channelName,
|
||||
uri: subscription.uri,
|
||||
},
|
||||
]);
|
||||
fetchedSubscriptions = fetchedSubscriptions.concat(channelClaims);
|
||||
});
|
||||
|
||||
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;
|
||||
return fetchedSubscriptions;
|
||||
}
|
||||
);
|
||||
|
||||
// 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 =>
|
||||
createSelector(selectSubscriptions, makeSelectClaimForUri(uri), (subscriptions, claim) => {
|
||||
if (!claim || !claim.channel_name) {
|
||||
createSelector(
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
.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 {
|
||||
&:not(.credit-amount--file-page) {
|
||||
color: $lbry-gray-5;
|
||||
}
|
||||
|
||||
&.credit-amount--file-page {
|
||||
background-color: $lbry-yellow-3;
|
||||
color: $lbry-black;
|
||||
}
|
||||
}
|
||||
|
||||
.credit-amount--inherit {
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
@charset "utf-8";
|
||||
|
||||
@import '~@lbry/color/lbry-color', 'reset', 'type', 'vars', 'gui', 'component/syntax-highlighter',
|
||||
'component/table', 'component/button', 'component/card', 'component/file-download',
|
||||
'component/form-field', 'component/header', 'component/menu', 'component/tooltip',
|
||||
|
@ -8,4 +6,4 @@
|
|||
'component/markdown-editor', 'component/scrollbar', 'component/spinner', 'component/nav',
|
||||
'component/file-list', 'component/file-render', 'component/search', 'component/toggle',
|
||||
'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;
|
||||
}
|
||||
|
||||
&.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 {
|
||||
// wtf?
|
||||
cursor: default;
|
||||
|
|
|
@ -5,6 +5,11 @@
|
|||
margin-top: $spacing-vertical * 2/3;
|
||||
}
|
||||
|
||||
.file-list__sort {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.file-list__header {
|
||||
font-size: 24px;
|
||||
padding-top: $spacing-vertical * 4/3;
|
||||
|
@ -14,12 +19,6 @@
|
|||
}
|
||||
}
|
||||
|
||||
.file-list__sort {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
padding-bottom: 20px;
|
||||
}
|
||||
|
||||
.file-tile {
|
||||
display: flex;
|
||||
font-size: 14px;
|
||||
|
|
|
@ -101,7 +101,7 @@ const compressor = createCompressor();
|
|||
// 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
|
||||
// const saveClaimsFilter = createFilter('claims', ['byId', 'claimsByUri']);
|
||||
const subscriptionsFilter = createFilter('subscriptions', ['subscriptions']);
|
||||
const subscriptionsFilter = createFilter('subscriptions', ['subscriptions', 'unread', 'viewMode']);
|
||||
const contentFilter = createFilter('content', ['positions', 'history']);
|
||||
|
||||
// We only need to persist the receiveAddress for the wallet
|
||||
|
|
|
@ -6,6 +6,7 @@ export type FileInfo = {
|
|||
channelName: ?string,
|
||||
pending?: boolean,
|
||||
channel_claim_id: string,
|
||||
file_name: string,
|
||||
value?: {
|
||||
publisherSignature: {
|
||||
certificateId: string,
|
||||
|
|
|
@ -2,5 +2,5 @@
|
|||
|
||||
// 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 GetState = () => {};
|
||||
export type GetState = () => any;
|
||||
export type ThunkAction<T> = (dispatch: Dispatch<T>, getState: GetState) => any;
|
||||
|
|
|
@ -1,7 +1,108 @@
|
|||
// @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 = {
|
||||
channelName: string, // @CryptoCandor,
|
||||
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 = {
|
||||
// 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: {
|
||||
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-types "^6.24.1"
|
||||
|
||||
babel-polyfill@^6.20.0, babel-polyfill@^6.26.0:
|
||||
babel-polyfill@^6.26.0:
|
||||
version "6.26.0"
|
||||
resolved "https://registry.yarnpkg.com/babel-polyfill/-/babel-polyfill-6.26.0.tgz#379937abc67d7895970adc621f284cd966cf2153"
|
||||
dependencies:
|
||||
|
@ -5670,16 +5670,16 @@ lbry-redux@lbryio/lbry-redux:
|
|||
proxy-polyfill "0.1.6"
|
||||
reselect "^3.0.0"
|
||||
|
||||
lbry-redux@lbryio/lbry-redux#67cae46983d9fea90dd1e4c5bd121dd5077a3f0e:
|
||||
lbry-redux@lbryio/lbry-redux#957d221c1830ecbb7a9e74fad78e711fb14539f4:
|
||||
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:
|
||||
proxy-polyfill "0.1.6"
|
||||
reselect "^3.0.0"
|
||||
|
||||
lbryinc@lbryio/lbryinc#de7ff055605b02a24821f0f9bab1d206eb7f235d:
|
||||
lbryinc@lbryio/lbryinc#3f34af546ee73ff2ee7d8ad05e540b3b0aa658fb:
|
||||
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:
|
||||
lbry-redux lbryio/lbry-redux
|
||||
reselect "^3.0.0"
|
||||
|
|
Loading…
Reference in a new issue