Fix scrolling sync, tweak styles

This commit is contained in:
Daniel Lautzenheiser 2022-09-20 09:14:34 -04:00
parent e33f3dec02
commit e2b11ba3ff
12 changed files with 165 additions and 77 deletions

View File

@ -0,0 +1,30 @@
import type { AnyAction } from 'redux';
import type { ThunkDispatch } from 'redux-thunk';
import type { State } from '../types/redux';
export const SCROLL_SYNC_ENABLED = 'cms.scroll-sync-enabled';
export const TOGGLE_SCROLL = 'TOGGLE_SCROLL';
export const SET_SCROLL = 'SET_SCROLL';
export function togglingScroll() {
return {
type: TOGGLE_SCROLL,
} as const;
}
export function loadScroll() {
return {
type: SET_SCROLL,
payload: localStorage.getItem(SCROLL_SYNC_ENABLED) !== 'false',
} as const;
}
export function toggleScroll() {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
return async (dispatch: ThunkDispatch<State, undefined, AnyAction>, _getState: () => State) => {
return dispatch(togglingScroll());
};
}
export type ScrollAction = ReturnType<typeof togglingScroll | typeof loadScroll>;

View File

@ -140,7 +140,7 @@ export function searchEntries(searchTerm: string, searchCollections: string[], p
try { try {
const response: SearchResponse = await searchPromise; const response: SearchResponse = await searchPromise;
return dispatch(searchSuccess(response.entries, response.pagination)); return dispatch(searchSuccess(response.entries, response.pagination));
} catch (error) { } catch (error: any) {
return dispatch(searchFailure(error)); return dispatch(searchFailure(error));
} }
}; };
@ -177,7 +177,7 @@ export function query(
try { try {
const response: QueryResponse = await queryPromise; const response: QueryResponse = await queryPromise;
return dispatch(querySuccess(namespace, response.hits)); return dispatch(querySuccess(namespace, response.hits));
} catch (error) { } catch (error: any) {
return dispatch(queryFailure(error)); return dispatch(queryFailure(error));
} }
}; };

View File

@ -87,7 +87,7 @@ export function checkBackendStatus() {
} }
dispatch(statusSuccess(status)); dispatch(statusSuccess(status));
} catch (error) { } catch (error: any) {
dispatch(statusFailure(error)); dispatch(statusFailure(error));
} }
}; };

View File

