Files
standardnotes-app-web/app/assets/javascripts/components/HistoryMenu.tsx
Mo 50c92619ce refactor: migrate remaining angular components to react (#833)
* refactor: menuRow directive to MenuRow component

* refactor: migrate footer to react

* refactor: migrate actions menu to react

* refactor: migrate history menu to react

* fix: click outside handler use capture to trigger event before re-render occurs which would otherwise cause node.contains to return incorrect result (specifically for the account menu)

* refactor: migrate revision preview modal to react

* refactor: migrate permissions modal to react

* refactor: migrate password wizard to react

* refactor: remove unused input modal directive

* refactor: remove unused delay hide component

* refactor: remove unused filechange directive

* refactor: remove unused elemReady directive

* refactor: remove unused sn-enter directive

* refactor: remove unused lowercase directive

* refactor: remove unused autofocus directive

* refactor(wip): note view to react

* refactor: use mutation observer to deinit textarea listeners

* refactor: migrate challenge modal to react

* refactor: migrate note group view to react

* refactor(wip): migrate remaining classes

* fix: navigation parent ref

* refactor: fully remove angular assets

* fix: account switcher

* fix: application view state

* refactor: remove unused password wizard type

* fix: revision preview and permissions modal

* fix: remove angular comment

* refactor: react panel resizers for editor

* feat: simple panel resizer

* fix: use simple panel resizer everywhere

* fix: simplify panel resizer state

* chore: rename simple panel resizer to panel resizer

* refactor: simplify column layout

* fix: editor mount safety check

* fix: use inline onLoad callback for iframe, as setting onload after it loads will never call it

* chore: fix note view test

* chore(deps): upgrade snjs
2022-01-30 19:01:30 -06:00

312 lines
9.1 KiB
TypeScript

import { WebApplication } from '@/ui_models/application';
import { NoteHistoryEntry, PayloadContent, SNNote } from '@standardnotes/snjs';
import { RevisionListEntry } from '@standardnotes/snjs';
import { alertDialog, confirmDialog } from '@/services/alertService';
import { PureComponent } from './Abstract/PureComponent';
import { MenuRow } from './MenuRow';
import { render } from 'preact';
import { RevisionPreviewModal } from './RevisionPreviewModal';
type HistoryState = {
sessionHistory?: NoteHistoryEntry[];
remoteHistory?: RevisionListEntry[];
fetchingRemoteHistory: boolean;
autoOptimize: boolean;
diskEnabled: boolean;
showRemoteOptions?: boolean;
showSessionOptions?: boolean;
};
type Props = {
application: WebApplication;
item: SNNote;
};
export class HistoryMenu extends PureComponent<Props, HistoryState> {
constructor(props: Props) {
super(props, props.application);
this.state = {
fetchingRemoteHistory: false,
autoOptimize: this.props.application.historyManager.autoOptimize,
diskEnabled: this.props.application.historyManager.isDiskEnabled(),
sessionHistory:
this.props.application.historyManager.sessionHistoryForItem(
this.props.item
) as NoteHistoryEntry[],
};
}
reloadState() {
this.setState({
fetchingRemoteHistory: this.state.fetchingRemoteHistory,
autoOptimize: this.props.application.historyManager.autoOptimize,
diskEnabled: this.props.application.historyManager.isDiskEnabled(),
sessionHistory:
this.props.application.historyManager.sessionHistoryForItem(
this.props.item
) as NoteHistoryEntry[],
});
}
componentDidMount(): void {
super.componentDidMount();
this.fetchRemoteHistory();
}
fetchRemoteHistory = async () => {
this.setState({ fetchingRemoteHistory: true });
try {
const remoteHistory =
await this.props.application.historyManager.remoteHistoryForItem(
this.props.item
);
this.setState({ remoteHistory });
} finally {
this.setState({ fetchingRemoteHistory: false });
}
};
private presentRevisionPreviewModal = (
uuid: string,
content: PayloadContent,
title: string
) => {
render(
<RevisionPreviewModal
application={this.application}
uuid={uuid}
content={content}
title={title}
/>,
document.body.appendChild(document.createElement('div'))
);
};
openSessionRevision = (revision: NoteHistoryEntry) => {
this.presentRevisionPreviewModal(
revision.payload.uuid,
revision.payload.content,
revision.previewTitle()
);
};
openRemoteRevision = async (revision: RevisionListEntry) => {
this.setState({ fetchingRemoteHistory: true });
const remoteRevision =
await this.props.application.historyManager.fetchRemoteRevision(
this.props.item.uuid,
revision
);
this.setState({ fetchingRemoteHistory: false });
if (!remoteRevision) {
alertDialog({
text: 'The remote revision could not be loaded. Please try again later.',
});
return;
}
this.presentRevisionPreviewModal(
remoteRevision.payload.uuid,
remoteRevision.payload.content,
this.previewRemoteHistoryTitle(revision)
);
};
classForSessionRevision = (revision: NoteHistoryEntry) => {
const vector = revision.operationVector();
if (vector === 0) {
return 'default';
} else if (vector === 1) {
return 'success';
} else if (vector === -1) {
return 'danger';
}
};
clearItemSessionHistory = async () => {
if (
await confirmDialog({
text: 'Are you sure you want to delete the local session history for this note?',
confirmButtonStyle: 'danger',
})
) {
this.props.application.historyManager.clearHistoryForItem(
this.props.item
);
this.reloadState();
}
};
clearAllSessionHistory = async () => {
if (
await confirmDialog({
text: 'Are you sure you want to delete the local session history for all notes?',
confirmButtonStyle: 'danger',
})
) {
await this.props.application.historyManager.clearAllHistory();
this.reloadState();
}
};
toggleSessionHistoryDiskSaving = async () => {
if (!this.state.diskEnabled) {
if (
await confirmDialog({
text:
'Are you sure you want to save history to disk? This will decrease general ' +
'performance, especially as you type. You are advised to disable this feature ' +
'if you experience any lagging.',
confirmButtonStyle: 'danger',
})
) {
this.props.application.historyManager.toggleDiskSaving();
}
} else {
this.props.application.historyManager.toggleDiskSaving();
}
this.reloadState();
};
toggleSessionHistoryAutoOptimize = () => {
this.props.application.historyManager.toggleAutoOptimize();
this.reloadState();
};
previewRemoteHistoryTitle(revision: RevisionListEntry) {
return new Date(revision.created_at).toLocaleString();
}
toggleShowRemoteOptions = ($event: Event) => {
$event.stopPropagation();
this.setState({
showRemoteOptions: !this.state.showRemoteOptions,
});
};
toggleShowSessionOptions = ($event: Event) => {
$event.stopPropagation();
this.setState({
showSessionOptions: !this.state.showSessionOptions,
});
};
render() {
return (
<div id="history-menu" className="sn-component">
<div className="sk-menu-panel dropdown-menu">
<div className="sk-menu-panel-header">
<div className="sk-menu-panel-header-title">
Session
<div className="sk-menu-panel-header-subtitle">
{this.state.sessionHistory?.length || 'No'} revisions
</div>
</div>
<a
className="sk-a info sk-h5"
onClick={this.toggleShowSessionOptions}
>
Options
</a>
</div>
{this.state.showSessionOptions && (
<div>
<MenuRow
action={this.clearItemSessionHistory}
label="Clear note local history"
/>
<MenuRow
action={this.clearAllSessionHistory}
label="Clear all local history"
/>
<MenuRow
action={this.toggleSessionHistoryAutoOptimize}
label={
(this.state.autoOptimize ? 'Disable' : 'Enable') +
' auto cleanup'
}
>
<div className="sk-sublabel">
Automatically cleans up small revisions to conserve space.
</div>
</MenuRow>
<MenuRow
action={this.toggleSessionHistoryDiskSaving}
label={
(this.state.diskEnabled ? 'Disable' : 'Enable') +
' saving history to disk'
}
>
<div className="sk-sublabel">
Saving to disk is not recommended. Decreases performance and
increases app loading time and memory footprint.
</div>
</MenuRow>
</div>
)}
{this.state.sessionHistory?.map((revision, index) => {
return (
<MenuRow
key={index}
action={this.openSessionRevision}
actionArgs={[revision]}
label={revision.previewTitle()}
>
<div
className={
this.classForSessionRevision(revision) +
' sk-sublabel opaque'
}
>
{revision.previewSubTitle()}
</div>
</MenuRow>
);
})}
<div className="sk-menu-panel-header">
<div className="sk-menu-panel-header-title">
Remote
<div className="sk-menu-panel-header-subtitle">
{this.state.remoteHistory?.length || 'No'} revisions
</div>
</div>
<a
onClick={this.toggleShowRemoteOptions}
className="sk-a info sk-h5"
>
Options
</a>
</div>
{this.state.showRemoteOptions && (
<MenuRow
action={this.fetchRemoteHistory}
label="Refresh"
disabled={this.state.fetchingRemoteHistory}
spinnerClass={
this.state.fetchingRemoteHistory ? 'info' : undefined
}
>
<div className="sk-sublabel">Fetch history from server.</div>
</MenuRow>
)}
{this.state.remoteHistory?.map((revision, index) => {
return (
<MenuRow
key={index}
action={this.openRemoteRevision}
actionArgs={[revision]}
label={this.previewRemoteHistoryTitle(revision)}
/>
);
})}
</div>
</div>
);
}
}