Merge pull request #2647 from lbryio/support-option
feat: experimental support option
This commit is contained in:
commit
f94cef0cee
16 changed files with 91 additions and 25 deletions
|
@ -124,7 +124,7 @@
|
||||||
"jsmediatags": "^3.8.1",
|
"jsmediatags": "^3.8.1",
|
||||||
"json-loader": "^0.5.4",
|
"json-loader": "^0.5.4",
|
||||||
"lbry-format": "https://github.com/lbryio/lbry-format.git",
|
"lbry-format": "https://github.com/lbryio/lbry-format.git",
|
||||||
"lbry-redux": "lbryio/lbry-redux#5080eb3ea1f09ce03c4f50d9224feddf737628d3",
|
"lbry-redux": "lbryio/lbry-redux#e550f0ff0448fcad5f3d0365ec7ad744376b41cf",
|
||||||
"lbryinc": "lbryio/lbryinc#a93596c51c8fb0a226cb84df04c26a6bb60a45fb",
|
"lbryinc": "lbryio/lbryinc#a93596c51c8fb0a226cb84df04c26a6bb60a45fb",
|
||||||
"lint-staged": "^7.0.2",
|
"lint-staged": "^7.0.2",
|
||||||
"localforage": "^1.7.1",
|
"localforage": "^1.7.1",
|
||||||
|
|
|
@ -41,7 +41,7 @@ class RewardSummary extends React.Component<Props> {
|
||||||
navigate="/$/rewards"
|
navigate="/$/rewards"
|
||||||
label={hasRewards ? __('Claim Rewards') : __('View Rewards')}
|
label={hasRewards ? __('Claim Rewards') : __('View Rewards')}
|
||||||
/>
|
/>
|
||||||
<Button button="link" label={__('Learn more')} href="https://lbry.com/faq/rewards" />
|
<Button button="link" label={__('Learn more')} href="https://lbry.com/faq/rewards" />.
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
|
@ -92,7 +92,10 @@ export default function TagSelect(props: Props) {
|
||||||
</ul>
|
</ul>
|
||||||
<TagsSearch onSelect={onSelect} suggestMature={suggestMature && !hasMatureTag} />
|
<TagsSearch onSelect={onSelect} suggestMature={suggestMature && !hasMatureTag} />
|
||||||
{help !== false && (
|
{help !== false && (
|
||||||
<p className="help">{help || __("The tags you follow will change what's trending for you.")}</p>
|
<p className="help">
|
||||||
|
{help || __("The tags you follow will change what's trending for you. ")}
|
||||||
|
<Button button="link" label={__('Learn more')} href="https://lbry.com/faq/trending" />.
|
||||||
|
</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -16,7 +16,7 @@ const select = (state, props) => ({
|
||||||
});
|
});
|
||||||
|
|
||||||
const perform = dispatch => ({
|
const perform = dispatch => ({
|
||||||
sendSupport: (amount, claimId, uri) => dispatch(doSendTip(amount, claimId, uri)),
|
sendSupport: (amount, claimId) => dispatch(doSendTip(amount, claimId)),
|
||||||
});
|
});
|
||||||
|
|
||||||
export default connect(
|
export default connect(
|
||||||
|
|
|
@ -13,6 +13,7 @@ type Props = {
|
||||||
onCancel: () => void,
|
onCancel: () => void,
|
||||||
sendTipCallback?: () => void,
|
sendTipCallback?: () => void,
|
||||||
balance: number,
|
balance: number,
|
||||||
|
isSupport: boolean,
|
||||||
};
|
};
|
||||||
|
|
||||||
type State = {
|
type State = {
|
||||||
|
@ -32,11 +33,11 @@ class WalletSendTip extends React.PureComponent<Props, State> {
|
||||||
}
|
}
|
||||||
|
|
||||||
handleSendButtonClicked() {
|
handleSendButtonClicked() {
|
||||||
const { claim, uri, sendSupport, sendTipCallback } = this.props;
|
const { claim, sendSupport, sendTipCallback } = this.props;
|
||||||
const { claim_id: claimId } = claim;
|
const { claim_id: claimId } = claim;
|
||||||
const { tipAmount } = this.state;
|
const { tipAmount } = this.state;
|
||||||
|
|
||||||
sendSupport(tipAmount, claimId, uri);
|
sendSupport(tipAmount, claimId);
|
||||||
|
|
||||||
// ex: close modal
|
// ex: close modal
|
||||||
if (sendTipCallback) {
|
if (sendTipCallback) {
|
||||||
|
@ -70,7 +71,7 @@ class WalletSendTip extends React.PureComponent<Props, State> {
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { title, isPending, uri, onCancel, claimIsMine } = this.props;
|
const { title, isPending, uri, onCancel, claimIsMine, isSupport } = this.props;
|
||||||
const { tipAmount, tipError } = this.state;
|
const { tipAmount, tipError } = this.state;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -99,11 +100,11 @@ class WalletSendTip extends React.PureComponent<Props, State> {
|
||||||
}
|
}
|
||||||
helper={
|
helper={
|
||||||
<p>
|
<p>
|
||||||
{claimIsMine
|
{claimIsMine || isSupport
|
||||||
? __('This will increase your overall bid amount for ')
|
? __('This will increase the overall bid amount for ')
|
||||||
: __('This will appear as a tip for ')}
|
: __('This will appear as a tip for ')}
|
||||||
{`"${title}" which will boost its ability to be discovered while active.`}{' '}
|
{`"${title}" which will boost its ability to be discovered while active.`}{' '}
|
||||||
<Button label={__('Learn more')} button="link" href="https://lbry.com/faq/tipping" />
|
<Button label={__('Learn more')} button="link" href="https://lbry.com/faq/tipping" />.
|
||||||
</p>
|
</p>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -16,3 +16,4 @@ export const AUTOPLAY = 'autoplay';
|
||||||
export const RESULT_COUNT = 'resultCount';
|
export const RESULT_COUNT = 'resultCount';
|
||||||
export const OS_NOTIFICATIONS_ENABLED = 'osNotificationsEnabled';
|
export const OS_NOTIFICATIONS_ENABLED = 'osNotificationsEnabled';
|
||||||
export const AUTO_DOWNLOAD = 'autoDownload';
|
export const AUTO_DOWNLOAD = 'autoDownload';
|
||||||
|
export const SUPPORT_OPTION = 'supportOption';
|
||||||
|
|
|
@ -21,6 +21,8 @@ class ModalRevokeClaim extends React.PureComponent<Props> {
|
||||||
getButtonLabel(type: string) {
|
getButtonLabel(type: string) {
|
||||||
if (type === txnTypes.TIP) {
|
if (type === txnTypes.TIP) {
|
||||||
return 'Confirm Tip Unlock';
|
return 'Confirm Tip Unlock';
|
||||||
|
} else if (type === txnTypes.SUPPORT) {
|
||||||
|
return 'Confirm Support Revoke';
|
||||||
}
|
}
|
||||||
return 'Confirm Claim Revoke';
|
return 'Confirm Claim Revoke';
|
||||||
}
|
}
|
||||||
|
@ -75,8 +77,8 @@ class ModalRevokeClaim extends React.PureComponent<Props> {
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
isOpen
|
isOpen
|
||||||
title={type === txnTypes.TIP ? __('Confirm Tip Unlock') : __('Confirm Claim Revoke')}
|
title={this.getButtonLabel(type)}
|
||||||
contentLabel={__('Confirm Claim Revoke')}
|
contentLabel={this.getButtonLabel(type)}
|
||||||
type="confirm"
|
type="confirm"
|
||||||
confirmButtonLabel={this.getButtonLabel(type)}
|
confirmButtonLabel={this.getButtonLabel(type)}
|
||||||
onConfirmed={this.revokeClaim}
|
onConfirmed={this.revokeClaim}
|
||||||
|
|
|
@ -8,11 +8,12 @@ type Props = {
|
||||||
closeModal: () => void,
|
closeModal: () => void,
|
||||||
uri: string,
|
uri: string,
|
||||||
claimIsMine: boolean,
|
claimIsMine: boolean,
|
||||||
|
isSupport: boolean,
|
||||||
};
|
};
|
||||||
|
|
||||||
class ModalSendTip extends React.PureComponent<Props> {
|
class ModalSendTip extends React.PureComponent<Props> {
|
||||||
render() {
|
render() {
|
||||||
const { closeModal, uri, claimIsMine } = this.props;
|
const { closeModal, uri, claimIsMine, isSupport } = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
|
@ -21,11 +22,23 @@ class ModalSendTip extends React.PureComponent<Props> {
|
||||||
type="custom"
|
type="custom"
|
||||||
title={
|
title={
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
{claimIsMine ? __('Add support to') : __('Send a tip')} <UriIndicator uri={uri} />
|
{claimIsMine || isSupport ? (
|
||||||
|
__('Add support to this claim')
|
||||||
|
) : (
|
||||||
|
<span>
|
||||||
|
{__('Send a tip to')} <UriIndicator uri={uri} />
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<SendTip uri={uri} claimIsMine={claimIsMine} onCancel={closeModal} sendTipCallback={closeModal} />
|
<SendTip
|
||||||
|
uri={uri}
|
||||||
|
claimIsMine={claimIsMine}
|
||||||
|
isSupport={isSupport}
|
||||||
|
onCancel={closeModal}
|
||||||
|
sendTipCallback={closeModal}
|
||||||
|
/>
|
||||||
</Modal>
|
</Modal>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -42,6 +42,7 @@ const select = (state, props) => ({
|
||||||
title: makeSelectTitleForUri(props.uri)(state),
|
title: makeSelectTitleForUri(props.uri)(state),
|
||||||
thumbnail: makeSelectThumbnailForUri(props.uri)(state),
|
thumbnail: makeSelectThumbnailForUri(props.uri)(state),
|
||||||
nsfw: makeSelectClaimIsNsfw(props.uri)(state),
|
nsfw: makeSelectClaimIsNsfw(props.uri)(state),
|
||||||
|
supportOption: makeSelectClientSetting(settings.SUPPORT_OPTION)(state),
|
||||||
});
|
});
|
||||||
|
|
||||||
const perform = dispatch => ({
|
const perform = dispatch => ({
|
||||||
|
|
|
@ -41,13 +41,14 @@ type Props = {
|
||||||
channelUri: string,
|
channelUri: string,
|
||||||
viewCount: number,
|
viewCount: number,
|
||||||
prepareEdit: ({}, string, {}) => void,
|
prepareEdit: ({}, string, {}) => void,
|
||||||
openModal: (id: string, { uri: string }) => void,
|
openModal: (id: string, { uri: string, claimIsMine: boolean, isSupport: boolean }) => void,
|
||||||
markSubscriptionRead: (string, string) => void,
|
markSubscriptionRead: (string, string) => void,
|
||||||
fetchViewCount: string => void,
|
fetchViewCount: string => void,
|
||||||
balance: number,
|
balance: number,
|
||||||
title: string,
|
title: string,
|
||||||
thumbnail: ?string,
|
thumbnail: ?string,
|
||||||
nsfw: boolean,
|
nsfw: boolean,
|
||||||
|
supportOption: boolean,
|
||||||
};
|
};
|
||||||
|
|
||||||
class FilePage extends React.Component<Props> {
|
class FilePage extends React.Component<Props> {
|
||||||
|
@ -149,6 +150,7 @@ class FilePage extends React.Component<Props> {
|
||||||
title,
|
title,
|
||||||
thumbnail,
|
thumbnail,
|
||||||
nsfw,
|
nsfw,
|
||||||
|
supportOption,
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
// File info
|
// File info
|
||||||
|
@ -246,9 +248,17 @@ class FilePage extends React.Component<Props> {
|
||||||
<Button
|
<Button
|
||||||
button="alt"
|
button="alt"
|
||||||
icon={claimIsMine ? icons.SUPPORT : icons.TIP}
|
icon={claimIsMine ? icons.SUPPORT : icons.TIP}
|
||||||
label={claimIsMine ? __('Add support') : __('Send a tip')}
|
label={claimIsMine ? __('Support') : __('Tip')}
|
||||||
onClick={() => openModal(MODALS.SEND_TIP, { uri, claimIsMine })}
|
onClick={() => openModal(MODALS.SEND_TIP, { uri, claimIsMine, isSupport: false })}
|
||||||
/>
|
/>
|
||||||
|
{!claimIsMine && supportOption && (
|
||||||
|
<Button
|
||||||
|
button="alt"
|
||||||
|
icon={icons.SUPPORT}
|
||||||
|
label={__('Support')}
|
||||||
|
onClick={() => openModal(MODALS.SEND_TIP, { uri, claimIsMine, isSupport: true })}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
}
|
}
|
||||||
<Button
|
<Button
|
||||||
|
|
|
@ -40,7 +40,7 @@ class RewardsPage extends PureComponent<Props> {
|
||||||
{__(
|
{__(
|
||||||
'This step is optional. You can continue to use this app without rewards, but LBC may be needed for some tasks.'
|
'This step is optional. You can continue to use this app without rewards, but LBC may be needed for some tasks.'
|
||||||
)}{' '}
|
)}{' '}
|
||||||
<Button button="link" label={__('Learn more')} href="https://lbry.com/faq/rewards" />
|
<Button button="link" label={__('Learn more')} href="https://lbry.com/faq/rewards" />.
|
||||||
</p>
|
</p>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
|
|
|
@ -25,6 +25,7 @@ const select = state => ({
|
||||||
walletEncrypted: selectWalletIsEncrypted(state),
|
walletEncrypted: selectWalletIsEncrypted(state),
|
||||||
osNotificationsEnabled: selectosNotificationsEnabled(state),
|
osNotificationsEnabled: selectosNotificationsEnabled(state),
|
||||||
autoDownload: makeSelectClientSetting(settings.AUTO_DOWNLOAD)(state),
|
autoDownload: makeSelectClientSetting(settings.AUTO_DOWNLOAD)(state),
|
||||||
|
supportOption: makeSelectClientSetting(settings.SUPPORT_OPTION)(state),
|
||||||
});
|
});
|
||||||
|
|
||||||
const perform = dispatch => ({
|
const perform = dispatch => ({
|
||||||
|
|
|
@ -44,6 +44,7 @@ type Props = {
|
||||||
updateWalletStatus: () => void,
|
updateWalletStatus: () => void,
|
||||||
walletEncrypted: boolean,
|
walletEncrypted: boolean,
|
||||||
osNotificationsEnabled: boolean,
|
osNotificationsEnabled: boolean,
|
||||||
|
supportOption: boolean,
|
||||||
};
|
};
|
||||||
|
|
||||||
type State = {
|
type State = {
|
||||||
|
@ -150,6 +151,7 @@ class SettingsPage extends React.PureComponent<Props, State> {
|
||||||
autoDownload,
|
autoDownload,
|
||||||
setDaemonSetting,
|
setDaemonSetting,
|
||||||
setClientSetting,
|
setClientSetting,
|
||||||
|
supportOption,
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
const noDaemonSettings = !daemonSettings || Object.keys(daemonSettings).length === 0;
|
const noDaemonSettings = !daemonSettings || Object.keys(daemonSettings).length === 0;
|
||||||
|
@ -276,9 +278,9 @@ class SettingsPage extends React.PureComponent<Props, State> {
|
||||||
name="show_nsfw"
|
name="show_nsfw"
|
||||||
onChange={() => setClientSetting(SETTINGS.SHOW_NSFW, !showNsfw)}
|
onChange={() => setClientSetting(SETTINGS.SHOW_NSFW, !showNsfw)}
|
||||||
checked={showNsfw}
|
checked={showNsfw}
|
||||||
label={__('Show NSFW content')}
|
label={__('Show mature content')}
|
||||||
helper={__(
|
helper={__(
|
||||||
'NSFW content may include nudity, intense sexuality, profanity, or other adult content. By displaying NSFW content, you are affirming you are of legal age to view mature content in your country or jurisdiction. '
|
'Mature content may include nudity, intense sexuality, profanity, or other adult content. By displaying mature content, you are affirming you are of legal age to view mature content in your country or jurisdiction. '
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</Form>
|
</Form>
|
||||||
|
@ -368,7 +370,7 @@ class SettingsPage extends React.PureComponent<Props, State> {
|
||||||
name="encrypt_wallet"
|
name="encrypt_wallet"
|
||||||
onChange={() => this.onChangeEncryptWallet()}
|
onChange={() => this.onChangeEncryptWallet()}
|
||||||
checked={walletEncrypted}
|
checked={walletEncrypted}
|
||||||
label={__('Encrypt my wallet with a custom password.')}
|
label={__('Encrypt my wallet with a custom password')}
|
||||||
helper={
|
helper={
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
{__('Secure your local wallet data with a custom password.')}{' '}
|
{__('Secure your local wallet data with a custom password.')}{' '}
|
||||||
|
@ -386,6 +388,23 @@ class SettingsPage extends React.PureComponent<Props, State> {
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<Form className="card__content">
|
<Form className="card__content">
|
||||||
|
<FormField
|
||||||
|
type="setting"
|
||||||
|
name="support_option"
|
||||||
|
onChange={() => setClientSetting(SETTINGS.SUPPORT_OPTION, !supportOption)}
|
||||||
|
checked={supportOption}
|
||||||
|
label={__('Enable claim support')}
|
||||||
|
helper={
|
||||||
|
<React.Fragment>
|
||||||
|
{__('This will add a Support button along side tipping. Similar to tips, supports help ')}
|
||||||
|
<Button button="link" label={__(' discovery ')} href="https://lbry.com/faq/trending" />
|
||||||
|
{__(' but the LBC is returned to your wallet if revoked.')}
|
||||||
|
{__(' Both also help secure ')}
|
||||||
|
<Button button="link" label={__('vanity names')} href="https://lbry.com/faq/naming" />.
|
||||||
|
</React.Fragment>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
|
||||||
<FormField
|
<FormField
|
||||||
type="setting"
|
type="setting"
|
||||||
name="auto_download"
|
name="auto_download"
|
||||||
|
|
|
@ -24,6 +24,7 @@ const defaultState = {
|
||||||
[SETTINGS.THEME]: getLocalStorageSetting(SETTINGS.THEME, 'light'),
|
[SETTINGS.THEME]: getLocalStorageSetting(SETTINGS.THEME, 'light'),
|
||||||
[SETTINGS.THEMES]: getLocalStorageSetting(SETTINGS.THEMES, []),
|
[SETTINGS.THEMES]: getLocalStorageSetting(SETTINGS.THEMES, []),
|
||||||
[SETTINGS.AUTOMATIC_DARK_MODE_ENABLED]: getLocalStorageSetting(SETTINGS.AUTOMATIC_DARK_MODE_ENABLED, false),
|
[SETTINGS.AUTOMATIC_DARK_MODE_ENABLED]: getLocalStorageSetting(SETTINGS.AUTOMATIC_DARK_MODE_ENABLED, false),
|
||||||
|
[SETTINGS.SUPPORT_OPTION]: getLocalStorageSetting(SETTINGS.SUPPORT_OPTION, false),
|
||||||
[SETTINGS.AUTOPLAY]: getLocalStorageSetting(SETTINGS.AUTOPLAY, false),
|
[SETTINGS.AUTOPLAY]: getLocalStorageSetting(SETTINGS.AUTOPLAY, false),
|
||||||
[SETTINGS.RESULT_COUNT]: Number(getLocalStorageSetting(SETTINGS.RESULT_COUNT, 50)),
|
[SETTINGS.RESULT_COUNT]: Number(getLocalStorageSetting(SETTINGS.RESULT_COUNT, 50)),
|
||||||
[SETTINGS.AUTO_DOWNLOAD]: getLocalStorageSetting(SETTINGS.AUTO_DOWNLOAD, true),
|
[SETTINGS.AUTO_DOWNLOAD]: getLocalStorageSetting(SETTINGS.AUTO_DOWNLOAD, true),
|
||||||
|
|
|
@ -578,5 +578,19 @@
|
||||||
"Invalid character %s in name: %s.": "Invalid character %s in name: %s.",
|
"Invalid character %s in name: %s.": "Invalid character %s in name: %s.",
|
||||||
"The better your tags are, the easier it will be for people to discover your channel.": "The better your tags are, the easier it will be for people to discover your channel.",
|
"The better your tags are, the easier it will be for people to discover your channel.": "The better your tags are, the easier it will be for people to discover your channel.",
|
||||||
"Thumbnail (300 x 300)": "Thumbnail (300 x 300)",
|
"Thumbnail (300 x 300)": "Thumbnail (300 x 300)",
|
||||||
"Cover (1000 x 160)": "Cover (1000 x 160)"
|
"Cover (1000 x 160)": "Cover (1000 x 160)",
|
||||||
|
"The tags you follow will change what's trending for you. ": "The tags you follow will change what's trending for you. ",
|
||||||
|
"Mature": "Mature",
|
||||||
|
"This will increase the overall bid amount for ": "This will increase the overall bid amount for ",
|
||||||
|
"This will appear as a tip for ": "This will appear as a tip for ",
|
||||||
|
"Show mature content": "Show mature content",
|
||||||
|
"Mature content may include nudity, intense sexuality, profanity, or other adult content. By displaying mature content, you are affirming you are of legal age to view mature content in your country or jurisdiction. ": "Mature content may include nudity, intense sexuality, profanity, or other adult content. By displaying mature content, you are affirming you are of legal age to view mature content in your country or jurisdiction. ",
|
||||||
|
"Encrypt my wallet with a custom password": "Encrypt my wallet with a custom password",
|
||||||
|
"Enable claim support": "Enable claim support",
|
||||||
|
"This will add a Support button along side tipping. Similar to tips, supports help ": "This will add a Support button along side tipping. Similar to tips, supports help ",
|
||||||
|
" discovery ": " discovery ",
|
||||||
|
" but the LBC is returned to your wallet if revoked.": " but the LBC is returned to your wallet if revoked.",
|
||||||
|
" Both also help secure ": " Both also help secure ",
|
||||||
|
"vanity names": "vanity names",
|
||||||
|
"Add support to this claim": "Add support to this claim"
|
||||||
}
|
}
|
|
@ -6653,9 +6653,9 @@ lazy-val@^1.0.3, lazy-val@^1.0.4:
|
||||||
yargs "^13.2.2"
|
yargs "^13.2.2"
|
||||||
zstd-codec "^0.1.1"
|
zstd-codec "^0.1.1"
|
||||||
|
|
||||||
lbry-redux@lbryio/lbry-redux#5080eb3ea1f09ce03c4f50d9224feddf737628d3:
|
lbry-redux@lbryio/lbry-redux#e550f0ff0448fcad5f3d0365ec7ad744376b41cf:
|
||||||
version "0.0.1"
|
version "0.0.1"
|
||||||
resolved "https://codeload.github.com/lbryio/lbry-redux/tar.gz/5080eb3ea1f09ce03c4f50d9224feddf737628d3"
|
resolved "https://codeload.github.com/lbryio/lbry-redux/tar.gz/e550f0ff0448fcad5f3d0365ec7ad744376b41cf"
|
||||||
dependencies:
|
dependencies:
|
||||||
proxy-polyfill "0.1.6"
|
proxy-polyfill "0.1.6"
|
||||||
reselect "^3.0.0"
|
reselect "^3.0.0"
|
||||||
|
|
Loading…
Add table
Reference in a new issue