@ -36,12 +36,12 @@ TopBarProgress.config({
const AppRoot = styled.div` const AppRoot = styled.div`
width: 100%; width: 100%;
min-height: 100%; min-height: 100vh;
`; `;
const AppWrapper = styled.div` const AppWrapper = styled.div`
width: 100%; width: 100%;
min-height: 100%; min-height: 100vh;
`; `;
const AppMainContainer = styled.div` const AppMainContainer = styled.div`
@ -96,6 +96,7 @@ class App extends React.Component {
useMediaLibrary: PropTypes.bool, useMediaLibrary: PropTypes.bool,
openMediaLibrary: PropTypes.func.isRequired, openMediaLibrary: PropTypes.func.isRequired,
showMediaButton: PropTypes.bool, showMediaButton: PropTypes.bool,
scrollSyncEnabled: PropTypes.bool.isRequired,
t: PropTypes.func.isRequired, t: PropTypes.func.isRequired,
}; };
@ -164,6 +165,7 @@ class App extends React.Component {
openMediaLibrary, openMediaLibrary,
t, t,
showMediaButton, showMediaButton,
scrollSyncEnabled,
} = this.props; } = this.props;
if (config === null) { if (config === null) {
@ -186,7 +188,7 @@ class App extends React.Component {
const hasWorkflow = publishMode === EDITORIAL_WORKFLOW; const hasWorkflow = publishMode === EDITORIAL_WORKFLOW;
return ( return (
<ScrollSync> <ScrollSync enabled={scrollSyncEnabled}>
<AppRoot id="cms-root"> <AppRoot id="cms-root">
<AppWrapper className="cms-wrapper"> <AppWrapper className="cms-wrapper">
<Notifs CustomComponent={Toast} /> <Notifs CustomComponent={Toast} />
@ -269,12 +271,13 @@ class App extends React.Component {
} }
function mapStateToProps(state) { function mapStateToProps(state) {
const { auth, config, collections, globalUI, mediaLibrary } = state; const { auth, config, collections, globalUI, mediaLibrary, scroll } = state;
const user = auth.user; const user = auth.user;
const isFetching = globalUI.isFetching; const isFetching = globalUI.isFetching;
const publishMode = config.publish_mode; const publishMode = config.publish_mode;
const useMediaLibrary = !mediaLibrary.get('externalLibrary'); const useMediaLibrary = !mediaLibrary.get('externalLibrary');
const showMediaButton = mediaLibrary.get('showMediaButton'); const showMediaButton = mediaLibrary.get('showMediaButton');
const scrollSyncEnabled = scroll.isScrolling;
return { return {
auth, auth,
config, config,
@ -284,6 +287,7 @@ function mapStateToProps(state) {
publishMode, publishMode,
showMediaButton, showMediaButton,
useMediaLibrary, useMediaLibrary,
scrollSyncEnabled,
}; };
} }

View File

@ -1,38 +1,39 @@
import { debounce } from 'lodash';
import { Loader } from 'netlify-cms-ui-default';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import React from 'react'; import React from 'react';
import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePropTypes from 'react-immutable-proptypes';
import { connect } from 'react-redux';
import { Loader } from 'netlify-cms-ui-default';
import { translate } from 'react-polyglot'; import { translate } from 'react-polyglot';
import { debounce } from 'lodash'; import { connect } from 'react-redux';
import { history, navigateToCollection, navigateToNewEntry } from '../../routing/history';
import { logoutUser } from '../../actions/auth'; import { logoutUser } from '../../actions/auth';
import { loadDeployPreview } from '../../actions/deploys';
import { import {
loadEntry, deleteUnpublishedEntry,
loadEntries,
createDraftDuplicateFromEntry,
createEmptyDraft,
discardDraft,
changeDraftField,
changeDraftFieldValidation,
persistEntry,
deleteEntry,
persistLocalBackup,
loadLocalBackup,
retrieveLocalBackup,
deleteLocalBackup,
} from '../../actions/entries';
import {
updateUnpublishedEntryStatus,
publishUnpublishedEntry, publishUnpublishedEntry,
unpublishPublishedEntry, unpublishPublishedEntry,
deleteUnpublishedEntry, updateUnpublishedEntryStatus,
} from '../../actions/editorialWorkflow'; } from '../../actions/editorialWorkflow';
import { loadDeployPreview } from '../../actions/deploys'; import {
import { selectEntry, selectUnpublishedEntry, selectDeployPreview } from '../../reducers'; changeDraftField,
changeDraftFieldValidation,
createDraftDuplicateFromEntry,
createEmptyDraft,
deleteEntry,
deleteLocalBackup,
discardDraft,
loadEntries,
loadEntry,
loadLocalBackup,
persistEntry,
persistLocalBackup,
retrieveLocalBackup,
} from '../../actions/entries';
import { loadScroll, toggleScroll } from '../../actions/scroll';
import { EDITORIAL_WORKFLOW, status } from '../../constants/publishModes';
import { selectDeployPreview, selectEntry, selectUnpublishedEntry } from '../../reducers';
import { selectFields } from '../../reducers/collections'; import { selectFields } from '../../reducers/collections';
import { status, EDITORIAL_WORKFLOW } from '../../constants/publishModes'; import { history, navigateToCollection, navigateToNewEntry } from '../../routing/history';
import EditorInterface from './EditorInterface'; import EditorInterface from './EditorInterface';
import withWorkflow from './withWorkflow'; import withWorkflow from './withWorkflow';
@ -79,6 +80,9 @@ export class Editor extends React.Component {
loadLocalBackup: PropTypes.func, loadLocalBackup: PropTypes.func,
persistLocalBackup: PropTypes.func.isRequired, persistLocalBackup: PropTypes.func.isRequired,
deleteLocalBackup: PropTypes.func, deleteLocalBackup: PropTypes.func,
toggleScroll: PropTypes.func.isRequired,
scrollSyncEnabled: PropTypes.bool.isRequired,
loadScroll: PropTypes.func.isRequired,
}; };
componentDidMount() { componentDidMount() {
@ -356,6 +360,9 @@ export class Editor extends React.Component {
slug, slug,
t, t,
editorBackLink, editorBackLink,
toggleScroll,
scrollSyncEnabled,
loadScroll,
} = this.props; } = this.props;
const isPublished = !newEntry && !unpublishedEntry; const isPublished = !newEntry && !unpublishedEntry;
@ -405,6 +412,9 @@ export class Editor extends React.Component {
deployPreview={deployPreview} deployPreview={deployPreview}
loadDeployPreview={opts => loadDeployPreview(collection, slug, entry, isPublished, opts)} loadDeployPreview={opts => loadDeployPreview(collection, slug, entry, isPublished, opts)}
editorBackLink={editorBackLink} editorBackLink={editorBackLink}
toggleScroll={toggleScroll}
scrollSyncEnabled={scrollSyncEnabled}
loadScroll={loadScroll}
t={t} t={t}
/> />
); );
@ -412,7 +422,7 @@ export class Editor extends React.Component {
} }
function mapStateToProps(state, ownProps) { function mapStateToProps(state, ownProps) {
const { collections, entryDraft, auth, config, entries, globalUI } = state; const { collections, entryDraft, auth, config, entries, globalUI, scroll } = state;
const slug = ownProps.match.params[0]; const slug = ownProps.match.params[0];
const collection = collections.get(ownProps.match.params.name); const collection = collections.get(ownProps.match.params.name);
const collectionName = collection.get('name'); const collectionName = collection.get('name');
@ -444,6 +454,8 @@ function mapStateToProps(state, ownProps) {
} }
} }
const scrollSyncEnabled = scroll.isScrolling;
return { return {
collection, collection,
collections, collections,
@ -466,6 +478,7 @@ function mapStateToProps(state, ownProps) {
publishedEntry, publishedEntry,
unPublishedEntry, unPublishedEntry,
editorBackLink, editorBackLink,
scrollSyncEnabled,
}; };
} }
@ -489,6 +502,8 @@ const mapDispatchToProps = {
unpublishPublishedEntry, unpublishPublishedEntry,
deleteUnpublishedEntry, deleteUnpublishedEntry,
logoutUser, logoutUser,
toggleScroll,
loadScroll,
}; };
export default connect(mapStateToProps, mapDispatchToProps)(withWorkflow(translate()(Editor))); export default connect(mapStateToProps, mapDispatchToProps)(withWorkflow(translate()(Editor)));

