Announcements framework (#1494)
This commit is contained in:
commit
581ae13c3f
24 changed files with 299 additions and 27 deletions
1
flow-typed/content.js
vendored
1
flow-typed/content.js
vendored
|
@ -15,6 +15,7 @@ declare type ContentState = {
|
||||||
// It can/should be '?Array<string>` instead -- set it to null, then clients
|
// It can/should be '?Array<string>` instead -- set it to null, then clients
|
||||||
// can cast it to a boolean. That, or rename the variable to `shuffle` if you
|
// can cast it to a boolean. That, or rename the variable to `shuffle` if you
|
||||||
// don't care about the URLs.
|
// don't care about the URLs.
|
||||||
|
lastViewedAnnouncement: ?string, // undefined = not seen in wallet.
|
||||||
};
|
};
|
||||||
|
|
||||||
declare type WatchHistory = {
|
declare type WatchHistory = {
|
||||||
|
|
|
@ -2259,6 +2259,8 @@
|
||||||
"Still Valid Until": "Still Valid Until",
|
"Still Valid Until": "Still Valid Until",
|
||||||
"Active channel": "Active channel",
|
"Active channel": "Active channel",
|
||||||
"Select your default active channel": "Select your default active channel",
|
"Select your default active channel": "Select your default active channel",
|
||||||
|
"What's New": "What's New",
|
||||||
|
"See what are the latest features and changes in Odysee.": "See what are the latest features and changes in Odysee.",
|
||||||
"This account has livestreaming disabled, please reach out to hello@odysee.com for assistance.": "This account has livestreaming disabled, please reach out to hello@odysee.com for assistance.",
|
"This account has livestreaming disabled, please reach out to hello@odysee.com for assistance.": "This account has livestreaming disabled, please reach out to hello@odysee.com for assistance.",
|
||||||
"Attach images by pasting or drag-and-drop.": "Attach images by pasting or drag-and-drop.",
|
"Attach images by pasting or drag-and-drop.": "Attach images by pasting or drag-and-drop.",
|
||||||
"There was a network error, but the publish may have been completed. Wait a few minutes, then check your Uploads or Wallet page to confirm.": "There was a network error, but the publish may have been completed. Wait a few minutes, then check your Uploads or Wallet page to confirm.",
|
"There was a network error, but the publish may have been completed. Wait a few minutes, then check your Uploads or Wallet page to confirm.": "There was a network error, but the publish may have been completed. Wait a few minutes, then check your Uploads or Wallet page to confirm.",
|
||||||
|
|
|
@ -1,7 +1,13 @@
|
||||||
import { hot } from 'react-hot-loader/root';
|
import { hot } from 'react-hot-loader/root';
|
||||||
import { connect } from 'react-redux';
|
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 { doUserSetReferrer } from 'redux/actions/user';
|
||||||
|
import { doSetLastViewedAnnouncement } from 'redux/actions/content';
|
||||||
import {
|
import {
|
||||||
selectOdyseeMembershipIsPremiumPlus,
|
selectOdyseeMembershipIsPremiumPlus,
|
||||||
selectUser,
|
selectUser,
|
||||||
|
@ -15,7 +21,7 @@ import { selectMyChannelClaimIds } from 'redux/selectors/claims';
|
||||||
import { selectLanguage, selectLoadedLanguages, selectThemePath } from 'redux/selectors/settings';
|
import { selectLanguage, selectLoadedLanguages, selectThemePath } from 'redux/selectors/settings';
|
||||||
import { selectModal, selectActiveChannelClaim, selectIsReloadRequired } from 'redux/selectors/app';
|
import { selectModal, selectActiveChannelClaim, selectIsReloadRequired } from 'redux/selectors/app';
|
||||||
import { selectUploadCount } from 'redux/selectors/publish';
|
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 { doSyncLoop } from 'redux/actions/sync';
|
||||||
import { doSignIn, doSetIncognito } from 'redux/actions/app';
|
import { doSignIn, doSetIncognito } from 'redux/actions/app';
|
||||||
import { doFetchModBlockedList, doFetchCommentModAmIList } from 'redux/actions/comments';
|
import { doFetchModBlockedList, doFetchCommentModAmIList } from 'redux/actions/comments';
|
||||||
|
@ -28,6 +34,7 @@ const select = (state) => ({
|
||||||
language: selectLanguage(state),
|
language: selectLanguage(state),
|
||||||
languages: selectLoadedLanguages(state),
|
languages: selectLoadedLanguages(state),
|
||||||
isReloadRequired: selectIsReloadRequired(state),
|
isReloadRequired: selectIsReloadRequired(state),
|
||||||
|
prefsReady: selectPrefsReady(state),
|
||||||
syncError: selectGetSyncErrorMessage(state),
|
syncError: selectGetSyncErrorMessage(state),
|
||||||
syncIsLocked: selectSyncIsLocked(state),
|
syncIsLocked: selectSyncIsLocked(state),
|
||||||
uploadCount: selectUploadCount(state),
|
uploadCount: selectUploadCount(state),
|
||||||
|
@ -51,6 +58,8 @@ const perform = {
|
||||||
setIncognito: doSetIncognito,
|
setIncognito: doSetIncognito,
|
||||||
fetchModBlockedList: doFetchModBlockedList,
|
fetchModBlockedList: doFetchModBlockedList,
|
||||||
fetchModAmIList: doFetchCommentModAmIList,
|
fetchModAmIList: doFetchCommentModAmIList,
|
||||||
|
doOpenAnnouncements,
|
||||||
|
doSetLastViewedAnnouncement,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default hot(connect(select, perform)(App));
|
export default hot(connect(select, perform)(App));
|
||||||
|
|
|
@ -74,6 +74,7 @@ type Props = {
|
||||||
balance: ?number,
|
balance: ?number,
|
||||||
syncIsLocked: boolean,
|
syncIsLocked: boolean,
|
||||||
syncError: ?string,
|
syncError: ?string,
|
||||||
|
prefsReady: boolean,
|
||||||
rewards: Array<Reward>,
|
rewards: Array<Reward>,
|
||||||
setReferrer: (string, boolean) => void,
|
setReferrer: (string, boolean) => void,
|
||||||
isAuthenticated: boolean,
|
isAuthenticated: boolean,
|
||||||
|
@ -87,6 +88,8 @@ type Props = {
|
||||||
fetchModBlockedList: () => void,
|
fetchModBlockedList: () => void,
|
||||||
fetchModAmIList: () => void,
|
fetchModAmIList: () => void,
|
||||||
homepageFetched: boolean,
|
homepageFetched: boolean,
|
||||||
|
doOpenAnnouncements: () => void,
|
||||||
|
doSetLastViewedAnnouncement: (hash: string) => void,
|
||||||
};
|
};
|
||||||
|
|
||||||
function App(props: Props) {
|
function App(props: Props) {
|
||||||
|
@ -103,6 +106,7 @@ function App(props: Props) {
|
||||||
history,
|
history,
|
||||||
syncError,
|
syncError,
|
||||||
syncIsLocked,
|
syncIsLocked,
|
||||||
|
prefsReady,
|
||||||
language,
|
language,
|
||||||
languages,
|
languages,
|
||||||
setLanguage,
|
setLanguage,
|
||||||
|
@ -119,6 +123,8 @@ function App(props: Props) {
|
||||||
hasPremiumPlus,
|
hasPremiumPlus,
|
||||||
fetchModAmIList,
|
fetchModAmIList,
|
||||||
homepageFetched,
|
homepageFetched,
|
||||||
|
doOpenAnnouncements,
|
||||||
|
doSetLastViewedAnnouncement,
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
const isMobile = useIsMobile();
|
const isMobile = useIsMobile();
|
||||||
|
@ -438,7 +444,7 @@ function App(props: Props) {
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps, (one time after locale is fetched)
|
// eslint-disable-next-line react-hooks/exhaustive-deps, (one time after locale is fetched)
|
||||||
}, [locale]);
|
}, [locale]);
|
||||||
|
|
||||||
React.useEffect(() => {
|
useEffect(() => {
|
||||||
if (locale) {
|
if (locale) {
|
||||||
const countryCode = locale.country;
|
const countryCode = locale.country;
|
||||||
const langs = getLanguagesForCountry(countryCode) || [];
|
const langs = getLanguagesForCountry(countryCode) || [];
|
||||||
|
@ -471,6 +477,19 @@ function App(props: Props) {
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [syncError, pathname, isAuthenticated]);
|
}, [syncError, pathname, isAuthenticated]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (prefsReady) {
|
||||||
|
doOpenAnnouncements();
|
||||||
|
}
|
||||||
|
}, [prefsReady]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
window.clearLastViewedAnnouncement = () => {
|
||||||
|
console.log('Clearing history. Please wait ...');
|
||||||
|
doSetLastViewedAnnouncement('');
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
// Keep this at the end to ensure initial setup effects are run first
|
// Keep this at the end to ensure initial setup effects are run first
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!hasSignedIn && hasVerifiedEmail) {
|
if (!hasSignedIn && hasVerifiedEmail) {
|
||||||
|
|
|
@ -143,6 +143,7 @@ export const CLEAR_CONTENT_POSITION = 'CLEAR_CONTENT_POSITION';
|
||||||
export const SET_CONTENT_LAST_VIEWED = 'SET_CONTENT_LAST_VIEWED';
|
export const SET_CONTENT_LAST_VIEWED = 'SET_CONTENT_LAST_VIEWED';
|
||||||
export const CLEAR_CONTENT_HISTORY_URI = 'CLEAR_CONTENT_HISTORY_URI';
|
export const CLEAR_CONTENT_HISTORY_URI = 'CLEAR_CONTENT_HISTORY_URI';
|
||||||
export const CLEAR_CONTENT_HISTORY_ALL = 'CLEAR_CONTENT_HISTORY_ALL';
|
export const CLEAR_CONTENT_HISTORY_ALL = 'CLEAR_CONTENT_HISTORY_ALL';
|
||||||
|
export const SET_LAST_VIEWED_ANNOUNCEMENT = 'SET_LAST_VIEWED_ANNOUNCEMENT';
|
||||||
export const RECOMMENDATION_UPDATED = 'RECOMMENDATION_UPDATED';
|
export const RECOMMENDATION_UPDATED = 'RECOMMENDATION_UPDATED';
|
||||||
export const RECOMMENDATION_CLICKED = 'RECOMMENDATION_CLICKED';
|
export const RECOMMENDATION_CLICKED = 'RECOMMENDATION_CLICKED';
|
||||||
export const TOGGLE_LOOP_LIST = 'TOGGLE_LOOP_LIST';
|
export const TOGGLE_LOOP_LIST = 'TOGGLE_LOOP_LIST';
|
||||||
|
|
|
@ -51,3 +51,4 @@ export const CONFIRM_REMOVE_COMMENT = 'CONFIRM_REMOVE_COMMENT';
|
||||||
export const CONFIRM_ODYSEE_MEMBERSHIP = 'CONFIRM_ODYSEE_MEMBERSHIP';
|
export const CONFIRM_ODYSEE_MEMBERSHIP = 'CONFIRM_ODYSEE_MEMBERSHIP';
|
||||||
export const MEMBERSHIP_SPLASH = 'MEMBERSHIP_SPLASH';
|
export const MEMBERSHIP_SPLASH = 'MEMBERSHIP_SPLASH';
|
||||||
export const HIDE_RECOMMENDATION = 'HIDE_RECOMMENDATION';
|
export const HIDE_RECOMMENDATION = 'HIDE_RECOMMENDATION';
|
||||||
|
export const ANNOUNCEMENTS = 'ANNOUNCEMENTS';
|
||||||
|
|
20
ui/modal/modalAnnouncements/index.js
Normal file
20
ui/modal/modalAnnouncements/index.js
Normal 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);
|
68
ui/modal/modalAnnouncements/view.jsx
Normal file
68
ui/modal/modalAnnouncements/view.jsx
Normal 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>
|
||||||
|
);
|
||||||
|
}
|
|
@ -1,6 +1,6 @@
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { selectModal } from 'redux/selectors/app';
|
import { selectModal } from 'redux/selectors/app';
|
||||||
import { doOpenModal, doHideModal } from 'redux/actions/app';
|
import { doHideModal } from 'redux/actions/app';
|
||||||
import { selectError } from 'redux/selectors/notifications'; // RENAME THIS 'selectNotificationError'
|
import { selectError } from 'redux/selectors/notifications'; // RENAME THIS 'selectNotificationError'
|
||||||
import ModalRouter from './view';
|
import ModalRouter from './view';
|
||||||
|
|
||||||
|
@ -9,9 +9,8 @@ const select = (state, props) => ({
|
||||||
error: selectError(state),
|
error: selectError(state),
|
||||||
});
|
});
|
||||||
|
|
||||||
const perform = (dispatch) => ({
|
const perform = {
|
||||||
openModal: (props) => dispatch(doOpenModal(props)),
|
hideModal: doHideModal,
|
||||||
hideModal: (props) => dispatch(doHideModal(props)),
|
};
|
||||||
});
|
|
||||||
|
|
||||||
export default connect(select, perform)(ModalRouter);
|
export default connect(select, perform)(ModalRouter);
|
||||||
|
|
|
@ -7,6 +7,7 @@ import { lazyImport } from 'util/lazyImport';
|
||||||
|
|
||||||
// prettier-ignore
|
// prettier-ignore
|
||||||
const MAP = Object.freeze({
|
const MAP = Object.freeze({
|
||||||
|
[MODALS.ANNOUNCEMENTS]: lazyImport(() => import('modal/modalAnnouncements' /* webpackChunkName: "modalAnnouncements" */)),
|
||||||
[MODALS.AFFIRM_PURCHASE]: lazyImport(() => import('modal/modalAffirmPurchase' /* webpackChunkName: "modalAffirmPurchase" */)),
|
[MODALS.AFFIRM_PURCHASE]: lazyImport(() => import('modal/modalAffirmPurchase' /* webpackChunkName: "modalAffirmPurchase" */)),
|
||||||
[MODALS.AUTO_GENERATE_THUMBNAIL]: lazyImport(() => import('modal/modalAutoGenerateThumbnail' /* webpackChunkName: "modalAutoGenerateThumbnail" */)),
|
[MODALS.AUTO_GENERATE_THUMBNAIL]: lazyImport(() => import('modal/modalAutoGenerateThumbnail' /* webpackChunkName: "modalAutoGenerateThumbnail" */)),
|
||||||
[MODALS.AUTO_UPDATE_DOWNLOADED]: lazyImport(() => import('modal/modalAutoUpdateDownloaded' /* webpackChunkName: "modalAutoUpdateDownloaded" */)),
|
[MODALS.AUTO_UPDATE_DOWNLOADED]: lazyImport(() => import('modal/modalAutoUpdateDownloaded' /* webpackChunkName: "modalAutoUpdateDownloaded" */)),
|
||||||
|
|
|
@ -1,3 +1,14 @@
|
||||||
import HelpPage from './view';
|
import HelpPage from './view';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import { doOpenModal } from 'redux/actions/app';
|
||||||
|
import { selectHomepageAnnouncement } from 'redux/selectors/settings';
|
||||||
|
|
||||||
export default HelpPage;
|
const select = (state) => ({
|
||||||
|
announcement: selectHomepageAnnouncement(state),
|
||||||
|
});
|
||||||
|
|
||||||
|
const perform = {
|
||||||
|
doOpenModal,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default connect(select, perform)(HelpPage);
|
||||||
|
|
|
@ -7,9 +7,35 @@ import Page from 'component/page';
|
||||||
import Card from 'component/common/card';
|
import Card from 'component/common/card';
|
||||||
import I18nMessage from 'component/i18nMessage';
|
import I18nMessage from 'component/i18nMessage';
|
||||||
|
|
||||||
export default function HelpPage() {
|
import * as MODALS from 'constants/modal_types';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
announcement: string,
|
||||||
|
doOpenModal: (string, ?{}) => void,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function HelpPage(props: Props) {
|
||||||
|
const { announcement, doOpenModal } = props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Page className="card-stack">
|
<Page className="card-stack">
|
||||||
|
{announcement && (
|
||||||
|
<Card
|
||||||
|
title={__("What's New")}
|
||||||
|
subtitle={__('See what are the latest features and changes in Odysee.')}
|
||||||
|
actions={
|
||||||
|
<div className="section__actions">
|
||||||
|
<Button
|
||||||
|
label={__("What's New")}
|
||||||
|
icon={ICONS.FEEDBACK}
|
||||||
|
button="secondary"
|
||||||
|
onClick={() => doOpenModal(MODALS.ANNOUNCEMENTS)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
<Card
|
<Card
|
||||||
title={__('Visit the %SITE_NAME% Help Hub', { SITE_NAME })}
|
title={__('Visit the %SITE_NAME% Help Hub', { SITE_NAME })}
|
||||||
subtitle={__('Our support posts answer many common questions.')}
|
subtitle={__('Our support posts answer many common questions.')}
|
||||||
|
|
|
@ -374,3 +374,12 @@ export function doToggleShuffleList(currentUri: string, collectionId: string, sh
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function doSetLastViewedAnnouncement(hash: string) {
|
||||||
|
return (dispatch: Dispatch) => {
|
||||||
|
dispatch({
|
||||||
|
type: ACTIONS.SET_LAST_VIEWED_ANNOUNCEMENT,
|
||||||
|
data: hash,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ import { doWalletReconnect } from 'redux/actions/wallet';
|
||||||
import * as SETTINGS from 'constants/settings';
|
import * as SETTINGS from 'constants/settings';
|
||||||
import * as DAEMON_SETTINGS from 'constants/daemon_settings';
|
import * as DAEMON_SETTINGS from 'constants/daemon_settings';
|
||||||
import * as ACTIONS from 'constants/action_types';
|
import * as ACTIONS from 'constants/action_types';
|
||||||
|
import * as MODALS from 'constants/modal_types';
|
||||||
import * as SHARED_PREFERENCES from 'constants/shared_preferences';
|
import * as SHARED_PREFERENCES from 'constants/shared_preferences';
|
||||||
import { doToast } from 'redux/actions/notifications';
|
import { doToast } from 'redux/actions/notifications';
|
||||||
import analytics from 'analytics';
|
import analytics from 'analytics';
|
||||||
|
@ -10,7 +11,7 @@ import SUPPORTED_LANGUAGES from 'constants/supported_languages';
|
||||||
import { launcher } from 'util/autoLaunch';
|
import { launcher } from 'util/autoLaunch';
|
||||||
import { selectClientSetting } from 'redux/selectors/settings';
|
import { selectClientSetting } from 'redux/selectors/settings';
|
||||||
import { doSyncLoop, doSyncUnsubscribe, doSetSyncLock } from 'redux/actions/sync';
|
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 { selectPrefsReady } from 'redux/selectors/sync';
|
||||||
import { Lbryio } from 'lbryinc';
|
import { Lbryio } from 'lbryinc';
|
||||||
import { getDefaultLanguage } from 'util/default-languages';
|
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() {
|
export function doFetchHomepages() {
|
||||||
return (dispatch) => {
|
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'
|
// @if USE_LOCAL_HOMEPAGE_DATA='true'
|
||||||
const homepages = require('homepages');
|
loadBuiltInHomepageData(dispatch);
|
||||||
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;
|
|
||||||
}
|
|
||||||
// @endif
|
// @endif
|
||||||
|
|
||||||
fetch('https://odysee.com/$/api/content/v2/get')
|
fetch('https://odysee.com/$/api/content/v2/get')
|
||||||
|
|
|
@ -421,6 +421,7 @@ type SharedData = {
|
||||||
editedCollections: CollectionGroup,
|
editedCollections: CollectionGroup,
|
||||||
builtinCollections: CollectionGroup,
|
builtinCollections: CollectionGroup,
|
||||||
savedCollections: Array<string>,
|
savedCollections: Array<string>,
|
||||||
|
lastViewedAnnouncement: string,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -439,6 +440,7 @@ function extractUserState(rawObj: SharedData) {
|
||||||
editedCollections,
|
editedCollections,
|
||||||
builtinCollections,
|
builtinCollections,
|
||||||
savedCollections,
|
savedCollections,
|
||||||
|
lastViewedAnnouncement,
|
||||||
} = rawObj.value;
|
} = rawObj.value;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
@ -454,6 +456,7 @@ function extractUserState(rawObj: SharedData) {
|
||||||
...(editedCollections ? { editedCollections } : {}),
|
...(editedCollections ? { editedCollections } : {}),
|
||||||
...(builtinCollections ? { builtinCollections } : {}),
|
...(builtinCollections ? { builtinCollections } : {}),
|
||||||
...(savedCollections ? { savedCollections } : {}),
|
...(savedCollections ? { savedCollections } : {}),
|
||||||
|
...(lastViewedAnnouncement ? { lastViewedAnnouncement } : {}),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -475,6 +478,7 @@ export function doPopulateSharedUserState(sharedSettings: any) {
|
||||||
editedCollections,
|
editedCollections,
|
||||||
builtinCollections,
|
builtinCollections,
|
||||||
savedCollections,
|
savedCollections,
|
||||||
|
lastViewedAnnouncement,
|
||||||
} = extractUserState(sharedSettings);
|
} = extractUserState(sharedSettings);
|
||||||
dispatch({
|
dispatch({
|
||||||
type: ACTIONS.USER_STATE_POPULATE,
|
type: ACTIONS.USER_STATE_POPULATE,
|
||||||
|
@ -491,6 +495,7 @@ export function doPopulateSharedUserState(sharedSettings: any) {
|
||||||
editedCollections,
|
editedCollections,
|
||||||
builtinCollections,
|
builtinCollections,
|
||||||
savedCollections,
|
savedCollections,
|
||||||
|
lastViewedAnnouncement,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
|
@ -13,6 +13,7 @@ const defaultState: ContentState = {
|
||||||
recommendationUrls: {},
|
recommendationUrls: {},
|
||||||
recommendationClicks: {},
|
recommendationClicks: {},
|
||||||
loopList: undefined,
|
loopList: undefined,
|
||||||
|
lastViewedAnnouncement: '',
|
||||||
};
|
};
|
||||||
|
|
||||||
reducers[ACTIONS.SET_PRIMARY_URI] = (state, action) =>
|
reducers[ACTIONS.SET_PRIMARY_URI] = (state, action) =>
|
||||||
|
@ -118,6 +119,8 @@ reducers[ACTIONS.CLEAR_CONTENT_HISTORY_URI] = (state, action) => {
|
||||||
|
|
||||||
reducers[ACTIONS.CLEAR_CONTENT_HISTORY_ALL] = (state) => ({ ...state, history: [] });
|
reducers[ACTIONS.CLEAR_CONTENT_HISTORY_ALL] = (state) => ({ ...state, history: [] });
|
||||||
|
|
||||||
|
reducers[ACTIONS.SET_LAST_VIEWED_ANNOUNCEMENT] = (state, action) => ({ ...state, lastViewedAnnouncement: action.data });
|
||||||
|
|
||||||
// reducers[LBRY_REDUX_ACTIONS.PURCHASE_URI_FAILED] = (state, action) => {
|
// reducers[LBRY_REDUX_ACTIONS.PURCHASE_URI_FAILED] = (state, action) => {
|
||||||
// return {
|
// return {
|
||||||
// ...state,
|
// ...state,
|
||||||
|
@ -125,6 +128,11 @@ reducers[ACTIONS.CLEAR_CONTENT_HISTORY_ALL] = (state) => ({ ...state, history: [
|
||||||
// };
|
// };
|
||||||
// };
|
// };
|
||||||
|
|
||||||
|
reducers[ACTIONS.USER_STATE_POPULATE] = (state, action) => {
|
||||||
|
const { lastViewedAnnouncement } = action.data;
|
||||||
|
return { ...state, lastViewedAnnouncement };
|
||||||
|
};
|
||||||
|
|
||||||
export default function reducer(state: ContentState = defaultState, action: any) {
|
export default function reducer(state: ContentState = defaultState, action: any) {
|
||||||
const handler = reducers[action.type];
|
const handler = reducers[action.type];
|
||||||
if (handler) return handler(state, action);
|
if (handler) return handler(state, action);
|
||||||
|
|
|
@ -26,6 +26,7 @@ export const selectPlayingUri = (state: State) => selectState(state).playingUri;
|
||||||
export const selectPrimaryUri = (state: State) => selectState(state).primaryUri;
|
export const selectPrimaryUri = (state: State) => selectState(state).primaryUri;
|
||||||
export const selectListLoop = (state: State) => selectState(state).loopList;
|
export const selectListLoop = (state: State) => selectState(state).loopList;
|
||||||
export const selectListShuffle = (state: State) => selectState(state).shuffleList;
|
export const selectListShuffle = (state: State) => selectState(state).shuffleList;
|
||||||
|
export const selectLastViewedAnnouncement = (state: State) => selectState(state).lastViewedAnnouncement;
|
||||||
|
|
||||||
export const makeSelectIsPlaying = (uri: string) =>
|
export const makeSelectIsPlaying = (uri: string) =>
|
||||||
createSelector(selectPrimaryUri, (primaryUri) => primaryUri === uri);
|
createSelector(selectPrimaryUri, (primaryUri) => primaryUri === uri);
|
||||||
|
|
|
@ -82,6 +82,18 @@ export const selectHomepageMeme = (state) => {
|
||||||
return homepages ? homepages['en'].meme || {} : {};
|
return homepages ? homepages['en'].meme || {} : {};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const selectHomepageAnnouncement = (state) => {
|
||||||
|
const homepageCode = selectHomepageCode(state);
|
||||||
|
const homepages = window.homepages;
|
||||||
|
if (homepages) {
|
||||||
|
const news = homepages[homepageCode].announcement;
|
||||||
|
if (news) {
|
||||||
|
return news;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return homepages ? homepages['en'].announcement || '' : '';
|
||||||
|
};
|
||||||
|
|
||||||
export const selectInRegionByCode = (state, code) => {
|
export const selectInRegionByCode = (state, code) => {
|
||||||
const hp = selectClientSetting(state, SETTINGS.HOMEPAGE);
|
const hp = selectClientSetting(state, SETTINGS.HOMEPAGE);
|
||||||
const lang = selectLanguage(state);
|
const lang = selectLanguage(state);
|
||||||
|
|
|
@ -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 {
|
.editor-preview {
|
||||||
background-color: rgba(var(--color-header-background-base), 1);
|
background-color: rgba(var(--color-header-background-base), 1);
|
||||||
}
|
}
|
||||||
|
|
|
@ -45,7 +45,7 @@ function enableBatching(reducer) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const contentFilter = createFilter('content', ['positions', 'history']);
|
const contentFilter = createFilter('content', ['positions', 'history', 'lastViewedAnnouncement']);
|
||||||
const fileInfoFilter = createFilter('fileInfo', [
|
const fileInfoFilter = createFilter('fileInfo', [
|
||||||
'fileListPublishedSort',
|
'fileListPublishedSort',
|
||||||
'fileListDownloadedSort',
|
'fileListDownloadedSort',
|
||||||
|
@ -141,6 +141,7 @@ const triggerSharedStateActions = [
|
||||||
ACTIONS.COLLECTION_DELETE,
|
ACTIONS.COLLECTION_DELETE,
|
||||||
ACTIONS.COLLECTION_NEW,
|
ACTIONS.COLLECTION_NEW,
|
||||||
ACTIONS.COLLECTION_PENDING,
|
ACTIONS.COLLECTION_PENDING,
|
||||||
|
ACTIONS.SET_LAST_VIEWED_ANNOUNCEMENT,
|
||||||
// MAYBE COLLECTOIN SAVE
|
// MAYBE COLLECTOIN SAVE
|
||||||
// ACTIONS.SET_WELCOME_VERSION,
|
// ACTIONS.SET_WELCOME_VERSION,
|
||||||
// ACTIONS.SET_ALLOW_ANALYTICS,
|
// ACTIONS.SET_ALLOW_ANALYTICS,
|
||||||
|
@ -183,6 +184,7 @@ const sharedStateFilters = {
|
||||||
editedCollections: { source: 'collections', property: 'edited' },
|
editedCollections: { source: 'collections', property: 'edited' },
|
||||||
// savedCollections: { source: 'collections', property: 'saved' },
|
// savedCollections: { source: 'collections', property: 'saved' },
|
||||||
unpublishedCollections: { source: 'collections', property: 'unpublished' },
|
unpublishedCollections: { source: 'collections', property: 'unpublished' },
|
||||||
|
lastViewedAnnouncement: { source: 'content', property: 'lastViewedAnnouncement' },
|
||||||
};
|
};
|
||||||
|
|
||||||
const sharedStateCb = ({ dispatch, getState, syncId }) => {
|
const sharedStateCb = ({ dispatch, getState, syncId }) => {
|
||||||
|
|
|
@ -23,3 +23,15 @@ export function getLocalStorageSummary() {
|
||||||
return 'inaccessible';
|
return 'inaccessible';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const localStorageAvailable = isLocalStorageAvailable();
|
||||||
|
|
||||||
|
export function getLocalStorageItem(key) {
|
||||||
|
return localStorageAvailable ? window.localStorage.getItem(key) : undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setLocalStorageItem(key, value) {
|
||||||
|
if (localStorageAvailable) {
|
||||||
|
window.localStorage.setItem(key, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -26,3 +26,7 @@ export function toCompactNotation(number: string | number, lang: ?string, minThr
|
||||||
export function stripLeadingAtSign(str: ?string) {
|
export function stripLeadingAtSign(str: ?string) {
|
||||||
return str && str.charAt(0) === '@' ? str.slice(1) : str;
|
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));
|
||||||
|
}
|
||||||
|
|
|
@ -1,9 +1,24 @@
|
||||||
|
const path = require('path');
|
||||||
const memo = {};
|
const memo = {};
|
||||||
|
|
||||||
|
const loadAnnouncements = (homepageKeys) => {
|
||||||
|
const fs = require('fs');
|
||||||
|
const announcements = {};
|
||||||
|
|
||||||
|
homepageKeys.forEach((key) => {
|
||||||
|
const file = path.join(__dirname, `../dist/announcement/${key.toLowerCase()}.md`);
|
||||||
|
const announcement = fs.readFileSync(file, 'utf8');
|
||||||
|
announcements[key] = announcement ? announcement.trim() : '';
|
||||||
|
});
|
||||||
|
|
||||||
|
return announcements;
|
||||||
|
};
|
||||||
|
|
||||||
// this didn't seem to help.
|
// this didn't seem to help.
|
||||||
if (!memo.homepageData) {
|
if (!memo.homepageData) {
|
||||||
try {
|
try {
|
||||||
memo.homepageData = require('../../custom/homepages/v2');
|
memo.homepageData = require('../../custom/homepages/v2');
|
||||||
|
memo.announcements = loadAnnouncements(Object.keys(memo.homepageData));
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.log('getHomepageJSON:', err);
|
console.log('getHomepageJSON:', err);
|
||||||
}
|
}
|
||||||
|
@ -30,7 +45,10 @@ const getHomepageJsonV2 = () => {
|
||||||
const v2 = {};
|
const v2 = {};
|
||||||
const homepageKeys = Object.keys(memo.homepageData);
|
const homepageKeys = Object.keys(memo.homepageData);
|
||||||
homepageKeys.forEach((hp) => {
|
homepageKeys.forEach((hp) => {
|
||||||
v2[hp] = memo.homepageData[hp];
|
v2[hp] = {
|
||||||
|
...memo.homepageData[hp],
|
||||||
|
announcement: memo.announcements[hp],
|
||||||
|
};
|
||||||
});
|
});
|
||||||
return v2;
|
return v2;
|
||||||
};
|
};
|
||||||
|
|
|
@ -68,6 +68,10 @@ const copyWebpackCommands = [
|
||||||
from: `${WEB_STATIC_ROOT}/pwa/`,
|
from: `${WEB_STATIC_ROOT}/pwa/`,
|
||||||
to: `${DIST_ROOT}/public/pwa/`,
|
to: `${DIST_ROOT}/public/pwa/`,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
from: `${STATIC_ROOT}/../custom/homepages/v2/announcement`,
|
||||||
|
to: `${DIST_ROOT}/announcement`,
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const CUSTOM_OG_PATH = `${CUSTOM_ROOT}/v2-og.png`;
|
const CUSTOM_OG_PATH = `${CUSTOM_ROOT}/v2-og.png`;
|
||||||
|
|
Loading…
Reference in a new issue