Add announcement modal and open it after prefs sync'd.

- Don't want to show it in Incognito.
- Only show it in when entered from homepage, or in the Help page.
- Record the hash of the viewed announcement and update the wallet with it.
This commit is contained in:
infinite-persistence 2022-05-18 17:13:14 +08:00
parent 79eb28cc55
commit 3719a73c81
No known key found for this signature in database
GPG key ID: B9C3252EDC3D0AA0
9 changed files with 168 additions and 18 deletions

View file

@ -1,6 +1,11 @@
import { hot } from 'react-hot-loader/root';
import { connect } from 'react-redux';
import { selectGetSyncErrorMessage, selectSyncFatalError, selectSyncIsLocked } from 'redux/selectors/sync';
import {
selectGetSyncErrorMessage,
selectPrefsReady,
selectSyncFatalError,
selectSyncIsLocked,
} from 'redux/selectors/sync';
import { doUserSetReferrer } from 'redux/actions/user';
import {
selectOdyseeMembershipIsPremiumPlus,
@ -15,7 +20,7 @@ import { selectMyChannelClaimIds } from 'redux/selectors/claims';
import { selectLanguage, selectLoadedLanguages, selectThemePath } from 'redux/selectors/settings';
import { selectModal, selectActiveChannelClaim, selectIsReloadRequired } from 'redux/selectors/app';
import { selectUploadCount } from 'redux/selectors/publish';
import { doSetLanguage } from 'redux/actions/settings';
import { doOpenAnnouncements, doSetLanguage } from 'redux/actions/settings';
import { doSyncLoop } from 'redux/actions/sync';
import { doSignIn, doSetIncognito } from 'redux/actions/app';
import { doFetchModBlockedList, doFetchCommentModAmIList } from 'redux/actions/comments';
@ -28,6 +33,7 @@ const select = (state) => ({
language: selectLanguage(state),
languages: selectLoadedLanguages(state),
isReloadRequired: selectIsReloadRequired(state),
prefsReady: selectPrefsReady(state),
syncError: selectGetSyncErrorMessage(state),
syncIsLocked: selectSyncIsLocked(state),
uploadCount: selectUploadCount(state),
@ -51,6 +57,7 @@ const perform = {
setIncognito: doSetIncognito,
fetchModBlockedList: doFetchModBlockedList,
fetchModAmIList: doFetchCommentModAmIList,
doOpenAnnouncements,
};
export default hot(connect(select, perform)(App));

View file

@ -74,6 +74,7 @@ type Props = {
balance: ?number,
syncIsLocked: boolean,
syncError: ?string,
prefsReady: boolean,
rewards: Array<Reward>,
setReferrer: (string, boolean) => void,
isAuthenticated: boolean,
@ -87,6 +88,7 @@ type Props = {
fetchModBlockedList: () => void,
fetchModAmIList: () => void,
homepageFetched: boolean,
doOpenAnnouncements: () => void,
};
function App(props: Props) {
@ -103,6 +105,7 @@ function App(props: Props) {
history,
syncError,
syncIsLocked,
prefsReady,
language,
languages,
setLanguage,
@ -119,6 +122,7 @@ function App(props: Props) {
hasPremiumPlus,
fetchModAmIList,
homepageFetched,
doOpenAnnouncements,
} = props;
const isMobile = useIsMobile();
@ -438,7 +442,7 @@ function App(props: Props) {
// eslint-disable-next-line react-hooks/exhaustive-deps, (one time after locale is fetched)
}, [locale]);
React.useEffect(() => {
useEffect(() => {
if (locale) {
const countryCode = locale.country;
const langs = getLanguagesForCountry(countryCode) || [];
@ -471,6 +475,12 @@ function App(props: Props) {
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [syncError, pathname, isAuthenticated]);
useEffect(() => {
if (prefsReady) {
doOpenAnnouncements();
}
}, [prefsReady]);
// Keep this at the end to ensure initial setup effects are run first
useEffect(() => {
if (!hasSignedIn && hasVerifiedEmail) {

View file

@ -51,3 +51,4 @@ export const CONFIRM_REMOVE_COMMENT = 'CONFIRM_REMOVE_COMMENT';
export const CONFIRM_ODYSEE_MEMBERSHIP = 'CONFIRM_ODYSEE_MEMBERSHIP';
export const MEMBERSHIP_SPLASH = 'MEMBERSHIP_SPLASH';
export const HIDE_RECOMMENDATION = 'HIDE_RECOMMENDATION';
export const ANNOUNCEMENTS = 'ANNOUNCEMENTS';

View file

@ -0,0 +1,20 @@
import ModalAnnouncements from './view';
import { connect } from 'react-redux';
import { doHideModal } from 'redux/actions/app';
import { doSetLastViewedAnnouncement } from 'redux/actions/content';
import { selectLastViewedAnnouncement } from 'redux/selectors/content';
import { selectHomepageAnnouncement } from 'redux/selectors/settings';
import { selectUserVerifiedEmail } from 'redux/selectors/user';
const select = (state) => ({
authenticated: selectUserVerifiedEmail(state),
announcement: selectHomepageAnnouncement(state),
lastViewedHash: selectLastViewedAnnouncement(state),
});
const perform = {
doHideModal,
doSetLastViewedAnnouncement,
};
export default connect(select, perform)(ModalAnnouncements);

View file

@ -0,0 +1,68 @@
// @flow
import React from 'react';
import { useHistory } from 'react-router-dom';
import Card from 'component/common/card';
import MarkdownPreview from 'component/common/markdown-preview';
import * as PAGES from 'constants/pages';
import { Modal } from 'modal/modal';
import { getSimpleStrHash } from 'util/string';
type Props = {
isAutoInvoked?: boolean,
// --- redux ---
authenticated?: boolean,
announcement: string,
lastViewedHash: ?string,
doHideModal: () => void,
doSetLastViewedAnnouncement: (hash: string) => void,
};
export default function ModalAnnouncements(props: Props) {
const {
authenticated,
announcement,
lastViewedHash,
isAutoInvoked,
doHideModal,
doSetLastViewedAnnouncement,
} = props;
const {
location: { pathname },
} = useHistory();
const [show, setShow] = React.useState(false);
React.useEffect(() => {
if (!authenticated || (pathname !== '/' && pathname !== `/$/${PAGES.HELP}`) || announcement === '') {
doHideModal();
return;
}
const hash = getSimpleStrHash(announcement);
if (lastViewedHash === hash) {
if (isAutoInvoked) {
doHideModal();
} else {
setShow(true);
}
} else {
setShow(true);
doSetLastViewedAnnouncement(hash);
}
}, []);
if (!show) {
return null;
}
return (
<Modal type="card" isOpen onAborted={doHideModal}>
<Card
className="announcement"
actions={<MarkdownPreview className="markdown-preview--announcement" content={announcement} simpleLinks />}
/>
</Modal>
);
}

View file

@ -7,6 +7,7 @@ import { lazyImport } from 'util/lazyImport';
// prettier-ignore
const MAP = Object.freeze({
[MODALS.ANNOUNCEMENTS]: lazyImport(() => import('modal/modalAnnouncements' /* webpackChunkName: "modalAnnouncements" */)),
[MODALS.AFFIRM_PURCHASE]: lazyImport(() => import('modal/modalAffirmPurchase' /* webpackChunkName: "modalAffirmPurchase" */)),
[MODALS.AUTO_GENERATE_THUMBNAIL]: lazyImport(() => import('modal/modalAutoGenerateThumbnail' /* webpackChunkName: "modalAutoGenerateThumbnail" */)),
[MODALS.AUTO_UPDATE_DOWNLOADED]: lazyImport(() => import('modal/modalAutoUpdateDownloaded' /* webpackChunkName: "modalAutoUpdateDownloaded" */)),

View file

@ -3,6 +3,7 @@ import { doWalletReconnect } from 'redux/actions/wallet';
import * as SETTINGS from 'constants/settings';
import * as DAEMON_SETTINGS from 'constants/daemon_settings';
import * as ACTIONS from 'constants/action_types';
import * as MODALS from 'constants/modal_types';
import * as SHARED_PREFERENCES from 'constants/shared_preferences';
import { doToast } from 'redux/actions/notifications';
import analytics from 'analytics';
@ -10,7 +11,7 @@ import SUPPORTED_LANGUAGES from 'constants/supported_languages';
import { launcher } from 'util/autoLaunch';
import { selectClientSetting } from 'redux/selectors/settings';
import { doSyncLoop, doSyncUnsubscribe, doSetSyncLock } from 'redux/actions/sync';
import { doAlertWaitingForSync, doGetAndPopulatePreferences } from 'redux/actions/app';
import { doAlertWaitingForSync, doGetAndPopulatePreferences, doOpenModal } from 'redux/actions/app';
import { selectPrefsReady } from 'redux/selectors/sync';
import { Lbryio } from 'lbryinc';
import { getDefaultLanguage } from 'util/default-languages';
@ -317,23 +318,38 @@ function populateCategoryTitles(categories) {
}
}
function loadBuiltInHomepageData(dispatch) {
const homepages = require('homepages');
if (homepages) {
const v2 = {};
const homepageKeys = Object.keys(homepages);
homepageKeys.forEach((hp) => {
v2[hp] = homepages[hp];
});
window.homepages = v2;
populateCategoryTitles(window.homepages?.en?.categories);
dispatch({ type: ACTIONS.FETCH_HOMEPAGES_DONE });
}
}
export function doOpenAnnouncements() {
return (dispatch) => {
// There is a weird useEffect in modalRouter that closes all modals on
// initial mount. Not sure what scenario that covers, so just delay a bit
// until it is mounted.
setTimeout(() => {
dispatch(doOpenModal(MODALS.ANNOUNCEMENTS, { isAutoInvoked: true }));
}, 1000);
};
}
export function doFetchHomepages() {
return (dispatch) => {
// -- Use this env flag to use local homepage data. Otherwise, it will grab from `/$/api/content/v*/get`.
// -- Use this env flag to use local homepage data and meme (faster).
// -- Otherwise, it will grab from `/$/api/content/v*/get`.
// @if USE_LOCAL_HOMEPAGE_DATA='true'
const homepages = require('homepages');
if (homepages) {
const v2 = {};
const homepageKeys = Object.keys(homepages);
homepageKeys.forEach((hp) => {
v2[hp] = homepages[hp];
});
window.homepages = v2;
populateCategoryTitles(window.homepages?.en?.categories);
dispatch({ type: ACTIONS.FETCH_HOMEPAGES_DONE });
return;
}
loadBuiltInHomepageData(dispatch);
// @endif
fetch('https://odysee.com/$/api/content/v2/get')

View file

@ -303,6 +303,29 @@
}
}
// TODO: Move .announcement to `modalAnnouncements/style.scss` when structure is ready.
.announcement {
@media (min-width: $breakpoint-small) {
max-height: 90vh;
}
}
.markdown-preview--announcement {
@media (min-width: $breakpoint-small) {
max-height: 80vh;
}
overflow: hidden;
overflow-y: auto;
img {
max-width: 40%;
display: block;
margin-left: auto;
margin-right: auto;
}
}
.editor-preview {
background-color: rgba(var(--color-header-background-base), 1);
}

View file

@ -26,3 +26,7 @@ export function toCompactNotation(number: string | number, lang: ?string, minThr
export function stripLeadingAtSign(str: ?string) {
return str && str.charAt(0) === '@' ? str.slice(1) : str;
}
export function getSimpleStrHash(s: string) {
return String(s.split('').reduce((a, b) => ((a << 5) - a + b.charCodeAt(0)) | 0, 0));
}