* 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
259 lines
7.4 KiB
TypeScript
259 lines
7.4 KiB
TypeScript
import { ApplicationGroup } from '@/ui_models/application_group';
|
|
import { getPlatformString } from '@/utils';
|
|
import { AppStateEvent, PanelResizedData } from '@/ui_models/app_state';
|
|
import {
|
|
ApplicationEvent,
|
|
Challenge,
|
|
PermissionDialog,
|
|
removeFromArray,
|
|
} from '@standardnotes/snjs';
|
|
import { PANEL_NAME_NOTES, PANEL_NAME_NAVIGATION } from '@/views/constants';
|
|
import { STRING_DEFAULT_FILE_ERROR } from '@/strings';
|
|
import { alertDialog } from '@/services/alertService';
|
|
import { WebAppEvent, WebApplication } from '@/ui_models/application';
|
|
import { PureComponent } from '@/components/Abstract/PureComponent';
|
|
import { Navigation } from '@/components/Navigation';
|
|
import { NotesView } from '@/components/NotesView';
|
|
import { NoteGroupView } from '@/components/NoteGroupView';
|
|
import { Footer } from '@/components/Footer';
|
|
import { SessionsModal } from '@/components/SessionsModal';
|
|
import { PreferencesViewWrapper } from '@/preferences/PreferencesViewWrapper';
|
|
import { ChallengeModal } from '@/components/ChallengeModal';
|
|
import { NotesContextMenu } from '@/components/NotesContextMenu';
|
|
import { PurchaseFlowWrapper } from '@/purchaseFlow/PurchaseFlowWrapper';
|
|
import { render } from 'preact';
|
|
import { PermissionsModal } from './PermissionsModal';
|
|
|
|
type Props = {
|
|
application: WebApplication;
|
|
mainApplicationGroup: ApplicationGroup;
|
|
};
|
|
|
|
type State = {
|
|
started?: boolean;
|
|
launched?: boolean;
|
|
needsUnlock?: boolean;
|
|
appClass: string;
|
|
challenges: Challenge[];
|
|
};
|
|
|
|
export class ApplicationView extends PureComponent<Props, State> {
|
|
public readonly platformString = getPlatformString();
|
|
|
|
constructor(props: Props) {
|
|
super(props, props.application);
|
|
this.state = {
|
|
appClass: '',
|
|
challenges: [],
|
|
};
|
|
this.onDragDrop = this.onDragDrop.bind(this);
|
|
this.onDragOver = this.onDragOver.bind(this);
|
|
this.addDragDropHandlers();
|
|
}
|
|
|
|
deinit() {
|
|
(this.application as unknown) = undefined;
|
|
window.removeEventListener('dragover', this.onDragOver, true);
|
|
window.removeEventListener('drop', this.onDragDrop, true);
|
|
(this.onDragDrop as unknown) = undefined;
|
|
(this.onDragOver as unknown) = undefined;
|
|
super.deinit();
|
|
}
|
|
|
|
componentDidMount(): void {
|
|
super.componentDidMount();
|
|
this.loadApplication();
|
|
}
|
|
|
|
async loadApplication() {
|
|
this.application.componentManager.setDesktopManager(
|
|
this.application.getDesktopService()
|
|
);
|
|
await this.application.prepareForLaunch({
|
|
receiveChallenge: async (challenge) => {
|
|
const challenges = this.state.challenges.slice();
|
|
challenges.push(challenge);
|
|
this.setState({ challenges: challenges });
|
|
},
|
|
});
|
|
await this.application.launch();
|
|
}
|
|
|
|
public removeChallenge = async (challenge: Challenge) => {
|
|
const challenges = this.state.challenges.slice();
|
|
removeFromArray(challenges, challenge);
|
|
this.setState({ challenges: challenges });
|
|
};
|
|
|
|
async onAppStart() {
|
|
super.onAppStart();
|
|
this.setState({
|
|
started: true,
|
|
needsUnlock: this.application.hasPasscode(),
|
|
});
|
|
|
|
this.application.componentManager.presentPermissionsDialog =
|
|
this.presentPermissionsDialog;
|
|
}
|
|
|
|
async onAppLaunch() {
|
|
super.onAppLaunch();
|
|
this.setState({
|
|
launched: true,
|
|
needsUnlock: false,
|
|
});
|
|
this.handleDemoSignInFromParams();
|
|
}
|
|
|
|
onUpdateAvailable() {
|
|
this.application.notifyWebEvent(WebAppEvent.NewUpdateAvailable);
|
|
}
|
|
|
|
/** @override */
|
|
async onAppEvent(eventName: ApplicationEvent) {
|
|
super.onAppEvent(eventName);
|
|
switch (eventName) {
|
|
case ApplicationEvent.LocalDatabaseReadError:
|
|
alertDialog({
|
|
text: 'Unable to load local database. Please restart the app and try again.',
|
|
});
|
|
break;
|
|
case ApplicationEvent.LocalDatabaseWriteError:
|
|
alertDialog({
|
|
text: 'Unable to write to local database. Please restart the app and try again.',
|
|
});
|
|
break;
|
|
}
|
|
}
|
|
|
|
/** @override */
|
|
async onAppStateEvent(eventName: AppStateEvent, data?: unknown) {
|
|
if (eventName === AppStateEvent.PanelResized) {
|
|
const { panel, collapsed } = data as PanelResizedData;
|
|
let appClass = '';
|
|
if (panel === PANEL_NAME_NOTES && collapsed) {
|
|
appClass += 'collapsed-notes';
|
|
}
|
|
if (panel === PANEL_NAME_NAVIGATION && collapsed) {
|
|
appClass += ' collapsed-navigation';
|
|
}
|
|
this.setState({ appClass });
|
|
} else if (eventName === AppStateEvent.WindowDidFocus) {
|
|
if (!(await this.application.isLocked())) {
|
|
this.application.sync();
|
|
}
|
|
}
|
|
}
|
|
|
|
addDragDropHandlers() {
|
|
/**
|
|
* Disable dragging and dropping of files (but allow text) into main SN interface.
|
|
* both 'dragover' and 'drop' are required to prevent dropping of files.
|
|
* This will not prevent extensions from receiving drop events.
|
|
*/
|
|
window.addEventListener('dragover', this.onDragOver, true);
|
|
window.addEventListener('drop', this.onDragDrop, true);
|
|
}
|
|
|
|
onDragOver(event: DragEvent) {
|
|
if (event.dataTransfer?.files.length) {
|
|
event.preventDefault();
|
|
}
|
|
}
|
|
|
|
onDragDrop(event: DragEvent) {
|
|
if (event.dataTransfer?.files.length) {
|
|
event.preventDefault();
|
|
void alertDialog({
|
|
text: STRING_DEFAULT_FILE_ERROR,
|
|
});
|
|
}
|
|
}
|
|
|
|
async handleDemoSignInFromParams() {
|
|
if (
|
|
window.location.href.includes('demo') &&
|
|
!this.application.hasAccount()
|
|
) {
|
|
await this.application.setCustomHost(
|
|
'https://syncing-server-demo.standardnotes.com'
|
|
);
|
|
this.application.signIn('demo@standardnotes.org', 'password');
|
|
}
|
|
}
|
|
|
|
presentPermissionsDialog = (dialog: PermissionDialog) => {
|
|
render(
|
|
<PermissionsModal
|
|
application={this.application}
|
|
callback={dialog.callback}
|
|
component={dialog.component}
|
|
permissionsString={dialog.permissionsString}
|
|
/>,
|
|
document.body.appendChild(document.createElement('div'))
|
|
);
|
|
};
|
|
|
|
render() {
|
|
return (
|
|
<div className={this.platformString + ' main-ui-view sn-component'}>
|
|
{!this.state.needsUnlock && this.state.launched && (
|
|
<div
|
|
id="app"
|
|
className={this.state.appClass + ' app app-column-container'}
|
|
>
|
|
<Navigation application={this.application} />
|
|
|
|
<NotesView
|
|
application={this.application}
|
|
appState={this.appState}
|
|
/>
|
|
|
|
<NoteGroupView application={this.application} />
|
|
</div>
|
|
)}
|
|
|
|
{!this.state.needsUnlock && this.state.launched && (
|
|
<Footer
|
|
application={this.application}
|
|
applicationGroup={this.props.mainApplicationGroup}
|
|
/>
|
|
)}
|
|
|
|
<SessionsModal
|
|
application={this.application}
|
|
appState={this.appState}
|
|
/>
|
|
|
|
<PreferencesViewWrapper
|
|
appState={this.appState}
|
|
application={this.application}
|
|
/>
|
|
|
|
{this.state.challenges.map((challenge) => {
|
|
return (
|
|
<div className="sk-modal">
|
|
<ChallengeModal
|
|
key={challenge.id}
|
|
application={this.application}
|
|
challenge={challenge}
|
|
onDismiss={this.removeChallenge}
|
|
/>
|
|
</div>
|
|
);
|
|
})}
|
|
|
|
<NotesContextMenu
|
|
application={this.application}
|
|
appState={this.appState}
|
|
/>
|
|
|
|
<PurchaseFlowWrapper
|
|
application={this.application}
|
|
appState={this.appState}
|
|
/>
|
|
</div>
|
|
);
|
|
}
|
|
}
|