feature: use internal-apis for subscriptions and add page loader

update subscription types

update changelog

Simplify subscriptions sync logic

add claim type

use let over const

change spinner color based on theme

clean up subscriptions
This commit is contained in:
Sean Yesmunt 2018-05-07 00:50:55 -04:00 committed by Jeremy Kauffman
parent 2bb4287408
commit 492b1601f6
38 changed files with 1090 additions and 294 deletions

View file

@ -34,6 +34,7 @@
"singleQuote": true
}],
"func-names": ["warn", "as-needed"],
"jsx-a11y/label-has-for": 0
"jsx-a11y/label-has-for": 0,
"import/prefer-default-export": 0
}
}

View file

@ -0,0 +1,6 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="Eslint" enabled="true" level="ERROR" enabled_by_default="true" />
</profile>
</component>

12
.idea/lbry-app.iml generated Normal file
View file

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="WEB_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$">
<excludeFolder url="file://$MODULE_DIR$/.tmp" />
<excludeFolder url="file://$MODULE_DIR$/temp" />
<excludeFolder url="file://$MODULE_DIR$/tmp" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

6
.idea/misc.xml generated Normal file
View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="JavaScriptSettings">
<option name="languageLevel" value="FLOW" />
</component>
</project>

8
.idea/modules.xml generated Normal file
View file

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/lbry-app.iml" filepath="$PROJECT_DIR$/.idea/lbry-app.iml" />
</modules>
</component>
</project>

6
.idea/vcs.xml generated Normal file
View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

547
.idea/workspace.xml generated Normal file
View file