View File

@ -21,7 +21,6 @@ import { FILES } from '../../constants/collectionTypes';
import { getFileFromSlug } from '../../reducers/collections'; import { getFileFromSlug } from '../../reducers/collections';
const PREVIEW_VISIBLE = 'cms.preview-visible'; const PREVIEW_VISIBLE = 'cms.preview-visible';
const SCROLL_SYNC_ENABLED = 'cms.scroll-sync-enabled';
const I18N_VISIBLE = 'cms.i18n-visible'; const I18N_VISIBLE = 'cms.i18n-visible';
const styles = { const styles = {
@ -103,6 +102,7 @@ const Editor = styled.div`
height: 100%; height: 100%;
margin: 0 auto; margin: 0 auto;
position: relative; position: relative;
background-color: ${colorsRaw.white};
`; `;
const PreviewPaneContainer = styled.div` const PreviewPaneContainer = styled.div`
@ -152,10 +152,15 @@ function isPreviewEnabled(collection, entry) {
class EditorInterface extends Component { class EditorInterface extends Component {
state = { state = {
previewVisible: localStorage.getItem(PREVIEW_VISIBLE) !== 'false', previewVisible: localStorage.getItem(PREVIEW_VISIBLE) !== 'false',
scrollSyncEnabled: localStorage.getItem(SCROLL_SYNC_ENABLED) !== 'false',
i18nVisible: localStorage.getItem(I18N_VISIBLE) !== 'false', i18nVisible: localStorage.getItem(I18N_VISIBLE) !== 'false',
}; };
constructor(props) {
super(props);
this.props.loadScroll();
}
handleOnPersist = async (opts = {}) => { handleOnPersist = async (opts = {}) => {
const { createNew = false, duplicate = false } = opts; const { createNew = false, duplicate = false } = opts;
await this.controlPaneRef.switchToDefaultLocale(); await this.controlPaneRef.switchToDefaultLocale();
@ -177,9 +182,8 @@ class EditorInterface extends Component {
}; };
handleToggleScrollSync = () => { handleToggleScrollSync = () => {
const newScrollSyncEnabled = !this.state.scrollSyncEnabled; const { toggleScroll } = this.props;
this.setState({ scrollSyncEnabled: newScrollSyncEnabled }); toggleScroll();
localStorage.setItem(SCROLL_SYNC_ENABLED, newScrollSyncEnabled);
}; };
handleToggleI18n = () => { handleToggleI18n = () => {
@ -222,11 +226,10 @@ class EditorInterface extends Component {
deployPreview, deployPreview,
draftKey, draftKey,
editorBackLink, editorBackLink,
scrollSyncEnabled,
t, t,
} = this.props; } = this.props;
const { scrollSyncEnabled } = this.state;
const previewEnabled = isPreviewEnabled(collection, entry); const previewEnabled = isPreviewEnabled(collection, entry);
const collectionI18nEnabled = hasI18n(collection); const collectionI18nEnabled = hasI18n(collection);
@ -255,7 +258,7 @@ class EditorInterface extends Component {
); );
const editor2 = ( const editor2 = (
<ControlPaneContainer overFlow={!this.state.scrollSyncEnabled}> <ControlPaneContainer overFlow={!this.props.scrollSyncEnabled}>
<EditorControlPane {...editorProps} locale={locales?.[1]} t={t} /> <EditorControlPane {...editorProps} locale={locales?.[1]} t={t} />
</ControlPaneContainer> </ControlPaneContainer>
); );
@ -265,27 +268,25 @@ class EditorInterface extends Component {
: entry; : entry;
const editorWithPreview = ( const editorWithPreview = (
<ScrollSync enabled={this.state.scrollSyncEnabled}> <>
<> <ReactSplitPaneGlobalStyles />
<ReactSplitPaneGlobalStyles /> <StyledSplitPane>
<StyledSplitPane> <ScrollSyncPane>{editor}</ScrollSyncPane>
<ScrollSyncPane>{editor}</ScrollSyncPane> <PreviewPaneContainer>
<PreviewPaneContainer> <EditorPreviewPane
<EditorPreviewPane collection={collection}
collection={collection} entry={previewEntry}
entry={previewEntry} fields={fields}
fields={fields} fieldsMetaData={fieldsMetaData}
fieldsMetaData={fieldsMetaData} locale={leftPanelLocale}
locale={leftPanelLocale} />
/> </PreviewPaneContainer>
</PreviewPaneContainer> </StyledSplitPane>
</StyledSplitPane> </>
</>
</ScrollSync>
); );
const editorWithEditor = ( const editorWithEditor = (
<ScrollSync enabled={this.state.scrollSyncEnabled}> <ScrollSync enabled={this.props.scrollSyncEnabled}>
<div> <div>
<StyledSplitPane> <StyledSplitPane>
<ScrollSyncPane>{editor}</ScrollSyncPane> <ScrollSyncPane>{editor}</ScrollSyncPane>
@ -406,6 +407,9 @@ EditorInterface.propTypes = {
deployPreview: PropTypes.object, deployPreview: PropTypes.object,
loadDeployPreview: PropTypes.func.isRequired, loadDeployPreview: PropTypes.func.isRequired,
draftKey: PropTypes.string.isRequired, draftKey: PropTypes.string.isRequired,
toggleScroll: PropTypes.func.isRequired,
scrollSyncEnabled: PropTypes.bool.isRequired,
loadScroll: PropTypes.func.isRequired,
t: PropTypes.func.isRequired, t: PropTypes.func.isRequired,
}; };

View File

@ -174,7 +174,7 @@ export class ErrorBoundary extends React.Component {
return this.props.children; return this.props.children;
} }
return ( return (
<ErrorBoundaryContainer> <ErrorBoundaryContainer key="error-boundary-container">
<h1>{t('ui.errorBoundary.title')}</h1> <h1>{t('ui.errorBoundary.title')}</h1>
<p> <p>
<span>{t('ui.errorBoundary.details')}</span> <span>{t('ui.errorBoundary.details')}</span>
@ -190,17 +190,15 @@ export class ErrorBoundary extends React.Component {
<p> <p>
{t('ui.errorBoundary.privacyWarning') {t('ui.errorBoundary.privacyWarning')
.split('\n') .split('\n')
.map((item, index) => ( .map((item, index) => [
<> <PrivacyWarning key={`private-warning-${index}`}>{item}</PrivacyWarning>,
<PrivacyWarning key={index}>{item}</PrivacyWarning> <br key={`break-${index}`} />,
<br /> ])}
</>
))}
</p> </p>
<hr /> <hr />
<h2>{t('ui.errorBoundary.detailsHeading')}</h2> <h2>{t('ui.errorBoundary.detailsHeading')}</h2>
<p>{errorMessage}</p> <p>{errorMessage}</p>
{backup && showBackup && <RecoveredEntry entry={backup} t={t} />} {backup && showBackup && <RecoveredEntry key="backup" entry={backup} t={t} />}
</ErrorBoundaryContainer> </ErrorBoundaryContainer>
); );
} }

View File

@ -14,6 +14,7 @@ import mediaLibrary from './mediaLibrary';
import deploys, * as fromDeploys from './deploys'; import deploys, * as fromDeploys from './deploys';
import globalUI from './globalUI'; import globalUI from './globalUI';
import status from './status'; import status from './status';
import scroll from './scroll';
import type { Status } from '../constants/publishModes'; import type { Status } from '../constants/publishModes';
import type { State, Collection } from '../types/redux'; import type { State, Collection } from '../types/redux';
@ -33,6 +34,7 @@ const reducers = {
deploys, deploys,
globalUI, globalUI,
status, status,
scroll,
}; };
export default reducers; export default reducers;

View File

@ -0,0 +1,28 @@
import { produce } from 'immer';
import { SCROLL_SYNC_ENABLED, SET_SCROLL, TOGGLE_SCROLL } from '../actions/scroll';
import type { ScrollAction } from '../actions/scroll';
export type ScrollState = {
isScrolling: boolean;
};
const defaultState: ScrollState = {
isScrolling: true,
};
const status = produce((state: ScrollState, action: ScrollAction) => {
switch (action.type) {
case TOGGLE_SCROLL:
state.isScrolling = !state.isScrolling;
localStorage.setItem(SCROLL_SYNC_ENABLED, `${state.isScrolling}`);
break;
case SET_SCROLL:
state.isScrolling = action.payload;
localStorage.setItem(SCROLL_SYNC_ENABLED, `${state.isScrolling}`);
break;
}
}, defaultState);
export default status;

View File

@ -1,15 +1,16 @@
import type { List, Map, OrderedMap, Set } from 'immutable';
import type { Action } from 'redux'; import type { Action } from 'redux';
import type { StaticallyTypedRecord } from './immutable';
import type { Map, List, OrderedMap, Set } from 'immutable';
import type { FILES, FOLDER } from '../constants/collectionTypes';
import type { MediaFile as BackendMediaFile } from '../backend'; import type { MediaFile as BackendMediaFile } from '../backend';
import type { Auth } from '../reducers/auth'; import type { FILES, FOLDER } from '../constants/collectionTypes';
import type { Status } from '../reducers/status';
import type { Medias } from '../reducers/medias';
import type { Deploys } from '../reducers/deploys';
import type { Search } from '../reducers/search';
import type { GlobalUI } from '../reducers/globalUI';
import type { formatExtensions } from '../formats/formats'; import type { formatExtensions } from '../formats/formats';
import type { Auth } from '../reducers/auth';
import type { Deploys } from '../reducers/deploys';
import type { GlobalUI } from '../reducers/globalUI';
import type { Medias } from '../reducers/medias';
import type { ScrollState } from '../reducers/scroll';
import type { Search } from '../reducers/search';
import type { Status } from '../reducers/status';
import type { StaticallyTypedRecord } from './immutable';
export type CmsBackendType = export type CmsBackendType =
| 'azure' | 'azure'
@ -699,6 +700,7 @@ export interface State {
search: Search; search: Search;
notifs: { message: { key: string }; kind: string; id: number }[]; notifs: { message: { key: string }; kind: string; id: number }[];
status: Status; status: Status;
scroll: ScrollState;
} }
export interface Integration { export interface Integration {

View File

@ -17,8 +17,8 @@ const StyledDropdownButton = styled(DropdownButton)`
${buttons.button}; ${buttons.button};
${buttons.default}; ${buttons.default};
display: block; display: block;
padding-left: 20px; padding-left: 20px!important;
padding-right: 40px; padding-right: 40px!important;
position: relative; position: relative;
&:after { &:after {

View File

@ -562,6 +562,11 @@ function GlobalStyles() {
textarea { textarea {
resize: none; resize: none;
} }
.ol-viewport {
position: absolute!important;
top: 0;
}
} }
`} `}
/> />