From 99ceaadf8b34c048537c9b21c9e3ad97a314aeae Mon Sep 17 00:00:00 2001
From: jessopb <36554050+jessopb@users.noreply.github.com>
Date: Thu, 2 Jun 2022 15:24:11 -0400
Subject: [PATCH] add hosting to first run (#7598)
* add hosting to first run, enable auto hosting
* take welcomeVersion out of sync
* app strings fix
* recommended view hosting limit
* small changes
* fixes
* appstrings
* small fix
---
.env.defaults | 2 +-
static/app-strings.json | 52 ++---
ui/component/appStorageVisualization/index.js | 21 ++
ui/component/appStorageVisualization/view.jsx | 126 +++++++++++
ui/component/hostingSplash/index.js | 27 +++
ui/component/hostingSplash/view.jsx | 156 +++++++++++++
ui/component/hostingSplashCustom/index.js | 3 +
ui/component/hostingSplashCustom/view.jsx | 31 +++
ui/component/privacyAgreement/view.jsx | 8 +-
ui/component/settingDataHosting/index.js | 3 +-
ui/component/settingDataHosting/view.jsx | 208 +++++-------------
ui/component/settingSaveBlobs/index.js | 17 ++
ui/component/settingSaveBlobs/view.jsx | 34 +++
ui/component/settingStorage/index.js | 25 +++
ui/component/settingStorage/view.jsx | 94 ++++++++
ui/component/settingSystem/view.jsx | 19 --
ui/component/settingViewHosting/index.js | 20 ++
ui/component/settingViewHosting/view.jsx | 166 ++++++++++++++
ui/component/settingsRow/view.jsx | 4 +-
ui/component/settingsSideNavigation/view.jsx | 5 +
ui/constants/action_types.js | 1 +
ui/constants/settings.js | 1 +
ui/page/settings/view.jsx | 2 +
ui/page/welcome/view.jsx | 24 +-
ui/redux/actions/comments.js | 4 +-
ui/redux/actions/settings.js | 54 +++--
ui/redux/reducers/app.js | 3 +-
ui/redux/reducers/settings.js | 6 +
ui/redux/selectors/settings.js | 17 ++
ui/scss/component/_form-field.scss | 23 +-
ui/scss/component/_main.scss | 1 +
ui/scss/component/_settings.scss | 92 ++++++++
ui/scss/component/section.scss | 4 +
ui/scss/init/_gui.scss | 17 --
ui/scss/themes/dark.scss | 9 +
ui/scss/themes/light.scss | 11 +-
ui/util/hosting.js | 8 +
37 files changed, 1044 insertions(+), 254 deletions(-)
create mode 100644 ui/component/appStorageVisualization/index.js
create mode 100644 ui/component/appStorageVisualization/view.jsx
create mode 100644 ui/component/hostingSplash/index.js
create mode 100644 ui/component/hostingSplash/view.jsx
create mode 100644 ui/component/hostingSplashCustom/index.js
create mode 100644 ui/component/hostingSplashCustom/view.jsx
create mode 100644 ui/component/settingSaveBlobs/index.js
create mode 100644 ui/component/settingSaveBlobs/view.jsx
create mode 100644 ui/component/settingStorage/index.js
create mode 100644 ui/component/settingStorage/view.jsx
create mode 100644 ui/component/settingViewHosting/index.js
create mode 100644 ui/component/settingViewHosting/view.jsx
create mode 100644 ui/util/hosting.js
diff --git a/.env.defaults b/.env.defaults
index b1da81ebe..e9f804a94 100644
--- a/.env.defaults
+++ b/.env.defaults
@@ -16,7 +16,7 @@ COMMENT_SERVER_NAME=Odysee
SEARCH_SERVER_API=https://lighthouse.odysee.com/search
SOCKETY_SERVER_API=wss://sockety.odysee.com/ws
THUMBNAIL_CDN_URL=https://image-processor.vanwanet.com/optimize/
-WELCOME_VERSION=1.1
+WELCOME_VERSION=1.2
# STRIPE
# STRIPE_PUBLIC_KEY='pk_test_NoL1JWL7i1ipfhVId5KfDZgo'
diff --git a/static/app-strings.json b/static/app-strings.json
index c9f577638..a553fec7f 100644
--- a/static/app-strings.json
+++ b/static/app-strings.json
@@ -2206,13 +2206,6 @@
"Enabling a minimum amount to comment will force all comments to have tips associated with them. This can help prevent spam.": "Enabling a minimum amount to comment will force all comments to have tips associated with them. This can help prevent spam.",
"Comments containing these words will be blocked.": "Comments containing these words will be blocked.",
"Enter the full channel name or URL to search.\n\nExamples:\n - @channel\n - @channel#3\n - https://odysee.com/@Odysee:8\n - lbry://@Odysee#8": "Enter the full channel name or URL to search.\n\nExamples:\n - @channel\n - @channel#3\n - https://odysee.com/@Odysee:8\n - lbry://@Odysee#8",
- "Disk Space": "Disk Space",
- "Data Hosting": "Data Hosting",
- "Limit": "Limit",
- "Limit Space Used": "Limit Space Used",
- "Apply": "Apply",
- "Limit in GB": "Limit in GB",
- "If you set a limit, playing videos may exceed your limit until cleanup runs every 30 minutes.": "If you set a limit, playing videos may exceed your limit until cleanup runs every 30 minutes.",
"Enable Data Hosting": "Enable Data Hosting",
"Data over the limit will be deleted within 30 minutes. This will make the Yrbl cry a little bit.": "Data over the limit will be deleted within 30 minutes. This will make the Yrbl cry a little bit.",
"Choose %asset%": "Choose %asset%",
@@ -2228,13 +2221,6 @@
"Enable Prerelease Updates": "Enable Prerelease Updates",
"Enable Upgrade to Test Builds": "Enable Upgrade to Test Builds",
"Prereleases may break things and we may not be able to fix them for you.": "Prereleases may break things and we may not be able to fix them for you.",
- "Limit (GB)": "Limit (GB)",
- "Limit Hosting for Content you Use": "Limit Hosting for Content you Use",
- "Allow (GB)": "Allow (GB)",
- "Content Data Hosting helps to seed things that you watch and download.": "Content Data Hosting helps to seed things that you watch and download.",
- "Network Data Hosting allows the p2p network to store blobs unrelated to your browsing.": "Network Data Hosting allows the p2p network to store blobs unrelated to your browsing.",
- "Content: Limit (GB)": "Content: Limit (GB)",
- "Network: Allow (GB)": "Network: Allow (GB)",
"A channel is required to repost on LBRY": "A channel is required to repost on LBRY",
"Admin": "Admin",
"Stickers": "Stickers",
@@ -2256,19 +2242,6 @@
"Move Up": "Move Up",
"Move Down": "Move Down",
"Trending for #Game": "Trending for #Game",
- "Help the P2P data network by hosting data.": "Help the P2P data network by hosting data.",
- "History hosting lets you choose how much storage to use helping content you've consumed.": "History hosting lets you choose how much storage to use helping content you've consumed.",
- "Automatic hosting lets you delegate some amount of storage for the network to automatically download ad host.": "Automatic hosting lets you delegate some amount of storage for the network to automatically download ad host.",
- "Playing videos may exceed your history hosting limit until cleanup runs every 30 minutes.": "Playing videos may exceed your history hosting limit until cleanup runs every 30 minutes.",
- "History: Limit (GB)": "History: Limit (GB)",
- "Automatic: Allow (GB)": "Automatic: Allow (GB)",
- "Automatic hosting lets you delegate some amount of storage for the network to automatically download and host.": "Automatic hosting lets you delegate some amount of storage for the network to automatically download and host.",
- "History Hosting": "History Hosting",
- "Automatic Hosting": "Automatic Hosting",
- "History Hosting lets you choose how much storage to use helping content you've consumed.": "History Hosting lets you choose how much storage to use helping content you've consumed.",
- "Automatic Hosting lets you delegate some amount of storage for the network to automatically download and host.": "Automatic Hosting lets you delegate some amount of storage for the network to automatically download and host.",
- "Help improve the P2P data network (and make LBRY happy) by hosting data.": "Help improve the P2P data network (and make LBRY happy) by hosting data.",
- "Limit Hosting of Content History": "Limit Hosting of Content History",
"Remove custom comment server": "Remove custom comment server",
"Use Https": "Use Https",
"Server URL": "Server URL",
@@ -2315,8 +2288,27 @@
"Clear Views": "Clear Views",
"Show Video View Progress": "Show Video View Progress",
"Display view progress on thumbnail. This setting will not hide any blockchain activity or downloads.": "Display view progress on thumbnail. This setting will not hide any blockchain activity or downloads.",
- "%anonymous%": "%anonymous%",
- "Anon --[used in <%anonymous% Reposted>]--": "Anon",
- "This will be visible in a few minutes after you submit this form.": "This will be visible in a few minutes after you submit this form.",
+ "Content Hosting": "Content Hosting",
+ "Hosting": "Hosting",
+ "Viewed Hosting": "Viewed Hosting",
+ "Auto Hosting": "Auto Hosting",
+ "Help creators and improve the P2P data network by hosting content.": "Help creators and improve the P2P data network by hosting content.",
+ "I'm happy with my settings": "I'm happy with my settings",
+ "We've noticed you already have some settings.": "We've noticed you already have some settings.",
+ "You choose how much data to host.": "You choose how much data to host.",
+ "Go back": "Go back",
+ "Custom Hosting": "Custom Hosting",
+ "Automatic Hosting (GB)": "Automatic Hosting (GB)",
+ "* Note that as peer-to-peer software, your IP address and potentially other system information can be sent to other users, though this information is not stored permanently.": "* Note that as peer-to-peer software, your IP address and potentially other system information can be sent to other users, though this information is not stored permanently.",
+ "Help improve the P2P data network (and make LBRY users happy) by hosting data.": "Help improve the P2P data network (and make LBRY users happy) by hosting data.",
+ "View History Hosting lets you choose how much storage to use hosting content you've consumed.": "View History Hosting lets you choose how much storage to use hosting content you've consumed.",
+ "Automatic Hosting downloads a small portion of content active on the network.": "Automatic Hosting downloads a small portion of content active on the network.",
+ "Publishes --[legend, storage category]--": "Publishes",
+ "Auto Hosting --[legend, storage category]--": "Auto Hosting",
+ "View Hosting --[legend, storage category]--": "View Hosting",
+ "%spaceUsed% of %limit% GB": "%spaceUsed% of %limit% GB",
+ "%spaceUsed% of %limit% Free GB": "%spaceUsed% of %limit% Free GB",
+ "Disabled": "Disabled",
+ "Free --[legend, unused disk space]--": "Free",
"--end--": "--end--"
}
diff --git a/ui/component/appStorageVisualization/index.js b/ui/component/appStorageVisualization/index.js
new file mode 100644
index 000000000..c4b20ecd9
--- /dev/null
+++ b/ui/component/appStorageVisualization/index.js
@@ -0,0 +1,21 @@
+import { connect } from 'react-redux';
+import StorageViz from './view';
+import {
+ selectViewBlobSpace,
+ selectViewHostingLimit,
+ selectAutoBlobSpace,
+ selectPrivateBlobSpace,
+ selectAutoHostingLimit,
+} from 'redux/selectors/settings';
+import { selectDiskSpace } from 'redux/selectors/app';
+
+const select = (state) => ({
+ diskSpace: selectDiskSpace(state),
+ viewHostingLimit: selectViewHostingLimit(state),
+ autoHostingLimit: selectAutoHostingLimit(state),
+ viewBlobSpace: selectViewBlobSpace(state),
+ autoBlobSpace: selectAutoBlobSpace(state),
+ privateBlobSpace: selectPrivateBlobSpace(state),
+});
+
+export default connect(select)(StorageViz);
diff --git a/ui/component/appStorageVisualization/view.jsx b/ui/component/appStorageVisualization/view.jsx
new file mode 100644
index 000000000..c8d40cbe9
--- /dev/null
+++ b/ui/component/appStorageVisualization/view.jsx
@@ -0,0 +1,126 @@
+// @flow
+import * as React from 'react';
+import I18nMessage from 'component/i18nMessage';
+
+type Props = {
+ // --- select ---
+ diskSpace: DiskSpace, // KB
+ viewHostingLimit: number, // MB
+ autoHostingLimit: number,
+ viewBlobSpace: number,
+ autoBlobSpace: number,
+ privateBlobSpace: number,
+};
+
+function StorageViz(props: Props) {
+ const { diskSpace, viewHostingLimit, autoHostingLimit, viewBlobSpace, autoBlobSpace, privateBlobSpace } = props;
+
+ if (!diskSpace || !diskSpace.total) {
+ return (
+
+
+
{__('Cannot get disk space information.')}
+
+
+ );
+ }
+
+ const totalMB = diskSpace && Math.floor(Number(diskSpace.total) / 1024);
+ const freeMB = diskSpace && Math.floor(Number(diskSpace.free) / 1024);
+ const otherMB = totalMB - (freeMB + viewBlobSpace + autoBlobSpace + privateBlobSpace);
+ const autoFree = autoHostingLimit - autoBlobSpace;
+ const viewFree = viewHostingLimit > 0 ? viewHostingLimit - viewBlobSpace : freeMB - autoFree;
+ const unallocFree = freeMB - viewFree - autoFree;
+ const viewLimit =
+ viewHostingLimit === 0
+ ? freeMB - (autoHostingLimit - autoBlobSpace) + viewBlobSpace
+ : viewHostingLimit + viewBlobSpace;
+
+ const getPercent = (val, lim = totalMB) => (val / lim) * 100;
+ const getGB = (val) => (Number(val) / 1024).toFixed(2);
+
+ const otherPercent = getPercent(otherMB);
+ const privatePercent = getPercent(privateBlobSpace);
+ const autoLimitPercent = getPercent(autoHostingLimit);
+ const viewLimitPercent = getPercent(viewLimit);
+ const viewUsedPercentOfLimit = getPercent(viewBlobSpace, viewLimit);
+ const autoUsedPercentOfLimit = getPercent(autoBlobSpace, autoHostingLimit);
+
+ return (
+
+
+
+
+
+
+ {viewHostingLimit !== 0 &&
}
+
+
+
+
+
+
{__('Publishes --[legend, storage category]--')}
+
{`${getGB(privateBlobSpace)} GB`}
+
+
+
+
+
+
{__('Auto Hosting --[legend, storage category]--')}
+
+ {autoHostingLimit === 0 ? (
+ __('Disabled')
+ ) : (
+
+ %spaceUsed% of %limit% GB
+
+ )}
+
+
+
+
+
+
+
{__('View Hosting --[legend, storage category]--')}
+
+ {viewHostingLimit === 1 ? (
+ __('Disabled')
+ ) : (
+
+ %spaceUsed% of %limit% Free GB
+
+ )}
+
+
+
+ {viewHostingLimit !== 0 && (
+
+
+
+
{__('Free --[legend, unused disk space]--')}
+
{`${getGB(unallocFree)} GB`}
+
+
+ )}
+
+
+ );
+}
+
+export default StorageViz;
diff --git a/ui/component/hostingSplash/index.js b/ui/component/hostingSplash/index.js
new file mode 100644
index 000000000..469b4cba6
--- /dev/null
+++ b/ui/component/hostingSplash/index.js
@@ -0,0 +1,27 @@
+import { connect } from 'react-redux';
+
+import HostingSplash from './view';
+import {
+ selectViewBlobSpace,
+ selectViewHostingLimit,
+ selectAutoBlobSpace,
+ selectAutoHostingLimit,
+ selectSaveBlobs,
+} from 'redux/selectors/settings';
+import { doSetDaemonSetting } from 'redux/actions/settings';
+import { selectDiskSpace } from 'redux/selectors/app';
+
+const select = (state) => ({
+ diskSpace: selectDiskSpace(state),
+ viewHostingLimit: selectViewHostingLimit(state),
+ autoHostingLimit: selectAutoHostingLimit(state),
+ viewBlobSpace: selectViewBlobSpace(state),
+ autoBlobSpace: selectAutoBlobSpace(state),
+ saveBlobs: selectSaveBlobs(state),
+});
+
+const perform = (dispatch) => ({
+ setDaemonSetting: (key, value) => dispatch(doSetDaemonSetting(key, value)),
+});
+
+export default connect(select, perform)(HostingSplash);
diff --git a/ui/component/hostingSplash/view.jsx b/ui/component/hostingSplash/view.jsx
new file mode 100644
index 000000000..efe9472a1
--- /dev/null
+++ b/ui/component/hostingSplash/view.jsx
@@ -0,0 +1,156 @@
+// @flow
+import React from 'react';
+import Button from 'component/button';
+import { FormField } from 'component/common/form-components/form-field';
+import { Form } from 'component/common/form-components/form';
+import { withRouter } from 'react-router-dom';
+
+// $FlowFixMe cannot resolve ...
+import image from 'static/img/yrblhappy.svg';
+import * as DAEMON_SETTINGS from 'constants/daemon_settings';
+
+type SetDaemonSettingArg = boolean | string | number;
+
+type Props = {
+ handleNextPage: () => void,
+ handleDone: () => void,
+ setDaemonSetting: (string, ?SetDaemonSettingArg) => void,
+ // --- select ---
+ diskSpace: DiskSpace, // KB
+ viewHostingLimit: number, // MB
+ autoHostingLimit: number,
+ viewBlobSpace: number,
+ autoBlobSpace: number,
+ privateBlobSpace: number,
+ saveBlobs: boolean,
+};
+
+const TWENTY_PERCENT = 0.2;
+const TEN_PRECNT = 0.1;
+
+function HostingSplash(props: Props) {
+ const {
+ handleNextPage,
+ diskSpace,
+ viewHostingLimit,
+ autoHostingLimit,
+ viewBlobSpace,
+ autoBlobSpace,
+ saveBlobs,
+ setDaemonSetting,
+ handleDone,
+ } = props;
+
+ const totalMB = diskSpace && Math.floor(Number(diskSpace.total) / 1024);
+ const freeMB = diskSpace && Math.floor(Number(diskSpace.free) / 1024);
+ const blobSpaceUsed = viewBlobSpace + autoBlobSpace;
+
+ const [hostingChoice, setHostingChoice] = React.useState('MANAGED');
+ function handleSubmit() {
+ if (hostingChoice === 'CUSTOM') {
+ handleNextPage();
+ } else {
+ handleAuto();
+ }
+ }
+
+ function getManagedLimitMB() {
+ const value =
+ freeMB > totalMB * TWENTY_PERCENT // lots of free space?
+ ? blobSpaceUsed > totalMB * TEN_PRECNT // using more than 10%?
+ ? (freeMB + blobSpaceUsed) / 2 // e.g. 40g used plus 30g free, knock back to 35g limit, freeing to 35g
+ : totalMB * TEN_PRECNT // let it go up to 10%
+ : (freeMB + blobSpaceUsed) / 2; // e.g. 40g used plus 10g free, knock back to 25g limit, freeing to 25g
+ return value > 10240 ? Math.floor(value / 1024) * 1024 : 0;
+ }
+
+ function getAutoLimit() {
+ // return floor of 10% of total
+ const totalGB = Math.floor(getManagedLimitMB() / 1024); // eg, 25GB
+ return Math.floor(totalGB / 10) * 1024; // eg, 2 GB -> 2048MB
+ }
+
+ function getViewedLimit() {
+ return getManagedLimitMB() - getAutoLimit();
+ }
+
+ function getManagedCopy() {
+ if (viewHostingLimit || autoHostingLimit || !saveBlobs) {
+ return __("I'm happy with my settings");
+ } else if (getManagedLimitMB() > 0) {
+ return __(`Host up to %percent% of my drive (%limit% GB)`, {
+ percent: `${Math.round((Math.floor(getManagedLimitMB() / 1024) / Math.floor(totalMB / 1024)) * 100)}%`,
+ limit: Math.floor(getManagedLimitMB() / 1024),
+ });
+ } else {
+ return __(`Not now, my disk is almost full.`);
+ }
+ }
+
+ function getManagedHelper() {
+ if (viewHostingLimit || autoHostingLimit || !saveBlobs) {
+ return __(`We've noticed you already have some settings.`);
+ } else if (getManagedLimitMB() > 0) {
+ return __(`Donate space without filling up your drive.`);
+ } else {
+ return __(`You can clear some space and check hosting settings later.`);
+ }
+ }
+
+ async function handleAuto() {
+ if (viewHostingLimit || autoHostingLimit || !saveBlobs) {
+ handleDone();
+ } else if (getManagedLimitMB() > 0) {
+ // limit to used // maybe move this to a single action function that doesn't live inside the component.
+ await setDaemonSetting(DAEMON_SETTINGS.BLOB_STORAGE_LIMIT_MB, getViewedLimit());
+ await setDaemonSetting(DAEMON_SETTINGS.NETWORK_STORAGE_LIMIT_MB, getAutoLimit());
+ handleDone();
+ } else {
+ // running low on space
+ handleDone();
+ }
+ }
+
+ return (
+
+
+
+
+
{__('Hosting')}
+
+ {__('Help creators and improve the P2P data network by hosting content.')}
+
+
+ setHostingChoice('MANAGED')}
+ />
+ {__('Custom')}>}
+ helper={__(`You choose how much data to host.`)}
+ onChange={(e) => setHostingChoice('CUSTOM')}
+ />
+
+
+
+
+
+
+
+
+
+ );
+}
+
+export default withRouter(HostingSplash);
diff --git a/ui/component/hostingSplashCustom/index.js b/ui/component/hostingSplashCustom/index.js
new file mode 100644
index 000000000..2385a145a
--- /dev/null
+++ b/ui/component/hostingSplashCustom/index.js
@@ -0,0 +1,3 @@
+import HostingSplashCustom from './view';
+
+export default HostingSplashCustom;
diff --git a/ui/component/hostingSplashCustom/view.jsx b/ui/component/hostingSplashCustom/view.jsx
new file mode 100644
index 000000000..86b1e85e2
--- /dev/null
+++ b/ui/component/hostingSplashCustom/view.jsx
@@ -0,0 +1,31 @@
+// @flow
+import React from 'react';
+import Button from 'component/button';
+import { Form } from 'component/common/form-components/form';
+import SettingStorage from 'component/settingStorage';
+import { withRouter } from 'react-router-dom';
+
+type Props = {
+ handleNextPage: () => void,
+ handleGoBack: () => void,
+};
+
+function HostingSplashCustom(props: Props) {
+ const { handleNextPage, handleGoBack } = props;
+
+ return (
+
+ );
+}
+
+export default withRouter(HostingSplashCustom);
diff --git a/ui/component/privacyAgreement/view.jsx b/ui/component/privacyAgreement/view.jsx
index b0d0715c8..af9189d40 100644
--- a/ui/component/privacyAgreement/view.jsx
+++ b/ui/component/privacyAgreement/view.jsx
@@ -72,9 +72,9 @@ function PrivacyAgreement(props: Props) {
{__('No')} 😢
>
}
- helper={__(`* Note that as
- peer-to-peer software, your IP address and potentially other system information can be sent to other
- users, though this information is not stored permanently.`)}
+ helper={__(
+ `* Note that as peer-to-peer software, your IP address and potentially other system information can be sent to other users, though this information is not stored permanently.`
+ )}
onChange={(e) => setShare(NONE)}
/>
{authenticated && (
@@ -92,7 +92,7 @@ function PrivacyAgreement(props: Props) {
)}
-
+
{share === NONE && (
diff --git a/ui/component/settingDataHosting/index.js b/ui/component/settingDataHosting/index.js
index c001ae8d1..d3c67819f 100644
--- a/ui/component/settingDataHosting/index.js
+++ b/ui/component/settingDataHosting/index.js
@@ -1,6 +1,6 @@
import { connect } from 'react-redux';
import { doSetDaemonSetting, doGetDaemonStatus, doCleanBlobs } from 'redux/actions/settings';
-import { selectDaemonStatus, selectDaemonSettings } from 'redux/selectors/settings';
+import { selectDaemonStatus, selectDaemonSettings, selectSettingDaemonSettings } from 'redux/selectors/settings';
import SettingWalletServer from './view';
import { selectDiskSpace } from 'redux/selectors/app';
@@ -8,6 +8,7 @@ const select = (state) => ({
daemonSettings: selectDaemonSettings(state),
daemonStatus: selectDaemonStatus(state),
diskSpace: selectDiskSpace(state),
+ isSetting: selectSettingDaemonSettings(state),
});
const perform = (dispatch) => ({
diff --git a/ui/component/settingDataHosting/view.jsx b/ui/component/settingDataHosting/view.jsx
index d2d05b458..43bf1e668 100644
--- a/ui/component/settingDataHosting/view.jsx
+++ b/ui/component/settingDataHosting/view.jsx
@@ -4,89 +4,36 @@ import React from 'react';
import { FormField } from 'component/common/form';
import Button from 'component/button';
import * as DAEMON_SETTINGS from 'constants/daemon_settings';
-import { formatBytes } from 'util/format-bytes';
import { isTrulyANumber } from 'util/number';
-import I18nMessage from 'component/i18nMessage';
-const BYTES_PER_MB = 1048576;
-const ENABLE_AUTOMATIC_HOSTING = false;
+import * as ICONS from 'constants/icons';
+import * as KEYCODES from 'constants/keycodes';
-type Price = {
- currency: string,
- amount: number,
-};
+import { convertGbToMbStr, isValidHostingAmount } from 'util/hosting';
-type DaemonStatus = {
- disk_space: {
- content_blobs_storage_used_mb: string,
- published_blobs_storage_used_mb: string,
- running: true,
- seed_blobs_storage_used_mb: string,
- total_used_mb: string,
- },
-};
-
-type SetDaemonSettingArg = boolean | string | number | Price;
+type SetDaemonSettingArg = boolean | string | number;
type DaemonSettings = {
- download_dir: string,
- share_usage_data: boolean,
- max_key_fee?: Price,
- max_connections_per_download?: number,
- save_files: boolean,
save_blobs: boolean,
- ffmpeg_path: string,
};
type Props = {
// --- select ---
daemonSettings: DaemonSettings,
- daemonStatus: DaemonStatus,
// --- perform ---
setDaemonSetting: (string, ?SetDaemonSettingArg) => void,
cleanBlobs: () => string,
- diskSpace?: DiskSpace,
getDaemonStatus: () => void,
+ isSetting: boolean,
};
function SettingDataHosting(props: Props) {
- const { daemonSettings, daemonStatus, setDaemonSetting, cleanBlobs, diskSpace, getDaemonStatus } = props;
+ const { daemonSettings, setDaemonSetting, cleanBlobs, getDaemonStatus, isSetting } = props;
- const { disk_space: blobSpace } = daemonStatus;
- const contentSpaceUsed = Number(blobSpace.content_blobs_storage_used_mb);
- const blobLimitSetting = daemonSettings[DAEMON_SETTINGS.BLOB_STORAGE_LIMIT_MB] || '0';
- const [contentBlobSpaceLimitGB, setContentBlobSpaceLimit] = React.useState(
- blobLimitSetting ? String(blobLimitSetting / 1024) : '10'
- );
- const [applying, setApplying] = React.useState(false);
-
- const networkSpaceUsed = Number(blobSpace.seed_blobs_storage_used_mb);
- const networkLimitSetting = daemonSettings[DAEMON_SETTINGS.NETWORK_STORAGE_LIMIT_MB] || '0';
+ const networkLimitSetting = daemonSettings[DAEMON_SETTINGS.NETWORK_STORAGE_LIMIT_MB] || 0;
const [networkBlobSpaceLimitGB, setNetworkBlobSpaceLimit] = React.useState(
- networkLimitSetting ? String(networkLimitSetting / 1024) : '0'
+ networkLimitSetting ? String(networkLimitSetting / 1024) : 0
);
- const [unlimited, setUnlimited] = React.useState(blobLimitSetting === '0');
-
- React.useEffect(() => {
- getDaemonStatus();
- }, []);
-
- function convertGbToMb(gb) {
- return Number(gb) * 1024;
- }
-
- function handleContentLimitChange(gb) {
- if (gb === '') {
- setContentBlobSpaceLimit('');
- } else if (gb === '0') {
- setContentBlobSpaceLimit('0.01'); // setting 0 means unlimited.
- } else {
- if (isTrulyANumber(Number(gb))) {
- setContentBlobSpaceLimit(gb);
- }
- }
- }
-
function handleNetworkLimitChange(gb) {
if (gb === '') {
setNetworkBlobSpaceLimit('');
@@ -98,109 +45,68 @@ function SettingDataHosting(props: Props) {
}
}
- async function handleApply() {
- setApplying(true);
- if (unlimited) {
- await setDaemonSetting(DAEMON_SETTINGS.BLOB_STORAGE_LIMIT_MB, '0');
- } else {
- await setDaemonSetting(
- DAEMON_SETTINGS.BLOB_STORAGE_LIMIT_MB,
- String(contentBlobSpaceLimitGB === '0.01' ? '1' : convertGbToMb(contentBlobSpaceLimitGB))
- );
+ function handleKeyDown(e) {
+ if (e.keyCode === KEYCODES.ESCAPE) {
+ e.preventDefault();
+ setNetworkBlobSpaceLimit(String(networkLimitSetting / 1024));
+ } else if (e.keyCode === KEYCODES.ENTER) {
+ e.preventDefault();
+ handleApply();
}
+ }
+
+ async function handleApply() {
await setDaemonSetting(
DAEMON_SETTINGS.NETWORK_STORAGE_LIMIT_MB,
- String(convertGbToMb(Number(networkBlobSpaceLimitGB)))
+ String(convertGbToMbStr(Number(networkBlobSpaceLimitGB)))
);
await cleanBlobs();
getDaemonStatus();
- setApplying(false);
- }
-
- function validHostingAmount(amountString) {
- const numberAmount = Number(amountString);
- return amountString.length && ((numberAmount && String(numberAmount)) || numberAmount === 0);
}
return (
<>
setDaemonSetting('save_blobs', !daemonSettings.save_blobs)}
- checked={daemonSettings.save_blobs}
- label={__('Enable Data Hosting')}
- helper={
- diskSpace && (
-
- %free% of %total% available
-
- )
+ name="network_blob_limit_gb"
+ type="number"
+ label={__(`Automatic Hosting (GB)`)}
+ disabled={!daemonSettings.save_blobs || isSetting}
+ onKeyDown={handleKeyDown}
+ inputButton={
+ <>
+
+ setNetworkBlobSpaceLimit(String(networkLimitSetting / 1024))}
+ aria-label={__('Reset')}
+ icon={ICONS.REMOVE}
+ />
+ >
}
- />
-
- {daemonSettings.save_blobs && (
-
-
setUnlimited(true)}
- />
- setUnlimited(false)}
- label={__('Choose View Hosting Limit')}
- />
- {!unlimited && (
- e.preventDefault()}
- label={__(`View Hosting Limit (GB)`)}
- onChange={(e) => handleContentLimitChange(e.target.value)}
- value={Number(contentBlobSpaceLimitGB) <= Number('0.01') ? '0' : contentBlobSpaceLimitGB}
- />
- )}
- {`Currently using ${formatBytes(contentSpaceUsed * BYTES_PER_MB)}`}
-
- )}
- {daemonSettings.save_blobs && ENABLE_AUTOMATIC_HOSTING && (
-
- handleNetworkLimitChange(e.target.value)}
- value={networkBlobSpaceLimitGB}
- />
- {`Auto-hosting ${formatBytes(networkSpaceUsed * BYTES_PER_MB)}`}
-
- )}
-
- handleNetworkLimitChange(e.target.value)}
+ value={networkBlobSpaceLimitGB}
/>
>
diff --git a/ui/component/settingSaveBlobs/index.js b/ui/component/settingSaveBlobs/index.js
new file mode 100644
index 000000000..924c79bc7
--- /dev/null
+++ b/ui/component/settingSaveBlobs/index.js
@@ -0,0 +1,17 @@
+import { connect } from 'react-redux';
+import { doSetDaemonSetting, doGetDaemonStatus } from 'redux/actions/settings';
+import { selectDaemonSettings } from 'redux/selectors/settings';
+import SettingWalletServer from './view';
+import { selectDiskSpace } from 'redux/selectors/app';
+
+const select = (state) => ({
+ daemonSettings: selectDaemonSettings(state),
+ diskSpace: selectDiskSpace(state),
+});
+
+const perform = (dispatch) => ({
+ getDaemonStatus: () => dispatch(doGetDaemonStatus()),
+ setDaemonSetting: (key, value) => dispatch(doSetDaemonSetting(key, value)),
+});
+
+export default connect(select, perform)(SettingWalletServer);
diff --git a/ui/component/settingSaveBlobs/view.jsx b/ui/component/settingSaveBlobs/view.jsx
new file mode 100644
index 000000000..772e35272
--- /dev/null
+++ b/ui/component/settingSaveBlobs/view.jsx
@@ -0,0 +1,34 @@
+// @flow
+
+import React from 'react';
+import { FormField } from 'component/common/form';
+
+type SetDaemonSettingArg = boolean | string | number;
+
+type DaemonSettings = {
+ save_blobs: boolean,
+};
+
+type Props = {
+ // --- select ---
+ daemonSettings: DaemonSettings,
+ // --- perform ---
+ setDaemonSetting: (string, ?SetDaemonSettingArg) => void,
+};
+
+function SettingDataHosting(props: Props) {
+ const { daemonSettings, setDaemonSetting } = props;
+
+ return (
+ <>
+ setDaemonSetting('save_blobs', !daemonSettings.save_blobs)}
+ checked={daemonSettings.save_blobs}
+ />
+ >
+ );
+}
+
+export default SettingDataHosting;
diff --git a/ui/component/settingStorage/index.js b/ui/component/settingStorage/index.js
new file mode 100644
index 000000000..5bb20322e
--- /dev/null
+++ b/ui/component/settingStorage/index.js
@@ -0,0 +1,25 @@
+import { connect } from 'react-redux';
+import { doWalletStatus } from 'redux/actions/wallet';
+import { doClearCache } from 'redux/actions/app';
+import { doSetDaemonSetting, doClearDaemonSetting, doCleanBlobs } from 'redux/actions/settings';
+import { selectDaemonSettings, selectDaemonStatus, selectSettingDaemonSettings } from 'redux/selectors/settings';
+
+import SettingStorage from './view';
+import { selectDiskSpace } from 'redux/selectors/app';
+
+const select = (state) => ({
+ daemonSettings: selectDaemonSettings(state),
+ diskSpace: selectDiskSpace(state),
+ daemonStatus: selectDaemonStatus(state),
+ isSetting: selectSettingDaemonSettings(state),
+});
+
+const perform = (dispatch) => ({
+ setDaemonSetting: (key, value) => dispatch(doSetDaemonSetting(key, value)),
+ clearDaemonSetting: (key) => dispatch(doClearDaemonSetting(key)),
+ clearCache: () => dispatch(doClearCache()),
+ cleanBlobs: () => dispatch(doCleanBlobs()),
+ updateWalletStatus: () => dispatch(doWalletStatus()),
+});
+
+export default connect(select, perform)(SettingStorage);
diff --git a/ui/component/settingStorage/view.jsx b/ui/component/settingStorage/view.jsx
new file mode 100644
index 000000000..cdc06f442
--- /dev/null
+++ b/ui/component/settingStorage/view.jsx
@@ -0,0 +1,94 @@
+// @flow
+import { SETTINGS_GRP } from 'constants/settings';
+import React from 'react';
+import Button from 'component/button';
+import Card from 'component/common/card';
+import SettingDataHosting from 'component/settingDataHosting';
+import SettingViewHosting from 'component/settingViewHosting';
+import SettingSaveBlobs from 'component/settingSaveBlobs';
+import SettingsRow from 'component/settingsRow';
+import AppStorageViz from 'component/appStorageVisualization';
+import Spinner from 'component/spinner';
+import classnames from 'classnames';
+
+type DaemonSettings = {
+ save_blobs: boolean,
+};
+
+type Props = {
+ daemonSettings: DaemonSettings,
+ isSetting: boolean,
+ isWelcome?: boolean,
+ cleanBlobs: () => Promise,
+};
+
+export default function SettingStorage(props: Props) {
+ const { daemonSettings, isSetting, isWelcome, cleanBlobs } = props;
+
+ const saveBlobs = daemonSettings && daemonSettings.save_blobs;
+ const [isCleaning, setCleaning] = React.useState(false);
+
+ // currently, it seems, blob space statistics are only updated during clean
+ React.useEffect(() => {
+ setCleaning(true);
+ cleanBlobs().then(() => {
+ setCleaning(false);
+ });
+ }, []);
+
+ return (
+ <>
+
+
+ {isWelcome ? __('Custom Hosting') : __('Hosting')}
+ {(isSetting || isCleaning) && }
+
+
+
+
+ {__('Help improve the P2P data network (and make LBRY users happy) by hosting data.')}
+
+ }
+ footer={ }
+ >
+
+
+
+ {__("View History Hosting lets you choose how much storage to use hosting content you've consumed.")}{' '}
+
+
+ }
+ >
+
+
+
+ {__('Automatic Hosting downloads a small portion of content active on the network.')}{' '}
+
+
+ }
+ >
+
+
+ >
+ }
+ />
+ >
+ );
+}
diff --git a/ui/component/settingSystem/view.jsx b/ui/component/settingSystem/view.jsx
index 19ad493a7..af40546f8 100644
--- a/ui/component/settingSystem/view.jsx
+++ b/ui/component/settingSystem/view.jsx
@@ -10,7 +10,6 @@ import I18nMessage from 'component/i18nMessage';
import SettingAutoLaunch from 'component/settingAutoLaunch';
import SettingClosingBehavior from 'component/settingClosingBehavior';
import SettingCommentsServer from 'component/settingCommentsServer';
-import SettingDataHosting from 'component/settingDataHosting';
import SettingShareUrl from 'component/settingShareUrl';
import SettingsRow from 'component/settingsRow';
import SettingWalletServer from 'component/settingWalletServer';
@@ -151,24 +150,6 @@ export default function SettingSystem(props: Props) {
checked={daemonSettings.save_files}
/>
-
- {__('Help improve the P2P data network (and make LBRY happy) by hosting data.')}{' '}
- {__("View History Hosting lets you choose how much storage to use helping content you've consumed.")}{' '}
- {/* {__( */}
- {/* 'Automatic Hosting lets you delegate some amount of storage for the network to automatically download and host.' */}
- {/* )}{' '} */}
- {__('Playing videos may exceed your history hosting limit until cleanup runs every 30 minutes.')}
-
-
-
- }
- >
-
-
({
+ viewHostingLimit: selectViewHostingLimit(state),
+ viewBlobSpace: selectViewBlobSpace(state),
+ diskSpace: selectDiskSpace(state),
+ isSetting: selectSettingDaemonSettings(state),
+});
+
+const perform = (dispatch) => ({
+ getDaemonStatus: () => dispatch(doGetDaemonStatus()),
+ setDaemonSetting: (key, value) => dispatch(doSetDaemonSetting(key, value)),
+ cleanBlobs: () => dispatch(doCleanBlobs()),
+});
+
+export default connect(select, perform)(SettingViewHosting);
diff --git a/ui/component/settingViewHosting/view.jsx b/ui/component/settingViewHosting/view.jsx
new file mode 100644
index 000000000..a4b881187
--- /dev/null
+++ b/ui/component/settingViewHosting/view.jsx
@@ -0,0 +1,166 @@
+// @flow
+
+import React from 'react';
+import { FormField } from 'component/common/form';
+import Button from 'component/button';
+import * as DAEMON_SETTINGS from 'constants/daemon_settings';
+import { isTrulyANumber } from 'util/number';
+import * as ICONS from 'constants/icons';
+import * as KEYCODES from 'constants/keycodes';
+import { convertGbToMbStr, isValidHostingAmount } from 'util/hosting';
+
+type SetDaemonSettingArg = boolean | string | number;
+
+type Props = {
+ // --- select ---
+ viewBlobSpace: number,
+ viewHostingLimit: number,
+ disabled?: boolean,
+ isSetting: boolean,
+ // --- perform ---
+ setDaemonSetting: (string, ?SetDaemonSettingArg) => void,
+ cleanBlobs: () => string,
+ getDaemonStatus: () => void,
+ diskSpace: DiskSpace,
+};
+
+const TWENTY_PERCENT = 0.2;
+const TEN_PERCENT = 0.1;
+const MINIMUM_VIEW_SETTING = '0.01';
+
+function SettingViewHosting(props: Props) {
+ const {
+ diskSpace,
+ viewHostingLimit,
+ viewBlobSpace,
+ setDaemonSetting,
+ cleanBlobs,
+ getDaemonStatus,
+ disabled,
+ isSetting,
+ } = props;
+
+ // best effort to recommend a hosting amount default for the user
+ const totalMB = diskSpace && Math.floor(Number(diskSpace.total) / 1024);
+ const freeMB = diskSpace && Math.floor(Number(diskSpace.free) / 1024);
+ const getGB = (val) => (Number(val) / 1024).toFixed(2);
+ const recommendedSpace =
+ freeMB > totalMB * TWENTY_PERCENT // plenty of space?
+ ? Math.ceil(Number(getGB(totalMB * TEN_PERCENT))) // 10% of total
+ : Math.ceil(Number(getGB(viewBlobSpace))); // current amount to avoid deleting
+ // daemon settings come in as 'number', but we manage them as 'String'.
+ const [contentBlobSpaceLimitGB, setContentBlobSpaceLimit] = React.useState(
+ viewHostingLimit === 0 ? String(recommendedSpace) : String(viewHostingLimit / 1024)
+ );
+
+ const [unlimited, setUnlimited] = React.useState(viewHostingLimit === 0);
+
+ function handleContentLimitChange(gb) {
+ if (gb === '') {
+ setContentBlobSpaceLimit('');
+ } else if (gb === '0') {
+ setContentBlobSpaceLimit(MINIMUM_VIEW_SETTING); // setting 0 means unlimited.
+ } else {
+ if (isTrulyANumber(Number(gb))) {
+ setContentBlobSpaceLimit(gb);
+ }
+ }
+ }
+
+ async function handleApply() {
+ if (unlimited) {
+ await setDaemonSetting(DAEMON_SETTINGS.BLOB_STORAGE_LIMIT_MB, '0');
+ } else {
+ await setDaemonSetting(
+ DAEMON_SETTINGS.BLOB_STORAGE_LIMIT_MB,
+ contentBlobSpaceLimitGB === MINIMUM_VIEW_SETTING ? '1' : convertGbToMbStr(contentBlobSpaceLimitGB)
+ );
+ }
+ await cleanBlobs();
+ getDaemonStatus();
+ }
+
+ function handleKeyDown(e) {
+ if (e.keyCode === KEYCODES.ESCAPE) {
+ e.preventDefault();
+ setContentBlobSpaceLimit(String(viewHostingLimit / 1024));
+ } else if (e.keyCode === KEYCODES.ENTER) {
+ e.preventDefault();
+ handleApply();
+ }
+ }
+
+ React.useEffect(() => {
+ if (unlimited) {
+ handleApply();
+ }
+ }, [unlimited]);
+
+ return (
+ <>
+
+ setUnlimited(!unlimited)}
+ />
+
+
+ setContentBlobSpaceLimit(String(viewHostingLimit / 1024))}
+ aria-label={__('Reset')}
+ icon={ICONS.REMOVE}
+ />
+ >
+ }
+ disabled={isSetting || disabled || unlimited}
+ onWheel={(e) => e.preventDefault()}
+ label={__(`View Hosting Limit (GB)`)}
+ onChange={(e) => handleContentLimitChange(e.target.value)}
+ value={Number(contentBlobSpaceLimitGB) <= Number(MINIMUM_VIEW_SETTING) ? '0' : contentBlobSpaceLimitGB}
+ />
+
+ >
+ );
+}
+
+export default SettingViewHosting;
diff --git a/ui/component/settingsRow/view.jsx b/ui/component/settingsRow/view.jsx
index f6864e3db..dd2e78623 100644
--- a/ui/component/settingsRow/view.jsx
+++ b/ui/component/settingsRow/view.jsx
@@ -9,10 +9,11 @@ type Props = {
useVerticalSeparator?: boolean, // Show a separator line between Label and Value. Useful when there are multiple Values.
disabled?: boolean,
children?: React$Node,
+ footer?: React$Node,
};
export default function SettingsRow(props: Props) {
- const { title, subtitle, multirow, useVerticalSeparator, disabled, children } = props;
+ const { title, subtitle, multirow, useVerticalSeparator, disabled, children, footer } = props;
return (
{title}
{subtitle &&
{subtitle}
}
+ {footer && footer}
= [
section: SETTINGS_GRP.SYSTEM,
icon: ICONS.SETTINGS,
},
+ {
+ title: 'Content Hosting',
+ section: SETTINGS_GRP.STORAGE,
+ icon: ICONS.PUBLISH,
+ },
];
export default function SettingsSideNavigation() {
diff --git a/ui/constants/action_types.js b/ui/constants/action_types.js
index ab04618ee..f3baef23b 100644
--- a/ui/constants/action_types.js
+++ b/ui/constants/action_types.js
@@ -249,6 +249,7 @@ export const SYNC_CLIENT_SETTINGS = 'SYNC_CLIENT_SETTINGS';
export const DAEMON_STATUS_RECEIVED = 'DAEMON_STATUS_RECEIVED';
export const SHARED_PREFERENCE_SET = 'SHARED_PREFERENCE_SET';
export const SAVE_CUSTOM_WALLET_SERVERS = 'SAVE_CUSTOM_WALLET_SERVERS';
+export const SETTING_DAEMON_SETTINGS = 'SETTING_DAEMON_SETTINGS';
// User
export const AUTHENTICATION_STARTED = 'AUTHENTICATION_STARTED';
diff --git a/ui/constants/settings.js b/ui/constants/settings.js
index d73339b38..a56b28fc3 100644
--- a/ui/constants/settings.js
+++ b/ui/constants/settings.js
@@ -53,4 +53,5 @@ export const SETTINGS_GRP = {
ACCOUNT: 'account',
CONTENT: 'content',
SYSTEM: 'system',
+ STORAGE: 'Storage',
};
diff --git a/ui/page/settings/view.jsx b/ui/page/settings/view.jsx
index c3817fbe1..9d8ae3816 100644
--- a/ui/page/settings/view.jsx
+++ b/ui/page/settings/view.jsx
@@ -5,6 +5,7 @@ import SettingAccount from 'component/settingAccount';
import SettingAppearance from 'component/settingAppearance';
import SettingContent from 'component/settingContent';
import SettingSystem from 'component/settingSystem';
+import SettingStorage from 'component/settingStorage';
type DaemonSettings = {
download_dir: string,
@@ -51,6 +52,7 @@ class SettingsPage extends React.PureComponent
{
+
)}
diff --git a/ui/page/welcome/view.jsx b/ui/page/welcome/view.jsx
index 015f9db46..9885b404b 100644
--- a/ui/page/welcome/view.jsx
+++ b/ui/page/welcome/view.jsx
@@ -1,14 +1,17 @@
// @flow
import React from 'react';
import PrivacyAgreement from 'component/privacyAgreement';
+import HostingSplash from 'component/hostingSplash';
+import HostingSplashCustom from 'component/hostingSplashCustom';
import WelcomeSplash from 'component/welcomeSplash';
import Page from 'component/page';
import { useHistory } from 'react-router-dom';
const SPLASH_PAGE = 0;
const PRIVACY_PAGE = 1;
-// const HOSTING_PAGE = 2;
-// const WELCOME_PAGES = [SPLASH_PAGE, PRIVACY_PAGE];
+const HOSTING_PAGE = 2;
+const HOSTING_ADVANCED = 3;
+
type DaemonStatus = {
disk_space: {
content_blobs_storage_used_mb: string,
@@ -44,6 +47,16 @@ export default function Welcome(props: Props) {
const handleNextPage = () => {
if (welcomePage === SPLASH_PAGE) {
setWelcomePage(PRIVACY_PAGE);
+ } else if (welcomePage === PRIVACY_PAGE) {
+ setWelcomePage(HOSTING_PAGE);
+ } else if (welcomePage === HOSTING_PAGE) {
+ setWelcomePage(HOSTING_ADVANCED);
+ }
+ };
+
+ const handleGoBack = () => {
+ if (welcomePage >= 1) {
+ setWelcomePage(welcomePage - 1);
}
};
@@ -55,8 +68,11 @@ export default function Welcome(props: Props) {
return (
{welcomePage === SPLASH_PAGE && }
- {welcomePage === PRIVACY_PAGE && }
- {/* {welcomePage === HOSTING_PAGE && } */}
+ {welcomePage === PRIVACY_PAGE && }
+ {welcomePage === HOSTING_PAGE && }
+ {welcomePage === HOSTING_ADVANCED && (
+
+ )}
);
}
diff --git a/ui/redux/actions/comments.js b/ui/redux/actions/comments.js
index 009b0c244..375a96757 100644
--- a/ui/redux/actions/comments.js
+++ b/ui/redux/actions/comments.js
@@ -1197,7 +1197,7 @@ export function doFetchModBlockedList() {
if (blockedChannel.blocked_channel_name) {
const channelUri = buildURI({
channelName: blockedChannel.blocked_channel_name,
- claimId: blockedChannel.blocked_channel_id,
+ channelClaimId: blockedChannel.blocked_channel_id,
});
if (!blockedList.find((blockedChannel) => isURIEqual(blockedChannel.channelUri, channelUri))) {
@@ -1215,7 +1215,7 @@ export function doFetchModBlockedList() {
if (blockedByMap !== undefined) {
const blockedByChannelUri = buildURI({
channelName: blockedChannel.blocked_by_channel_name,
- claimId: blockedChannel.blocked_by_channel_id,
+ channelClaimId: blockedChannel.blocked_by_channel_id,
});
if (blockedByMap[channelUri]) {
diff --git a/ui/redux/actions/settings.js b/ui/redux/actions/settings.js
index bab4db508..aaf8b75dc 100644
--- a/ui/redux/actions/settings.js
+++ b/ui/redux/actions/settings.js
@@ -104,35 +104,59 @@ export function doSetDaemonSetting(key, value, doNotDispatch = false) {
return (dispatch, getState) => {
const state = getState();
const ready = selectPrefsReady(state);
-
if (!ready) {
return dispatch(doAlertWaitingForSync());
}
-
+ dispatch({
+ type: ACTIONS.SETTING_DAEMON_SETTINGS,
+ data: {
+ val: true,
+ },
+ });
const newSettings = {
key,
value: !value && value !== false ? null : value,
};
- Lbry.settings_set(newSettings).then((newSetting) => {
- if (SDK_SYNC_KEYS.includes(key) && !doNotDispatch) {
+ Lbry.settings_set(newSettings)
+ .then((newSetting) => {
+ if (SDK_SYNC_KEYS.includes(key) && !doNotDispatch) {
+ dispatch({
+ type: ACTIONS.SHARED_PREFERENCE_SET,
+ data: { key: key, value: newSetting[key] },
+ });
+ }
+ // hardcoding this in lieu of a better solution
+ if (key === DAEMON_SETTINGS.LBRYUM_SERVERS) {
+ dispatch(doWalletReconnect());
+ // todo: add sdk reloadsettings() (or it happens automagically?)
+ }
+ })
+ .then(() => {
+ dispatch(doFetchDaemonSettings());
+ })
+ .then(() => {
dispatch({
- type: ACTIONS.SHARED_PREFERENCE_SET,
- data: { key: key, value: newSetting[key] },
+ type: ACTIONS.SETTING_DAEMON_SETTINGS,
+ data: {
+ val: false,
+ },
});
- }
- // hardcoding this in lieu of a better solution
- if (key === DAEMON_SETTINGS.LBRYUM_SERVERS) {
- dispatch(doWalletReconnect());
- // todo: add sdk reloadsettings() (or it happens automagically?)
- }
- });
- dispatch(doFetchDaemonSettings());
+ })
+ .catch((e) => {
+ console.log('error setting or fetching daemon setting', e.message);
+ dispatch({
+ type: ACTIONS.SETTING_DAEMON_SETTINGS,
+ data: {
+ val: false,
+ },
+ });
+ });
};
}
export function doCleanBlobs() {
return (dispatch) => {
- Lbry.blob_clean().then(() => {
+ return Lbry.blob_clean().then(() => {
dispatch(doFetchDaemonSettings());
return 'done';
});
diff --git a/ui/redux/reducers/app.js b/ui/redux/reducers/app.js
index db8371ccf..6fbdde650 100644
--- a/ui/redux/reducers/app.js
+++ b/ui/redux/reducers/app.js
@@ -327,10 +327,9 @@ reducers[ACTIONS.DISK_SPACE] = (state, action) => {
};
reducers[ACTIONS.SYNC_STATE_POPULATE] = (state, action) => {
- const { welcomeVersion, allowAnalytics } = action.data;
+ const { allowAnalytics } = action.data;
return {
...state,
- ...(welcomeVersion !== undefined ? { welcomeVersion } : {}),
...(allowAnalytics !== undefined ? { allowAnalytics } : {}),
};
};
diff --git a/ui/redux/reducers/settings.js b/ui/redux/reducers/settings.js
index 7ceea70d3..36e6250db 100644
--- a/ui/redux/reducers/settings.js
+++ b/ui/redux/reducers/settings.js
@@ -14,6 +14,7 @@ settingLanguage.push('en');
const defaultState = {
isNight: false,
+ isSettingDaemonSettings: false,
findingFFmpeg: false,
loadedLanguages: [...Object.keys(window.i18n_messages), 'en'] || ['en'],
customWalletServers: [],
@@ -93,6 +94,11 @@ reducers[ACTIONS.FINDING_FFMPEG_STARTED] = (state) =>
findingFFmpeg: true,
});
+reducers[ACTIONS.SETTING_DAEMON_SETTINGS] = (state, action) =>
+ Object.assign({}, state, {
+ isSettingDaemonSettings: action.data.val,
+ });
+
reducers[ACTIONS.FINDING_FFMPEG_COMPLETED] = (state) =>
Object.assign({}, state, {
findingFFmpeg: false,
diff --git a/ui/redux/selectors/settings.js b/ui/redux/selectors/settings.js
index ea1900891..40527462e 100644
--- a/ui/redux/selectors/settings.js
+++ b/ui/redux/selectors/settings.js
@@ -9,10 +9,23 @@ const homepages = require('homepages');
const selectState = (state) => state.settings || {};
export const selectDaemonSettings = createSelector(selectState, (state) => state.daemonSettings);
+export const selectSettingDaemonSettings = createSelector(selectState, (state) => state.isSettingDaemonSettings);
export const selectDaemonStatus = createSelector(selectState, (state) => state.daemonStatus);
export const selectFfmpegStatus = createSelector(selectDaemonStatus, (status) => status.ffmpeg_status);
+export const selectViewBlobSpace = createSelector(
+ selectDaemonStatus,
+ (status) => status.disk_space.content_blobs_storage_used_mb
+);
+export const selectAutoBlobSpace = createSelector(
+ selectDaemonStatus,
+ (status) => status.disk_space.seed_blobs_storage_used_mb
+);
+export const selectPrivateBlobSpace = createSelector(
+ selectDaemonStatus,
+ (status) => status.disk_space.published_blobs_storage_used_mb
+);
export const selectFindingFFmpeg = createSelector(selectState, (state) => state.findingFFmpeg || false);
@@ -82,6 +95,10 @@ export const selectHomepageData = createSelector(
}
);
+export const selectSaveBlobs = createSelector(selectDaemonSettings, (state) => state.save_blobs || false);
+export const selectAutoHostingLimit = createSelector(selectDaemonSettings, (state) => state.network_storage_limit || 0);
+export const selectViewHostingLimit = createSelector(selectDaemonSettings, (state) => state.blob_storage_limit || 0);
+
export const selectosNotificationsEnabled = makeSelectClientSetting(SETTINGS.OS_NOTIFICATIONS_ENABLED);
export const selectDisableAutoUpdates = makeSelectClientSetting(SETTINGS.DISABLE_AUTO_UPDATES);
diff --git a/ui/scss/component/_form-field.scss b/ui/scss/component/_form-field.scss
index c68788a80..0f5d9e8dd 100644
--- a/ui/scss/component/_form-field.scss
+++ b/ui/scss/component/_form-field.scss
@@ -172,17 +172,28 @@ input-submit {
margin: 0;
}
+ & > *:not(:first-child):not(:last-child) {
+ border-top-right-radius: 0;
+ border-bottom-right-radius: 0;
+ border-top-left-radius: 0;
+ border-bottom-left-radius: 0;
+ }
+
& > *:first-child {
border-top-right-radius: 0;
border-bottom-right-radius: 0;
+ border-top-left-radius: var(--border-radius);
+ border-bottom-left-radius: var(--border-radius);
+ border-right: none;
+ }
+
+ & > *:last-child {
+ border-top-left-radius: 0;
+ border-bottom-left-radius: 0;
+ border-top-right-radius: var(--border-radius);
+ border-bottom-right-radius: var(--border-radius);
border-right: none;
}
- // FIX THIS e.g. copyable text vs editable text
- //& > *:nth-child(2) {
- // border-top-left-radius: 0;
- // border-bottom-left-radius: 0;
- // border: 1px solid var(--color-input-border);
- //}
}
.checkbox,
diff --git a/ui/scss/component/_main.scss b/ui/scss/component/_main.scss
index d96af4dca..5528c020a 100644
--- a/ui/scss/component/_main.scss
+++ b/ui/scss/component/_main.scss
@@ -548,6 +548,7 @@ body {
border-top: unset;
.settings__row {
+ align-items: flex-start;
padding: var(--spacing-s);
border-bottom: 1px solid var(--color-border);
.checkbox {
diff --git a/ui/scss/component/_settings.scss b/ui/scss/component/_settings.scss
index 63f763ef3..b9d9e3412 100644
--- a/ui/scss/component/_settings.scss
+++ b/ui/scss/component/_settings.scss
@@ -29,3 +29,95 @@
text-align: right;
padding-top: var(--spacing-m);
}
+
+.storage__wrapper {
+ .storage__bar {
+ bottom: 0;
+ left: 0;
+ width: 100%;
+ height: var(--spacing-xl);
+ background-color: var(--color-storage-free);
+ display: flex;
+ flex-direction: row;
+ justify-content: flex-start;
+ border-radius: var(--border-radius);
+ > :last-of-type {
+ border-bottom-right-radius: var(--border-radius);
+ border-top-right-radius: var(--border-radius);
+ }
+ .storage__other {
+ height: 100%;
+ background-color: var(--color-gray-7);
+ border-top-left-radius: var(--border-radius);
+ border-bottom-left-radius: var(--border-radius);
+ }
+ .storage__private {
+ height: 100%;
+ background-color: var(--color-storage-published);
+ }
+ box-sizing: border-box;
+
+ .storage__viewed {
+ height: 100%;
+ background-color: var(--color-storage-viewed-free);
+ border: var(--color-storage-viewed) 1px solid;
+ box-sizing: border-box;
+ .storage__viewed--used {
+ height: 100%;
+ background-color: var(--color-storage-viewed);
+ box-sizing: border-box;
+ }
+ }
+
+ .storage__auto {
+ height: 100%;
+ background-color: var(--color-storage-auto-free);
+ border: var(--color-storage-auto) 1px solid;
+ box-sizing: border-box;
+ .storage__auto--used {
+ height: 100%;
+ background-color: var(--color-storage-auto);
+ }
+ }
+ }
+ .storage__legend-wrapper {
+ margin-top: var(--spacing-m);
+ display: flex;
+ flex-direction: row;
+ justify-content: flex-start;
+ .storage__legend-item {
+ display: flex;
+ flex-direction: row;
+ .storage__legend-item-swatch {
+ padding: var(--spacing-xs);
+ margin-right: var(--spacing-s);
+ width: var(--spacing-l);
+ border-radius: var(--border-radius);
+ }
+ .storage__legend-item-label {
+ margin-right: var(--spacing-m);
+ display: flex;
+ flex-direction: column;
+ .help {
+ margin-top: 0;
+ }
+ }
+
+ .storage__legend-item-swatch--other {
+ background-color: var(--color-gray-7);
+ }
+ .storage__legend-item-swatch--private {
+ background-color: var(--color-storage-published);
+ }
+ .storage__legend-item-swatch--viewed {
+ background-color: var(--color-storage-viewed);
+ }
+ .storage__legend-item-swatch--auto {
+ background-color: var(--color-storage-auto);
+ }
+ .storage__legend-item-swatch--free {
+ background-color: var(--color-storage-free);
+ }
+ }
+ }
+}
diff --git a/ui/scss/component/section.scss b/ui/scss/component/section.scss
index 951d2cf6f..332ca7e9e 100644
--- a/ui/scss/component/section.scss
+++ b/ui/scss/component/section.scss
@@ -293,6 +293,9 @@
&:only-child {
border-top: none;
}
+ &.section__actions--between {
+ align-items: flex-start;
+ }
}
.card__main-actions.settings__row {
@@ -301,6 +304,7 @@
margin-top: 0;
margin-bottom: 0;
}
+ padding-bottom: var(--spacing-m);
}
.settings__row--title {
diff --git a/ui/scss/init/_gui.scss b/ui/scss/init/_gui.scss
index 41140c433..1676015d1 100644
--- a/ui/scss/init/_gui.scss
+++ b/ui/scss/init/_gui.scss
@@ -901,23 +901,6 @@ img {
}
}
-.scheduledLivestream-wrapper {
- @media (max-width: $breakpoint-small) {
- padding: var(--spacing-s);
- padding-top: 0;
-
- .card__main-actions {
- .claim-preview__wrapper {
- a {
- .button__content {
- align-items: unset;
- }
- }
- }
- }
- }
-}
-
// Temporary master classes
.date_time {
font-size: var(--font-xsmall);
diff --git a/ui/scss/themes/dark.scss b/ui/scss/themes/dark.scss
index 6aa238753..20fe82b5f 100644
--- a/ui/scss/themes/dark.scss
+++ b/ui/scss/themes/dark.scss
@@ -200,4 +200,13 @@
radial-gradient(circle at 50% 117%, rgba(25, 25, 25, 0.2) 0, #202020 100%);
--mui-background: #000;
+
+ // storage vis
+ --color-storage-published: var(--color-brand-blue);
+ --color-storage-free: var(--color-gray-4);
+ --color-storage-used: var(--color-gray-7);
+ --color-storage-auto: #ff993c;
+ --color-storage-auto-free: #9f5f25;
+ --color-storage-viewed: #a93cff;
+ --color-storage-viewed-free: #602192;
}
diff --git a/ui/scss/themes/light.scss b/ui/scss/themes/light.scss
index 2eb6d81a6..67b79ac6c 100644
--- a/ui/scss/themes/light.scss
+++ b/ui/scss/themes/light.scss
@@ -83,7 +83,7 @@
// Input
--color-input-bg-selected: var(--color-primary-alt);
--color-input-color: #111111;
- --color-input-label: var(--color-gray-5);
+ --color-input-label: var(--color-text-base);
--color-input-placeholder: #212529;
--color-input-bg: var(--color-white);
--color-input-border: var(--color-border);
@@ -206,4 +206,13 @@
--mui-background: #fff;
--mui-button: var(--color-header-button);
+
+ // Storage vis
+ --color-storage-published: var(--color-brand-blue);
+ --color-storage-free: var(--color-gray-4);
+ --color-storage-used: var(--color-gray-7);
+ --color-storage-auto: #ff993c;
+ --color-storage-auto-free: #9f5f25;
+ --color-storage-viewed: #a93cff;
+ --color-storage-viewed-free: #602192;
}
diff --git a/ui/util/hosting.js b/ui/util/hosting.js
new file mode 100644
index 000000000..f09f361bc
--- /dev/null
+++ b/ui/util/hosting.js
@@ -0,0 +1,8 @@
+export function convertGbToMbStr(gb) {
+ return String(Number(gb) * 1024);
+}
+
+export function isValidHostingAmount(amountString) {
+ const numberAmount = Number(amountString);
+ return amountString.length && ((numberAmount && String(numberAmount)) || numberAmount === 0);
+}