@ -0,0 +1,547 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ChangeListManager">
<list default="true" id="061e089e-71dc-4d6b-b27c-9c614b097257" name="Default" comment="">
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/src/renderer/types/claim.js" />
<change type="MODIFICATION" beforePath="$PROJECT_DIR$/src/renderer/component/fileDetails/view.jsx" afterPath="$PROJECT_DIR$/src/renderer/component/fileDetails/view.jsx" />
<change type="MODIFICATION" beforePath="$PROJECT_DIR$/src/renderer/component/uriIndicator/view.jsx" afterPath="$PROJECT_DIR$/src/renderer/component/uriIndicator/view.jsx" />
<change type="MODIFICATION" beforePath="$PROJECT_DIR$/src/renderer/component/video/view.jsx" afterPath="$PROJECT_DIR$/src/renderer/component/video/view.jsx" />
<change type="MODIFICATION" beforePath="$PROJECT_DIR$/src/renderer/component/walletSendTip/view.jsx" afterPath="$PROJECT_DIR$/src/renderer/component/walletSendTip/view.jsx" />
<change type="MODIFICATION" beforePath="$PROJECT_DIR$/src/renderer/page/channel/view.jsx" afterPath="$PROJECT_DIR$/src/renderer/page/channel/view.jsx" />
<change type="MODIFICATION" beforePath="$PROJECT_DIR$/src/renderer/page/file/view.jsx" afterPath="$PROJECT_DIR$/src/renderer/page/file/view.jsx" />
<change type="MODIFICATION" beforePath="$PROJECT_DIR$/src/renderer/page/show/view.jsx" afterPath="$PROJECT_DIR$/src/renderer/page/show/view.jsx" />
<change type="MODIFICATION" beforePath="$PROJECT_DIR$/src/renderer/page/subscriptions/view.jsx" afterPath="$PROJECT_DIR$/src/renderer/page/subscriptions/view.jsx" />
</list>
<ignored path="$PROJECT_DIR$/.tmp/" />
<ignored path="$PROJECT_DIR$/temp/" />
<ignored path="$PROJECT_DIR$/tmp/" />
<option name="EXCLUDED_CONVERTED_TO_IGNORED" value="true" />
<option name="TRACKING_ENABLED" value="true" />
<option name="SHOW_DIALOG" value="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" />
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
<option name="LAST_RESOLUTION" value="IGNORE" />
</component>
<component name="FileEditorManager">
<leaf SIDE_TABS_SIZE_LIMIT_KEY="300">
<file leaf-file-name="view.jsx" pinned="false" current-in-tab="false">
<entry file="file://$PROJECT_DIR$/src/renderer/modal/modalRouter/view.jsx">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="0">
<caret line="0" column="0" lean-forward="false" selection-start-line="0" selection-start-column="0" selection-end-line="0" selection-end-column="0" />
<folding>
<element signature="e#0#26#0" expanded="true" />
<marker date="1526068149928" expanded="true" signature="5366:5471" ph="{...}" />
</folding>
</state>
</provider>
</entry>
</file>
<file leaf-file-name="index.js" pinned="false" current-in-tab="false">
<entry file="file://$PROJECT_DIR$/src/renderer/modal/modalRouter/index.js">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="216">
<caret line="12" column="0" lean-forward="false" selection-start-line="12" selection-start-column="0" selection-end-line="12" selection-end-column="0" />
<folding>
<marker date="1525123536618" expanded="true" signature="1172:1177" ph="{...}" />
</folding>
</state>
</provider>
</entry>
</file>
<file leaf-file-name="package.json" pinned="false" current-in-tab="false">
<entry file="file://$PROJECT_DIR$/package.json">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="1332">
<caret line="74" column="0" lean-forward="false" selection-start-line="74" selection-start-column="0" selection-end-line="74" selection-end-column="0" />
<folding>
<marker date="1526397284183" expanded="true" signature="2668:3140" ph="{&quot;axios&quot;: &quot;^0.18.0&quot;...}" />
</folding>
</state>
</provider>
</entry>
</file>
<file leaf-file-name="join.js" pinned="false" current-in-tab="false">
<entry file="file://$PROJECT_DIR$/node_modules/bluebird/js/release/join.js">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="144">
<caret line="8" column="11" lean-forward="false" selection-start-line="8" selection-start-column="11" selection-end-line="8" selection-end-column="11" />
<folding />
</state>
</provider>
</entry>
</file>
<file leaf-file-name="view.jsx" pinned="false" current-in-tab="true">
<entry file="file://$PROJECT_DIR$/src/renderer/page/file/view.jsx">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="287">
<caret line="18" column="15" lean-forward="false" selection-start-line="18" selection-start-column="14" selection-end-line="18" selection-end-column="15" />
<folding />
</state>
</provider>
</entry>
</file>
</leaf>
</component>
<component name="FindInProjectRecents">
<findStrings>
<find>TYPE_FEATURED_DOWNLOAD</find>
<find>TYPE_FEATURED_DOW</find>
<find>doOpen</find>
<find>doNotify</find>
<find>settings</find>
</findStrings>
</component>
<component name="Git.Settings">
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
</component>
<component name="IdeDocumentHistory">
<option name="CHANGED_PATHS">
<list>
<option value="$PROJECT_DIR$/src/renderer/modal/modalRouter/index.js" />
<option value="$PROJECT_DIR$/package.json" />
<option value="$PROJECT_DIR$/README.md" />
<option value="$PROJECT_DIR$/src/renderer/page/file/view.jsx" />
</list>
</option>
</component>
<component name="JsBuildToolGruntFileManager" detection-done="true" sorting="DEFINITION_ORDER" />
<component name="JsBuildToolPackageJson" detection-done="true" sorting="DEFINITION_ORDER">
<package-json value="$PROJECT_DIR$/package.json" />
</component>
<component name="JsFlowSettings">
<service-enabled>true</service-enabled>
<exe-path />
<annotation-enable>false</annotation-enable>
<other-services-enabled>true</other-services-enabled>
<auto-save>true</auto-save>
</component>
<component name="JsGulpfileManager">
<detection-done>true</detection-done>
<sorting>DEFINITION_ORDER</sorting>
</component>
<component name="NodeModulesDirectoryManager">
<handled-path value="$PROJECT_DIR$/dist/linux-unpacked/resources/app.asar.unpacked/node_modules" />
<handled-path value="$PROJECT_DIR$/node_modules" />
</component>
<component name="PhpWorkspaceProjectConfiguration" backward_compatibility_performed="true" />
<component name="ProjectFrameBounds" extendedState="6">
<option name="y" value="24" />
<option name="width" value="1920" />
<option name="height" value="1056" />
</component>
<component name="ProjectLevelVcsManager" settingsEditedManually="true" />
<component name="ProjectView">
<navigator currentView="ProjectPane" proportions="" version="1">
<flattenPackages />
<showMembers />
<showModules />
<showLibraryContents />
<hideEmptyPackages />
<abbreviatePackageNames />
<autoscrollToSource />
<autoscrollFromSource />
<sortByType />
<manualOrder />
<foldersAlwaysOnTop value="true" />
</navigator>
<panes>
<pane id="Scope" />
<pane id="ProjectPane">
<subPane>
<expand>
<path>
<item name="lbry-app" type="b2602c69:ProjectViewProjectNode" />
<item name="lbry-app" type="2a2b976b:PhpTreeStructureProvider$1" />
</path>
</expand>
<select />
</subPane>
</pane>
<pane id="Scratches" />
</panes>
</component>
<component name="PropertiesComponent">
<property name="WebServerToolWindowFactoryState" value="false" />
<property name="nodejs_interpreter_path" value="/usr/local/bin/node" />
<property name="HbShouldOpenHtmlAsHb" value="" />
<property name="node.js.path.for.package.eslint" value="project" />
<property name="node.js.detected.package.eslint" value="true" />
<property name="node.js.selected.package.eslint" value="$PROJECT_DIR$/node_modules/eslint" />
<property name="last_opened_file_path" value="$PROJECT_DIR$" />
</component>
<component name="RunDashboard">
<option name="ruleStates">
<list>
<RuleState>
<option name="name" value="ConfigurationTypeDashboardGroupingRule" />
</RuleState>
<RuleState>
<option name="name" value="StatusDashboardGroupingRule" />
</RuleState>
</list>
</option>
</component>
<component name="ShelveChangesManager" show_recycled="false">
<option name="remove_strategy" value="false" />
</component>
<component name="SvnConfiguration">
<configuration />
</component>
<component name="TaskManager">
<task active="true" id="Default" summary="Default task">
<changelist id="061e089e-71dc-4d6b-b27c-9c614b097257" name="Default" comment="" />
<created>1522354295512</created>
<option name="number" value="Default" />
<option name="presentableId" value="Default" />
<updated>1522354295512</updated>
<workItem from="1522354299246" duration="1866000" />
<workItem from="1522643117648" duration="2000" />
<workItem from="1524064618567" duration="2674000" />
<workItem from="1525381677126" duration="197000" />
<workItem from="1525382363305" duration="597000" />
<workItem from="1525461458836" duration="70000" />
<workItem from="1526067492327" duration="897000" />
<workItem from="1526397339713" duration="27000" />
</task>
<servers />
</component>
<component name="TimeTrackingManager">
<option name="totallyTimeSpent" value="6330000" />
</component>
<component name="ToolWindowManager">
<frame x="0" y="24" width="1920" height="1055" extended-state="6" />
<editor active="true" />
<layout>
<window_info id="Project" active="false" anchor="left" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="true" show_stripe_button="true" weight="0.25" sideWeight="0.5" order="0" side_tool="false" content_ui="combo" />
<window_info id="TODO" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="6" side_tool="false" content_ui="tabs" />
<window_info id="Event Log" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="7" side_tool="true" content_ui="tabs" />
<window_info id="Version Control" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="7" side_tool="false" content_ui="tabs" />
<window_info id="npm" active="false" anchor="left" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="2" side_tool="true" content_ui="tabs" />
<window_info id="Run" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="2" side_tool="false" content_ui="tabs" />
<window_info id="Structure" active="false" anchor="left" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.25" sideWeight="0.5" order="1" side_tool="false" content_ui="tabs" />
<window_info id="Terminal" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="7" side_tool="false" content_ui="tabs" />
<window_info id="Favorites" active="false" anchor="left" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="2" side_tool="true" content_ui="tabs" />
<window_info id="Debug" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.4" sideWeight="0.5" order="3" side_tool="false" content_ui="tabs" />
<window_info id="Cvs" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.25" sideWeight="0.5" order="4" side_tool="false" content_ui="tabs" />
<window_info id="Message" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="0" side_tool="false" content_ui="tabs" />
<window_info id="Commander" active="false" anchor="right" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.4" sideWeight="0.5" order="0" side_tool="false" content_ui="tabs" />
<window_info id="Inspection" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.4" sideWeight="0.5" order="5" side_tool="false" content_ui="tabs" />
<window_info id="Hierarchy" active="false" anchor="right" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.25" sideWeight="0.5" order="2" side_tool="false" content_ui="combo" />
<window_info id="Find" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="1" side_tool="false" content_ui="tabs" />
<window_info id="Database" active="false" anchor="right" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="3" side_tool="false" content_ui="tabs" />
<window_info id="Ant Build" active="false" anchor="right" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.25" sideWeight="0.5" order="1" side_tool="false" content_ui="tabs" />
</layout>
</component>
<component name="TypeScriptGeneratedFilesManager">
<option name="version" value="1" />
</component>
<component name="VcsContentAnnotationSettings">
<option name="myLimit" value="2678400000" />
</component>
<component name="XDebuggerManager">
<breakpoint-manager />
<watches-manager />
</component>
<component name="editorHistoryManager">
<entry file="file://$PROJECT_DIR$/src/renderer/modal/modalRouter/view.jsx">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="0">
<caret line="0" column="0" lean-forward="false" selection-start-line="0" selection-start-column="0" selection-end-line="0" selection-end-column="0" />
<folding>
<element signature="e#0#26#0" expanded="true" />
<marker date="1526068149928" expanded="true" signature="5366:5471" ph="{...}" />
</folding>
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/src/renderer/modal/modalRouter/index.js">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="216">
<caret line="12" column="0" lean-forward="false" selection-start-line="12" selection-start-column="0" selection-end-line="12" selection-end-column="0" />
<folding>
<marker date="1525123536618" expanded="true" signature="1172:1177" ph="{...}" />
</folding>
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/package.json">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="1332">
<caret line="74" column="0" lean-forward="false" selection-start-line="74" selection-start-column="0" selection-end-line="74" selection-end-column="0" />
<folding>
<marker date="1526397284183" expanded="true" signature="2668:3140" ph="{&quot;axios&quot;: &quot;^0.18.0&quot;...}" />
</folding>
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/node_modules/bluebird/js/release/join.js">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="144">
<caret line="8" column="11" lean-forward="false" selection-start-line="8" selection-start-column="11" selection-end-line="8" selection-end-column="11" />
<folding />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/src/renderer/modal/modalRouter/view.jsx">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="0">
<caret line="0" column="0" lean-forward="false" selection-start-line="0" selection-start-column="0" selection-end-line="0" selection-end-column="0" />
<folding>
<element signature="e#0#26#0" expanded="true" />
<marker date="1526068149928" expanded="true" signature="5366:5471" ph="{...}" />
</folding>
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/package.json">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="1332">
<caret line="74" column="0" lean-forward="false" selection-start-line="74" selection-start-column="0" selection-end-line="74" selection-end-column="0" />
<folding>
<marker date="1526397284183" expanded="true" signature="2668:3140" ph="{&quot;axios&quot;: &quot;^0.18.0&quot;...}" />
</folding>
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/src/renderer/modal/modalRouter/index.js">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="216">
<caret line="12" column="0" lean-forward="false" selection-start-line="12" selection-start-column="0" selection-end-line="12" selection-end-column="0" />
<folding>
<marker date="1525123536618" expanded="true" signature="1172:1177" ph="{...}" />
</folding>
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/node_modules/bluebird/js/release/join.js">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="0">
<caret line="0" column="0" lean-forward="false" selection-start-line="0" selection-start-column="0" selection-end-line="0" selection-end-column="0" />
<folding />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/src/renderer/modal/modalRouter/view.jsx">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="0">
<caret line="0" column="0" lean-forward="false" selection-start-line="0" selection-start-column="0" selection-end-line="0" selection-end-column="0" />
<folding>
<element signature="e#0#26#0" expanded="true" />
<marker date="1526068149928" expanded="true" signature="5366:5471" ph="{...}" />
</folding>
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/src/renderer/modal/modalRouter/index.js">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="216">
<caret line="12" column="0" lean-forward="false" selection-start-line="12" selection-start-column="0" selection-end-line="12" selection-end-column="0" />
<folding>
<marker date="1525123536618" expanded="true" signature="1172:1177" ph="{...}" />
</folding>
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/package.json">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="2322">
<caret line="129" column="38" lean-forward="false" selection-start-line="129" selection-start-column="38" selection-end-line="129" selection-end-column="38" />
<folding>
<marker date="1526397284183" expanded="true" signature="2668:3140" ph="{&quot;axios&quot;: &quot;^0.18.0&quot;...}" />
</folding>
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/src/renderer/modal/modalRouter/view.jsx">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="0">
<caret line="0" column="0" lean-forward="false" selection-start-line="0" selection-start-column="0" selection-end-line="0" selection-end-column="0" />
<folding>
<element signature="e#0#26#0" expanded="true" />
<marker date="1526068149928" expanded="true" signature="5366:5471" ph="{...}" />
</folding>
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/src/renderer/modal/modalRouter/index.js">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="144">
<caret line="19" column="68" lean-forward="true" selection-start-line="19" selection-start-column="68" selection-end-line="19" selection-end-column="68" />
<folding>
<marker date="1525123536618" expanded="true" signature="1172:1177" ph="{...}" />
</folding>
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/src/renderer/page/rewards/view.jsx">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="-122">
<caret line="0" column="0" lean-forward="false" selection-start-line="0" selection-start-column="0" selection-end-line="0" selection-end-column="0" />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/src/renderer/redux/reducers/rewards.js">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="-28">
<caret line="0" column="0" lean-forward="false" selection-start-line="0" selection-start-column="0" selection-end-line="0" selection-end-column="0" />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/src/renderer/redux/actions/rewards.js">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="-832">
<caret line="13" column="61" lean-forward="false" selection-start-line="13" selection-start-column="36" selection-end-line="13" selection-end-column="61" />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/src/renderer/rewards.js">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="324">
<caret line="18" column="0" lean-forward="false" selection-start-line="18" selection-start-column="0" selection-end-line="18" selection-end-column="0" />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/src/renderer/modal/modalRouter/view.jsx">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="0">
<caret line="0" column="0" lean-forward="false" selection-start-line="0" selection-start-column="0" selection-end-line="0" selection-end-column="0" />
<folding>
<element signature="e#0#26#0" expanded="true" />
<marker date="1526068149928" expanded="true" signature="5366:5471" ph="{...}" />
</folding>
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/src/renderer/modal/modalRouter/index.js">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="216">
<caret line="12" column="0" lean-forward="false" selection-start-line="12" selection-start-column="0" selection-end-line="12" selection-end-column="0" />
<folding>
<marker date="1525123536618" expanded="true" signature="1172:1177" ph="{...}" />
</folding>
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/package.json">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="693">
<caret line="129" column="38" lean-forward="false" selection-start-line="129" selection-start-column="38" selection-end-line="129" selection-end-column="38" />
<folding>
<marker date="1526397284183" expanded="true" signature="2668:3140" ph="{&quot;axios&quot;: &quot;^0.18.0&quot;...}" />
</folding>
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/src/renderer/modal/modalRouter/view.jsx">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="0">
<caret line="0" column="0" lean-forward="false" selection-start-line="0" selection-start-column="0" selection-end-line="0" selection-end-column="0" />
<folding>
<element signature="e#0#26#0" expanded="true" />
<marker date="1526068149928" expanded="true" signature="5366:5471" ph="{...}" />
</folding>
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/src/renderer/modal/modalRouter/index.js">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="144">
<caret line="19" column="68" lean-forward="true" selection-start-line="19" selection-start-column="68" selection-end-line="19" selection-end-column="68" />
<folding>
<marker date="1525123536618" expanded="true" signature="1172:1177" ph="{...}" />
</folding>
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/src/renderer/page/rewards/view.jsx">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="-122">
<caret line="0" column="0" lean-forward="false" selection-start-line="0" selection-start-column="0" selection-end-line="0" selection-end-column="0" />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/src/renderer/redux/reducers/rewards.js">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="-28">
<caret line="0" column="0" lean-forward="false" selection-start-line="0" selection-start-column="0" selection-end-line="0" selection-end-column="0" />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/src/renderer/redux/actions/rewards.js">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="-832">
<caret line="13" column="61" lean-forward="false" selection-start-line="13" selection-start-column="36" selection-end-line="13" selection-end-column="61" />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/src/renderer/rewards.js">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="324">
<caret line="18" column="0" lean-forward="false" selection-start-line="18" selection-start-column="0" selection-end-line="18" selection-end-column="0" />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/src/renderer/modal/modalRouter/view.jsx">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="0">
<caret line="0" column="0" lean-forward="false" selection-start-line="0" selection-start-column="0" selection-end-line="0" selection-end-column="0" />
<folding>
<element signature="e#0#26#0" expanded="true" />
<marker date="1526068149928" expanded="true" signature="5366:5471" ph="{...}" />
</folding>
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/src/renderer/modal/modalRouter/index.js">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="216">
<caret line="12" column="0" lean-forward="false" selection-start-line="12" selection-start-column="0" selection-end-line="12" selection-end-column="0" />
<folding>
<marker date="1525123536618" expanded="true" signature="1172:1177" ph="{...}" />
</folding>
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/package.json">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="1332">
<caret line="74" column="0" lean-forward="false" selection-start-line="74" selection-start-column="0" selection-end-line="74" selection-end-column="0" />
<folding>
<marker date="1526397284183" expanded="true" signature="2668:3140" ph="{&quot;axios&quot;: &quot;^0.18.0&quot;...}" />
</folding>
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/CONTRIBUTING.md">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="540">
<caret line="168" column="0" lean-forward="true" selection-start-line="168" selection-start-column="0" selection-end-line="168" selection-end-column="0" />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/README.md">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="846">
<caret line="47" column="81" lean-forward="true" selection-start-line="47" selection-start-column="81" selection-end-line="47" selection-end-column="81" />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/node_modules/bluebird/js/release/join.js">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="144">
<caret line="8" column="11" lean-forward="false" selection-start-line="8" selection-start-column="11" selection-end-line="8" selection-end-column="11" />
<folding />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/src/renderer/page/file/view.jsx">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="287">
<caret line="18" column="15" lean-forward="false" selection-start-line="18" selection-start-column="14" selection-end-line="18" selection-end-column="15" />
<folding />
</state>
</provider>
</entry>
</component>
</project>

View file

@ -18,6 +18,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/).
### Changed
* Add flair to snackbar ([#1313](https://github.com/lbryio/lbry-app/pull/1313))
* Made font in price badge larger ([#1420](https://github.com/lbryio/lbry-app/pull/1420))
* Store subscriptions in internal database ([#1424](https://github.com/lbryio/lbry-app/pull/1424))
### Fixed
* Fix content-type not shown correctly in file description ([#863](https://github.com/lbryio/lbry-app/pull/863))

View file

@ -2,7 +2,6 @@
import mixpanel from 'mixpanel-browser';
import Lbryio from 'lbryio';
import isDev from 'electron-is-dev';
import type { Subscription } from 'redux/reducers/subscriptions';
if (isDev) {
mixpanel.init('691723e855cabb9d27a7a79002216967');
@ -15,8 +14,6 @@ type Analytics = {
setUser: Object => void,
toggle: (boolean, ?boolean) => void,
apiLogView: (string, string, string) => void,
apiLogSubscribe: Subscription => void,
apiLogUnsubscribe: Subscription => void,
};
let analyticsEnabled: boolean = false;
@ -56,20 +53,6 @@ const analytics: Analytics = {
}).catch(() => {});
}
},
apiLogSubscribe: (subscription: Subscription): void => {
if (analyticsEnabled) {
Lbryio.call('subscription', 'new', {
subscription,
}).catch(() => {});
}
},
apiLogUnsubscribe: (subscription: Subscription): void => {
if (analyticsEnabled) {
Lbryio.call('subscription', 'delete', {
subscription,
}).catch(() => {});
}
},
};
export default analytics;

View file

@ -1,28 +0,0 @@
// @flow
import * as React from 'react';
import classnames from 'classnames';
type Props = {
dark?: boolean,
};
class Spinner extends React.Component<Props> {
static defaultProps = {
dark: false,
};
render() {
const { dark } = this.props;
return (
<div className={classnames('spinner', { 'spinner--dark': dark })}>
<div className="rect rect1" />
<div className="rect rect2" />
<div className="rect rect3" />
<div className="rect rect4" />
<div className="rect rect5" />
</div>
);
}
}
export default Spinner;

View file

@ -3,9 +3,10 @@ import * as React from 'react';
import ReactMarkdown from 'react-markdown';
import Button from 'component/button';
import path from 'path';
import type { Claim } from 'types/claim';
type Props = {
claim: {},
claim: Claim,
fileInfo: {
download_path: string,
},

View file

@ -53,7 +53,6 @@ class FileList extends React.PureComponent<Props, State> {
if (fileInfo1.pending) {
return -1;
}
const height1 = this.props.claimsById[fileInfo1.claim_id]
? this.props.claimsById[fileInfo1.claim_id].height
: 0;
@ -145,6 +144,10 @@ class FileList extends React.PureComponent<Props, State> {
const { sortBy } = this.state;
const content = [];
if (!fileInfos) {
return null;
}
this.sortFunctions[sortBy](fileInfos).forEach(fileInfo => {
const {
channel_name: channelName,

View file

@ -1,33 +1,98 @@
// @flow
import * as React from 'react';
import classnames from 'classnames';
import Spinner from 'component/spinner';
import { isShowingChildren } from 'util/dom';
// time in ms to wait to show loading spinner
const LOADER_TIMEOUT = 1500;
type Props = {
children: React.Node,
children: React.Node | Array<React.Node>,
pageTitle: ?string,
noPadding: ?boolean,
extraPadding: ?boolean,
notContained: ?boolean, // No max-width, but keep the padding
loading: ?boolean,
};
const Page = (props: Props) => {
const { pageTitle, children, noPadding, extraPadding, notContained } = props;
return (
<main
className={classnames('main', {
'main--contained': !notContained && !noPadding && !extraPadding,
'main--no-padding': noPadding,
'main--extra-padding': extraPadding,
})}
>
{pageTitle && (
<div className="page__header">
{pageTitle && <h1 className="page__title">{pageTitle}</h1>}
</div>
)}
{children}
</main>
);
type State = {
showLoader: ?boolean,
};
class Page extends React.PureComponent<Props, State> {
static getDerivedStateFromProps(nextProps: Props, prevState: State) {
const { children } = nextProps;
const { showLoader } = prevState;
// If we aren't showing the loader, don't bother updating
if (!showLoader) {
return null;
}
if (isShowingChildren(children)) {
return {
showLoader: false,
};
}
return null;
}
constructor() {
super();
this.state = {
showLoader: false,
};
this.loaderTimeout = null;
}
componentDidMount() {
const { children } = this.props;
if (!isShowingChildren(children))
this.loaderTimeout = setTimeout(() => {
this.setState({ showLoader: true });
}, LOADER_TIMEOUT);
}
componentWillUnmount() {
this.loaderTimeout = null;
}
loaderTimeout: ?TimeoutID;
render() {
const { pageTitle, children, noPadding, extraPadding, notContained, loading } = this.props;
const { showLoader } = this.state;
// We don't want to show the loading spinner right away if it will only flash on the
// screen for a short time, wait until we know it will be loading for a bit before showing it
const shouldShowLoader = !isShowingChildren(children) && showLoader;
return (
<main
className={classnames('main', {
'main--contained': !notContained && !noPadding && !extraPadding,
'main--no-padding': noPadding,
'main--extra-padding': extraPadding,
})}
>
{pageTitle && (
<div className="page__header">
{pageTitle && <h1 className="page__title">{pageTitle}</h1>}
</div>
)}
{!loading && children}
{shouldShowLoader && (
<div className="page__empty">
<Spinner />
</div>
)}
</main>
);
}
}
export default Page;

View file

@ -0,0 +1,9 @@
import { connect } from 'react-redux';
import { selectTheme } from 'redux/selectors/settings';
import Spinner from './view';
const mapStateToProps = state => ({
theme: selectTheme(state),
});
export default connect(mapStateToProps, null)(Spinner);

View file

@ -0,0 +1,36 @@
// @flow
import * as React from 'react';
import classnames from 'classnames';
import { DARK_THEME, LIGHT_THEME } from 'constants/themes';
type Props = {
dark?: boolean, // always a dark spinner
light?: boolean, // always a light spinner
theme: string,
};
const Spinner = (props: Props) => {
const { dark, light, theme } = props;
return (
<div
className={classnames('spinner', {
'spinner--dark': !light && (dark || theme === LIGHT_THEME),
'spinner--light': !dark && (light || theme === DARK_THEME),
})}
>
<div className="rect rect1" />
<div className="rect rect2" />
<div className="rect rect3" />
<div className="rect rect4" />
<div className="rect rect5" />
</div>
);
};
Spinner.defaultProps = {
dark: false,
light: false,
};
export default Spinner;

View file

@ -3,7 +3,7 @@ import React from 'react';
import { MODALS } from 'lbry-redux';
import * as icons from 'constants/icons';
import Button from 'component/button';
import type { Subscription } from 'redux/reducers/subscriptions';
import type { Subscription } from 'types/subscription';
type SubscribtionArgs = {
channelName: string,

View file

@ -3,21 +3,18 @@ import React from 'react';
import Button from 'component/button';
import { buildURI } from 'lbry-redux';
import classnames from 'classnames';
// import Icon from 'component/common/icon';
import type { Claim } from 'types/claim';
type Props = {
isResolvingUri: boolean,
resolveUri: string => void,
claim: {
channel_name: string,
has_signature: boolean,
signature_is_valid: boolean,
value: {
publisherSignature: { certificateId: string },
},
},
uri: string,
claim: Claim,
link: ?boolean,
// Lint thinks we aren't using these, even though we are.
// Possibly because the resolve function is an arrow function that is passed in props?
/* eslint-disable react/no-unused-prop-types */
resolveUri: string => void,
uri: string,
/* eslint-enable react/no-unused-prop-types */
};
class UriIndicator extends React.PureComponent<Props> {

View file

@ -1,6 +1,6 @@
// @flow
import React from 'react';
import Spinner from 'component/common/spinner';
import Spinner from 'component/spinner';
type Props = {
spinner: boolean,
@ -16,7 +16,7 @@ class LoadingScreen extends React.PureComponent<Props> {
const { status, spinner } = this.props;
return (
<div className="content__loading">
{spinner && <Spinner />}
{spinner && <Spinner light />}
<span className="content__loading-text">{status}</span>
</div>

View file

@ -2,6 +2,7 @@
import React from 'react';
import { Lbry } from 'lbry-redux';
import classnames from 'classnames';
import type { Claim } from 'types/claim';
import VideoPlayer from './internal/player';
import VideoPlayButton from './internal/play-button';
import LoadingScreen from './internal/loading-screen';
@ -26,7 +27,7 @@ type Props = {
contentType: string,
changeVolume: number => void,
volume: number,
claim: {},
claim: Claim,
uri: string,
doPlay: () => void,
doPause: () => void,

View file

@ -3,11 +3,12 @@ import React from 'react';
import Button from 'component/button';
import { FormField } from 'component/common/form';
import UriIndicator from 'component/uriIndicator';
import type { Claim } from 'types/claim';
type Props = {
uri: string,
title: string,
claim: { claim_id: string },
claim: Claim,
errorMessage: string,
isPending: boolean,
sendSupport: (number, string, string) => void,

View file

@ -1,3 +1,13 @@
/*
Constants for redux actions
All names should be in present tense
ex:
XXX_START
XXX_SUCCESS
XXX_FAIL
XXX_COMPLETE // if there is no fail case
*/
export const WINDOW_FOCUSED = 'WINDOW_FOCUSED';
export const DAEMON_READY = 'DAEMON_READY';
export const DAEMON_VERSION_MATCH = 'DAEMON_VERSION_MATCH';
@ -166,6 +176,9 @@ export const SET_SUBSCRIPTION_NOTIFICATIONS = 'SET_SUBSCRIPTION_NOTIFICATIONS';
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';
// Video controls
export const SET_VIDEO_PAUSE = 'SET_VIDEO_PAUSE';

View file

@ -0,0 +1,4 @@
// css theme values
// saved in settings and found at /static/themes/{theme}.css
export const DARK_THEME = 'dark';
export const LIGHT_THEME = 'light';

View file

@ -6,6 +6,7 @@ import ReactPaginate from 'react-paginate';
import SubscribeButton from 'component/subscribeButton';
import Page from 'component/page';
import FileList from 'component/fileList';
import type { Claim } from 'types/claim';
type Props = {
uri: string,
@ -13,10 +14,7 @@ type Props = {
totalPages: number,
fetching: boolean,
params: { page: number },
claim: {
name: string,
claim_id: string,
},
claim: Claim,
claimsInChannel: Array<{}>,
fetchClaims: (string, number) => void,
fetchClaimCount: string => void,
@ -58,8 +56,8 @@ class ChannelPage extends React.PureComponent<Props> {
}
render() {
const { fetching, claimsInChannel, claim, uri, page, totalPages } = this.props;
const { name } = claim;
const { fetching, claimsInChannel, claim, page, totalPages } = this.props;
const { name, permanent_url: permanentUrl } = claim;
let contentList;
if (fetching) {
@ -78,7 +76,7 @@ class ChannelPage extends React.PureComponent<Props> {
<section className="card__channel-info card__channel-info--large">
<h1>{name}</h1>
<div className="card__actions card__actions--no-margin">
<SubscribeButton uri={uri} channelName={name} />
<SubscribeButton uri={permanentUrl} channelName={name} />
</div>
</section>
<section>{contentList}</section>

View file

@ -16,18 +16,10 @@ import SubscribeButton from 'component/subscribeButton';
import Page from 'component/page';
import player from 'render-media';
import * as settings from 'constants/settings';
import type { Claim } from 'types/claim';
type Props = {
claim: {
claim_id: string,
height: number,
channel_name: string,
value: {
publisherSignature: ?{
certificateId: ?string,
},
},
},
claim: Claim,
fileInfo: {},
metadata: {
title: string,

View file

@ -5,16 +5,13 @@ import ChannelPage from 'page/channel';
import FilePage from 'page/file';
import Page from 'component/page';
import Button from 'component/button';
import type { Claim } from 'types/claim';
type Props = {
isResolvingUri: boolean,
resolveUri: string => void,
uri: string,
claim: {
name: string,
txid: string,
nout: number,
},
claim: Claim,
blackListedOutpoints: Array<{
txid: string,
nout: number,

View file

@ -1,27 +1,25 @@
import React from 'react';
import { connect } from 'react-redux';
import {
selectSubscriptionsFromClaims,
selectSubscriptionClaims,
selectSubscriptions,
selectHasFetchedSubscriptions,
selectSubscriptionsBeingFetched,
selectIsFetchingSubscriptions,
selectNotifications,
} from 'redux/selectors/subscriptions';
import { doFetchClaimsByChannel } from 'redux/actions/content';
import {
setHasFetchedSubscriptions,
setSubscriptionNotifications,
} from 'redux/actions/subscriptions';
import { setSubscriptionNotifications, doFetchMySubscriptions } from 'redux/actions/subscriptions';
import SubscriptionsPage from './view';
const select = state => ({
hasFetchedSubscriptions: state.subscriptions.hasFetchedSubscriptions,
savedSubscriptions: selectSubscriptions(state),
subscriptions: selectSubscriptionsFromClaims(state),
isFetchingSubscriptions: selectIsFetchingSubscriptions(state),
subscriptionsBeingFetched: selectSubscriptionsBeingFetched(state),
subscriptions: selectSubscriptions(state),
subscriptionClaims: selectSubscriptionClaims(state),
notifications: selectNotifications(state),
});
export default connect(select, {
doFetchClaimsByChannel,
setHasFetchedSubscriptions,
setSubscriptionNotifications,
doFetchMySubscriptions,
})(SubscriptionsPage);

View file

@ -1,39 +1,28 @@
// @flow
import React from 'react';
import Page from 'component/page';
import CategoryList from 'component/common/category-list';
import type { Subscription } from 'redux/reducers/subscriptions';
import type { Subscription } from 'types/subscription';
import * as NOTIFICATION_TYPES from 'constants/notification_types';
import Button from 'component/button';
type SavedSubscriptions = Array<Subscription>;
import FileList from 'component/fileList';
import type { Claim } from 'types/claim';
type Props = {
doFetchClaimsByChannel: (string, number) => any,
savedSubscriptions: SavedSubscriptions,
// TODO build out claim types
subscriptions: Array<any>,
setHasFetchedSubscriptions: () => void,
hasFetchedSubscriptions: boolean,
doFetchClaimsByChannel: (string, number) => void,
doFetchMySubscriptions: () => void,
setSubscriptionNotifications: ({}) => void,
subscriptions: Array<Subscription>,
isFetchingSubscriptions: boolean,
subscriptionClaims: Array<{ uri: string, claims: Array<Claim> }>,
subscriptionsBeingFetched: {},
notifications: {},
};
export default class extends React.PureComponent<Props> {
// setHasFetchedSubscriptions is a terrible hack
// it allows the subscriptions to load correctly when refresing on the subscriptions page
// currently the page is rendered before the state is rehyrdated
// that causes this component to be rendered with zero savedSubscriptions
// we need to wait until persist/REHYDRATE has fired before rendering the page
componentDidMount() {
const {
savedSubscriptions,
setHasFetchedSubscriptions,
notifications,
setSubscriptionNotifications,
} = this.props;
if (savedSubscriptions.length) {
this.fetchSubscriptions(savedSubscriptions);
setHasFetchedSubscriptions();
}
const { notifications, setSubscriptionNotifications, doFetchMySubscriptions } = this.props;
doFetchMySubscriptions();
const newNotifications = {};
Object.keys(notifications).forEach(cur => {
if (notifications[cur].type === NOTIFICATION_TYPES.DOWNLOADING) {
@ -43,40 +32,37 @@ export default class extends React.PureComponent<Props> {
setSubscriptionNotifications(newNotifications);
}
componentWillReceiveProps(props: Props) {
const { savedSubscriptions, hasFetchedSubscriptions, setHasFetchedSubscriptions } = props;
componentDidUpdate() {
const {
subscriptions,
subscriptionClaims,
doFetchClaimsByChannel,
subscriptionsBeingFetched,
} = this.props;
if (!hasFetchedSubscriptions && savedSubscriptions.length) {
this.fetchSubscriptions(savedSubscriptions);
setHasFetchedSubscriptions();
}
}
const subscriptionClaimMap = {};
subscriptionClaims.forEach(claim => {
subscriptionClaimMap[claim.uri] = 1;
});
fetchSubscriptions(savedSubscriptions: SavedSubscriptions) {
const { doFetchClaimsByChannel } = this.props;
if (savedSubscriptions.length) {
// can this use batchActions?
savedSubscriptions.forEach(sub => {
subscriptions.forEach(sub => {
if (!subscriptionClaimMap[sub.uri] && !subscriptionsBeingFetched[sub.uri]) {
doFetchClaimsByChannel(sub.uri, 1);
});
}
}
});
}
render() {
const { subscriptions, savedSubscriptions } = this.props;
const { subscriptions, subscriptionClaims, isFetchingSubscriptions } = this.props;
// TODO: if you are subscribed to an empty channel, this will always be true (but it should not be)
const someClaimsNotLoaded = Boolean(
subscriptions.find(subscription => !subscription.claims.length)
);
const fetchingSubscriptions =
!!savedSubscriptions.length &&
(subscriptions.length !== savedSubscriptions.length || someClaimsNotLoaded);
let claimList = [];
subscriptionClaims.forEach(claimData => {
claimList = claimList.concat(claimData.claims);
});
return (
<Page noPadding isLoading={fetchingSubscriptions}>
{!savedSubscriptions.length && (
<Page notContained loading={isFetchingSubscriptions}>
{!subscriptions.length && (
<div className="page__empty">
{__("It looks like you aren't subscribed to any channels yet.")}
<div className="card__actions card__actions--center">
@ -84,28 +70,7 @@ export default class extends React.PureComponent<Props> {
</div>
</div>
)}
{!!savedSubscriptions.length && (
<div>
{!!subscriptions.length &&
subscriptions.map(subscription => {
if (!subscription.claims.length) {
// will need to update when you can subscribe to empty channels
// for now this prevents issues with FeaturedCategory being rendered
// before the names (claim uris) are populated
return '';
}
return (
<CategoryList
key={subscription.channelName}
categoryLink={subscription.uri}
category={subscription.channelName}
names={subscription.claims}
/>
);
})}
</div>
)}
{!!claimList.length && <FileList hideFilter sortByHeight fileInfos={claimList} />}
</Page>
);
}

View file

@ -138,6 +138,7 @@ export function doUpdateLoadStatus(uri, outpoint) {
: acc,
0
);
const notif = new window.Notification(notifications[uri].subscription.channelName, {
body: `Posted ${fileInfo.metadata.title}${
count > 1 && count < 10 ? ` and ${count - 1} other new items` : ''

View file

@ -2,56 +2,127 @@
import * as ACTIONS from 'constants/action_types';
import * as NOTIFICATION_TYPES from 'constants/notification_types';
import type {
Subscription,
Dispatch,
SubscriptionState,
SubscriptionNotifications,
} from 'redux/reducers/subscriptions';
import type { Subscription } from 'types/subscription';
import { selectSubscriptions } from 'redux/selectors/subscriptions';
import { Lbry, buildURI } from 'lbry-redux';
import { Lbry, buildURI, parseURI } from 'lbry-redux';
import { doPurchaseUri } from 'redux/actions/content';
import { doNavigate } from 'redux/actions/navigation';
import analytics from 'analytics';
import Promise from 'bluebird';
import Lbryio from 'lbryio';
const CHECK_SUBSCRIPTIONS_INTERVAL = 60 * 60 * 1000;
const SUBSCRIPTION_DOWNLOAD_LIMIT = 1;
export const doChannelSubscribe = (subscription: Subscription) => (dispatch: Dispatch) => {
dispatch({
type: ACTIONS.CHANNEL_SUBSCRIBE,
data: subscription,
});
export const doFetchMySubscriptions = () => (dispatch: Dispatch, getState: () => any) => {
const {
subscriptions: subscriptionState,
settings: { daemonSettings },
} = getState();
const { subscriptions: reduxSubscriptions } = subscriptionState;
const { share_usage_data: isSharingData } = daemonSettings;
analytics.apiLogSubscribe(subscription);
if (!isSharingData && isSharingData !== undefined) {
// They aren't sharing their data, subscriptions will be handled by persisted redux state
return;
}
dispatch(doCheckSubscription(subscription, true));
// most of this logic comes from scenarios where the db isn't synced with redux
// this will happen if the user stops sharing data
dispatch({ type: ACTIONS.FETCH_SUBSCRIPTIONS_START });
Lbryio.call('subscription', 'list')
.then(dbSubscriptions => {
const storedSubscriptions = dbSubscriptions || [];
// User has no subscriptions in db or redux
if (!storedSubscriptions.length && (!reduxSubscriptions || !reduxSubscriptions.length)) {
return [];
}
// There is some mismatch between redux state and db state
// If something is in the db, but not in redux, add it to redux
// If something is in redux, but not in the db, add it to the db
if (storedSubscriptions.length !== reduxSubscriptions.length) {
const dbSubMap = {};
const reduxSubMap = {};
const subsNotInDB = [];
const subscriptionsToReturn = reduxSubscriptions.slice();
storedSubscriptions.forEach(sub => {
dbSubMap[sub.claim_id] = 1;
});
reduxSubscriptions.forEach(sub => {
const { claimId } = parseURI(sub.uri);
reduxSubMap[claimId] = 1;
if (!dbSubMap[claimId]) {
subsNotInDB.push({
claim_id: claimId,
channel_name: sub.channelName,
});
}
});
storedSubscriptions.forEach(sub => {
if (!reduxSubMap[sub.claim_id]) {
const uri = `lbry://${sub.channel_name}#${sub.claim_id}`;
subscriptionsToReturn.push({ uri, channelName: sub.channel_name });
}
});
return Promise.all(subsNotInDB.map(payload => Lbryio.call('subscription', 'new', payload)))
.then(() => subscriptionsToReturn)
.catch(
() =>
// let it fail, we will try again when the navigate to the subscriptions page
subscriptionsToReturn
);
}
// DB is already synced, just return the subscriptions in redux
return reduxSubscriptions;
})
.then(subscriptions => {
dispatch({
type: ACTIONS.FETCH_SUBSCRIPTIONS_SUCCESS,
data: subscriptions,
});
})
.catch(() => {
dispatch({
type: ACTIONS.FETCH_SUBSCRIPTIONS_FAIL,
});
});
};
export const doChannelUnsubscribe = (subscription: Subscription) => (dispatch: Dispatch) => {
export const setSubscriptionLatest = (subscription: Subscription, uri: string) => (
dispatch: Dispatch
) =>
dispatch({
type: ACTIONS.CHANNEL_UNSUBSCRIBE,
data: subscription,
type: ACTIONS.SET_SUBSCRIPTION_LATEST,
data: {
subscription,
uri,
},
});
analytics.apiLogUnsubscribe(subscription);
};
export const doCheckSubscriptions = () => (
dispatch: Dispatch,
getState: () => SubscriptionState
) => {
const checkSubscriptionsTimer = setInterval(
() =>
selectSubscriptions(getState()).map((subscription: Subscription) =>
dispatch(doCheckSubscription(subscription, true))
),
CHECK_SUBSCRIPTIONS_INTERVAL
);
export const setSubscriptionNotification = (
subscription: Subscription,
uri: string,
notificationType: string
) => (dispatch: Dispatch) =>
dispatch({
type: ACTIONS.CHECK_SUBSCRIPTIONS_SUBSCRIBE,
data: { checkSubscriptionsTimer },
type: ACTIONS.SET_SUBSCRIPTION_NOTIFICATION,
data: {
subscription,
uri,
type: notificationType,
},
});
};
export const doCheckSubscription = (subscription: Subscription, notify?: boolean) => (
dispatch: Dispatch
@ -114,31 +185,6 @@ export const doCheckSubscription = (subscription: Subscription, notify?: boolean
});
};
export const setSubscriptionLatest = (subscription: Subscription, uri: string) => (
dispatch: Dispatch
) =>
dispatch({
type: ACTIONS.SET_SUBSCRIPTION_LATEST,
data: {
subscription,
uri,
},
});
export const setSubscriptionNotification = (
subscription: Subscription,
uri: string,
notificationType: string
) => (dispatch: Dispatch) =>
dispatch({
type: ACTIONS.SET_SUBSCRIPTION_NOTIFICATION,
data: {
subscription,
uri,
type: notificationType,
},
});
export const setSubscriptionNotifications = (notifications: SubscriptionNotifications) => (
dispatch: Dispatch
) =>
@ -149,5 +195,68 @@ export const setSubscriptionNotifications = (notifications: SubscriptionNotifica
},
});
export const setHasFetchedSubscriptions = () => (dispatch: Dispatch) =>
dispatch({ type: ACTIONS.HAS_FETCHED_SUBSCRIPTIONS });
export const doChannelSubscribe = (subscription: Subscription) => (
dispatch: Dispatch,
getState: () => any
) => {
const {
settings: { daemonSettings },
} = getState();
const { share_usage_data: isSharingData } = daemonSettings;
dispatch({
type: ACTIONS.CHANNEL_SUBSCRIBE,
data: subscription,
});
// if the user isn't sharing data, keep the subscriptions entirely in the app
if (isSharingData) {
const { claimId } = parseURI(subscription.uri);
// They are sharing data, we can store their subscriptions in our internal database
Lbryio.call('subscription', 'new', {
channel_name: subscription.channelName,
claim_id: claimId,
});
}
dispatch(doCheckSubscription(subscription, true));
};
export const doChannelUnsubscribe = (subscription: Subscription) => (
dispatch: Dispatch,
getState: () => any
) => {
const {
settings: { daemonSettings },
} = getState();
const { share_usage_data: isSharingData } = daemonSettings;
dispatch({
type: ACTIONS.CHANNEL_UNSUBSCRIBE,
data: subscription,
});
if (isSharingData) {
const { claimId } = parseURI(subscription.uri);
Lbryio.call('subscription', 'delete', {
claim_id: claimId,
});
}
};
export const doCheckSubscriptions = () => (
dispatch: Dispatch,
getState: () => SubscriptionState
) => {
const checkSubscriptionsTimer = setInterval(
() =>
selectSubscriptions(getState()).map((subscription: Subscription) =>
dispatch(doCheckSubscription(subscription, true))
),
CHECK_SUBSCRIPTIONS_INTERVAL
);
dispatch({
type: ACTIONS.CHECK_SUBSCRIPTIONS_SUBSCRIBE,
data: { checkSubscriptionsTimer },
});
};

View file

@ -2,12 +2,7 @@
import * as ACTIONS from 'constants/action_types';
import * as NOTIFICATION_TYPES from 'constants/notification_types';
import { handleActions } from 'util/redux-utils';
export type Subscription = {
channelName: string,
uri: string,
latest: ?string,
};
import type { Subscription } from 'types/subscription';
export type NotificationType =
| NOTIFICATION_TYPES.DOWNLOADING
@ -24,8 +19,8 @@ export type SubscriptionNotifications = {
// Subscription redux types
export type SubscriptionState = {
subscriptions: Array<Subscription>,
hasFetchedSubscriptions: boolean,
notifications: SubscriptionNotifications,
loading: boolean,
};
// Subscription action types
@ -39,10 +34,6 @@ type doChannelUnsubscribe = {
data: Subscription,
};
type HasFetchedSubscriptions = {
type: ACTIONS.HAS_FETCHED_SUBSCRIPTIONS,
};
type setSubscriptionLatest = {
type: ACTIONS.SET_SUBSCRIPTION_LATEST,
data: {
@ -75,10 +66,14 @@ type CheckSubscriptionCompleted = {
type: ACTIONS.CHECK_SUBSCRIPTION_COMPLETED,
};
type fetchedSubscriptionsSucess = {
type: ACTIONS.FETCH_SUBSCRIPTIONS_SUCCESS,
data: Array<Subscription>,
};
export type Action =
| doChannelSubscribe
| doChannelUnsubscribe
| HasFetchedSubscriptions
| setSubscriptionLatest
| setSubscriptionNotification
| CheckSubscriptionStarted
@ -88,8 +83,8 @@ export type Dispatch = (action: Action) => any;
const defaultState = {
subscriptions: [],
hasFetchedSubscriptions: false,
notifications: {},
loading: false,
};
export default handleActions(
@ -122,10 +117,6 @@ export default handleActions(
subscriptions: newSubscriptions,
};
},
[ACTIONS.HAS_FETCHED_SUBSCRIPTIONS]: (state: SubscriptionState): SubscriptionState => ({
...state,
hasFetchedSubscriptions: true,
}),
[ACTIONS.SET_SUBSCRIPTION_LATEST]: (
state: SubscriptionState,
action: setSubscriptionLatest
@ -155,6 +146,22 @@ export default handleActions(
...state,
notifications: action.data.notifications,
}),
[ACTIONS.FETCH_SUBSCRIPTIONS_START]: (state: SubscriptionState): SubscriptionState => ({
...state,
loading: true,
}),
[ACTIONS.FETCH_SUBSCRIPTIONS_FAIL]: (state: SubscriptionState): SubscriptionState => ({
...state,
loading: false,
}),
[ACTIONS.FETCH_SUBSCRIPTIONS_SUCCESS]: (
state: SubscriptionState,
action: fetchedSubscriptionsSucess
): SubscriptionState => ({
...state,
loading: false,
subscriptions: action.data,
}),
},
defaultState
);

View file

@ -1,15 +1,21 @@
import { createSelector } from 'reselect';
import { selectAllClaimsByChannel, selectClaimsById } from 'lbry-redux';
import {
selectAllClaimsByChannel,
selectClaimsById,
selectAllFetchingChannelClaims,
} from 'lbry-redux';
// get 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
export const selectSubscriptions = createSelector(selectState, state => state.subscriptions);
export const selectSubscriptionsFromClaims = createSelector(
export const selectSubscriptionClaims = createSelector(
selectAllClaimsByChannel,
selectClaimsById,
selectSubscriptions,
@ -37,9 +43,6 @@ export const selectSubscriptionsFromClaims = createSelector(
});
}
// all we really need is a uri for each claim
channelClaims = channelClaims.map(claim => `${claim.name}#${claim.claim_id}`);
fetchedSubscriptions.push({
claims: channelClaims,
channelName: subscription.channelName,
@ -50,3 +53,19 @@ export const selectSubscriptionsFromClaims = createSelector(
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;
}
);

View file

@ -221,6 +221,9 @@ p {
margin-top: 200px;
text-align: center;
font-family: 'metropolis-medium';
display: flex;
flex-direction: column;
align-items: center;
}
.columns {

View file

@ -299,23 +299,10 @@
display: inline-block;
vertical-align: top;
margin-bottom: 60px;
width: calc((100% / 4) - (60px / 4));
@media only screen and (max-width: $medium-breakpoint) {
width: calc((100% / 3) - (40px / 3));
&:not(:nth-child(3n + 1)) {
margin-left: 20px;
}
}
}
@media only screen and (min-width: $medium-breakpoint) {
.card {
width: calc((100% / 4) - (60px / 4));
&:not(:nth-child(4n + 1)) {
margin-left: 20px;
}
&:not(:nth-child(4n + 1)) {
margin-left: 20px;
}
}
}
@ -341,8 +328,8 @@
display: inline-block;
vertical-align: top;
overflow: visible;
// 35 px to handle to padding between cards
width: calc((100% / 3) - 35px);
// 31 px to handle to padding between cards
width: calc((100% / 4) - 31px);
}
.card:not(:first-of-type) {
@ -352,13 +339,6 @@
.card:last-of-type {
margin-right: 20px;
}
@media only screen and (min-width: $medium-breakpoint) {
.card {
// 31 px to handle to padding between cards
width: calc((100% / 4) - 31px);
}
}
}
.card__success-msg {

View file

@ -10,7 +10,6 @@
height: 100%;
width: 6px;
margin: 0 2px;
background-color: var(--color-white);
animation: sk-stretchdelay 1.2s infinite ease-in-out;
&.rect2 {
@ -31,6 +30,12 @@
}
}
.spinner--light {
.rect {
background-color: var(--color-white);
}
}
.spinner--dark {
.rect {
background-color: var(--color-black);

View file

@ -102,6 +102,7 @@ const store = createStore(
const compressor = createCompressor();
const saveClaimsFilter = createFilter('claims', ['byId', 'claimsByUri']);
const subscriptionsFilter = createFilter('subscriptions', ['subscriptions']);
// We only need to persist the receiveAddress for the wallet
const walletFilter = createFilter('wallet', ['receiveAddress']);

View file

@ -0,0 +1,30 @@
// @flow
// Actual claim type has more values than this
// Add them as they are used
export type Claim = {
address: string,
amount: number,
claim_id: string,
claim_sequence: number,
decoded_claim: boolean,
depth: number,
effective_amount: number,
has_signature: boolean,
height: number,
has_signature: boolean,
hex: string,
name: string,
nout: number,
permanent_url: string,
channel_name: ?string,
txid: string,
nout: number,
signature_is_valid: boolean,
valid_at_height: number,
value: {
publisherSignature: ?{
certificateId: ?string,
},
},
};

View file

@ -0,0 +1,7 @@
// @flow
export type Subscription = {
channelName: string, // @CryptoCandor,
uri: string, // lbry://@CryptoCandor#9152f3b054f692076a6882d1b58a30e8781cc8e6
latest: string, // substratum#b0ab143243020e7831fd070d9f871e1fda948620
};

12
src/renderer/util/dom.js Normal file
View file

@ -0,0 +1,12 @@
// @flow
import * as React from 'react';
// is a child component being rendered?
export const isShowingChildren = (children: React.Node): boolean => {
if (Array.isArray(children)) {
const firstChildIndex = children.findIndex(child => child);
return firstChildIndex > -1;
}
return !!children;
};